I'm using a #Composable where I need to pass via parameter an ImageBitmap, the problem is that I get the images from the server given an url so I need to load these images, convert them into a Bitmap and then to a ImageBitmap but I'm quite stuck because I don't know how to convert this to an ImageBitmap, this is my #Composable
#ExperimentalComposeUiApi
#Composable
fun MyCanvas(
myImage: ImageBitmap,
modifier: Modifier = Modifier,
) {
Canvas(modifier = modifier
.size(220.dp)
.clipToBounds()
.clip(RoundedCornerShape(size = 16.dp)) {
...
val canvasWidth = size.width.toInt()
val canvasHeight = size.height.toInt()
val imageSize = IntSize(width = canvasWidth, height = canvasHeight)
drawImage(
image = myImage, dstSize = imageSize
)
...
}
}
So, when I call this #Composable I need to load the image but not sure how to start with and I need to know what's better either using Glide or Coil.
You don't need ImageBitmap to draw it into Canvas. You can draw painter inside DrawScope. You don't even actually need Canvas function either. androidx.compose.foundation.Canvas is nothing other than Spacer with Modifier.drawBehind
#Composable
fun Canvas(modifier: Modifier, onDraw: DrawScope.() -> Unit) =
Spacer(modifier.drawBehind(onDraw))
And answer to your question is
#Composable
private fun MyComposable() {
val sizeModifier = Modifier
.fillMaxWidth()
val url =
"https://avatars3.githubusercontent.com/u/35650605?s=400&u=058086fd5c263f50f2fbe98ed24b5fbb7d437a4e&v=4"
Column(
modifier =Modifier.fillMaxSize()
) {
val painter = rememberAsyncImagePainter(
model = url
)
Canvas(modifier = Modifier
.clip(RoundedCornerShape(size = 16.dp))
.size(220.dp)
) {
with(painter) {
draw(size = size)
}
}
}
}
And when you apply Modifier.clip() you don't have to apply Modifier.clipToBounds()
Both are the same thing with clip uses shape extra to clipToBounds
/**
* Clip the content to the bounds of a layer defined at this modifier.
*/
#Stable
fun Modifier.clipToBounds() = graphicsLayer(clip = true)
/**
* Clip the content to [shape].
*
* #param shape the content will be clipped to this [Shape].
*/
#Stable
fun Modifier.clip(shape: Shape) = graphicsLayer(shape = shape, clip = true)
So, obviously, Dr. Thrace has provided you with an optimum solution which he extracted out of his centuries of experience treating the diseased code. You could go with that. However, from the description you provided in the question, it seemed as if majorly all you wanted was just a way to convert a Bitmap to an ImageBitmap object. There's a handy dandy asImageBitmap() extension function available to use with bitmaps now. So, you could just leverage that easy.
Far as Glide vs Coil is concerned, both have popular API methods that I think achieve similar goals/performance. Now, as far as I can remember, Coil was the first to migrate to Compose. I've only used Coil since I required fetching an image from a url and wanted to simply display it, so Coil provided a pretty damn straight forward API for that, rememberCoilPainter() which worked like gravity. Haven't really used the other one but just Google both looking for a comparison. It'll be available absolutely, or just dig through the documentation of both to see which methods are available and design your code structure in your mind while doing so; choose the one that implements easier. The site isn't generally used for taking opinions so I'll leave it there. Nice trick might be - the better the documentation the better the API.
Related
I have an icon to indicate sequence.
My requirement is that when the order is reversed, the lower half of the icon turns blue; when the order is turned, the upper half of the icon turns blue.
I found a related question, but it conflicts with my needs in two points. First, I don't know how to write such code in compose. Second, I prefer to use code to control the color transformation.
Using BlendModes you can manipulate any pixel using another shape, png file, drawing or Path. You can refer these answers for more details
Jetpack Compose Applying PorterDuffMode to Image
How to clip or cut a Composable?
Result
Implementation
#Preview
#Composable
private fun Test() {
Image(
modifier = Modifier
.size(100.dp)
.drawWithContent {
val height = size.height
with(drawContext.canvas.nativeCanvas) {
val checkPoint = saveLayer(null, null)
// Destination
drawContent()
// Source
drawRect(
Color.Red,
topLeft = Offset(0f, height / 2),
size = Size(size.width, size.height / 2),
blendMode = BlendMode.SrcIn
)
}
},
painter = painterResource(id = R.drawable.arrows),
contentDescription = null
)
}
I am trying to make the ImageComposable wrap its height and width according to its content, along with the two Text composable, align to the bottom of Assemble composable. Following is the code for that:
#Composable
fun ImageComposable(url:String){
val painter = rememberAsyncImagePainter(
model = ImageRequest.Builder(LocalContext.current).data(url).apply{
placeholder(drawableResId = R.drawable.ic_broken_pic)
}.build()
)
Image(painter = painter, contentDescription = null, Modifier.padding(2.dp).border(width = 2.dp, shape = CircleShape, color = MaterialTheme.colors.onPrimary)
}
#Composable
fun Assemble(url:String){
Column (modifier = Modifier.fillMaxWidth().height(400.dp).background(MaterialTheme.colors.primary)
.padding(16.dp), verticalArrangement = Arrangement.Bottom) {
ImageComposable(url)
Text(text = "title")
Text(text = "Body")
}
}
but the ImageComposable ends up taking all the height and width of the Assemble composable and I am not able to see the two Text composables that I added in the column. So I am confused as to what is the exact problem here. I thought at least it should show the ImageComposable along with the two Text composable but it is not happening.
I am using coil image loading library here for parsing the image from url. For now in testing, I am passing url as an Empty String. Hence I am calling the composable as:
Assemble("")
I didn't find any document that would help me understand this behavior. So I wanted to know the reason to this problem and possible solutions to overcome it.
You can explicitly specify the height of each component:
fun ImageComposable(modifier: Modifier = Modifier, url: String){
//...
Image(modifier = modifier, //...
}
Column(//..
ImageComposable(modifier = Modifier.height(200.dp)//...
Text(modifier = Modifier.height(50.dp)//...
Text(modifier = Modifier.height(150.dp)//...
}
Or you can specify a fraction of the maximum height it will take up:
fun ImageComposable(modifier: Modifier = Modifier, url: String){
//...
Image(modifier = modifier, //...
}
Column(//..
ImageComposable(modifier = Modifier.fillMaxHeight(0.75f)//...
Text(modifier = Modifier.fillMaxHeight(0.1f)//...
Text(modifier = Modifier.fillMaxHeight(0.15f)//...
}
You can also try playing with the weight modifier:
fun ImageComposable(modifier: Modifier = Modifier, url: String){
//...
Image(modifier = modifier, //...
}
Column(//..
ImageComposable(modifier = Modifier.weight(1f)//...
Text(modifier = Modifier.weight(1f, fill = false)//...
Text(modifier = Modifier.weight(1f, fill = false)//...
}
It would be easier to solve your problem if there would be a sketch of what you want to achieve.
Nevertheless, I hope I can help:
It looks like the issue you are facing can be handled by Intrinsic measurements in Compose layouts.
The column measures each child individually without the dimension of your text constraining the image size. For this Intrinsics can be used.
Intrinsics lets you query children before they're actually measured.
For example, if you ask the minIntrinsicHeight of a Text with infinite width, it'll return the height of the Text as if the text was drawn in a single line.
By using IntrinsicSize.Max for the width of the Assemble composable like this:
#Composable
fun Assemble(url: String) {
Column(
modifier = Modifier
.width(IntrinsicSize.Max)
.background(MaterialTheme.colors.primary)
.padding(16.dp), verticalArrangement = Arrangement.Bottom
) {
ImageComposable(url)
Text(text = "title")
Text(text = "Body")
}
}
you can can create a layout like this:
(Please note that I am using a local drawable here)
You can now see the 2 texts and the width of the image is adjusted to the width of the texts.
Using Intrinsics to measure children in dependance to each other should help you to achieve what you wanted.
Please let me know if this layout does not meet your expectations.
compose.animation:animation-graphics API will certainly be removed and so I want to know what is the best way to use animated vector drawable with JetPack Compose?
Here is the ancient code of using the API:
val image = animatedVectorResource(drawableId)
var atEnd by remember { mutableStateOf(false) }
Image(
painter = image.painterFor(atEnd),
contentDescription = "Your content description",
modifier = Modifier.size(64.dp).clickable {
atEnd = !atEnd
}
)
From android docs:
Note: This API is transient and will be likely removed for encouraging async resource loading.
I have a pager (Accompanist) with image that are get from web with Coil in Compose.
The rememberPainter() seem to only call the request when the Image composable is shown for the first time.
So when I slide page in the pager, the Image show only at that moment and so we have to wait a moment.
Any way to force the rememberPainter (Coil) to preload ?
Edit 1 :
Here is a simple version of my implementation (with lots of stuff around removed but that had no impact on the result) :
#Composable
private fun Foo(imageList: List<ImageHolder>) {
if (imageList.isEmpty())
return
val painterList = imageList.map {
rememberImagePainter(
data = it.getDataForPainter(),
builder = {
crossfade(true)
})
}
ImageLayout(imageList, painterList)
}
#Composable
fun ImageLayout(imageList: List<ImageHolder>, painterList: List<Painter>) {
HorizontalPager(
count = imageList.size,
modifier = Modifier.fillMaxWidth(),
) { page ->
Image(
painter = painterList[page],
"",
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.background(
imageList[page].colorAverage
),
contentScale = ContentScale.Crop
)
}
}
I tried with having directly the rememberImagePainter at the Image too just in case.
But the problem is clearly that Image() of page 2, 3, 4 isn't rendered so Image don't do the call to the painter. I tried to look how it work inside but couldn't find.
Edit 2 : I found a workaround but it's not clean
for (painter in painterList)
Image(painter = painter, contentDescription = "", modifier = Modifier.size(0.001.dp))
It force coil to load image and with a size really small like 0.001.dp (with 0 it don't load).
Also the problem is that in the builder you need to force a size or it will just load one pixel, so I force the full size of image as I don't know what size would have been available for the image.
In the Coil documentation there is a section about preloading. Depending on your architecture, you can do this in different places, the easiest is to use LaunchedEffect:
val context = LocalContext.current
LaunchedEffect(Unit) {
val request = ImageRequest.Builder(context)
.data("https://www.example.com/image.jpg")
// Optional, but setting a ViewSizeResolver will conserve memory by limiting the size the image should be preloaded into memory at.
// For example you can set static size, or size resolved with Modifier.onSizeChanged
// .size(coil.size.PixelSize(width = 100, height = 100))
// or resolve display size if your images are full screen
// .size(DisplaySizeResolver(context))
.build()
context.imageLoader.enqueue(request)
}
Before version 2.0.0 use LocalImageLoader.current instead of context.imageLoader to get the image loader.
Here is full code for accompanist pager with coil preloading and indicator with video example
I'm migrating my app and for the grid view I used LazyVerticalGrid that loads images using coilPainter but the scrolling stutters a lot and it feels laggy. Does anyone have a solution or a better implementation for this?
val photos: List<Photo>? by photosViewModel.photosLiveData.observeAsState(null)
Surface(modifier = Modifier.fillMaxSize()) {
Box(
modifier = Modifier.fillMaxSize()
) {
LazyVerticalGrid(
cells = GridCells.Fixed(3)
) {
photos?.let { photosList ->
items(photosList) { photo ->
Image(
modifier = Modifier.padding(2.dp).clickable {
photosViewModel.onPhotoClicked(photo)
},
painter = rememberCoilPainter(photo.url),
contentDescription = stringResource(R.string.cd_details_photo),
contentScale = ContentScale.Fit,
alignment = Alignment.Center,
)
}
}
}
}
}
Update
Trying to move rememberCoilPainter above Image like val painter = rememberCoilPainter(photo.url) and use the painter inside the Image did not work
items takes an additional parameter key, assign this parameter to a unique value like the index of each photo of your list, If you don't know how does that relate to scrolling jank? Or what the heck am I talking about? check out the following documentation for a detailed explanation:
https://developer.android.com/jetpack/compose/lifecycle#add-info-smart-recomposition
After updating my compose image loading library to coil-compose I was forced to set a size to either the request builder or the image itself. The problem of the stuttering was caused by allowing the photo to load the original size and thus recomposing was done multiple times and loading the image had to wait for the picture to load. By setting e fixed height to the Image fixed the issue because the Images() were created and the recomposition was made only for the picture inside not for the Image itself with different height.