I'm trying to display a low-res image as a full tv background while loading the high quality image. With this solution I could avoid the Black-To-image blink effect.
My low-res image is loaded in a previous fragment and is stored in the cache.
I try to achieve this with Coil. I've seen there is a memoryCachePlaceholder, but I couldn't make it work with my solution. Any hint ?
requireContext().imageLoader.enqueue(ImageRequest.Builder(requireContext())
.data(myLowResUrl)
.crossfade(0)
.target(object : TransitionTarget {
override val drawable get() = showImageView!!.drawable
override val view get() = showImageView!!
override fun onSuccess(result: Drawable) {
showImageView?.setImageDrawable(result)
showImageView?.load(myHighResUrl) {
placeholderMemoryCacheKey(showImageView?.result?.request?.memoryCacheKey)
}
}
})
.build())
Related
I want to use palette to get a color and fill it before glide successfully loads the picture.
Just like bing.com or twitter.com, a color will cover the image before it is successfully loaded.
I use the following methods to achieve it:
Glide
.with(imageView.context)
.asBitmap()
.load(imageUrl)
.listener(object : RequestListener<Bitmap> {
override fun onLoadFailed(e: GlideException?, model: Any, target: Target<Bitmap>, isFirstResource: Boolean) = false
override fun onResourceReady(resource: Bitmap, model: Any, target: Target<Bitmap>, dataSource: DataSource, isFirstResource: Boolean): Boolean {
Palette
.from(resource)
.generate(PaletteAsyncListener { palette ->
val darkVibrantSwatch = palette!!.darkVibrantSwatch
val dominantSwatch = palette.dominantSwatch
val lightVibrantSwatch = palette.lightVibrantSwatch
if (darkVibrantSwatch != null) {
imageView.setBackgroundColor(darkVibrantSwatch.rgb)
} else if (dominantSwatch != null) {
imageView.setBackgroundColor(dominantSwatch.rgb)
} else {
imageView.setBackgroundColor(lightVibrantSwatch!!.rgb)
}
})
return false
}
})
.into(imageView)
However, through the above code, I will load the image directly (imagview will display white before the image is loaded successfully), without filling it with solid color first.
I think maybe glide is loading too fast?
Is there any way to ensure that my picture is filled with solid color for a period of time, such as 500ms?
use Palette.from(resource!!.toBitmap) this will force and rap the resources to bitmap. but firstly you will need to use let on the resources variable so that resources doe's not give you issues
I have the following code -
verifyOtpViewModel.userData.observe(this, Observer {
if (it == null) return#Observer
if (it.profileImage != null) {
...
}
}
profileImage is my image URL.
I need an updated way, by 2020 standard to get the image bitmap from the URL and then get the URI from that bitmap.
All answers on this subject use the soon-to-be deprecated AsyncTask and I was hoping for a better solution, maybe a 3rd party library even.
Thanks!
First download the image if it's not already downloaded
GlideApp is the generated API:
Add a class with exact same name. Be sure to Rebuild after adding this code snippet:
you need to use kapt instead of annotactionProccesser on Kotlin.
#GlideModule
class MyAppGlideModule : AppGlideModule()
diskCacheStrategy(DiskCacheStrategy.NONE) don't let Glide cache this image.
Target.Size_Original keep the original size when you download the image from url.
GlideApp
.with(context)
.asBitmap()
.load(uri)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(object : CustomTarget<Bitmap>(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) {
override fun onLoadCleared(placeholder: Drawable?) {}
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
//Save image file
saveFile(resource)
//Load into imageview if needed
}
}
})
Save the image file. It's recommended to use background threads for this.
val randomFilename = //generate a name based on your preferences
val file = File(saveDirectory, randomFilename)
val fos = FileOutputStream(file)
val bos = BufferedOutputStream(fos)
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos)
bos.close()
Update your room database with the name of the file to retrieve that later.
#Query("UPDATE table_name SET image_filename = :fileName WHERE id = :id ")
fun updateImageFilename(id: Int, filename: String)
I have a recyclerview which shows a list of images in horizontal view. I want to show a gif around each imageview like an outline similar to instagram's story view.
Here is the code for loading the same.
GlideApp
.with(itemView.context).asGif()
.load(R.drawable.red)
.error(R.drawable.red)
.diskCacheStrategy(DiskCacheStrategy.DATA)
.into(ivBackground)
I tried adding this code on both places i.e. onCreateViewHolder as well as onBindViewHolder but the gif is not playing properly.
It only shows one frame at a time. If I pause current activity by pressing the back button and then from recent app menu, navigate back to the app, then it shows the next frame.
My gif is stored in the drawable folder and I have the exact same gif working properly in an activity/fragment.
Issue is it's not playing properly inside a recyclerview adapter.
This is the complete adapter class.
class LiveViewAdapter(
val context: Context,
val liveList: ArrayList<DataItem>,
private val listener: LiveViewAdapterListener
) :
RecyclerView.Adapter<LiveViewHolder>() {
private val inflater: LayoutInflater
interface LiveViewAdapterListener {
fun onLiveClicked(url: String)
}
init {
inflater = LayoutInflater.from(context)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LiveViewHolder {
val view = inflater.inflate(R.layout.item_live, parent, false)
return LiveViewHolder(view)
}
override fun getItemCount(): Int {
return liveList.size
}
override fun onBindViewHolder(holder: LiveViewHolder, position: Int) {
val requestOptions = RequestOptions().apply {
placeholder(R.mipmap.ic_launcher_round)
error(R.mipmap.ic_launcher_round)
}
val videoId = liveList[holder.adapterPosition].liveUrl?.substring(liveList[holder.adapterPosition].liveUrl?.lastIndexOf("=")!! + 1)
val imageUrl = "https://img.youtube.com/vi/$videoId/hqdefault.jpg"
Glide.with(context)
.load(liveList[position].liveUrl)
.apply(requestOptions)
.thumbnail(Glide.with(context).load(imageUrl))
.into(holder.ivLive)
holder.ivLive.setOnClickListener {
listener.onLiveClicked(liveList[holder.adapterPosition].liveUrl!!)
}
}
}
Glide version used
implementation 'com.github.bumptech.glide:glide:4.8.0'
kapt 'com.github.bumptech.glide:compiler:4.8.0'
UPDATE
I figured out that the above code works fine on android devices which have sdk version less than pie. On devices with android pie, it's not working properly.
Are you using roundedImageView or just native imageview ?
I met the same the same problem while using roundedImageView. I replace it with native ImageView, the gif play.
I have a recyclerview that loads a set of items which mainly display an image. I retrieve this items in the background, in batches of 100. I load the images using Picasso. Images are quite big, but I resize them using fit().
Whenever the screen is loaded or refreshed using SwipeRefreshLayout, the UI blocks for less than a second, but enough to be noticeable. If I dont load the images but put just the text, then the UI block does not happen.
I put logging lines on Picasso and on every refresh the 100 images are retrieved, but I would guess Picasso is working in a background thread?
Adapter:
#ActivityScope
class LimitableListAdapter #Inject constructor() : RecyclerView.Adapter<LimitableListAdapter.ViewHolder>() {
private var events: MutableList<Event> = mutableListOf()
private var itemClick: ((Event, View) -> Unit)? = null
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val binding : ItemVideoGridScoreBinding = holder.binding
var viewModel = binding.viewModel
val event = events[position]
//Unbind old viewModel if we have one
viewModel?.unbind()
// Create new ViewModel, set it, and bind it
viewModel = EventViewModel(event)
binding.viewModel = viewModel
viewModel.bind()
holder.setClickListener(itemClick)
}
override fun getItemCount(): Int = events.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = DataBindingUtil.inflate<ItemVideoGridScoreBinding>(
LayoutInflater.from(parent.context),
R.layout.item_video_grid_score,
parent,
false
)
return ViewHolder(binding)
}
fun updateEvents(events: List<Event>, stride: Int) {
var size = this.events.size
Timber.w("Updating with: " + events.joinToString(",", transform = { e -> e.id.toString() }))
this.events = events.toMutableList()
notifyDataSetChanged()
/*if (size == 0) {
Timber.w("branch 1")
var mutableList = events.toMutableList()
if(mutableList.size == 0)
return
mutableList.add(Event.mockEvent(stride))
this.events.addAll(mutableList)
notifyDataSetChanged()
} else {
if (size > 2) {
Timber.w("branch 2.1")
this.events.addAll(size - 1, events.toMutableList())
notifyItemRangeChanged(size-1, events.size)
}
else {
Timber.w("branch 2.2")
this.events.addAll(size, events.toMutableList())
notifyItemRangeChanged(size, events.size)
}
}*/
Timber.i("New list is: " + this.events.joinToString(",", transform = { e -> e.id.toString() }))
}
fun clearList(){
this.events.clear()
notifyDataSetChanged()
}
fun setClickListener(itemClick: ((Event, View) -> Unit)?) {
this.itemClick = itemClick
}
class ViewHolder(val binding: ItemVideoGridScoreBinding) : RecyclerView.ViewHolder(binding.root) {
fun setClickListener(callback: ((Event, View) -> Unit)?) {
binding.viewModel.clicks().subscribe() {
callback?.invoke(binding.viewModel.event, itemView)
}
}
}
}
BindingUtils:
#BindingAdapter({"app:coverUrl"})
public static void loadCover(ImageView view, String imageUrl) {
Picasso p = Picasso.with(view
.getContext());
p.setIndicatorsEnabled(true);
p.load(imageUrl)
.fit()
.centerInside()
.error(R.drawable.ic_broken_image)
.into(view);
}
}
xml:
(...)
<ImageView
android:id="#+id/event_cover"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:coverUrl="#{videoItem.cover}"
tools:src="#drawable/img"
/>
(...)
Assuming you are not able to create thumbnails on the server side (which would be the easiest solution), my suggestion would be to go with one of the following
Using a local drawable as a Placeholder. This will not block the UI thread and the image can load in the background. Behaviour will be similar to how Instagram behaves when you load the grid of images. Something like below.
p.load(imageurl).placeholder(R.drawable.localFile).fit().centerInside().error(R.drawable.ic_broken_image).into(view)
Use Glide. Glide does have powerful capability to create Thumbnails. And you can load the Thumbnails into the view much faster than the full images.
Resizing several big images takes some time. While Picasso does caching over the resized images, the first time it will still need to resize every one (and subsequent times it will just use the resized ones on the cache, thus taking less time).
Also, using fit can take more time than just using resize because it needs to calculate the size depending on the layout. If you can calculate the static size yourself (just once) and then use resize it should improve loading times.
But most of the times, it would be better if your server could send you smaller images/thumbnails when previewing, and then a full size image if you want to see it on some detail screen.
As you are loading 100 images and as you mentioned images are big,
so it will block the UI Thread for sure, if you want to avoid this so You can resize the images with Picasso:
Picasso.with(context)
.load(-yourImgURL-)
.resize(450, 200) // resizes image to desired dimensions here
.into(-yourImageView);
I have followed this tutorial where it is declared a custom Glide Module to load different image sizes from server depending on the ImageView size. I have also taken a look to this Glide wiki which explains the same.
But the implementation on the tutorial and the Glide wiki only works if the String you send to the Custom Module is a http/https url. How can I modify this Custom Module or create a new one in order to handle all other types (String, Uri, int, etc as Glide.load() does) and keep the functionality presented in the tutorial?
Instead of registering the new ModelLoader with append(), which handle new types of data, register it using prepend(), which handle subsets of existing data where you do want to fall back to Glide’s default behavior if your ModelLoader fails. So instead of creating the new Glide's input data (in the tutorial named as CustomImageSizeModelFutureStudio), tell Glide, in the case of a String, to check if you want to modify the String and create your url or let Glide do his work without modifying the String. Here is my implementation in Kotlin. In this case, if your input is "https://...." it will request your custom url. If your input is "content://..." your ModelLoader will fail because of the handles() method and Glide will do it's work.
The implementation of AppGlideModule:
#GlideModule
class MyGlideModule : AppGlideModule() {
override fun registerComponents(context: Context?, glide: Glide?, registry: Registry?) {
registry?.prepend(String::class.java, InputStream::class.java, CustomImageSizeUrlLoaderFactory())
}
}
The implementation of ModelLoaderFactory:
class CustomImageSizeUrlLoaderFactory : ModelLoaderFactory<String, InputStream> {
private val modelCache = ModelCache<String, GlideUrl>(500)
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<String, InputStream> {
val modelLoader = multiFactory.build(GlideUrl::class.java, InputStream::class.java)
return CustomImageSizeUrlLoader(modelLoader, modelCache)
}
override fun teardown() {
}
}
The implementation of BaseGlideUrlLoader:
class CustomImageSizeUrlLoader(concreteLoader: ModelLoader<GlideUrl, InputStream>, modelCache: ModelCache<String, GlideUrl>?) : BaseGlideUrlLoader<String>(concreteLoader, modelCache) {
override fun getUrl(baseImageUrl: String, width: Int, height: Int, options: Options?): String {
return baseImageUrl + "?w=" + width + "&h=" + height;
}
override fun handles(model: String): Boolean {
return baseImageUrl.startsWith("http://")
|| baseImageUrl.startsWith("https://")
}
}
And call your Glide as you will normally do, not as the tutorial does.
To load image in different sizes, you can use glide's default method override
Check below code snippet for load image from different sizes.
GlideApp
.with(context)
.load(url)
.override(customwidth, customheight) // resizes the image to these dimensions (in pixel). resize does not respect aspect ratio
.into(imageViewResize);
If you want to maintain aspect ratio as well, you can use fitCenter() or centerCrop().