What are advances of glide recyclerview integration? - android

I just tried to use glide recyclerview integration and read a document about it and it said: "The RecyclerView integration library makes the RecyclerViewPreloader available in your application. RecyclerViewPreloader can automatically load images just ahead of where a user is scrolling in a RecyclerView", but I don't realise any difference between glide recyclerview integration and only glide, please explain what are advances of glide recyclerview integration? And how can I see the difference?
Here's my code:
GlideModule.kt
#GlideModule
class GlideModule : AppGlideModule() {
override fun applyOptions(context: Context?, builder: GlideBuilder?) {
val requestOp = RequestOptions.noAnimation()
.priority(Priority.LOW)
builder?.setDefaultRequestOptions(requestOp)
?.setLogLevel(Log.VERBOSE)
super.applyOptions(context, builder)
}
// Disable manifest parsing to avoid adding similar modules twice.
override fun isManifestParsingEnabled(): Boolean {
return false
}
}
MyPreloadModelProvide.kt
class MyPreloadModelProvide(val listUrls: List<String>, val context: Context) : PreloadModelProvider<Any> {
override fun getPreloadItems(position: Int): MutableList<Any> {
val url = listUrls.get(position)
if (TextUtils.isEmpty(url)) {
return Collections.emptyList();
}
return Collections.singletonList(url);
}
override fun getPreloadRequestBuilder(url: Any?): RequestBuilder<*>? {
return GlideApp.with(context)
.load(url)
}
}
MyAdapter.kt
class MyAdapter(val listUrl: List<String>, val context: Context) : RecyclerView.Adapter<MyViewHolder>() {
override fun getItemCount(): Int = listUrl.size
#SuppressLint("CheckResult")
override fun onBindViewHolder(holder: MyViewHolder?, position: Int) {
GlideApp.with(context)
.load(listUrl[position])
.into(holder?.imageView)
holder?.imageView?.setOnClickListener { Toast.makeText(context, listUrl[position], Toast.LENGTH_LONG).show() }
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): MyViewHolder = MyViewHolder(LayoutInflater.from(parent?.context).inflate(R.layout.item, parent, false))
}
class MyViewHolder(view: View?) : RecyclerView.ViewHolder(view) {
var imageView: ImageView
init {
imageView = view!!.findViewById(R.id.img)
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var preloadSizeProvider: ViewPreloadSizeProvider<Any>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// glide
var listUrls = listOf(
"https://img.pokemondb.net/artwork/bulbasaur.jpg",
"https://img.pokemondb.net/artwork/ivysaur.jpg",
"https://img.pokemondb.net/artwork/komala.jpg",
"https://img.pokemondb.net/artwork/turtonator.jpg",
"https://img.pokemondb.net/artwork/togedemaru.jpg",
"https://img.pokemondb.net/artwork/mimikyu.jpg",
"https://img.pokemondb.net/artwork/nihilego.jpg",
"https://img.pokemondb.net/artwork/buzzwole.jpg",
"https://img.pokemondb.net/artwork/pheromosa.jpg",
"https://img.pokemondb.net/artwork/xurkitree.jpg",
"https://img.pokemondb.net/artwork/celesteela.jpg",
"https://img.pokemondb.net/artwork/kartana.jpg",
"https://img.pokemondb.net/artwork/guzzlord.jpg",
"https://img.pokemondb.net/artwork/necrozma.jpg",
"https://img.pokemondb.net/artwork/magearna.jpg",
"https://img.pokemondb.net/artwork/marshadow.jpg"
)
preloadSizeProvider = ViewPreloadSizeProvider<Any>()
val modelProvider = MyPreloadModelProvide(listUrls, this)
val preloader = RecyclerViewPreloader(GlideApp.with(this), modelProvider, preloadSizeProvider, 2 /*maxPreload*/)
// recycler view
recycler_view.layoutManager = LinearLayoutManager(this)
recycler_view.setHasFixedSize(true)
recycler_view.adapter = MyAdapter(listUrls, this)
// THERE ARE NO DIFFERENCES IF I COMMENT THIS LINE
recycler_view.addOnScrollListener(preloader)
}
}
THERE ARE NO DIFFERENCES IF I COMMENT THIS LINE
recycler_view.addOnScrollListener(preloader)

The RecyclerView integration library makes the RecyclerViewPreloader available in your application.
And RecyclerViewPreloader can automatically load images just ahead of where a user is scrolling in a RecyclerView.
Combined with the right image size and an effective disk cache strategy, this library can dramatically decrease the number of loading tiles/indicators users see when scrolling through lists of images by ensuring that the images the user is about to reach are already in memory.
To use the RecyclerView integration library, add a dependency on it in your build.gradle file:
compile ("com.github.bumptech.glide:recyclerview-integration:4.4.0") {
/*Excludes the support library
because it's already included by Glide.*/
transitive = false
}

The issue in your code is that you create preloadSizeProvider of type ViewPreloadSizeProvider, but you never call preloadSizeProvider.setView(...) on it. So it doesn't know the size of your target view, so it can't preload images in correct size, so you see no improvement.
I recommend first trying to make it work with fixed size. So instead of using ViewPreloadSizeProvider create FixedPreloadSizeProvider(WIDTH, HEIGHT) and make sure to use same size when loading images via Glide as such Glide.with(this).load(imageUri).override(WIDTH, HEIGHT).into(imageView);
If you want to check if it works, enable logging for both Glide requests like this (I used Java code there for my simplicity):
MyPreloadModelProvide.kt
class MyPreloadModelProvide(val listUrls: List<String>, val context: Context) : PreloadModelProvider<Any> {
override fun getPreloadRequestBuilder(url: Any?): RequestBuilder<*>? {
return GlideApp.with(context)
.override(250, 250)
.dontTransform()
.listener(new RequestListener<Drawable>() {
#Override
public boolean onLoadFailed(#Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
return false;
}
#Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
Log.d("IMAGE PRELOAD", "onResourceReady() called with: model = [" + model + "], target = [" + target + "], dataSource = [" + dataSource + "]");
return false;
}
})
.load(url)
}
}
MyAdapter.kt
class MyAdapter(val listUrl: List<String>, val context: Context) : RecyclerView.Adapter<MyViewHolder>() {
override fun getItemCount(): Int = listUrl.size
#SuppressLint("CheckResult")
override fun onBindViewHolder(holder: MyViewHolder?, position: Int) {
GlideApp.with(context)
.load(listUrl[position])
.override(250, 250)
.dontTransform()
.listener(new RequestListener<Drawable>() {
#Override
public boolean onLoadFailed(#Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
return false;
}
#Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
Log.d("IMAGE LOAD", "onResourceReady() called with: model = [" + model + "], target = [" + target + "], dataSource = [" + dataSource + "]");
return false;
}
})
.into(holder?.imageView)
holder?.imageView?.setOnClickListener { Toast.makeText(context, listUrl[position], Toast.LENGTH_LONG).show() }
}
}
And in logcat you should see that PRELOAD requests have dataSource = [LOCAL] or [RESOURCE_DISK_CACHE] and LOAD requests have dataSource = [MEMORY_CACHE].
It if works, great. Then you can rewrite it to use ViewPreloadSizeProvider. In that case you remove the fixed image size (.override(250,250)) from Glide requests and then don't forget to call preloadSizeProvider.setView(holder.imageView) for example in onCreateViewHolder method.

Suppose the user is scrolling a RecyclerView that loads large images, so, the user should wait the delay of loading the image before seeing it in the corresponding list item. RecyclerViewPreloader loads the images in advance, so when the user scrolls, there are already loaded and ere displayed instantly.
In your code you are using a small list, and a small maxPreload value, so you won't notice the difference of using RecyclerViewPreloader. To see it in action you should use a long list with many different images, and scroll fast with and without the preloader. Without the preloader you will see empty images until loaded, and with it, images should be displayed rapidly and smoothly.

My previous answer to this question was deleted, probably as from a quick glance of a moderator, it looks like it was totally unrelated, so I'll try and rephrase and add some meat.
Glide has a RecyclerView integration page that you might assume is the required way to get Glide working with an Android RecycerView. But it really just adds a memory cache preloader, which is a bit complicated to implement; the simple way of just adding Glide.with(view.context).load(url) into your RecyclerView onBindViewHolder works just as well and won't result in any memory leaks. And by default, Glide has a built in disk cache which works great.
So OP's question is what are the advantages of this memory preloader. The stated advantages are obvious, preloading images before the user scrolls to them.
But a better question is are the advantages worth the extra effort to get it working. This is a subjective question. It's only noticeable for users with slow Internet connections, and even then, I'm not sure it's worth the hassle. You've no idea where the user will scroll, you could preload images and then the user will scroll to the very bottom and images will need to be loaded new anyway. So IMO, it's not worth the hassle. But you'll have to make up your own mind.

Related

Drag & Dropping the first item of the RecyclerView moves several random positions

Currently, I have a RecyclerView implementing the new ListAdapter, using submitList to differ elements and proceed to update the UI automatically.
Lately i had to implement drag & drop to the list using the well known ItemTouchHelper. Here is my implementation, pretty straight forward:
class DraggableItemTouchHelper(private val adapter: DestinationsAdapter) : ItemTouchHelper.Callback() {
private val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
private val swipeFlags = 0
override fun isLongPressDragEnabled() = false
override fun isItemViewSwipeEnabled() = false
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
return makeMovementFlags(dragFlags, swipeFlags)
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
val oldPos = viewHolder.adapterPosition
val newPos = target.adapterPosition
adapter.swap(oldPos, newPos)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
}
}
this is my swap function inside the adapter:
fun swap(from: Int, to: Int) {
submitList(ArrayList(currentList).also {
it[from] = currentList[to]
it[to] = currentList[from]
})
}
Everything works well EXCEPT when moving the FIRST item of the list. Sometimes it behaves OK, but most of the time (like 90%), it snaps several positions even when moving it slightly above the second item (to move 1st item on 2nd position for example). The new position seems random and i couldn't figure out the issue.
As a guide, i used the https://github.com/material-components/material-components-android example to implement Drag&Drop and for their (simple) list&layout works well. My list is a bit complex since it's inside a viewpager, using Navigation component and having many other views constrained together in that screen, but i don't think this should be related.
At this point i don't even know how to search on the web for this issue anymore.
The closest solution I found for this might be https://issuetracker.google.com/issues/37018279 but after implementing and having the same behaviour, I am thinking it's because I use ListAdapter which differs and updates the list asynchronously, when the solution uses RecyclerView.Adapter which uses notifyItemMoved and other similar methods.
Switching to RecyclerView.Adapter is not a solution.
This seems to be a bug in AsyncListDiffer, which is used under the hood by ListAdapter. My solution lets you manually diff changes when you need to. However, it's rather hacky, uses reflection, and may not work with future appcompat versions (The version I've tested it with is 1.3.0).
Since mDiffer is private in ListAdapter and you need to work directly with it, you'll have to create your own ListAdapter implementation(you can just copy the original source). And then add the following method:
fun setListWithoutDiffing(list: List<T>) {
setOf("mList", "mReadOnlyList").forEach { fieldName ->
val field = mDiffer::class.java.getDeclaredField(fieldName)
field.isAccessible = true
field.set(mDiffer, list)
}
}
This method silently changes the current list in the underlying AsyncListDiffer without triggering any diffing, as submitList() would.
The resulting file should look like this:
package com.example.yourapp
import androidx.recyclerview.widget.AdapterListUpdateCallback
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.AsyncListDiffer.ListListener
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
abstract class ListAdapter<T, VH : RecyclerView.ViewHolder?> : RecyclerView.Adapter<VH> {
private val mDiffer: AsyncListDiffer<T>
private val mListener =
ListListener<T> { previousList, currentList -> onCurrentListChanged(previousList, currentList) }
protected constructor(diffCallback: DiffUtil.ItemCallback<T>) {
mDiffer = AsyncListDiffer(
AdapterListUpdateCallback(this),
AsyncDifferConfig.Builder(diffCallback).build()
).apply {
addListListener(mListener)
}
}
protected constructor(config: AsyncDifferConfig<T>) {
mDiffer = AsyncListDiffer(AdapterListUpdateCallback(this), config).apply {
addListListener(mListener)
}
}
fun setListWithoutDiffing(list: List<T>) {
setOf("mList", "mReadOnlyList").forEach { fieldName ->
val field = mDiffer::class.java.getDeclaredField(fieldName)
field.isAccessible = true
field.set(mDiffer, list)
}
}
open fun submitList(list: List<T>?) {
mDiffer.submitList(list)
}
fun submitList(list: List<T>?, commitCallback: Runnable?) {
mDiffer.submitList(list, commitCallback)
}
protected fun getItem(position: Int): T {
return mDiffer.currentList[position]
}
override fun getItemCount(): Int {
return mDiffer.currentList.size
}
val currentList: List<T>
get() = mDiffer.currentList
open fun onCurrentListChanged(previousList: List<T>, currentList: List<T>) {}
}
Now you need to change your adapter implementation to inherit from your custom ListAdapter rather than androidx.recyclerview.widget.ListAdapter.
Finally you'll need to change your adapter's swap() method implementation to use the setListWithoutDiffing() and notifyItemMoved() methods:
fun swap(from: Int, to: Int) {
setListWithoutDiffing(ArrayList(currentList).also {
it[from] = currentList[to]
it[to] = currentList[from]
})
notifyItemMoved(from, to)
}
An alternative solution would be to create a custom AsyncListDiffer version that lets you do the same without reflection, but this way seems easier. I will also file a feature request for supporting manual diffing out of the box and update the question with a Google Issue Tracker link.
I kept a copy of the items in my adapter, modified the copy, and used notifyItemMoved to update the UI as the user was dragging. I only save the updated items/order AFTER the user finishes dragging. This works for me because 1) I had a fixed length list of 9 items; 2) I was able to use clearView to know when the drag ended.
ListAdapter - kotlin:
var myItems: MutableList<MyItem> = mutableListOf()
fun onMove(fromPosition: Int, toPosition: Int): Boolean {
if (fromPosition < toPosition) {
for (i in fromPosition until toPosition) {
Collections.swap(myItems, i, i + 1)
}
} else {
for (i in fromPosition downTo toPosition + 1) {
Collections.swap(myItems, i, i - 1)
}
}
notifyItemMoved(fromPosition, toPosition)
return true
}
ItemTouchHelper.Callback() - kotlin:
// my items are only ever selected during drag, so when selection clears, drag has ended
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
// clear drag style after item moved
viewHolder.itemView.requestLayout()
// trigger callback after item moved
val itemViewHolder = viewHolder as MyItemViewHolder
itemViewHolder.onItemMovedCallback(adapter.myItems)
}
MyItemViewHolder - kotlin
fun onItemMovedCallback(reorderedItems: List<MyItem>) {
// user has finished drag
// save new item order to database or submit list properly to adapter
}
I also had an itemOrder field on MyItem. I updated that field using the index of the re-ordered items when I saved it to the DB. I could probably update each items itemOrder field when I swap the items, but it seemed pointless (I just calculate the new order after the drag is finished).
I'm using LiveData from my database. I found the views "flickered" after the final database save because I changed the itemOrder on all the items and moved the items around in the adapter list. If this happens to you and you don't like it, just temporarily disable the recycler view item animator (I achieved this by setting it to null after the drag and restoring it after the list is updated in the RecyclerView/Adapter).
This worked for me and my specific case. Let me know if you need more details.

Gif loaded with Glide in RecyclerView Adapter not playing properly

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.

How to update images in a RecyclerView?

I have a RecyclerView of images that the user is able to edit directly in the RecyclerView itself.
The editing part is fine and works well. It's done on a bitmap that overlays the images and then saves any changes to the image file. As soon as the user scrolls the recyclerview, the bitmap is destroyed and becomes invisible.
The trouble is, any changes the user makes aren't visible when they scroll. They scroll the OLD image, not the EDITED image. They have to get out of the recycler and back into it to see the changes.
So how do I force the RecyclerView to reload the image that was just saved. I'm using Glide in my adapter, and as you can see I have the caching based on the save time of the image.
class InfinityPageAdapter(val memo: Memo) : ListAdapter<Page, InfinityPageAdapter.ViewHolder>(PageDiffCallback()) {
class ViewHolder(pageView: View) : RecyclerView.ViewHolder(pageView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InfinityPageAdapter.ViewHolder {
val pageView = LayoutInflater.from(parent.context).inflate(R.layout.infinity_page_list_item, parent, false)
return ViewHolder(pageView)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val page = getItem(position)
val imageUrl = getMemoPath(memo.uuid) + page.uuid + ".webp"
if (imageUrl.isNotEmpty()) {
Glide.with(holder.itemView.context)
.load(imageUrl)
.apply(RequestOptions()
.signature(ObjectKey(System.currentTimeMillis()))
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
.override(memo.pageWidth,memo.pageHeight)
)
.transition(DrawableTransitionOptions.withCrossFade())
.into(holder.itemView.findViewById(R.id.memo_page_image))
}
}
Is there a way to notify the recyclerview that the image has changed, and force a reload of the image?
I'm using DiffUtil on the adapter. My understanding is, I don't need to use notifyDataSetChanged() when you're using DiffUtil. But I don't understand how you notify of an image change.
I don't know whether the Diff Callback is the issue. For what it's worth, here it is. It doesn't look at the image file itself, just the name of the image file. It might be that, because the name isn't changing, the image doesn't update. But I don't want to be changing the name of the file with every edit.
class PageDiffCallback : DiffUtil.ItemCallback<Page>() {
override fun areItemsTheSame(oldItem: Page, newItem: Page): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Page, newItem: Page): Boolean {
return oldItem == newItem
}
}
thanks!
John
This thing is, you are not updating an entire element in your recycler view, this is, a row. The notifyDataSetChanged() will force the data update like you said, but in this case it will also update your layout.
With DiffUtil, besides contents you are also comparing if the items are the same, which they are, since you are comparing their ID, and that isn't changing when you update your image.
Before trying with notifyDataSetChanged() tho, could you try to do this in your areContentsTheSame()method:
override fun areContentsTheSame(oldItem: Page, newItem: Page): Boolean {
return oldItem.id == newItem.id && oldItem.image == newItem.image // If you want just add more validations here
}

RecyclerView with Images blocking the UI

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);

Glide Module Loading url Images In Custom Sizes with paths and any uri handling

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().

Categories

Resources