I'm using coil:0.10.0 in my project but it's not as expected, I want to change the image's background
val painter = rememberCoilPainter(
fadeIn = true,
request = ImageRequest.Builder(LocalContext.current)
.data(entry.imageUrl)
.target {
viewModel.calcDominantColor(it) { color ->
dominantColor = color
}
}
.build()
)
Column {
Image(
painter = painter,
contentDescription = entry.pokemonName,
modifier = Modifier
.size(120.dp)
.align(CenterHorizontally)
)
}
}
Expect:
Try Modifier.background(brush = verticalGradient(listOf(dominantColor, Color.White)))
Related
I want to display web images by URL links. I want to display progress bar until image will be loaded and then hide progress bar and display image.
How to detect when painter loaded image?
HorizontalPager(count = photoUrlList.size, state = photoPagerState) { page ->
var shouldBeProgressBarDisplayed by mutableStateOf(true)
val painter = rememberImagePainter(photoUrlList[page], builder = {
this.listener(
onSuccess = { request, ex ->
shouldBeProgressBarDisplayed = false
})
})
if (shouldBeProgressBarDisplayed) {
ProgressBar()
} else {
Image(
painter = painter,
modifier = Modifier
.padding(vertical = 100.dp)
.fillMaxSize(),
contentScale = ContentScale.Crop,
contentDescription = ""
)
}
}
You can use painter.state to achieve this
HorizontalPager(count = photoUrlList.size, state = photoPagerState) { page ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(vertical = 100.dp)
) {
val painter = rememberImagePainter(
data = photoUrlList[page],
builder = {
crossfade(500)
})
val painterState = painter.state
if (painterState is ImagePainter.State.Loading) {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center),
color = MaterialTheme.colors.secondary
)
} else {
Image(
painter = painter,
modifier = Modifier
.fillMaxSize(),
contentScale = ContentScale.Crop,
contentDescription = ""
)
}
}
}
This question already has answers here:
Auto Height Jetpack Compose Coil Image
(4 answers)
Closed 1 year ago.
Am trying to load multiple images inside a LazyRow composable , the painter state is always giving me an empty state, i dont know what am missing.
The code of the composable
fun NetworkImage(
url: String,
contentDescription: String = "",
modifier: Modifier,
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
alpha: Float = DefaultAlpha,
colorFilter: ColorFilter? = null,
placeholderDrawableRes: Int? = null,
crossFade: Int? = null,
transformations: List<Transformation>? = null,
onLoading: #Composable () -> Unit,
onError: #Composable () -> Unit
) {
Box(
modifier = modifier
){
val painter = rememberImagePainter(
data = url,
builder = {
placeholderDrawableRes?.let {
placeholder( drawableResId = it )
}
error(
R.drawable.ic_warning
)
crossFade?.let {
crossfade(durationMillis = it)
}
transformations?.let {
transformations(transformations = it)
}
}
)
val imageState = painter.state
if(imageState is ImagePainter.State.Loading){
onLoading()
}
if(imageState is ImagePainter.State.Error){
onError()
}
Image(
painter = painter,
contentDescription = contentDescription,
contentScale = contentScale,
alignment = alignment,
alpha = alpha,
colorFilter = colorFilter
)
}
}
Inside The LazyRow
LazyRow(modifier = modifier) {
items(items = urls){ url ->
NetworkImage(
url = url,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize(),
onLoading = {
ConstraintLayout(
modifier = Modifier.fillMaxSize()
) {
val indicator = createRef()
CircularProgressIndicator(
modifier = Modifier.constrainAs(indicator) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
end.linkTo(parent.end)
}
)
}
},
onError = {
Icon(
painter = painterResource(id = R.drawable.ic_warning),
contentDescription = "",
tint = Color.Red
)
}
)
}
}
When i use the NetworkImage Composable only once it works fine , but when i use it inside a loop or a LazyRow , it renders an empty Box.
Any hints.
You better use Accompanists Pager(https://google.github.io/accompanist/pager/)
add Pagers liberary to app level module
implementation "com.google.accompanist:accompanist-pager:0.18.0"
then use this code as a sample
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
val pagerState = rememberPagerState(pageCount = urls.size)
HorizontalPager(state = pagerState) { index ->
NetworkImage(
url = urls[index],
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxWidth()
.height(300.dp),
onLoading = {
ConstraintLayout(
modifier = Modifier.fillMaxSize()
) {
val indicator = createRef()
CircularProgressIndicator(
modifier = Modifier.constrainAs(indicator) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
end.linkTo(parent.end)
}
)
}
},
onError = {
Icon(
painter = painterResource(id = R.drawable.ic_warning),
contentDescription = "",
tint = Color.Red
)
}
)
}
}
use NetworkImage composable as it is.
I recently migrated from Accompanist's ImagePainter to Coil's, below is the pertinent code after my updates.
val painter = rememberImagePainter(DRAWABLE_RESOURCE_ID)
when (painter.state) {
is ImagePainter.State.Empty -> Timber.w("Empty")
is ImagePainter.State.Loading -> {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.wrapContentSize()
) {
CircularProgressIndicator()
}
}
is ImagePainter.State.Success -> {
Image(
painter = painter,
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier
.padding(8.dp)
.size(84.dp)
.clip(RoundedCornerShape(corner = CornerSize(16.dp)))
)
}
is ImagePainter.State.Error -> Timber.e("Error")
}
Now those images don't render and painter.state is always Empty. My legacy Accompanist implementation displayed images by this point in the code. It also works if I use the stock painterResource(resId) from Compose.
What am I missing to execute Coil's new painter through its states?
As suggested by #Philip Dukhov you don't need coil to load local resources.
If you want to use it, you can simply your code using:
val painter = rememberImagePainter(R.drawable.xxx)
val state = painter.state
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.wrapContentSize()
) {
AnimatedVisibility(visible = (state is ImagePainter.State.Loading)) {
CircularProgressIndicator()
}
Image(
painter = painter,
contentDescription = null,
modifier = Modifier.size(128.dp)
)
}
You can use the drawable's resource ID as model for the coil's AsyncImage composable.
AsyncImage(
model = R.drawable.bg_gradient,
contentDescription = "Gradient background",
)
There's a significant performance difference between the composables - Image and AsyncImage. So Coil's AsyncImage loading is very handy at times.
You don't need coil to load local resources. You can use system painterResource:
Image(
painter = painterResource(id = R.drawable.test),
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier
.padding(8.dp)
.size(84.dp)
.clip(RoundedCornerShape(corner = CornerSize(16.dp)))
)
If you would use it for remove image loading: since move from accompanist to coil, painter won't start loading unless Image is in the view tree hierarchy. So you can move Image into a Box with your while:
Box(contentAlignment = Alignment.Center) {
val painter = rememberImagePainter(R.drawable.test)
Image(
painter = painter,
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier
.padding(8.dp)
.size(84.dp)
.clip(RoundedCornerShape(corner = CornerSize(16.dp)))
)
when (painter.state) {
is ImagePainter.State.Empty -> Timber.w("Empty")
is ImagePainter.State.Loading -> {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.wrapContentSize()
) {
CircularProgressIndicator()
}
}
is ImagePainter.State.Success -> {
}
is ImagePainter.State.Error -> Timber.e("Error")
}
}
Also it may not start loading when you're not providing enough size modifiers(that's not your case, just for you to know). Check out this answer for more information.
val context = LocalContext.current
val imageLoader = ImageLoader(context)
val request = ImageRequest.Builder(context)
.data(thumbnailUrl)
.build()
val painter = rememberImagePainter(
request = request,
imageLoader = imageLoader
)
val state = painter.state
Image(
painter = painter,
contentDescription = "thumbnail image",
modifier = Modifier
.fillMaxSize()
.placeholder(
visible = state is ImagePainter.State.Loading,
color = PlaceholderDefaults.color(
backgroundColor = SMXTheme.colors.shimmer.copy(0.1f),
),
highlight = PlaceholderHighlight.shimmer(),
),
contentScale = ContentScale.Crop
)
I am trying to show an image from the gallery if anything chosen by the user, or an image from an image file in the drawable resource as the default image, which is not working. I am using Coil for Compose and added the dependency already. Here is the code:
class MainActivity : ComponentActivity() {
private var imageUriState = mutableStateOf<Uri?>(null)
private val selectImageLauncher = registerForActivityResult(GetContent()) { uri ->
imageUriState.value = uri
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ImageSourceActivityScreen()
}
}
#Composable
fun ImageSourceActivityScreen() {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center
) {
Image(
painter = rememberImagePainter(
if (imageUriState != null) {
imageUriState.value
} else {
R.drawable.blank_profile_picture
}
),
contentDescription = "profile image",
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxWidth()
)
...
}
No error is showing, but the default image is also not showing. Please help to make it work. Thanks!
Your code doesn't work because you're comparing imageUriState with null which is always true
You have two options:
Specify painter depending on your state value
Image(
painter = if (imageUriState.value != null) {
rememberImagePainter(
imageUriState.value
)
} else {
painterResource(id = R.drawable.blank_profile_picture)
},
contentDescription = "profile image",
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxWidth()
)
By default, coil won't display you a placeholder if you pass null as data, which generally makes sense. If you wanna see your placeholder both when there's no image, and when you're waiting image to be loaded, you can create such function:
#Composable
inline fun rememberImagePainter(
data: Any?,
#DrawableRes emptyPlaceholder: Int,
builder: ImageRequest.Builder.() -> Unit = {},
): Painter {
val painter = rememberImagePainter(
data,
builder = {
placeholder(emptyPlaceholder)
builder()
}
)
if (data == null) {
return painterResource(emptyPlaceholder)
}
return painter
}
// usage
Image(
painter = rememberImagePainter(
"https://i.stack.imgur.com/rkyep.jpg",
emptyPlaceholder = R.drawable.test,
),
contentDescription = "profile image",
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
You can use the builder parameter to set a placeholder:
val painter = rememberImagePainter(
imageUriState.value,
builder = {
placeholder(R.drawable.blank_profile_picture)
}
)
Image(
painter,
contentDescription = "profile image",
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxWidth()
)
Found hints of where to look for the bug from #Gabriele Mariotti's answer. I was calling imageUriState without .value. Fixed that, and it is working now. Thanks, everyone for your help!
Image(
painter = rememberImagePainter(
if (imageUriState.value != null) {
imageUriState.value
} else {
R.drawable.blank_profile_picture
}
),
contentDescription = "profile image",
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxWidth()
)
I am using accompanist-coil:0.12.0. I want to load image from a url and then pass the drawable to a method. I am using this:
val painter = rememberCoilPainter(
request = ImageRequest.Builder(LocalContext.current)
.data(imageUrl)
.target {
viewModel.calcDominantColor(it) { color ->
dominantColor = color
}
}
.build(),
fadeIn = true
)
and then passing the painter to Image like this:
Image(
painter = painter,
contentDescription = "Some Image",
)
The image loads without any problem but the method calcDominantColor is never called.
Am I doing it the wrong way?
UPDATE:
I was able to call the method using Transformation in requestBuilder but I am not sure, if this is how it is supposed to be done because I am not actually transforming the Bitmap itself:
val painter = rememberCoilPainter(
request = entry.imageUrl,
requestBuilder = {
transformations(
object: Transformation{
override fun key(): String {
return entry.imageUrl
}
override suspend fun transform(
pool: BitmapPool,
input: Bitmap,
size: Size
): Bitmap {
viewModel.calcDominantColor(input) { color ->
dominantColor = color
}
return input
}
}
)
}
)
This works fine for first time but when the composable recomposes, transformation is returned from cache and my method doesn't run.
I think you want to use LaunchedEffect along with an ImageLoader to access the bitmap from the loader result.
val context = LocalContext.current
val imageLoader = ImageLoader(context)
val request = ImageRequest.Builder(context)
.transformations(RoundedCornersTransformation(12.dp.value))
.data(imageUrl)
.build()
val imagePainter = rememberCoilPainter(
request = request,
imageLoader = imageLoader
)
LaunchedEffect(key1 = imagePainter) {
launch {
val result = (imageLoader.execute(request) as SuccessResult).drawable
val bitmap = (result as BitmapDrawable).bitmap
val vibrant = Palette.from(bitmap)
.generate()
.getVibrantColor(defaultColor)
// do something with vibrant color
}
}
I would suggest using the new coil-compose library. Just copy the following and add it to the app build.gradle file:
implementation "io.coil-kt:coil-compose:1.4.0"
I was also following the tutorial and got stuck at this point. I would suggest copying and pasting the following code:
Column {
val painter = rememberImagePainter(
data = entry.imageUrl
)
val painterState = painter.state
Image(
painter = painter,
contentDescription = entry.pokemonName,
modifier = Modifier
.size(120.dp)
.align(CenterHorizontally),
)
if (painterState is ImagePainter.State.Loading) {
CircularProgressIndicator(
color = MaterialTheme.colors.primary,
modifier = Modifier
.scale(0.5f)
.align(CenterHorizontally)
)
}
else if (painterState is ImagePainter.State.Success) {
LaunchedEffect(key1 = painter) {
launch {
val image = painter.imageLoader.execute(painter.request).drawable
viewModel.calcDominantColor(image!!) {
dominantColor = it
}
}
}
}
Text(
text = entry.pokemonName,
fontFamily = RobotoCondensed,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
Replace your "AsyncImage" with "AsyncImageWithDrawable" :
#Composable
fun AsyncImageWithDrawable(
model: Any?,
contentDescription: String?,
modifier: Modifier = Modifier,
placeholderResId: Int? = null,
errorResId: Int? = null,
fallbackResId: Int? = errorResId,
contentScale: ContentScale,
onDrawableLoad: (Drawable?) -> Unit) {
val painter = rememberAsyncImagePainter(
ImageRequest.Builder(LocalContext.current).data(data = model)
.apply(block = fun ImageRequest.Builder.() {
crossfade(true)
placeholderResId?.let { placeholder(it) }
errorResId?.let { error(it) }
fallbackResId?.let { fallback(it) }
allowHardware(false)
}).build()
)
val state = painter.state
Image(
painter = painter,
contentDescription = contentDescription,
modifier = modifier,
contentScale = contentScale
)
when (state) {
is AsyncImagePainter.State.Success -> {
LaunchedEffect(key1 = painter) {
launch {
val drawable: Drawable? =
painter.imageLoader.execute(painter.request).drawable
onDrawableLoad(drawable)
}
}
}
else -> {}
}
}
I've created this Composable inspired by #Aknk answer and Coil source code.
Hint: You can use this Composable to Load and Render your Image from Url and get your Image Palette by the returned Drawable.
You can use the onSuccess callback to get the drawable:
AsyncImage(
model = url,
contentDescription = null,
onSuccess = { success ->
val drawable = success.result.drawable
}
)
You can use the same approach also with rememberAsyncImagePainter.