I have an Android widget for the home screen, whose layout is basically an ImageView:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/rl_ultraviolet_widget_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true"
android:gravity="center">
<ImageView
android:id="#+id/iv_ultraviolet_widget_image"
android:layout_width="#dimen/widget_size"
android:layout_height="#dimen/widget_size"
android:src="#drawable/ic_uvi_loading" />
</RelativeLayout>
The ImageView will be updated over the time with the "current" value, which is represented with an image (a drawable) using this code:
/**
* Updates ultraviolet index widget view.
*/
#JvmStatic
private fun updateWidgetViews(context: Context, appWidgetId: Int, #DrawableRes drawableId: Int) {
val widgetView = RemoteViews(context.packageName, R.layout.ultraviolet_widget)
// Set an Intent to launch MainActivity when clicking on the widget
val intent = Intent(context, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
widgetView.setOnClickPendingIntent(R.id.rl_ultraviolet_widget_layout, pendingIntent)
val appWidgetTarget = AppWidgetTarget(context, R.id.iv_ultraviolet_widget_image, widgetView, appWidgetId)
Glide
.with(context)
.asBitmap() // This is needed because AppWidgetTarget is a Bitmap target. See: https://github.com/bumptech/glide/issues/2717#issuecomment-351791721
.transition(BitmapTransitionOptions.withCrossFade())
.load(drawableId)
.into(appWidgetTarget)
pushWidgetUpdate(context, appWidgetId, widgetView)
}
/**
* Pushes update for a given widget.
*/
#JvmStatic
fun pushWidgetUpdate(context: Context, appWidgetId: Int, remoteViews: RemoteViews) {
val manager = AppWidgetManager.getInstance(context)
// Remember to check always for null in platform types (types ended in !).
// See: https://stackoverflow.com/a/43826700/5189200
manager?.let {
manager.updateAppWidget(appWidgetId, remoteViews)
Timber.d("Widget %d was updated", appWidgetId)
}
}
My question is how to also update the contentDescription property in the widget's ImageView with a description on every update and using Glide, so that my widget is more accessible and it can be used with TalkBack. I have searched, but I haven't found any example with Glide and a remote view.
Thanks in advance,
Xavi
You can add listener to Glide when load an image. So, when load image success, you update the contentDescription of imageView.
You can read how to add listener to Gilde in this: https://github.com/codepath/android_guides/wiki/Displaying-Images-with-the-Glide-Library#loading-errors
GlideApp.with(context)
.load("http://via.placeholder.com/300.png")
.placeholder(R.drawable.placeholder)
.error(R.drawable.imagenotfound)
.listener(new RequestListener<Drawable>() {
#Override
public boolean onLoadFailed(#Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
// log exception
Log.e("TAG", "Error loading image", e);
return false; // important to return false so the error placeholder can be placed
}
#Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
// load image success, update the contentDescription here
return false;
}
})
.into(ivImg);
Thanks to the help of #Nguyễn Quang Thọ, I found the solution. First, I created a data class (a tuple) in Kotlin for holding drawable and its associated string:
data class UltravioletImageContents(#DrawableRes val drawableId: Int, val contentDescription: String)
Second, I used the Glide result listener to add the contentDescription update and push the changes to the widget in my updateWidgetViews function.
#JvmStatic
private fun updateWidgetViews(context: Context, appWidgetId: Int, ultravioletImageContents: UltravioletImageContents) {
val widgetView = RemoteViews(context.packageName, R.layout.ultraviolet_widget)
// Set an Intent to launch MainActivity when clicking on the widget
val intent = Intent(context, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
widgetView.setOnClickPendingIntent(R.id.rl_ultraviolet_widget_layout, pendingIntent)
// Set image
val appWidgetTarget = AppWidgetTarget(context, R.id.iv_ultraviolet_widget_image, widgetView, appWidgetId)
Glide
.with(context)
.asBitmap() // This is needed because AppWidgetTarget is a Bitmap target. See: https://github.com/bumptech/glide/issues/2717#issuecomment-351791721
.transition(BitmapTransitionOptions.withCrossFade())
.load(ultravioletImageContents.drawableId)
.listener(object : RequestListener<Bitmap> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: com.bumptech.glide.request.target.Target<Bitmap>?, isFirstResource: Boolean): Boolean {
Timber.e(e, "Drawable cannot be loaded and set in widget image view")
return false // Important to return false so the error placeholder can be placed
}
override fun onResourceReady(resource: Bitmap?, model: Any?, target: com.bumptech.glide.request.target.Target<Bitmap>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
// Set contentDescription in order to enable accessibility and be usable with TalkBack
widgetView.setContentDescription(R.id.iv_ultraviolet_widget_image, ultravioletImageContents.contentDescription)
pushWidgetUpdate(context, appWidgetId, widgetView)
return false
}
})
.into(appWidgetTarget)
}
Lastly, I would like to remark that pushWidgetUpdate was moved inside onResourceReady, since I think it doesn't have sense to update my widget without the image (but, maybe, I am missing something).
Here is a sample in Kotlin
Glide.with(context)
.load(data.imageUrl) // URL Of Image
.placeholder(R.drawable.placeholder)
.error(R.drawable.image_not_found)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
return false // important to return false so the error placeholder can be placed
}
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
myImageView.contentDescription = data.accessibilityText
return false
}
})
.into(myImageView)
Related
I'm trying to solve a problem with getting the dominant color in an image, but I can't convert it to a bitmap, because I get null all the time. What could the problem be?
View itself in XML:
<androidx.appcompat.widget.AppCompatImageView
android:id="#+id/ivIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintCircleRadius="8dp"
app:srcCompat="#drawable/icon_default"
tools:srcCompat="#drawable/icon_default" />
Getting the image goes through Glide by loading the image by url (fun loadInto (...)) and after that I try to get the drawable:
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewOutput.iconFlow.collect { photoUrl ->
if (url != null) {
loadInto(url, binding.Icon)
getDominantColor(binding.ivIcon) <- null here
}
}
}
Update:
private fun getDominantColor(image: AppCompatImageView) {
val bitmap: Bitmap = (image.drawable as BitmapDrawable).bitmap
Palette.from(bitmap).generate { palette ->
val dom: Int = palette!!.getDominantColor(0x000000)
setGradientColor(dom)
}
}
fun loadInto(
url: String,
imageView: ImageView,
placeHolder: Int = 0,
errorHolder: Int = 0,
) {
Glide
.with(imageView)
.load(url)
.run { if (placeHolder != 0) placeholder(placeHolder) else this }
.run { if (errorHolder != 0) error(errorHolder) else this }
.into(imageView)
}
As mentioned by #Lino, Glide will make an asynchronous call to get the image. This will take some time and it may return null if you get the Bitmap from the ImageView right after it.
Your attempt to create a listener callback is correct. And if it is properly implemented, onResourceReady() method should be called once ready. You may refer the sample below:
Glide
.with(this#MainActivity)
.load(url)
.run { this }
.run { this }
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
// Process with your bitmap in callback here
Palette.from((resource!! as BitmapDrawable).bitmap).generate { palette ->
val dom: Int = palette!!.getDominantColor(0x000000)
setGradientColor(dom)
}
return false
}
})
.into(ivIcon)
This is kotlin code. This glide load the GIF image and once complete one cycle then go to next activity. This is work well.
Glide.with(this)
.load(R.drawable.splash_logo1)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
(resource as GifDrawable).setLoopCount(1)
resource.registerAnimationCallback(object :
Animatable2Compat.AnimationCallback() {
override fun onAnimationEnd(drawable: Drawable) {
startActivity(
Intent(
this#SplashActivity,
LanguageActivity::class.java
)
)
finish()
}
})
return false
}
})
.into(imageView)
This is composable code
val painter = rememberAsyncImagePainter(
ImageRequest.Builder(context).data(data = R.drawable.splash_logo).apply {
size(Size.ORIGINAL)
}.build(), imageLoader = imageLoader
)
Image(
painter = painter,
contentDescription = null,
modifier = Modifier.fillMaxSize()
)
Here GIF image loading continously. I've need listiner to know the cycle compliction and no repeat mode then go to next process. How to achive in Composable.
If anyone know please share your comments.
Thanks.
I need to download an image and keep it as variable to put it into a notification:
.setLargeIcon(bitmap)
This is the code, I hope, it's clear by the comments what I try:
var bitmap = BitmapFactory.decodeResource(this#MainActivity.resources, R.drawable.notif_smiley) // create placeholder bitmap
val requestOptions = RequestOptions()
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
bitmap = Glide.with(this#MainActivity)
.asBitmap()
.load(imgurl)
.listener(object : RequestListener<Bitmap> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
// just dont do anything, keep the placeholder bitmap
return false
}
})
.apply(requestOptions)
.submit()
.get()
binding.contentMain.testingGlide.setImageBitmap(bitmap) // this is just for easy testing
I get all kind of error, I tried this based on an answer but there the bitmap goes directly into a view, please help :D
Inside your load failed method you're not setting any image so try changing that also try adding the onerror() method below code implementation should fix it
try{
lateinit var bitmap:Bitmap
bitmap = Glide.with(context)
.asBitmap()
.placeholder(R.mipmap.avengerswallp)
.load(imgUri)
.error(getnothumb())
.listener(object : RequestListener<Bitmap>{
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Bitmap>?, isFirstResource: Boolean): Boolean {
bitmap = myPlaceHolderBitmap
return true
}
override fun onResourceReady(resource: Bitmap?, model: Any?, target: Target<Bitmap>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
return false
}
}).submit().get()
}
catch(ex:Exception)
{
}
Loading an image through data binding is easy. I am using Glide in my project. I have to set placeholder image which will change as per some selection by user. Can we use some expression which accepts imageurl and placeHolder image reference.
<androidx.appcompat.widget.AppCompatImageView
android:id="#+id/vehicle_1_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/margin_twenty"
app:layout_constraintEnd_toEndOf="#id/centerGuideline"
app:layout_constraintStart_toStartOf="#id/centerGuideline"
app:layout_constraintTop_toBottomOf="#id/txt_enter_vehicle_name"
app:loadImage="#{viewModel.imgUrl}" />
#BindingAdapter({"loadImage"})
public static void loadUrlImage(ImageView view, String url, int placeHolderImage){
ImageLoaderUtil.getInstance().loadImageWithCache(view, url, placeHolderImage);
}
public void loadImageWithCache(ImageView imageView, String url, int placeholderImage) {
Glide.with(imageView.getContext())
.load(url)
.apply(getDefaultGlideOptions())
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
.placeholder(placeholderImage)
.into(imageView);
}
Found this nice article: https://ayusch.com/databinding-with-glide-android/
We can also accept multiple arguments in our bindingadapter. For example, one may need to load an error image, or a placeholder while our image loads.
So I think listeners is the answer. Posting also the code in case the link is dead.
companion object {
#JvmStatic
#BindingAdapter(value = ["profileImage", "error"], requireAll = false)
fun loadImage(view: ImageView, profileImage: String, error: Int) {
Glide.with(view.context)
.load(profileImage)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
view.setImageResource(error)
return true
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
view.setImageDrawable(resource)
return true
}
})
.into(view)
}
}
and in your layout:
app:error="#{user.errorImage}"
You can add multiple parameter in BindingAdapter just like this.
#BindingAdapter("url","placeHolderImage")
public static void loadUrlImage(ImageView view, String url, int placeHolderImage)
{
ImageLoaderUtil.getInstance().loadImageWithCache(view, url, placeHolderImage);
}
And you have to add field in Imageview xml just like this.
<androidx.appcompat.widget.AppCompatImageView
android:id="#+id/vehicle_1_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/margin_twenty"
app:layout_constraintEnd_toEndOf="#id/centerGuideline"
app:layout_constraintStart_toStartOf="#id/centerGuideline"
app:layout_constraintTop_toBottomOf="#id/txt_enter_vehicle_name"
app:url="#{viewModel.imgUrl}"
app:placeHolderImage="#{viewModel.}"
/>
You have pass two thing xml Url and Placeholder.
I use glide version 4.7.1
I wanna use default image when onLoadFailed.
this is my code
Glide.with(context).load(imageUrl).listener(new RequestListener<Drawable>() {
#Override
public boolean onLoadFailed(#Nullable GlideException e, Object model, com.bumptech.glide.request.target.Target<Drawable> target, boolean isFirstResource) {
// fail
// How can i use default image in imgvAssetPicture?
}
#Override
public boolean onResourceReady(Drawable resource, Object model, com.bumptech.glide.request.target.Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
// success
imgvLoadingProgressbar.setVisibility(View.INVISIBLE);
imgvAssetPicture.setVisibility(View.VISIBLE);
return false;
}
}).into(imgvAssetPicture);
Glide.with(passContext)
.applyDefaultRequestOptions(new RequestOptions()
.placeholder(R.drawable.ic_user_default)
.error(R.drawable.ic_user_default))
.load(url)
.into(image);
With version 4.7.1 (you are using), you can easily set these options.
placeHolder which shows when there is not image.
error when some URL fails to load.
Bonus
Are you using some ProgressBar with setting visibility, that's very old way to do.
See CircularProgressDrawable, which is very easy to use. Just pass this CircularProgressDrawable in your placeHolder.
Just Copy and Past these line of code wherever you want to use
Easy and simple
val circularProgressDrawable = CircularProgressDrawable(mContext)
circularProgressDrawable.strokeWidth = 5f
circularProgressDrawable.centerRadius = 30f
circularProgressDrawable.start()
Glide.with(mContext)
.load(specificCategoryObject.imgUrl)
.placeholder(R.drawable.circularProgressDrawable)
.transition(DrawableTransitionOptions.withCrossFade())
.error(R.drawable.img_place_holder)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
circularProgressDrawable.alpha = 0
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean,
): Boolean {
circularProgressDrawable.alpha = 0
return false
}
})
.into(holder.ivProductImage)