앱 개발 시, 긴 이미지를 화면에 보여줘야 하는 경우가 종종 있습니다. 특히 상세 화면에서 긴 스크롤 이미지를 사용하는 경우, Flutter의 이미지 처리 제한 때문에 문제가 발생할 수 있습니다.
이미지 높이 제한:
우선 제가 확인한 바로는 Flutter에서 지원하는 최대 이미지 높이는 8192 픽셀입니다. 이 제한을 초과하면 이미지가 압축되어 찌그러져 보이는 현상이 발생합니다. 기기에 따라 다를수 있다고는 하지만, ios, android,web 여러 가지로 빌드해서 확인했을때 동일한 문제가 발생하는 것을 확인할 수 있습니다.
Flutter 상에서 이미지 분할 속도:
Flutter의 dart:ui를 사용해 이미지를 잘라내는 경우, 성능 문제가 발생할 수 있습니다. 긴 이미지를 분할해 화면에 표시하는 데 많은 시간이 소요되어 사용자 경험(UX)에 악영향을 줍니다.
위 문제를 해결하기 위해 네이티브 코드를 사용하여 이미지를 분할하는 방법을 구현했습니다. 네이티브 코드를 활용하면 Flutter의 제한을 우회하고, 성능을 크게 개선할 수 있습니다.
네이티브 코드로 이미지 분할
플랫폼별로 Android와 iOS에서 네이티브 코드를 작성하여 이미지를 분할합니다.
네이티브와 Flutter 간 통신
MethodChannel을 사용해 Flutter와 네이티브 코드를 연결합니다. Flutter에서 이미지를 분할 요청하면 네이티브 코드에서 이미지를 처리하고 결과를 반환합니다.
캐싱을 활용해 성능 최적화
이미지를 다시 요청할 때마다 네이티브에서 분할하지 않도록, 분할된 이미지 경로를 로컬 캐시에 저장하고 재활용합니다.
1. Flutter 코드: MethodChannel 사용
Flutter에서 네이티브와 통신하기 위해 MethodChannel을 설정합니다.
import 'package:flutter/services.dart';
class ImageSplitter {
static const MethodChannel _channel = MethodChannel('com.example.image_splitter');
/// 네이티브 코드로 이미지를 분할
static Future<List<String>> splitImage(String imageUrl, int maxChunkSize) async {
try {
final List<dynamic> result = await _channel.invokeMethod('splitImageFromUrl', {
'imageUrl': imageUrl,
'maxChunkSize': maxChunkSize,
});
return result.cast<String>();
} catch (e) {
print('Error splitting image: $e');
return [];
}
}
}
2. Android 코드: 이미지 분할 구현
Android에서는 네이티브 코드에서 이미지를 다운로드하고, 지정된 높이(maxChunkSize)로 분할한 후 저장합니다.
private fun splitImageFromUrl(imageUrl: String, maxChunkSize: Int): List<String> {
val connection = URL(imageUrl).openConnection() as HttpURLConnection
connection.doInput = true
connection.connect()
val inputStream = connection.inputStream
val originalBitmap = BitmapFactory.decodeStream(inputStream)
val chunks = mutableListOf<String>()
val width = originalBitmap.width
val height = originalBitmap.height
var currentY = 0
while (currentY < height) {
val chunkHeight = if (currentY + maxChunkSize > height) {
height - currentY
} else {
maxChunkSize
}
val chunkBitmap = Bitmap.createBitmap(originalBitmap, 0, currentY, width, chunkHeight)
val chunkFile = File(applicationContext.cacheDir, "chunk_${System.currentTimeMillis()}.jpg")
FileOutputStream(chunkFile).use { fos ->
chunkBitmap.compress(Bitmap.CompressFormat.JPEG, 75, fos)
}
chunks.add(chunkFile.absolutePath)
currentY += chunkHeight
}
return chunks
}
3. iOS 코드: 이미지 분할 구현
iOS에서는 비슷한 로직으로 이미지를 다운로드하고 분할합니다.
private func splitImage(image: UIImage, maxChunkSize: Int) -> [String] {
var chunks = [String]()
let width = Int(image.size.width)
let height = Int(image.size.height)
var currentY = 0
while currentY < height {
let chunkHeight = min(maxChunkSize, height - currentY)
let rect = CGRect(x: 0, y: currentY, width: width, height: chunkHeight)
if let cgImage = image.cgImage?.cropping(to: rect) {
let chunkImage = UIImage(cgImage: cgImage)
let chunkPath = NSTemporaryDirectory() + "chunk_\(Date().timeIntervalSince1970).jpg"
if let data = chunkImage.jpegData(compressionQuality: 0.75) {
try? data.write(to: URL(fileURLWithPath: chunkPath))
chunks.append(chunkPath)
}
}
currentY += chunkHeight
}
return chunks
}
4. Flutter 캐싱과 UI 연결
이미지 경로를 로컬 캐시에 저장하여, 재요청 시 성능 문제를 줄입니다. ListView.builder를 사용해 분할된 이미지를 화면에 표시합니다.
ListView.builder(
itemCount: images.length,
itemBuilder: (context, index) {
return Image.file(
File(images[index]),
fit: BoxFit.fitWidth,
);
},
);
이미지 다운로드 및 분할:
Android와 iOS의 네이티브 코드를 통해 이미지를 다운로드한 후, maxChunkSize에 따라 분할합니다.
캐싱 전략:
분할된 이미지를 로컬에 저장하고, 다음 요청 시 로컬 데이터를 우선 사용합니다.
Flutter와 네이티브 통합:
Flutter에서 MethodChannel을 통해 네이티브 코드를 호출하여 성능과 사용자 경험(UX)을 동시에 개선합니다.
결과 및 성능 비교
Flutter 상에서 이미지 처리:
긴 이미지를 분할하면 앱 성능이 저하되고, 처리 시간이 길어집니다.
네이티브 이미지 처리:
네이티브 코드를 활용하면 처리 속도가 훨씬 빠르며, Flutter와의 연동도 매끄럽게 이루어집니다.
Flutter에서 긴 이미지를 처리할 때 발생하는 문제를 네이티브 코드로 해결하면 성능과 사용자 경험을 모두 개선할 수 있습니다. 특히, 상세 화면에서 긴 이미지를 보여주는 앱(예: 전자상거래, 지도, 상세 페이지 등)에 적합한 솔루션입니다. 다만 캐쉬를 쌓으면 앱 사용에 영향을 미칠수 있어서 일정 시간 동안은 유지해서 반복적으로 이미지를 분할하지 않고 다시 불러오고, 시간이 지나면 삭제하는 기능을 구현했으나 이 부분이 정상적으로 작동하지 않아 개선이 필요한 상황입니다.
이 코드를 기반으로 더 나은 사용자 경험을 제공하는 앱을 개발해 보세요! 😊