Lazy load recyclerview images from mediastore - java

I want to load images I have in my recyclerview after 350ms and I think I'm using wrong method for that. This is my code:
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
holder.songView.setText(objects_.get(position).getAlbum());
holder.artistView.setText(objects_.get(position).getArtist());
holder.cover.setImageDrawable(context.getResources().getDrawable(R.drawable.song));
if(holder.r!=null){
handler.removeCallbacks(holder.r);
}
holder.r = new Runnable() {
#Override
public void run() {
Drawable img = Drawable.createFromPath(objects_.get(position).getCover());
if (img != null) {
holder.cover.setImageDrawable(img);
Bitmap bitmap = ((BitmapDrawable) img).getBitmap();
Palette palette = Palette.from(bitmap).generate();
Palette.Swatch p = palette.getVibrantSwatch();
if (p != null) {
holder.albumholder.setBackgroundColor(p.getRgb());
}
}
}
};
handler.postDelayed(holder.r,300);
}
But I have a problem with this. when I fast scroll recyclerview images of previous items loads at first then changes to new items picture. You can see result in GIF from this link:
http://8pic.ir/images/nkaaeqdvigqy4c6g2h5n.gif
what can I do to fix it?

I don't understand why do you need this 350ms delay but if you want to do it try some other approach:
Your problem is linked to the fact that RecyclerView recycles (suprise...) item views instead of creating new. That means that you will see previously load image, and if you have posted delayed task (handler.postDelayed(...)) it will be executed event if view was recycled, so wrong image can be loaded for particular list item.
General problem is that you're doing to much work in your onBindViewHolder. You should try to reduce computations here, or at least try to move them to some separate thread (handler is using this same thread it was created - in this case the UI thread).
Create handler inside view holder instead of inside your adapter.
Set some placeholder as an image
clear tasks (messages) currently waiting to be executed:
holder.handler.removeCallbacksAndMessages(null);
post load task (handler.postDelayed(...))
It's also possible that all you need is some nice image loading library like Picasso.

As we know recycler view reuse same view during scroll so it is displaying older images while you lazy load for some moments and after that it will update your imageview.
Solution is simply reset your imageview to default( ie white background or default image) state before lazyload .
You should avoid setimageresource() ,instead use setimagedrawable()
setImageResource Vs setDrawable

Instead of using the runnable for loading images, use AsyncTask. You'll need to execute a separate AsyncTask for each image. This AsyncTask will be saved as a WeakReference inside the drawable object which will be set in the respective ImageView.
WeakReferences are used for mapping purposes. The advantage of using WeakReferences is that the entries can be removed from the memory as soon as they are not required by your app. They will be removed by the Garbage Collector. We need to use the WeakReferences because there can be a large number of AsyncTasks getting executed (equal to the number of items present in the RecyclerView) at the same time and Android system or your app will not be able to identify which AsyncTask belongs to which ImageView without these references.
Now, as the drawable is set in the ImageView, it will contain the WeakReference to its respective AsyncTask. This AsyncTask will process the respective bitmap or drawable to be set on the ImageView and all this will be done off the UI thread.
In order to set the AsyncTask in the drawable object, you'll need to create a custom drawable class which will work as a Drawable object but will have the benefit of attaching an AsyncTask to it.
This Drawable object and AsyncTask will take care of loading the images.
The complete explanation and code for this concept has been provided on Android Developers website. Visit the link: http://developer.android.com/training/displaying-bitmaps/process-bitmap.html

Related

setOnMouseEntered not Working for ImageView in ListView

i have a problem with setOnMouseEntered on JavaFx for ImageViews.
I'm trying to change the brightness of ImageViews placed in a Listview with ColorAdjust. The effect itself works for ImageViews that are not in the ListView.
I guess that only the ListView triggers the setOnMouseEntered, but not the ImageViews, which is my goal.
The same problem is caused by the hover effects of the ImageViews, which are not triggered as soon as they are in a ListView.
fxml:
<ListView fx:id="cardsView" />
java-Code:
#FXML private ListView<ImageView> cardsView;
private ObservableMap<ImageView, Card> hCards;
#FXML
public void initialize() {
hCards= FXCollections.observableHashMap();
cardsView.getItems().setAll(hCards.keySet());
hCards.addListener(
(MapChangeListener<ImageView, Card>)
change -> {
cardsView.getItems().removeAll(change.getKey());
if (change.wasAdded()) {
cardsView.getItems().add(change.getKey());
}
});
}
Later, for each of these ImageViews will be added:
private void addLightEffectOnMouseEntered(ImageView imageView) {
imageView.setOnMouseEntered(
t -> {
ColorAdjust colorAdjust = new ColorAdjust();
colorAdjust.setBrightness(0.4);
imageView.setEffect(colorAdjust);
});
}
While debugging I figured out that things like css and the setOnMouseEntered are added correctly. So it seems to be somehow blocked by the ListView that the ChildNodes get the setOnMouseEntered or Hover effect instead of the ListView
Your problem is basically the same as this one: Adding EventHandler To ImageView contained in a Label. All Cell specializations, including ListCell, inherit from Labeled and all their default skins inherit from LabeledSkinBase, which is the source of your problem. As a fix to a bug (see other Q&A) when an ImageView is used as the graphic of a Labeled it is set to be mouse-transparent. Since the the ImageView is mouse-transparent your MOUSE_ENTERED handler can never be invoked for obvious reasons.
If you're not aware, the default cell factory of ListView returns a ListCell implementation that, when the item is an instance of Node, sets the graphic of the cell to the item. An easy fix is to use your own ListCell implementation that wraps the ImageView in another node, such as Pane. Here's an example:
listView.setCellFactory(lv -> new ListCell<>() {
private final Pane imageViewContainer = new Pane();
#Override
protected void updateItem(ImageView item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
imageViewContainer.getChildren().clear();
setGraphic(null);
} else {
imageViewContainer.getChildren().setAll(item);
setGraphic(imageViewContainer);
}
}
});
This will prevent the ImageView from becoming mouse-transparent.
As a side note, it's typically not a good idea to use GUI objects (e.g. ImageView) as the model item of a ListView (or any other virtualized control). In this case, it may be an even worse idea since this set-up encourages holding every Image related to your application in memory simultaneously. Depending on how many images there are, as well as how large those images are, this can easily lead to an OutOfMemoryError or at least consume an unnecessary amount of your users' RAM.
You may want to consider using Card as the model item combined with a memory-constrained cache of Image objects (see WeakReference / SoftReference, though you could also look for a third-party caching library). The Card class could hold the location of its associated image or the cache could derive the location based on the state of the Card.
You would still use an ImageView as the graphic of your ListCell, however, so you would still need to use the workaround mentioned above. What using a memory-constrained cache helps with is that, if a Card isn't being displayed in a ListCell then its associated Image possibly becomes eligible for garbage collection, thus reducing the memory demands of your application.
The cache also allows you to use the same Image everywhere in your application (the same Image can be shared between multiple ImageViews) and means you don't always load a new Image when a particular one is needed (as it could still be in memory when requested). In other words, the typical functionality provided by any cache.

Changing the specific item in row in recycler view

How to change specific view in row in RecyclerView. Adapter.notifyDataSetChanged or Adapter.notifyItemChanged updating row. But I need change the specific view in row, without touching other views in this row.
First of all, use Glide (By Google) instead of Picasso. It's more efficient.
Secondly, your device won't download the same image again if it's already present inside your Cache memory.
Now,
Simply change the data inside your List and then update your adapter or recycler view.
Inside your AdapterClass
Create an Interface, let's say
public interface TheUpdater{
public void updateMyAdapter(int position);
}
Then implement this interface in your Activity and override this method.
Now, in order to get the position of the View.
You can override onClick() of a View (Button or even the Whole Parent View i.e. Layout) and perform the following statement.
mTappedPosition = getAdapterPosition();
listener.updateMyAdapter(mTappedPosition); //Update the adapter
Where mTappedPosition is your global variable.
Inside your Activity
#Override
updateMyAdapter(int position){
---- //Do stuff
mAdapter.notifyItemSetChanged(position);
}
you can use following code
MyRecyclerAdapter.ViewHolder viewHolder= (MyRecyclerAdapter.ViewHolder) recycleView.findViewHolderForAdapterPosition(1);
viewHolder.txtView.SetText("Some Text");
I think to avoid the image loading issue you have to use image caching technique. So that already loaded image won't be load again.
or
If the image is already loaded and each and every row is showing same image set then you can keep a boolean flag to your itemModel (The dto which has the adapter item's data ). and change that model and then call
yourListToAdapter(index).setFlag(showImage1); // showImage1 boolean flag
yourAdapter.notifyItemChanged(index);

What Android code mimics the screenshot function (volume down and power button simultaneously)?

I am looking for a way to take screenshots of android device programmatically. I have looked at several solutions in stackoverflow that involve taking a screenshot of the running activity. I want to mimic the behavior that is by default triggered in Android by pressing volume down and power button simultaneously. Please note, I don't want to take screenshot of my own activity or running activity. I want to, for example, launch a dialog activity with a button, and when the button is clicked, I finish my activity and start a background task that will take a screenshot of the visible section including status-bar, like holding volume-down and power does. How do I achieve this?
If you are taking a screenshot of another Activity you will somehow have to get a reference to the View that encompasses the section of the screen you want to screenshot OR just capture the entire screen. If it is the entire screen you can get that with
// get the top level view from the activity in this context
View theView = getWindow().getDecorView();
Then to capture that View as a Bitmap you can use
// Take the Screen shot of a View
public static Bitmap takeScreenshot(final View theView) {
theView.setDrawingCacheEnabled(true);
return theView.getDrawingCache();
}
// Release the drawing cache after you have captured the Bitmap
// from takeScreenshot()
public static void clearScreenshotCache(final View theView) {
theView.setDrawingCacheEnabled(false);
}
From there you can save the Bitmap into a JPEG or PNG by calling .compress on the Bitmap.
As for running a task on a background thread there's enough documentation on several ways to do that like here.
Hope that gets you started!

ListView: Prevent a view from recycling

I have a ListView wich use recycled views. I'm trying to prevent a view from recycle.
So I use setHasTransientState:
android.support.v4.view.ViewCompatJB.setHasTransientState(View view, boolean hasTransientState)
It works very well on Jellybean version but it doesn't do anything on Api < 16.
Is there a way to make it work or there is different approach for pre Jellybean ?
I found out how to set a RecyclerListener like #Daniel Chow suggested.
listView.setRecyclerListener(new RecyclerListener() {
#Override
public void onMovedToScrapHeap(View view) {
// Stop animation on this view
}
});
For pre Jellybean, I think you can just use setRecyclerListener on ListView and when RecyclerListener#onMovedToScrapHeap(View view) is called, clear the animation on the view who has been recycled and directly do the final job which was supposed to be done when animation ends.
The code inside onMovedToScrapHeap(View view) depends on how you implement the animation, e.g. you can call View#clearAnimation() if you previously used View#startAnimation to start animation.
Use android.support.v4.view.ViewCompat.setHasTransientState(View view, boolean hasTransientState) instead of android.support.v4.view.ViewCompatJB.setHasTransientState(View view, boolean hasTransientState)
Besides the animation problem Daniel talked about, another issue where knowing when your view is recycled has to do with memory management. If you are putting large, memory intensive bitmaps in your list items, it may be possible that you don't want your view recycled if its not likely to be re-used by other items. This hook gives you a chance to clear the bitmap you may have assigned to an ImageView. Hopefully, this is a rare problem.

Most performant way to display a downloaded image in a ListView?

I am currently using a custom adapter to display an ImageView and two TextViews per row in a ListView.
Within the overridden getView for the adapter, I have this code for the ImageView:
final ImageView img = (ImageView) view.findViewById(R.id.rowImg);
new Thread(new Runnable() {
public void run() {
final BitmapDrawable b = downloadAvatar(urlToDownload);
img.post(new Runnable() {
public void run() {
img.setImageDrawable(b);
}
});
}
}).start();
The downloadAvatar method basically just uses AndroidHttpClient and HttpGet. The above method works, but my question is how do I optimize it? Scrolling is choppy; I know it's probably calling getView() and downloading the image each and every time it enters the viewable area. Any tips?
Please see below Lazy Loading listview's source link and universal image loader example for that, it may help you.
Lazy Loading Listview
Android Universal Image Loader
In your case, you'll need to do what's called "lazy load of images" which will cache those images once downloaded. Check this out: Lazy load of images in ListView
Here's an option:
Inside getView check if you have the current image downloaded. If it isn't downloaded yet - download it and store it in a directory of your choice. If it is present in that directory - just load it.
Depending on the image size and count you can also make some kind of cache to store the image objects required for the visible (and maybe the closest invisible above and below the visible) items.
I would suggest to use the ignition-library which has an component called RemoteImageView, which will load asynchronously url images, and the library will also keep a cache of those images, so they are not downloaded every time you re-create your ListView rows.
I use it, and I find it very usefull and robust.

Categories