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.
Related
When I click on an image within a RecyclerView, I try to enlarge it. So far everything works wonderfully. The only problem I have now is that the image of its output position is always the same. In other words, if I click on the 3rd picture, the 2nd picture enlarges with the correct content but in the wrong position. The same applies to the click on the 1st picture.
This is my AdapterDetail.kt class:
class AdapterDetail(val context: Context, private val listImg:ArrayList<String>,private var listen: CustomerAdapter.OnItemClickListener): RecyclerView.Adapter<AdapterDetail.MyCustomViewHolder>() {
...
inner class MyCustomViewHolder(view: View):RecyclerView.ViewHolder(view){
val preview_img:ImageView = view.findViewById(R.id.preview_img)
fun bind(item: String){
imgView = itemView.findViewById(R.id.preview_img)
imgView.setOnClickListener {
listen.onItemClick(imgView, adapterPosition)
}
}
}
override fun onBindViewHolder(holder: MyCustomViewHolder, position: Int) {
val item = listImg[position]
holder.bind(item)
}
In the Activity i need the correct ImageView position to inflate them:
adapterDetail = AdapterDetail(this, house!!.objektBilder as ArrayList<String>,object: CustomerAdapter.OnItemClickListener{
override fun onItemClick(view: View, position: Int) {
//Here I need the ImageView that was clicked on in the RecyclerView
var viewPosition = view
zoomImageFromThumb(viewPosition, house!!.objektBilder!![position])
}
})
Sounds like an off by one issue, since you are using the deprecated adapterPosition. Use bindingAdapterPosition instead, which gives the position as RecyclerView sees it:
listen.onItemClick(imgView, bindingAdapterPosition)
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.
I get JSON data in my back-end API.
[
{
id: "is random"
image: "my url"
},
......10000+
]
My id is random number without continuity.
Each json object has image.
I call api and put data in RecyclerView .
But when RecyclerView is displayed, it will get stuck or crash.
I use Glide load image.
GlideApp.with(context).load(myUrl).placeholder(myPlaceholder).into(uiImage)
How can I let RecyclerView read it smoothly?
Can I use Paging Library to let it display five data at the beginning?
But I don't know which kind of DataSource is suitable for my json format.
ItemKeyedDataSource? or PageKeyedDataSource? or PositionalDataSource?
Update:
class MyAdapter(): ListAdapter<Item, ItemHolder>(Item.DIFF_CALLBACK){
override fun onCreateViewHolder(parent: ViewGroup, type: Int): ItemHolder=
ItemHolder(LayoutInflater.from(parent.context), parent).also {}
override fun onBindViewHolder(holder: ItemHolder, position: Int): Unit =
with(holder) {
val item = getItem(position)
uiTextView.text = item.id
GlideApp.with(context).load(item.image).placeholder(myPlaceholder).into(uiImage)
}
}
Update:
class ItemHolder(inflater: LayoutInflater, parent: ViewGroup)
: RecyclerView.ViewHolder(inflater.inflate(R.layout.my_Item, parent, false)) {
val uiTextView: TextView = itemView.findViewById(R.id.my_textView)
val uiImage: ImageView = itemView.findViewById(R.id.my_image)
}
Sometimes it crashes if I remove the placeholder.
GlideApp.with(context).load(myUrl).into(uiImage)
Log message:
I/Choreographer: Skipped 3660 frames! The application may be doing too much work on its main thread.
inject GlideApp, instead of creating new instance every-time, which will make your app more smooth.
And also if you are using Paging Library, then use PagedListAdapter.
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.
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);