Jetpack Compose ile Image
Merhaba, bu yazımızda Jetpack Compose da Image kullanımından bahsedeceğiz. Ayrıca internetten resmi nasıl yükleyebiliriz, gelen resmi nasıl özelleştirebiliriz ya da performans açısından nelere dikkat etmeliyiz bunlara değineceğiz. O zaman başlayalım;
Resim Gösterme
Diskten bir resim gösterme
Image
içinde bir resim gösterirken painter
parametresini doldurmamız gerekir. painter
sadece Painter
tipini kabul eder. Bunun için painterResource
apisine diskten göstermek istediğimiz herhangi bir resmin yolunu vererek basitçe ekranda gösterebiliriz.
Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = null
)
Ayrıca contentDescription
parametresini de tanımlamamız gerekmektedir. Ancak burada herhangi bir description verme zorunluluğumuz yoktur sadece null
değerini vererek de tanımlamayı yapabiliriz.
İnternetten bir resim gösterme
Resmi bir url adresinden çekmek için çok fazla third-party kütüphane bulunuyor. Bu kütüphaneler bir resmi internetten çekip kullanmak dışında resmi ön bellekte tutarak ekran her güncellendiğinde tekrar tekrar resmi internetten çekmeye çalışmaz ve internet kullanımını önemli ölçüde azaltmış olurlar. Burada iki farklı kütüphaneyi önerebilirim, biri Coil diğeri ise Glide. Aşağıda gösterdiğim örnekte Coil kütüphanesi kullanılmıştır.
AsyncImage(
model = "https://example.com/image.jpg",
contentDescription = "Translated description of what the image contains"
)
ImageBitmap ve ImageVector Farkları
Resimlerin nasıl kullanılacağından bahsettiysek biraz da Image içinde kullanılan ImageBitmap
ve ImageVector
farklarından bahsedelim. Şunu biliyoruz bilinen ve en yaygın kullanılan iki görüntü tipinden birisi Bitmap
bir diğeri ise Vector
dür.
Bitmap
formatı aslında bir resmin piksellerini içinde tutar ve her bir piksel kendi içinde kırmızı, yeşil, mavi ve alfa (RGBA) değerlerinden oluşur. Bu piksellerin birleşimi de bize Bitmap
‘ i yani resmi verir. Ancak bu da eğer resim yakınlaştırılırsa ayrıntıların kaybolması ve pikselleşme sorununu yanında getirir. Bitmap
grafik tipindeki resim formatlarına JPEG, PNG ve WEBP örnek verilebilir.
Vector
resimler ise ekrandaki görsel bir öğenin ölçeklenebilir matematiksel bir temsilidir. Yani vektör piksellerden oluşmaz ekrana çizilir. Bunu kendi içindeki bir dizi komut meydana getirir. Bu da görsel ne kadar yakınlaştırılırsa yakınlaştırılsın görüntünün bozulmaması olanağını sağlar. Çünkü matematiksel formül diğer komutlar arasındaki ilişkiyi korur. Vektörel resimlere en iyi örnek Material semboller verilebilir.
ImageBitmap
Bitmap
bir resim Bitmap
‘ de kullanılırken klasik yöntem ile yazılabilir. painterResource
içine verilen bir Bitmap
kaynağını ImageBitmap
içine taşır. Ardından BitmapPainter
resmi ekrana çizer.
Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description)
)
Farz edelim ki bir Bitmap
‘ i özelleştirmek bazı pikselleriyle oynamak istedik. O zaman da ImageBitmap
tipinde bir değişken oluşturup. Üzerinde değişiklikler yapıp doğruca Image
içine verebiliriz.
val imageBitmap = ImageBitmap.imageResource(R.drawable.dog)
Image(
imageBitmap,
contentDescription = null
)
ImageVector
Vector
resimleri ekrana çizmekten VectorPainter
sorumludur. ImageVector
SVG tipindeki görselleri ekrana çizebilir. Her resim Vector
olarak kullanılamaz ya da Vector
‘ e dönüştürülemez, örneğin kameradan çekilen herhangi bir fotoğraf.
Not: Sadece dışarıdan
Vector
tipinde görsel almamız gerekmez. Kendimiz de .xml bir dosya oluşturup vektörel bir görsel çizebilir ve bunu Image içinde kullanabiliriz.
Vector
bir resim Image
‘ de kullanılırken klasik yöntem ile yazılabilir. painterResource
içine verilen bir Vector
kaynağını ImageVector
içine taşır. Ardından VectorPainter
resmi ekrana çizer.
Image(
painter = painterResource(id = R.drawable.baseline_shopping_cart_24),
contentDescription = stringResource(id = R.string.dog_content_description)
)
Ayrıca Bitmap
gibi Vector
‘ ü de özelleştirmek istersek ImageVector
tipinde bir değişken oluşturup gerekli değişiklikleri yapıp doğruca Image
içine verebiliriz.
val imageVector = ImageVector.vectorResource(id = R.drawable.baseline_shopping_cart_24)
Image(
imageVector,
contentDescription = null
)
Resmi Özelleştirme
Bu kısımda ise bir resmin içeriğini ContentScale
ile ölçeklendirecek, ColorFilter
ile renkleri ve pikselleri ile oynayacak ve Modifier
ile de şeklini değiştireceğiz.
ContentScale
ContentScale
adından da anlaşılacağı gibi içine tanımlanan görsel içeriğinin ölçeklendirilmesi için kullanılıyor. Öncelikle ContentScale
‘ in kod içinde kullanımını görelim;
Image(
painter= painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = null,
contentScale = ContentScale.Fit
)
Kod içinde kullanımı bu kadar kolay. Peki ContentScale.Fit
ne işe yarıyor? Ya da diğer ContentScale
tipleri neler hadi bunlara yakından bakalım;
ContentScale.Fit
Varsayılan ölçeklendirme yöntemi olan ContentScale.Fit
resmin en boy oranını koruyarak çizileceği sınırların içine sığdırır.
ContentScale.Crop
Uygun sınırlar içerisinde içeriği ortadan kırpar.
ContentScale.FillHeight
En boy oranını koruyarak içeriği sınır yüksekliğe sığdırır.
ContentScale.FillWidth
En boy oranını koruyarak içeriği sınır genişliğe sığdırır.
ContentScale.FillBounds
En boy oranına dikkat etmeden içeriği sınırları dolduracak şekilde ayarlamaya çalışır. Bu genellikle bize sağlıklı bir görünüm vermez.
ContentScale.Inside
Bu tipte iki farklı durumumuz var: Content’in sınır boyutundan büyük olduğu durumlarda en boy oranına dikkat edilerek görsel sınırların içine yerleştirilir. Content in sınır boyutundan küçük olduğu durumlarda ise herhangi bir sığdırma yapılmaz ve sınırların tam merkezine yerleştirilir.
ContentScale.None
Herhangi bir ölçeklendirme kullanılmaz. Eğer içerik sınırlardan küçükse merkeze yerleştirilir. Eğer büyükse de içeriğe sığan kısım sınırlar içerisinde gösterilir.
Resmi Kırpma
Bir görseli sınırlar içerisinde nasıl ölçeklendireceğimizi gördük. Hadi şimdi de görsel sınırlarına biraz şekil verelim. Bunun için Modifier.clip
kullanmamız gerekmektedir. Eğer resmimize çember şeklini vermek istiyorsanız CircleShape
methodunu kullanmak yeterli olacaktır.
Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description),
contentScale = ContentScale.Crop,
modifier = Modifier
.size(200.dp)
.clip(CircleShape)
)
Eğer resmimizin köşeleri yuvarlatılmış olsun istiyorsak RoundedCornerShape
kullanmamız yeterli olacaktır. Methodun içine gireceğimiz Dp
değeri ile görselin köşelerine ait keskinliği istediğimiz ölçüde azaltabiliriz.
Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description),
contentScale = ContentScale.Crop,
modifier = Modifier
.size(200.dp)
.clip(RoundedCornerShape(16.dp))
)
Ayrıca istersek kendi özel Shape
‘ imizi oluşturabiliriz. Aşağıdaki örnekte SquashedOval
şekli Shape
‘ den kalıtım aldığı için görsel özelleştirilmiş olduk.
Resme Çerçeve Ekleme
Genellikle resme çerçeve eklerken yukarıda bahsettiğimiz kırpma işlemiyle birlikte kullanırız. Aşağıdaki örnekte resme çerçeve eklenirken CircleShape
olacağı belirtiliyor. Aynı zamanda görsel CircleShape
şeklinde kırpılıyor ve bu sayede en düzgün çerçeve eklenmiş oluyor.
val borderWidth = 4.dp
Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description),
contentScale = ContentScale.Crop,
modifier = Modifier
.size(150.dp)
.border(
BorderStroke(borderWidth, Color.Yellow),
CircleShape
)
.padding(borderWidth)
.clip(CircleShape)
)
Eğer çerçevemizin gradyan olmasını istiyorsak Brush
apisini kullanarak bunu yapabiliriz. Tek yapmamız gereken Brush
içine gradyanda kullanılacak renkleri vermek.
Resme özel en boy oranı belirleme
Herhangi bir resim için en boy oranı belirlemek istediğimizde Modifier.aspectRatio(16f/9f)
gibi istediğimiz değerleri verebiliriz.
Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description),
modifier = Modifier.aspectRatio(16f / 9f)
)
Resme renk filtresi uygulama
Image
‘ e colorFilter
parametresi tanımlayarak resmin tek tek pikselleri ile oynama yapabiliriz.
Resmi renklendirme
ColorFilter.tint(color,blendMode)
şeklinde filtre vererek resmi renklendirebiliriz. Varsayılan blendMode
değeri BlendMode.SrcIn
‘ dir. Bu resmi renklendirmede en uygun tiptir.
Dipnot: Bu renklendirme işlemi daha çok ikonlarda kullanılır
Image(
painter = painterResource(id = R.drawable.baseline_directions_bus_24),
contentDescription = stringResource(id = R.string.bus_content_description),
colorFilter = ColorFilter.tint(Color.Yellow)
)
Diğer BlendMode
tipleri farklı çıktı sonuçlarına sebep olacaktır. Örneğin BlendMode.Darken
ve Color.Green
verdiğimizde aşağıdaki çıktıyı elde ederiz;
Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description),
colorFilter = ColorFilter.tint(Color.Green, blendMode = BlendMode.Darken)
)
Diğer BlendMode
tipleri için buraya tıklayabilirsin.
Resmi siyah beyaz yapma
ColorMatrix kullanırken eğer saturation
değerini 0f
yaparsak resmi siyah beyaz olarak ayarlamış oluruz.
Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description),
colorFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) })
)
Resmin kontrastını ve parlaklığını değiştirme
val contrast = 2f // 0f..10f (1 değeri varsayılan değerdir)
val brightness = -180f // -255f..255f (0 değeri varsayılan değerdir)
val colorMatrix = floatArrayOf(
contrast, 0f, 0f, 0f, brightness,
0f, contrast, 0f, 0f, brightness,
0f, 0f, contrast, 0f, brightness,
0f, 0f, 0f, 1f, 0f
)
Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description),
colorFilter = ColorFilter.colorMatrix(ColorMatrix(colorMatrix))
)
Resmin renklerini tersine çevirme
val colorMatrix = floatArrayOf(
-1f, 0f, 0f, 0f, 255f,
0f, -1f, 0f, 0f, 255f,
0f, 0f, -1f, 0f, 255f,
0f, 0f, 0f, 1f, 0f
)
Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description),
colorFilter = ColorFilter.colorMatrix(ColorMatrix(colorMatrix))
)
Resmi bulanıklaştırma
Bir resmi bulanıklaştırmak için Modifier.blur()
kullanılır. X ve Y yönleri için radiusX
ve radiusY
değerleri verilmesi gerekir.
Image(
painter = painterResource(id = R.drawable.dog),
contentDescription = stringResource(id = R.string.dog_content_description),
contentScale = ContentScale.Crop,
modifier = Modifier
.size(150.dp)
.blur(
radiusX = 10.dp,
radiusY = 10.dp,
edgeTreatment = BlurredEdgeTreatment(RoundedCornerShape(8.dp))
)
)
Dipnot: Eğer bulanıklaştıracağımız resme şekil vereceksek aynı şekilde bulanıklaştırmada bu şekli belirtmek,
edgeTreatment
parametresiniBlurredEdgeTreatment.Unbounded
yerineBlurredEdgeTreatment(Shape)
şeklinde şekil tanımlamak önerilen yöntemdir. Eğer resme şekil vermeyeceksekBlurredEdgeTreatment.Unbounded
sorun yaratmayacaktır.
Performans açısından dikkat edilmesi gerekenler
Şimdiye kadar resim ekranda nasıl gösterilir ve nasıl özelleştirilir gibi konulara değindik. Peki bu resmi gösterirken uygulama performansı nasıl etkileniyor? Daha hızlı ve az yer kaplayan resimler kullanmak için neler yapmalıyız yakından bakalım.
Sadece ihtiyacın olan boyutta bitmap göster
Çoğu akıllı telefon kameraları yüksek kalitede resimler çekmemize olanak tanıyor. Eğer bu çekilen görseli ekranda göstermek istersek boyutunu küçültmemiz gerekmektedir. Eğer bunu yapmazsak GPU ‘ nun önbelleğini tüketebilir ve uygulama ekranları açılırken takılarak açılma gibi sorunlarla karşılaşabiliriz. Resim boyutunu küçültmek için:
- Çıktı görüntüsünü etkilemeyecek kadar resimlerin boyutlarını küçültebiliriz.
- JPEG ya da PNG kullanmak yerine resimlerimizi WEBP’ ye dönüştürebiliriz.
- Farklı ekran çözünürlükleri için farklı çözünürlükte görseller sağlayabiliriz.
- Yukarıda bahsettiğim third-party kütüphaneleri kullanarak fazladan kod yazmadan görsel boyutunu küçülterek ekranda gösterebiliriz.
Yapılabiliyorsa bitmap yerine vector kullan
Ekranda bir resim gösterirken bu resmi acaba vector olarak ekleyebilir miyim diye düşünmek gerekir çünkü bu sayede vector hem daha az yer kaplar, hem de çözünürlük açısından pikselleşme olmaz.
Farklı ekran çözünürlükler için resmin farklı boyutlarını ekle
Farz edelim ki elimizde yüksek çözünürlüklü görsel var, biliyoruz ki bu görseli direkt drawable klasöre ekler ve kullanırsak çözünürlük farketmeksizin bütün cihazlarda bu görsel görüntülenecek. Bu da hem eski cihazlar için render sorununa hem de uygulama boyutunun büyümesine sorun olacaktır. Bunu şu şekilde çözebiliriz; respresso.io gibi kütüphaneler yüksek çözünürlüklü görselleri farklı çözünürlüklere dönüştürmemize yarar. Bu toolu kullanarak elde ettiğimiz yeni çözünürlükteki görselleri aşağıda gösterdiğim şekile projemize ekleyelim. Bu sayede android ekran çözünürlüğüne göre eklediğimiz görsellerden en uygunu seçer ve kullanır.
Dipnot: Bir çok farklı çözünürlükte görsel ekliyoruz diye uygulamamızın boyutu daha fazla büyümez. Uygulamayı store a yüklediğimizde bütün çözünürlükler ile yüklenir ama herhangi bir kullanıcı uygulamayı indirirken store, cihazın çözünürlüğüne bakar ve cihaza en uygun görselle uygulamayı kullandırır. Bu da tam aksine boyut küçülmesine katkı sağlar.
ImageBitmap kullanacaksan ekranda göstermeden önce prepareToDraw methodunu çağır
ImageBitmap
kullanırken henüz görseli ekranda göstermeden önce ImageBitmap#prepareToDraw()
methodunu çağırılırsa GPU ‘ nun görseli hazırlamasına ve ekranda gösterileceği zaman performansın iyileşmesine katkı sağlar. Ama hali hazırda bir third-party bir resim yükleme kütüphanesi varsa muhtemelen o da bu işi kendi içinde yapıyordur.
Composable içine painter atamak yerine url ya da Int DrawableRes ata
Başlık çok açıklayıcı olmasa da şöyle aşağıdaki kod örneğinden ne demek istediğimizi açıklayalım;
// Bunun yerine:
@Composable
fun MyImage(painter: Painter) {
}
// Bunu tercih et:
@Composable
fun MyImage(url: String) {
}
Bunun sebebi Painter
api Stable class değildir ve içinde verdiğimiz image — özellikle Bitmap
— karmaşık bir yapı olduğu için derleyici stable olmayan classların yani Painter
‘ in değişip değişmediğini kolay kolay anlayamaz. Bundan dolayı da tekrar tekrar görseli ekrana çizmeye çalışır. Eğer bir url ya da id verdiğimizde değişiklik kolaylıkla anlaşılacağı için gereksiz ekran yenilemeleri olmaz.
Çok gerekmedikçe Bitmap’ i parametre içinde tutma
Bitmap bellek açısından çok yer kapladığı eğer çok fazla Bitmap bellekte tutarsak ilerleyen zamanda out of memory hatasıyla karşılaşmamız çok olağan. Örneğin resimlerden oluşan bir liste göstereceğimiz zaman LazyColumn
ya da LazyRow
kullanmak bizim için daha faydalı olur. Çünkü bu Composable’ lar görsel ekranda olmadığında bellekte tutmaz ve gereksiz yer tutmasını önler.
Büyük boyutlu resimler APK/AAB dosyasında barındırma
En çok karşılaşılan durumlardan biri de büyük boyutlu görsellerin APK/AAB dosyalarda barındırılamasıdır diyebiliriz. Bunun için APK analyzer toolu kullanıp apk mızı analiz edebiliriz. Sadece gerekli resim dosyalarını apk da barındırmak önemlidir.
Bu yazımızda Jetpack Compose Image ile ilgili bir çok konuya değinmeye çalıştık. Okuduğun için teşekkür ederim, bir sonraki yazıda görüşmek üzere.