I am using Recyclerview Gridlayout and I am facing lag while scroll.
Since I am using RecyclerView.Adapter ViewHolder is already there.
Other than that there are no conditions in onBindViewHolder i.e. onCreateView method.
Also I am making sure new objects are not created.
I am using Universal imageloader and I am pausing recyclerview imageloader on scroll as well.
I found that Image size on disk memory varies from 50 to 300 kb aprox is that have something to do with scroll speed?
public class DealsRecyclerAdapter extends RecyclerView.Adapter<DealsRecyclerAdapter.ViewHolder> {
public ArrayList<DealItem> items = new ArrayList<DealItem>();
Context context;
DisplayImageOptions options;
protected ImageLoader imageLoader;
DealItemOnClickListener dealItemOnClickListener;
private static final String RESULTS = "results";
int layoutId;
DealItem item;
String storeName;
public static final String RS = "Rs.";
public static final String AVAIL_FROM = "available from ";
public static final String OFF = "% off";
static class ViewHolder extends RecyclerView.ViewHolder {
protected TextView itemName;
protected TextView itemPriceOrig;
protected TextView itemDisc;
protected TextView itemPrice;
protected ImageView itemImage;
protected TextView itemStore;
protected TextView itemSpecialText;
public ViewHolder(View itemView) {
super(itemView);
itemImage = (ImageView) itemView
.findViewById(R.id.logo);
itemName = ((CustomTextView) itemView
.findViewById(R.id.title_product_search));
itemPriceOrig = ((CustomTextView) itemView
.findViewById(R.id.price_product_orig));
itemDisc = ((CustomTextView) itemView
.findViewById(R.id.product_disc));
itemPrice = ((CustomTextView) itemView
.findViewById(R.id.price_product));
itemStore = (TextView) itemView
.findViewById(R.id.prod_store);
itemSpecialText = (TextView) itemView
.findViewById(R.id.tvspecial);
itemPriceOrig.setPaintFlags(itemPriceOrig
.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
}
}
public DealsRecyclerAdapter(Context context, JSONObject jsonObject, int layoutId) {
try {
this.layoutId = layoutId;
this.context = context;
dealItemOnClickListener = (DealItemOnClickListener) (context);
options = new DisplayImageOptions.Builder()
.showImageOnLoading(R.drawable.load)
.showImageForEmptyUri(R.drawable.load)
.showImageOnFail(R.drawable.notfound)
.cacheInMemory(true)
.cacheOnDisk(true)
.considerExifParams(true)
.resetViewBeforeLoading(true)
.imageScaleType(ImageScaleType.IN_SAMPLE_INT)
.bitmapConfig(Bitmap.Config.RGB_565)
.imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2)
.build();
imageLoader = ImageLoader.getInstance();
JSONArray jsonArray = jsonObject.getJSONArray(RESULTS);
for (int i = 0; i < jsonArray.length(); i++) {
try {
items.add(Utils.getdealIemFromJson(jsonArray.getJSONObject(i)));
} catch (Exception e) {
}
}
} catch (Exception e) {
}
}
public void add(DealItem item) {
items.add(item);
}
#Override
public int getItemCount() {
return items.size();
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
v.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (dealItemOnClickListener != null)
dealItemOnClickListener.dealItemClicked(view);
}
});
ViewHolder vh = new ViewHolder(v);
return vh;
}
#Override
public void onBindViewHolder(final ViewHolder holder, int position) {
item = (DealItem) items.get(position);
holder.itemDisc.setText("(" + (int) Math.abs(item.discount) + OFF + ")");
holder.itemPriceOrig.setText(RS + item.originalPrice);
holder.itemPriceOrig.setVisibility(item.priceVisiblity);
holder.itemDisc.setVisibility(item.discountVisiblity);
holder.itemPrice.setText(RS + item.dealPrice);
storeName = AVAIL_FROM + "<b>" + item.store + "</b>";
holder.itemStore.setText(Html.fromHtml(storeName));
holder.itemName.setText(item.title);
holder.itemSpecialText.setText(item.specialText);
holder.itemSpecialText.setVisibility(item.specialTextVisibility);
imageLoader.displayImage(item.imageUrl, holder.itemImage, options);
holder.itemView.setTag(item);
}
#Override
public long getItemId(int arg0) {
return 0;
}
public interface DealItemOnClickListener {
void dealItemClicked(View view);
}
public void setLayoutId(int layoutId) {
this.layoutId = layoutId;
}
}
Both runtime scaling and image size can have an impact.
runtime scaling is usually discouraged and image sizes directly impacts how much could be cached at any one time.
I would also join #Alireza's question and would add a question; if you comment off the image loading is it ok?
I would also refer you to what i believe are better 3rd party image loading libraries such as Fresco, Glide and Picasso who i have a lot of experience with and can vouch for their efficiency.
And last but certainly not least i would invite you to view a session given by a friend of mine called Ran Nachmany where he explains how to analyze such issues with overdraw, systrace and other tools here.
Goodluck.
I read your code carefully, The only line that seems to make problem is this
imageLoader.displayImage(item.imageUrl, holder.itemImage, options);
I didn't work with UniversalImageLoader. Does it handles image loading in seprate thread?
any way you can simply make sure if this line is making problem by putting this exact images in resource directory and using imageView.setImageResource to see if you still have problem or not.
In case i'm right and UniversalImageLoader makes your app slow you can create an AsyncTask and run this line in separate thread or perhaps using another image loading library like Picaso.
The next candidate to cause problem is images. 300K may be low but if you sum up images sizes you get magabytes of memory usage which is enough to cause problem in even best android devices. (Android memory monitor is a really useful tool here!) and also some image formats like .jpg use compression to reduce the size and this makes decompressing file slower. Solution for that is photoshop, simply create new photo and copy and past the existing one and save. default options are ok most of the times!
And one final thought about unrelated matter! I see your handling jsonObject in adapter which is not a good practice. Puting is code inside your activity/fragment or better in some Network utility class would be a lot better.
Sorry if i can't be any more help. good luck
Related
since a long time now, I am having the same issue over and over with the recyclerview.
The short version of this: When the RV is scrolling quickly and need to do a lot of computing in short time, it sometimes just "skips" lines of code.
I have videotaped this behaviour in this 10-sec video:
https://www.youtube.com/watch?v=SVAvY6X5yr0.
Notice how I am scrolling down to the song file "Supreme". It is said to be by Billy Talent which it isn't. It is actually by Robbie Williams. And the RV does in fact know this.
When I scroll down until the views are way off the screen and then scroll back up, it seems to have "corrected" itself. The song is now by Robbie Williams. Also, the incorrect album cover art to the left of it is now also fixed and it shows the standart image, since there was never an album cover art on my phone.
When scrolling becomes very intens, the results are getting worse and worse. Sometimes items double or even tripple, while when clicked on the right song is being played.
Also interessting that the RV picked out the correct song name: "Supreme" but messed up the album cover art and the artist name.
Another thing worth noting: Every album cover art is supposed to be cropped into the same circle. However, the incorrect album cover art for "Supreme" is simple ignored and left into its rectangular form. Again: As if the RV is just "skipping" lines of code.
Well, speaking of it: this is basically the whole thing:
The Init function, called from OnCreate():
private void InitRecView()
{
ImageView lnBg = (ImageView)FindViewById(Resource.Id.background_recView);
mAdapter = new PhotoAlbumAdapter(GetSortedListWithAllSongs(), this, dbSeekObj, seekObj, mAudioManager, this, lnBg);
// Get our RecyclerView layout:
mRecyclerView = FindViewById<RecyclerView>(Resource.Id.recyclerView);
// Plug the adapter into the RecyclerView:
mRecyclerView.SetAdapter(mAdapter);
mRecyclerView.SetItemViewCacheSize(50);
mRecyclerView.DrawingCacheEnabled = true;
mRecyclerView.DrawingCacheQuality = DrawingCacheQuality.High;
mLayoutManager = new PreCachingLayoutManager(this);
mLayoutManager.ItemPrefetchEnabled = true;
mRecyclerView.SetLayoutManager(mLayoutManager);
}
The PhotoViewHolder:
public class PhotoViewHolder : RecyclerView.ViewHolder
{
public ImageView Image { get; private set; }
public TextView SongName { get; private set; }
public LinearLayout lnContainer { get; private set; }
public TextView AristName { get; private set; }
public ImageButton CoverArt { get; private set; }
public Drawable dr { get; set; }
public PhotoViewHolder(View itemView, List<MP3objectSmall> mp3Obj, Activity_Player act, MediaMetadataRetriever reader, DataBase db, List<SeekObj> seekObj, AudioManager audioManager, ImageView lnBg, Context ctx) : base(itemView)
{
// Locate and cache view references:
SongName = itemView.FindViewById<TextView>(Resource.Id.textView);
AristName = itemView.FindViewById<TextView>(Resource.Id.textView2);
lnContainer = itemView.FindViewById<LinearLayout>(Resource.Id.linlay_cardview);
CoverArt = itemView.FindViewById<ImageButton>(Resource.Id.musical_note);
this.mp3Obj = mp3Obj;
this.act = act;
this.reader = reader;
this.db = db;
this.seekObj = seekObj;
this.ctx = ctx;
this.audioManager = audioManager;
this.lnBg = lnBg;
lnContainer.Click += delegate
{
int pos = AdapterPosition;
ClickEvent(pos, AristName.Text, SongName.Text, CoverArt, lnBg);
};
}
private void ClickEvent(int position, string artist, string song, ImageView CoverArt, ImageView lnBackground)
{
if (PhotoAlbumAdapter.NewSongUri != null)
{
PhotoAlbumAdapter.OldSongUri = PhotoAlbumAdapter.NewSongUri;
}
PhotoAlbumAdapter.NewSongUri = Android.Net.Uri.Parse(mp3Obj[position].Mp3Uri);
if (!FirstStart) // dont save the very first uri, only get it for playing
{
ObjectToBeSaved = WriteMetaDataToFileList(PhotoAlbumAdapter.OldSongUri.ToString());
}
Activity_Player.CurrentSongObject = WriteMetaDataToFileList(PhotoAlbumAdapter.NewSongUri.ToString());
Activity_Player.txt_CurrentSong.Text = song;
Activity_Player.txt_CurrentArtist.Text = artist;
PlayMusic(PhotoAlbumAdapter.NewSongUri);
FirstStart = false;
Activity_Player.SetBackgroundToHeader(dr, lnBg, Activity_Player.btn_Settings, ctx);
}
And the probably most important class,
the PhotoAlbumAdapter:
public class PhotoAlbumAdapter : RecyclerView.Adapter
{
public List<MP3objectSmall> mp3Obj;
Context ctx;
Activity_Player act;
MediaMetadataRetriever reader;
DataBase db;
List<SeekObj> seekObj;
Typeface tf;
AudioManager audioManager;
ImageView lnBg;
LinearLayout ln;
public static Android.Net.Uri NewSongUri = null;
public static Android.Net.Uri OldSongUri = null;
public PhotoAlbumAdapter(List<MP3objectSmall> mp3Obj, Context ctx, DataBase db, List<SeekObj> seekObj, AudioManager audioManager, Activity_Player act, ImageView lnBg)
{
this.lnBg = lnBg;
this.mp3Obj = mp3Obj;
this.ctx = ctx;
this.db = db;
this.seekObj = seekObj;
this.act = act;
this.audioManager = audioManager;
reader = new MediaMetadataRetriever();
tf = Typeface.CreateFromAsset(ctx.Assets, "Baiti.ttf");
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
View itemView = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.CardView, parent, false);
PhotoViewHolder vh = new PhotoViewHolder(itemView, mp3Obj, act, reader, db, seekObj, audioManager, lnBg, ctx);
ln = itemView.FindViewById<LinearLayout>(Resource.Id.linlay_album_art);
ln.LayoutParameters.Height = ctx.Resources.DisplayMetrics.HeightPixels / 10;
ln.RequestLayout();
return vh;
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
PhotoViewHolder vh = holder as PhotoViewHolder;
SetContent(vh, position);
}
public override void OnViewRecycled(Java.Lang.Object holder)
{
base.OnViewRecycled(holder);
}
private async void SetContent(PhotoViewHolder vh, int position) {
await SetContentAsync(vh, position);
}
private async Task SetContentAsync(PhotoViewHolder vh, int position)
{
string SongName = "";
string ArtistName = "";
Bitmap bitmap = null;
byte[] data = null;
RequestOptions requestOptions = null;
try
{
reader.SetDataSource(mp3Obj[position].Mp3Uri);
}
catch { }
await Task.Run(() => // cause problems with the reload
{
SongName = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyTitle);
ArtistName = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyArtist);
data = reader.GetEmbeddedPicture();
if (data != null)
{
// try
// {
bitmap = BitmapFactory.DecodeByteArray(data, 0, data.Length);
requestOptions = new RequestOptions();
requestOptions.InvokeDiskCacheStrategy(DiskCacheStrategy.None);
// requestOptions.SkipMemoryCache(true);
requestOptions.CircleCrop();
requestOptions.CenterInside();
requestOptions.FitCenter();
requestOptions.OptionalCircleCrop();
ConvertBitmapToBackground(bitmap, vh, data); // Set As Backgorund, blurry and black ( just sets the variable)
// }
// catch { }
}
});
((Activity)ctx).RunOnUiThread(() =>
{
vh.SongName.SetTypeface(tf, TypefaceStyle.Normal);
vh.AristName.SetTypeface(tf, TypefaceStyle.Normal);
vh.SongName.Text = SongName;
vh.AristName.Text = ArtistName;
// try
// {
if (data != null)
{
Glide
.With(ctx)
.Load(data)
.Apply(requestOptions)
.Into(vh.CoverArt);
}
else // because recycler items inherit their shit and if it is altered it just shows views were there shouldnt be any ...
{
vh.CoverArt.SetImageResource(Resource.Drawable.btn_musicalnote);
vh.dr = null;
}
// }
// catch { }
});
}
public override int ItemCount
{
get
{
if (mp3Obj != null)
{
return mp3Obj.Count();
}
else
return 0;
}
}
}
}
In SetContentAsync() is where the RV deceides where to put what item to. This is where I am clearly stating what it is supposed to do and this is where I believe lines of code are getting skipped.
I really, really need help here. I think I am doing everything right - can anyone explain this odd behavior?
Thanks a lot!
I would say that the Async processing is likely to blame. Sometimes you may have one iteration completing before one that was initiated earlier which essentially allows the earlier one to overwrite the later "correct" values.
To counter this, I would place a UUID flag in each holder before you initiate the async operation and only allow the view to be updated if the flags match once the async operation completes. This would ensure that the "stale" operations are not capable of updating the UI.
I'm trying to get JSON data to display in a RecyclerView list, but whenever I try to make the call it seems that the RecyclerView comes up empty. I have it set up so that I get the API service/manager in onCreate prior to the configViews() method which I wrote. Am I making the call too early? I thought the problem would be to create the views/adapter prior to the call, but it doesn't seem to be making a difference.
This is the code for the Retrofit call:
listCall.enqueue(new Callback<List<Character>>() {
#Override
public void onResponse(Call<List<Character>> call, Response<List<Character>> response) {
if(response.isSuccessful()){
List<Character> characterList = response.body();
Log.v(TAG, response.toString());
for (int i = 0; i < characterList.size(); i++){
Character character = characterList.get(i);
character.setName(characterList.get(i).getName());
character.setDescription(characterList.get(i).getDescription());
characterAdapter.addCharacter(character);
}
configViews();
characterAdapter.notifyDataSetChanged();
}
else {
int sc = response.code();
}
}
#Override
public void onFailure(Call<List<Character>> call, Throwable t) {
}
});
And that configViews() method is here, albeit simpler than when I first started (I have been moving bits around to test whether they will affect inflation of the RecyclerView):
private void configViews() {
characterAdapter = new CharacterAdapter(this);
recyclerView.setAdapter(characterAdapter);
}
EDIT: Thank you all for your replies! As requested here is the Adapter class:
public class CharacterAdapter extends
RecyclerView.Adapter<CharacterAdapter.Holder> {
private static final String TAG = CharacterAdapter.class.getSimpleName();
private final CharacterClickListener clickListener;
private List<Character> characters;
//Constructor for CharacterAdapter.
public CharacterAdapter(CharacterClickListener listener){
characters = new ArrayList<>();
clickListener = listener;
}
//Inflates CardView layout
#Override
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
View row =
LayoutInflater.from(parent.getContext()).inflate(R.layout.row_item, parent,
false);
return new Holder(row);
}
#Override
public void onBindViewHolder(Holder holder, int position) {
Character currentCharacter = characters.get(position);
holder.name.setText(currentCharacter.getName());
holder.description.setText(currentCharacter.getDescription());
//Picasso loads image from URL
Picasso.with(holder.itemView.getContext())
.load("http://gateway.marvel.com/"+
currentCharacter.getThumbnail()).into(holder.thumbnail);
}
#Override
public int getItemCount() {
return characters.size();
}
public void addCharacter(Character character) {
//Log.d(TAG, character.getThumbnail());
characters.add(character);
notifyDataSetChanged();
}
public Character getSelectedCharacter(int position) {
return characters.get(position);
}
public class Holder extends RecyclerView.ViewHolder implements
View.OnClickListener{
//Holder class created to be implemented by adapter.
private ImageView thumbnail;
private TextView name, description;
public Holder(View itemView) {
super(itemView);
thumbnail = (ImageView)
itemView.findViewById(R.id.character_thumbnail);
name = (TextView) itemView.findViewById(R.id.character_name);
description = (TextView)
itemView.findViewById(R.id.character_description);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
clickListener.onClick(getLayoutPosition());
}
}
public interface CharacterClickListener {
void onClick(int position);
}
}
In principle you have done the right thing. You can instantiate the views before and fire a fetch request. Once you receive the response, you can notify the adapter that the datatset has changed and the adapter will refresh your views.
The only correction here is, you need to call your configViews(); before the for loop. So your code should be like -
configViews();
for (int i = 0; i < characterList.size(); i++){
Character character = characterList.get(i);
character.setName(characterList.get(i).getName());
character.setDescription(characterList.get(i).getDescription());
characterAdapter.addCharacter(character);
}
characterAdapter.notifyDataSetChanged();
Because right now what is happening is you add all your data using add character and then after that you again call config views which reinitialises your adapter at
characterAdapter = new CharacterAdapter(this);
Hence the empty views.
PS: I don't know your adapter's addCharacter method but i am hoping it is doing the right job. If it still doesn't work let me know and then add your addCharacter code as well.
Based on code the snippets, in configViews() method you are creating new instance of CharacterAdapter. The following code snippets will work.
private void configViews() {
//characterAdapter = new CharacterAdapter(this);
recyclerView.setAdapter(characterAdapter);
}
You should leave the call configViews() call in onCreate but move the rest of it into onResume. It'll achieve following:
Ensure views are ready to display data
Refresh data if your app was brought to background and re-opened
I am populating OrderHistory by retrieving data from Server in my app. Here I need to populate listitem depends on OrderId. If OrderId repeats I need to hide the LinearLayout which is marked in green in screenshot. Otherwise the Layout has to be usual view.Please help me.
MyAdapter Class:
public class MyOrderAdapter extends RecyclerView.Adapter<MyOrderAdapter.ViewHolder> {
Context mContext;
List<CartRes> mOrderList = new ArrayList<>();
private ImageLoader imageLoader;
public MyOrderAdapter(Context context, List<CartRes> orderList) {
mContext = context;
mOrderList = orderList;
Log.e("Json Adapter", "" + mOrderList);
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.my_order_history, parent, false);
ViewHolder viewHolder = new ViewHolder(v);
return viewHolder;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.cartResOrder = mOrderList.get(position);
imageLoader = CustomVolleyRequest.getInstance(mContext).getImageLoader();
String IMAGE_URL = "http://" + Config.IMAGE_URL + holder.cartResOrder.ORDER_IMAGE;
Log.e("Image URL", IMAGE_URL + " " + holder.cartResOrder.ORDER_IMAGE);
imageLoader.get(IMAGE_URL, ImageLoader.getImageListener(holder.orderImage, 0, 0));
holder.orderImage.setImageUrl(IMAGE_URL, imageLoader);
holder.txtOrderId.setText(holder.cartResOrder.ORDER_ID);
holder.txtOrderProduct.setText(holder.cartResOrder.ORDER_PRODUCT);
holder.txtorderedDate.setText(holder.cartResOrder.ORDER_DATE);
holder.txtDeliveredDate.setText(holder.cartResOrder.ORDER_DELIVERY_DATE);
}
#Override
public int getItemCount() {
return mOrderList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView txtOrderId, txtOrderProduct, txtorderedDate, txtDeliveredDate;
Button butDetail, butRemove;
NetworkImageView orderImage;
CartRes cartResOrder;
public ViewHolder(View itemView) {
super(itemView);
txtOrderId = (TextView) itemView.findViewById(R.id.orderId);
txtOrderProduct = (TextView) itemView.findViewById(R.id.orderProduct);
txtorderedDate = (TextView) itemView.findViewById(R.id.orderedDate);
txtDeliveredDate = (TextView) itemView.findViewById(R.id.orderDeliveredDate);
butDetail = (Button) itemView.findViewById(R.id.orderViewDetail);
butRemove = (Button) itemView.findViewById(R.id.orderRemove);
orderImage = (NetworkImageView) itemView.findViewById(R.id.OrderproductImage);
}
}
}
UPDATE:
public void onBindViewHolder(ViewHolder holder, int position) {
holder.cartResOrder = mOrderList.get(position);
imageLoader = CustomVolleyRequest.getInstance(mContext).getImageLoader();
String IMAGE_URL = "http://" + Config.IMAGE_URL + holder.cartResOrder.ORDER_IMAGE;
Log.e("Image URL", IMAGE_URL + " " + holder.cartResOrder.ORDER_IMAGE);
imageLoader.get(IMAGE_URL, ImageLoader.getImageListener(holder.orderImage, 0, 0));
holder.orderImage.setImageUrl(IMAGE_URL, imageLoader);
holder.txtOrderId.setText(holder.cartResOrder.ORDER_ID);
holder.txtOrderProduct.setText(holder.cartResOrder.ORDER_PRODUCT);
holder.txtorderedDate.setText(holder.cartResOrder.ORDER_DATE);
holder.txtDeliveredDate.setText(holder.cartResOrder.ORDER_DELIVERY_DATE);
if (position > 0 && mOrderList.get(position).ORDER_ID == mOrderList.get(position - 1).ORDER_ID) {
//make sure it is not the first one, and make sure it has the same ID as previous.
holder.OrderLinear.setVisibility(View.GONE); //hide it, you need to set the reference first.
holder.txtOrderId.setVisibility(View.GONE);
}
}
One way you could do it:
Have a new holder variable that refers to your LinearLayout you want to hide.
In your onBindViewHolder()
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
//setup everything else above...
if (position > 0 && mOrderList.get(position).ORDER_ID == mOrderList.get(position - 1).ORDER_ID){
//make sure it is not the first one, and make sure it has the same ID as previous.
holder.thelinearlayout.setVisibility(View.GONE); //hide it, you need to set the reference first.
}else holder.thelinearlayout.setVisibility(View.VISIBLE);
//if somehow order gets changed.(e.g. previous row is deleted).
}
EDIT:
I'm assuming that the data inside is sorted by ORDER_ID, so there won't be a case where there are two elements with the same ORDER_ID that is not next to each other.
You might want to sort them based on ORDER_ID before passing the list into adapter.
EDIT2:
Added else condition as suggested in the comment.
The screenshot does not tell that but if your view is RecyclerView or ListView then I'd preprocess the data prior feeding it to the adapter (i.e. by wrapping your model into another class with additional attributes).
Alternatively you can try checking previous row (by fetching it from your data source or adapter) while populating current one and adjust the view
maybe just add field boolean repeatOrderId to your CartRes class and in constructor do some for loop and flag all items? you may check also in runtime, but you have pretty fixed list, preparing in construtor this flag is better for performance
for(int i=0; i<mOrderList.getCount(); i++){
CartRes cr = mOrderList.get(i);
cr.repeatOrderId=true;
for(int j=0; j<i; j++){
if(cr.ORDER_ID==mOrderList.get(j).ORDER_ID){
cr.repeatOrderId=false; //default true
break;
}
}
}
then inside onBindViewHolder check this flag and setVisibility(cr.repeatOrderId); for desired layout
I was using recycler view to List Images but it was getting laggy after even caching and all. So I Decided to use Glide library but still laggy as hell. I finally thought to check with just a single drawable and still it's laggy. I don't get why. Please help. Here is the code. There is not much code still. You can see the code I was using for my Image Viewing commented out.
public class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ImageHolder>
{
private File file;
private String logging=getClass().getSimpleName();
private int size;
private MemCache memCache;
private Context context;
private Bitmap bitmap;
public ImageAdapter(File file,int Size,MemCache memCache,Context context)
{
this.file = file;
size=Size;
this.memCache=memCache;
this.context=context;
bitmap=BitmapFactory.decodeResource(context.getResources(),R.drawable.empty_photo);
}
#Override
public ImageHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
return new ImageHolder(new ImageView(context),size);
}
#Override
public void onBindViewHolder(ImageHolder holder, int position)
{
Glide.with(context).load(R.drawable.empty_photo).into(holder.getImageView());
// if (memCache.get(file.listFiles()[position].toString())!=null)
// {
// holder.getImageView().setImageBitmap(memCache.get(file.listFiles()[position].toString()));
// }
// else
// {
// ImageSyncTask imageSyncTask = new ImageSyncTask(size, holder.getImageView(),memCache);
// imageSyncTask.executeOnExecutor(imageSyncTask.THREAD_POOL_EXECUTOR, file.listFiles()[position]);
// }
// Glide.with(context).load(file.listFiles()[position]).crossFade().into(holder.getImageView());
}
#Override
public int getItemCount()
{
if (file!=null&&file.listFiles()!=null) return file.listFiles().length;
else return 0;
}
public static class ImageHolder extends RecyclerView.ViewHolder
{
private ImageView imageView;
public ImageHolder(View itemView,int size)
{
super(itemView);
imageView=(ImageView)itemView;
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setMinimumWidth(size);
imageView.setMinimumHeight(size);
}
public ImageView getImageView()
{
return imageView;
}
public void clearImage()
{
imageView.setImageResource(android.R.color.transparent);
}
}
}
I know there is one loading Bitmap but still its just one. That shouldn't make that much lag. And yes I have used the typical setImageBitmap instead of Glide but still laggy.
There is the view holder. Just Simple function. And I was previously using a proper layout for it but it was not working as well. In this I have just used New ImageView() and setParamaters just to make sure if there was a problem in Layout.
Please Help. I don't get why the typical Adapter is creating Lag.
Original MainActivity
public class MainActivity extends AppCompatActivity
{
RecyclerView recyclerView;
int cnum;
private MemCache memCache;
private String logging=getClass().getSimpleName();
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DisplayMetrics displayMetrics=new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int maxmem=(int)Runtime.getRuntime().maxMemory()/1024;
int cache_mem=maxmem/10;
memCache=new MemCache(cache_mem);
int orientation=getResources().getConfiguration().orientation;
if (orientation== Configuration.ORIENTATION_PORTRAIT)
cnum=3;
else cnum=5;
recyclerView=(RecyclerView)findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new GridLayoutManager(getApplicationContext(),cnum));
recyclerView.setAdapter(new ImageAdapter(getGallery(),displayMetrics.widthPixels/cnum,memCache,getApplicationContext()));
}
private File getGallery()
{
return new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath(),"Trial");
}
}
I have also tried and removed maximum of calls which can be removed to make sure there is not much task on UI thread.
Doing disk I/O on the main application thread is a common source of "jank", as that I/O may take a bit of time. StrictMode can help you determine where you are doing disk I/O on the main application thread.
In general, you want to load your model data (e.g., a list of files) on a background thread, then work off of the in-memory representation of that model data on the main application thread.
So I've decided to try out the new Volley library as shown on Google IO 2013.
I've tried it out while using the easy solution of NetworkImageView to show multiple images on a GridView.
It works nice and shows images, but if I let it download the images and then I turn off the WiFi during the download, it doesn't show an error as if everything still loads. Not only that, but if I restore the connection, it doesn't resume the loading.
Why does it occur, and how can I fix it? Maybe it's actually a bug?
Here's my sample code, if anyone wishes to try it out (BitmapCacheLru code here):
public class MainActivity extends Activity {
private static final int COLUMNS_COUNT = 4;
private RequestQueue _requestQueue;
private ImageLoader _imageLoader;
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
_requestQueue=Volley.newRequestQueue(this);
_imageLoader=new ImageLoader(_requestQueue, new BitmapLruCache());
final GridView gridView = new GridView(this);
gridView.setNumColumns(COLUMNS_COUNT);
final int screenWidth = getResources().getDisplayMetrics().widthPixels;
gridView.setAdapter(new BaseAdapter() {
#Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
NetworkImageView rootView = (NetworkImageView) convertView;
if (rootView == null) {
rootView = new NetworkImageView(MainActivity.this);
rootView.setLayoutParams(new AbsListView.LayoutParams(screenWidth / COLUMNS_COUNT, screenWidth / COLUMNS_COUNT));
rootView.setScaleType(ScaleType.CENTER_CROP);
rootView.setDefaultImageResId(android.R.drawable.sym_def_app_icon);
rootView.setErrorImageResId(android.R.drawable.ic_dialog_alert);
}
final String url = getItem(position);
rootView.setImageUrl(url, _imageLoader);
return rootView;
}
#Override
public long getItemId(final int position) {
return 0;
}
#Override
public String getItem(final int position) {
return Images.imageThumbUrls[position];
}
#Override
public int getCount() {
return Images.imageThumbUrls.length;
}
});
setContentView(gridView);
}
#Override
protected void onStop() {
_requestQueue.cancelAll(this);
super.onStop();
}
}
P.S. If you want to see the code of NetworkImageView, I think it's available here .
I think the problem is that the volley does not help you to reload the image.
A quick inspection shows that the NetworkImageView only loads data when onLayout method is called and the method loadImageIfNecessary will queue the network request if necessary.
When there is no Internet connection, the error callback will be called and there is no further action once the Internet get itself connected.
However, since you have the NetworkImage in a list, when you scroll the list, I suppose you will reuse the cell view and call setImageURL once again. If the Internet connection is available, the image will be loaded automatically. Alternatively, once the Internet connection is up, you can refresh the list view and so that the image will be loaded automatically.