I just implemented expandable layout in recycle view .ExpandableLayout library
Issue : Recycle view scroll slow at first time . expand/collpase working good but list scroll slow
NOTE : Its working smoothly on 4 GB RAM Device but laggy scroll on 2 GB RAM Device . I tested with Moto g3 (2 GB RAM) and Moto
g5 Plus (4 GB RAM)
Data coming from web-service and passing array-list through adapter.
Layout row file (XML) in Nested Linear Layouts. (may be its viewgroups load but i also tried with Constraint layout .Still recycleview load laggy)
I am replacing view during expansion and collapse item view (I already commented that code. but still my recycle view is slow )
There is no use of any image storing in itemview .
Already tried setHasFixedSize , notifyDataChanged , setCache , adapter.setHasStableIds(true);
but still recycleview loads laggy at first time only .
Help me to fix this issue. I am still finding the issue !
You can check my Adapter Code!
public class PendingOrdersAdapter extends RecyclerView.Adapter<PendingOrdersAdapter.ViewHolder> {
public LayoutInflater mInflater;
public HashSet<Integer> mExpandedPositionSet = new HashSet<>();
Context context;
ArrayList<CustomerDetails> list;
CustomerDetails details;
public PendingOrdersAdapter(Context context, ArrayList<CustomerDetails> list) {
this.context = context;
this.list = list;
this.mInflater = LayoutInflater.from(context);
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View item = mInflater.inflate(R.layout.pending_order_item, parent, false);
return new ViewHolder(item);
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
details = list.get(position);
holder.updateItem(position, holder);
holder.tvOrderNo.setText("" + details.getOrderID());
holder.tvCustomerName.setText("" + details.getCustomerName());
holder.tvTotalPrice.setText("" + details.getTotalPrice());
holder.tvCustomerContactNo.setText("" + details.getPrimaryContactNo());
holder.tvProductWeight.setText("" + details.getProductWeight());
holder.tvCustomerStatus.setText("" + details.getCustomerStatus());
holder.tvDeliveryManStatus.setText("" + details.getDeliveryManStatus());
holder.tvAddress.setText("" + details.getAddress());
holder.tvDeliveryMan.setText("" + details.getCustomerName() + " ( " + details.getPrimaryContactNo() + " ) ");
holder.tvTime.setText("" + details.getTime());
holder.tvPickUpDate.setText("" + details.getPickupDate());
holder.tvDeliveryCharge.setText("" + details.getDeliveryCharge());
//Opening DialogFragment on Click Listner
holder.tvDeliveryMan.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
AppCompatActivity activity = (AppCompatActivity) (context);
android.app.FragmentTransaction ft = activity.getFragmentManager().beginTransaction();
android.app.Fragment prev = activity.getFragmentManager().findFragmentByTag("dvdialog");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
DeliveryManDetailsDialog newFragment = new DeliveryManDetailsDialog();
newFragment.show(ft, "dvdialog");
}
});
holder.tvAddress.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
final int DRAWABLE_RIGHT = 2;
//Address icon click listner right side
if (event.getAction() == MotionEvent.ACTION_UP) {
if (event.getRawX() >= (holder.tvAddress.getRight() - holder.tvAddress.getCompoundDrawables()[DRAWABLE_RIGHT].getBounds().width())) {
// your action here
Intent intent = new Intent(context, RouteActivity.class);
intent.putExtra("custLat", details.getCustLatitude());
intent.putExtra("custLong", details.getCustLongitude());
intent.putExtra("boLat", details.getBoLatitude());
intent.putExtra("boLong", details.getBosLongitude());
context.startActivity(intent);
}
return true;
}
return true;
}
});
}
#Override
public int getItemViewType(int position) {
return position;
}
#Override
public int getItemCount() {
return list.size();
}
public void registerExpand(int position, ViewHolder holder) {
if (mExpandedPositionSet.contains(position)) {
//Replacing views at runtime and arrow animation
ViewGroupUtils.replaceView(holder.timeLayout, holder.statusLayout);
holder.orderbox.setBackgroundResource(R.drawable.box_fill_drawable);
holder.ivArrow.animate().rotation(360).start();
removeExpand(position);
} else {
ViewGroupUtils.replaceView(holder.statusLayout, holder.timeLayout);
holder.orderbox.setBackgroundResource(R.drawable.box_fill_drawable_top);
holder.ivArrow.animate().rotation(180).start();
addExpand(position);
}
}
public void removeExpand(int position) {
mExpandedPositionSet.remove(position);
}
public void addExpand(int position) {
mExpandedPositionSet.add(position);
}
public class ViewHolder extends RecyclerView.ViewHolder {
public ExpandableLayout expandableLayout;
public LinearLayout orderbox, orderbox_bottom;
public ImageView ivArrow;
public TextView tvAddress, tvOrderNo, tvCustomerName, tvTotalPrice, tvCustomerContactNo;
public TextView tvProductWeight, tvCustomerStatus, tvDeliveryManStatus;
public TextView tvDeliveryCharge, tvDeliveryMan, tvTime;
public TextView tvPickUpDate;
public LinearLayout statusLayout, statusParentLayout, timeLayout, timeParentLayout, addressLayout;
public ViewHolder(final View itemView) {
super(itemView);
expandableLayout = (ExpandableLayout)
itemView.findViewById(R.id.expandable_layout);
orderbox = (LinearLayout) itemView.findViewById(R.id.orderbox);
orderbox_bottom = (LinearLayout)
itemView.findViewById(R.id.orderbox_bottom);
ivArrow = (ImageView) itemView.findViewById(R.id.ivArrow);
tvOrderNo = (TextView) itemView.findViewById(R.id.tvOrderNo);
tvCustomerName = (TextView)
itemView.findViewById(R.id.tvCustomerName);
tvTotalPrice = (TextView)
itemView.findViewById(R.id.tvTotalPrice);
tvCustomerContactNo = (TextView)
itemView.findViewById(R.id.tvCustomerContactNo);
tvProductWeight = (TextView)
itemView.findViewById(R.id.tvProductWeight);
tvCustomerStatus = (TextView)
itemView.findViewById(R.id.tvCustomerStatus);
tvDeliveryManStatus = (TextView)
itemView.findViewById(R.id.tvDeliveryManStatus);
tvDeliveryCharge = (TextView)
itemView.findViewById(R.id.tvDeliveryCharge);
tvAddress = (TextView) itemView.findViewById(R.id.tvAddress);
tvDeliveryMan = (TextView)
itemView.findViewById(R.id.tvDeliveryMan);
tvTime = (TextView) itemView.findViewById(R.id.tvTime);
tvPickUpDate = (TextView)
itemView.findViewById(R.id.tvPickUpDate);
statusParentLayout = (LinearLayout)
itemView.findViewById(R.id.statusParentLayout);
statusLayout = (LinearLayout)
itemView.findViewById(R.id.statusLayout);
timeParentLayout = (LinearLayout)
itemView.findViewById(R.id.timeParentLayout);
timeLayout = (LinearLayout)
itemView.findViewById(R.id.timeDateLayout);
addressLayout = (LinearLayout)
itemView.findViewById(R.id.addressLayout);
}
public void updateItem(final int position, final ViewHolder holder) {
holder.orderbox.setBackgroundResource(R.drawable.box_fill_drawable_top);
holder.expandableLayout.setOnExpandListener(new ExpandableLayout.OnExpandListener() {
#Override
public void onExpand(boolean expanded) {
registerExpand(position, holder);
}
});
expandableLayout.setExpand(mExpandedPositionSet.contains(position));
}
}
Calling Adapter from Fragment
** PendingOrdersAdapter adapter = new PendingOrdersAdapter(getActivity(), detailsArrayList);
recyclerview.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setHasFixedSize(true);
recyclerView.setItemViewCacheSize(20);
recyclerView.setDrawingCacheEnabled(true);
recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
recyclerview.setAdapter(adapter);
adapter.notifyDataSetChanged();**
Make one class, for example, Util and added this method to the class and use it.
public static void setDivider(Context context, RecyclerView recyclerView, RecyclerView.LayoutManager layoutManager) {
recyclerView.setLayoutManager(layoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setHasFixedSize(true);
recyclerView.setItemViewCacheSize(20);
recyclerView.setDrawingCacheEnabled(true);
recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
DividerItemDecoration horizontalDecoration = new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL);
Drawable horizontalDivider = ContextCompat.getDrawable(context, R.drawable.horizontal_divider);
horizontalDecoration.setDrawable(horizontalDivider);
recyclerView.addItemDecoration(horizontalDecoration);
}
here first parameter is context, second one pass your recyclerview and third one pass the layout manager.
if you have more stuff then change the CacheSize
use this
recyclerView.setNestedScrollingEnabled(false);
Related
I have two recycler views. The first recyclerview is basically a list of data where I can choose an item and its quantity and I am storing this chosen item data into a map. The second one is the list of the selected data. which I am generating from getting the values() from the map. The second one also has similar viewholder and I can change the quantity there also. One the quantity reaches zero I remove the item from the list and try to notifydatasetChanged().
The problem is the removing of an item from the second list is not working properly and the app crashes with error
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder
I am using a listener interface on my first Recycler so that when the quantity is changed and the item is added to the map. The adapter of the second recycler is notified of the changes. below is the code i am using to update the second recycler view.
public void updateList(){
mMap = ((UserMainActivity)getActivity()).getItemMap();
inputs.clear();
// adapter.notifyDataSetChanged();
adapter = new MyCartAdapter(inputs,getContext());
cartList.setAdapter(adapter);
for(AllItems t:mMap.values()) {
inputs.add(t);
}
adapter.notifyDataSetChanged();
}
Below is my second recycler view's adapter. Where I am changing the quantities of the selected items.
public class MyCartAdapter extends RecyclerView.Adapter<MyCartAdapter.MyCartViewHolder>{
private List<AllItems> listItems1;
private Context context;
private Typeface typeface;
public MyCartAdapter(List<AllItems> listItems1, Context context) {
this.listItems1 = listItems1;
this.context = context;
}
#NonNull
#Override
public MyCartAdapter.MyCartViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.cart_items_layout, parent, false);
return new MyCartViewHolder(v);
}
#Override
public void onBindViewHolder(#NonNull final MyCartAdapter.MyCartViewHolder holder, final int position) {
final AllItems orderItem = listItems1.get(position);
holder.setProductImage(orderItem.getImageUrl(),context);
holder.name.setText(orderItem.getName());
String price = String.valueOf(orderItem.getPrice());
holder.price.setText(price);
final HashMap<String, AllItems> items = ((UserMainActivity)context).getItemMap();
holder.counter.setText(orderItem.getQuantity());
holder.add.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String quantity = String.valueOf(holder.counter.getText());
int count = Integer.parseInt(quantity)+1;
holder.counter.setText(String.valueOf(count));
String url = orderItem.getImageUrl();
AllItems newitem = new AllItems(orderItem.getName(),orderItem.getComname(),url, String.valueOf(count),orderItem.getWeight,orderItem.getPrice());
((UserMainActivity)context).addItem(orderitemname,newitem);
// notifyItemChanged(position);
}
});
//counter text iitem.textview showing the quantity of the selected item . integer count returns the value of counter text below i am checking if its zero than it simply sets the value to zero and else reduce it and update the map.
holder.minus.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String counterText = String.valueOf(holder.counter.getText());
int count = Integer.parseInt(counterText);
if (count==0){
holder.counter.setText("0");
}
if (count==1){
holder.counter.setText("0");
AllItems item = items.get(orderItem.getName());
if (item!=null){
String orderit = orderItem.getName();
((UserMainActivity)context).removeItem(orderit);
// here i am removing the value from the list which throws the exception
listItems1.remove(position);
notifyItemRemoved(position);
}
}
if (count>1){
String quantity = String.valueOf(count-1);
holder.counter.setText(quantity);
String orderitemname = orderItem.getName();
String url = orderItem.getImageUrl();
String weight = "100";
long weightl = Long.parseLong(weight);
AllItems newitem = new AllItems(orderItem.getName(),orderItem.getComname(),url, quantity,weight,orderItem.getPrice());
((UserMainActivity)context).addItem(orderitemname,newitem);
// listItems1.set(position, newitem);
// notifyItemChanged(position);
}
}
});
}
#Override
public int getItemCount() {
return listItems1.size();
}
public class MyCartViewHolder extends RecyclerView.ViewHolder {
public TextView name,price,count,comname;
public TextView weight;
LinearLayout add,minus;
TextView counter;
public MyCartViewHolder(View itemView) {
super(itemView);
name = (TextView) itemView.findViewById(R.id.ProName);
price = (TextView) itemView.findViewById(R.id.proPrice);
weight = (TextView) itemView.findViewById(R.id.ProWeight);
counter = (TextView) itemView.findViewById(R.id.counter);
add = (LinearLayout) itemView.findViewById(R.id.addLin);
minus= (LinearLayout) itemView.findViewById(R.id.minusLin);
}
public void setProductImage(final String thumb_image, final Context ctx){
productImage = (ImageView) itemView.findViewById(R.id.ProImage);
Picasso.with(ctx).setIndicatorsEnabled(false);
Picasso.with(ctx)
.load(thumb_image)
.networkPolicy(NetworkPolicy.OFFLINE)
.placeholder(R.drawable.basket_b).into(productImage, new Callback() {
#Override
public void onSuccess() {
}
#Override
public void onError() {
Picasso.with(ctx).load(thumb_image).placeholder(R.drawable.basket).into(productImage);
}
});
}
public void setComname(String name){
comname = (TextView)itemView.findViewById(R.id.proComName);
comname.setText(name);
}
}
}
This jumps out at me:
listItems1.remove(position);
notifyItemChanged(position);
The notifyItemChanged() method exists to tell the adapter that the data at the given position has changed, and that the ViewHolder should be re-bound. This is not what you're doing; you're removing an item.
Probably your app is crashing because you're removing the last item in your data set (e.g. position 10) and then telling the adapter that the item at position 10 has changed... but now the maximum position in your data set is 9.
Instead, you should use the notifyItemRemoved() method.
listItems1.remove(position);
notifyItemRemoved(position);
I have a recycler view which is showing fruit list. Each item has a buy button.When I click the buy button of an item in the list, the details of that item should pass to the order list page through intent and should be displayed in recycler view.I have used ArrayList for passing the data.Then when I click the continue button it will come back to fruit list page and I want to buy another fruit.But my issue is, when I click the buy button of another item, only that particular item is displayed in the order list.the previous item is missing and the recycler view is showing only one row.(ie, latest data).Can any one please help?
this is the adapter code:
public ViewHolder(View view) {
super(view);
fruitname = (TextView) view.findViewById(R.id.category_name);
rate = (TextView) view.finenter code heredViewById(R.id.rate);
buy = (TextView) view.findViewById(R.id.buy);
mProfilepic = (ImageView) view.findViewById(R.id.profilepic);
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View rootView = LayoutInflater.
from(mContext).inflate(R.layout.list_item, null, false);
RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
rootView.setLayoutParams(lp);
return new ViewHolder(rootView);
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, final int position) {
item = mListItem.get(position);
final ViewHolder mViewHolder = (ViewHolder) viewHolder;
mViewHolder.fruitname.setText(item.get_name());
mViewHolder.rate.setText(item.get_email()+"/kg");
}
mViewHolder.buy.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
item1.set_contact(mListItem.get(position).get_designation());
item1.set_bank_fee(mListItem.get(position).get_name());
item1.set_category(mListItem.get(position).get_email());
dataItem.add(item1);
Intent intent = new Intent(mContext,OrderListActivity.class);
intent.putExtra("arraylist",dataItem);
mContext.startActivity(intent);
}
});
}
#Override
public int getItemCount() {
return mListItem.size();
}
note:I have used notifyDataSetChanged.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.order_list);
mrecyclerview = (RecyclerView)findViewById(R.id.fruits_recycler);
mrecyclerview.setLayoutManager(new
LinearLayoutManager(getApplicationContext()));
dataItem =
(ArrayList<ListItem>)getIntent().getSerializableExtra("arraylist");
mOrderAdapter = new OrderAdapter(OrderListActivity.this,dataItem);
mrecyclerview.setAdapter(mOrderAdapter);
mOrderAdapter.notifyDataSetChanged();
}
}
Try this .
Intent intent = new Intent(mContext,OrderListActivity.class);
// edited here
intent.putParcelableArrayListExtra("arraylist", productList);
mContext.startActivity(intent);
And next Activity .
ArrayList<ListItem>) dataItem = getIntent().getParcelableArrayListExtra("arraylist");
if (dataItem != null && dataItem.size() > 0) {
mOrderAdapter = new OrderAdapter(OrderListActivity.this, dataItem);
mrecyclerview.setAdapter(mOrderAdapter);
// remove this
// mOrderAdapter.notifyDataSetChanged();
}
I have a recycler view within a fragment and basically I m trying to load song list in the recycler view .Each row of recycler view contains an imageview (for album art) and textview ( for song name). I am having trouble when the size of the dataset is huge, that is when there are too many songs, the recycler view lags and the app ends up giving an ANR.I am using Glide to load album arts in each row's imageview.
How is google music player able to show such large number of songs without any lag?
Edit:
This is my SongsFragment
public class SongsFragment extends Fragment {
static {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
ProgressBar progressBar; // progress bar to show after every 30 items
NestedScrollView nestedScrollView; //for smooth scrolling of recyclerview as well as to detect the end of recyclerview
RecyclerView recyclerView;
ArrayList<Song> songMainList = new ArrayList<>(); //partial list in which items are added
ArrayList<Song> songAllList = new ArrayList<>(); //Complete List of songs
SongAdapter songsAdapter;
private LinearLayoutManager layoutManager;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_songs, container, false);
nestedScrollView = (NestedScrollView) rootView.findViewById(R.id.nestedScrollView);
progressBar = (ProgressBar) rootView.findViewById(R.id.progressBar);
String songJson = getActivity().getIntent().getStringExtra("songList");
songAllList = new Gson().fromJson(songJson, new TypeToken<ArrayList<Song>>() {
}.getType());
//Getting list of all songs in songAllList
if (songAllList.size() > 30) {
songMainList = new ArrayList<>(songAllList.subList(0,30));
} else {
songMainList = songAllList;
}
//if size of fetched songAllList>30 then add only 30 rows to songMainList
recyclerView = (RecyclerView) rootView.findViewById(R.id.songs);
int spanCount = 1; // 2 columns
int spacing = 4; // 50px
recyclerView.addItemDecoration(new GridItemDecoration(spanCount, spacing, true));
recyclerView.setHasFixedSize(true);
recyclerView.setNestedScrollingEnabled(false);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
songsAdapter = new SongAdapter(getActivity(), songMainList, recyclerView);
nestedScrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
View view = (View) nestedScrollView.getChildAt(nestedScrollView.getChildCount() - 1);
int diff = (view.getBottom() - (nestedScrollView.getHeight() + nestedScrollView
.getScrollY()));
if (diff == 0) { //NestedScrollView scrolled to bottom
progressBar.setVisibility(View.VISIBLE); //show progressbar
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
if (songMainList.size() < songAllList.size()) {
int x = 0, y = 0;
if ((songAllList.size() - songMainList.size()) >= 30) {
x = songMainList.size();
y = x + 30;
} else {
x = songMainList.size();
y = x + songAllList.size() - songMainList.size();
}
for (int i = x; i < y; i++) {
songMainList.add(songAllList.get(i)); //Adding new items from songAllList to songMainList one by one
songsAdapter.notifyDataSetChanged();
}
}
progressBar.setVisibility(View.GONE);
}
}, 1500);
}
}
});
recyclerView.setAdapter(songsAdapter);
return rootView;
}
}
And this is my RecyclerViewAdapter along with viewholder
public class SongAdapter extends RecyclerView.Adapter {
private List<Song> songsList;
private Context c;
private RecyclerView.ViewHolder holder;
public SongAdapter(Context context) {
mainActivityContext = context;
}
public SongAdapter(Context context, List<Song> songs, RecyclerView recyclerView) {
songsList = songs;
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
c = context;
}
public SongAdapter getInstance() {
return SongAdapter.this;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.song_list_row, parent, false);
return new SongViewHolder(view,c);
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
if (holder instanceof SongViewHolder) {
Song song = songsList.get(position);
this.holder = holder;
String name = song.getName();
String artist = song.getArtist();
String imagepath = song.getImagepath();
((SongViewHolder) holder).name.setText(name);
((SongViewHolder) holder).artist.setText(artist);
if (!imagepath.equalsIgnoreCase("no_image")) //if the album art has valid imagepath for this song
Glide.with(c).load(imagepath)
.centerCrop()
.into(((SongViewHolder) holder).iv);
else
((SongViewHolder) holder).iv.setImageResource(R.drawable.empty);
((SongViewHolder) holder).song = song;
}
}
#Override
public int getItemCount() {
return songsList.size();
}
static class SongViewHolder extends RecyclerView.ViewHolder{
ImageView iv;
TextView name, artist;
CardView songListCard;
private Context ctx;
private OnLongPressListener mListener;
SongViewHolder(View v, Context context) {
super(v);
this.ctx = context;
iv= (ImageView) v.findViewById(R.id.album_art);
name= (TextView) v.findViewById(R.id.name);
artist= (TextView) v.findViewById(R.id.artist_mini);
songListCard = (CardView) v.findViewById(R.id.song_list_card);
}
}
The recyclerview works fine when there are only 150-200 items but when reaching to 600-700 items , the whole app slows down. Could this be because of the way I have used glide in onBindViewHolder?
Sort answer:
LinearLayoutManager(context).apply { isAutoMeasureEnabled = false }
// or in Java
layoutManager.setAutoMeasureEnabled(false)
UPDATE 2020.08.14
Deprecated RecyclerView.LayoutManager#setAutoMeasureEnabled
This method was deprecated in API level 27.1.0.
Implementors of LayoutManager should define whether or not it uses AutoMeasure by overriding isAutoMeasureEnabled()
From the doc of RecyclerView.LayoutManager#setAutoMeasureEnabled() we know :
This method is usually called by the LayoutManager with value {#code true} if it wants to support WRAP_CONTENT
It works by calling {#link LayoutManager#onLayoutChildren(Recycler, State)} during an {#link RecyclerView#onMeasure(int, int)} call, then calculating desired dimensions based on children's positions.
If we set mAutoMeasure = true, it will call LayoutManager#onLayoutChildren(Recycler, State) during an RecyclerView#onMeasure(int, int) call. Every child view's onMeasure() method will be called, this cost too much time.
Let's look at LinearLayoutManager's constructor
public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
setOrientation(orientation);
setReverseLayout(reverseLayout);
setAutoMeasureEnabled(true);
}
So, after we set mAutoMeasure = false, everything will be ok.
Solved the problem by removing the NestedScrollView over the recyclerview.
The nestedscrollview was not allowing the recyclerview.addOnScrollListener() to be called because of which I was getting a lag on loading more items.
Here is how i implemented the loadOnScroll for RecyclerView-
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (!recyclerView.canScrollVertically(1))
onScrolledToBottom();
}
});
private void onScrolledToBottom() {
if (songMainList.size() < songAllList.size()) {
int x, y;
if ((songAllList.size() - songMainList.size()) >= 50) {
x = songMainList.size();
y = x + 50;
} else {
x = songMainList.size();
y = x + songAllList.size() - songMainList.size();
}
for (int i = x; i < y; i++) {
songMainList.add(songAllList.get(i));
}
songsAdapter.notifyDataSetChanged();
}
}
Do you load the data all at once? RecycleView should not have problem, I think if you have too much data the processing itself can take too much time. You should load the data in chunks and check the scroll state of the user and load the next batch etc. kinda how Instagram or Facebook does it.
you may achieve this by using AndroidX Room & Paging
cache your data from network to local Room Database
using Paging to load your data from Room Database
Desired Result
Maintain scroll position and give right position on specific view of custom row.
I am using RecyclerView in which I am trying to add on Demand Data on beginning of list as well as end of list.
Fetching data through Scrolling bottom is working perfectly.
But Scrolling top having issues as follows:
Set Click listener in Adapter's onBindViewHolder.
Set Click listener in Recyclerview's OnItemTouchListener.
Here is the case:
When I scroll up the list and then It loads 15 data and add it to the top of list.
Our intention is to Maintain scroll position.
Which can be achieved through
adapter.notifyItemRangeInserted(0, stringArrayListTemp.size());
But the Issue is while I am clicking on any Item without scrolling then View's OnClickListener gives wrong position.
You can check adapter's method:
#Override
public void onBindViewHolder(MyViewHolder holder, final int position) {
String item = moviesList.get(position);
holder.title.setText(item);
holder.title.setTag(position);
holder.title.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.e("onClick", "stringArrayList position: " + moviesList.get(position) + " - view position: " + moviesList.get((int) view.getTag()));
}
});
}
But at the same time in Recyclerview's addOnItemTouchListener gives right position with stringArrayList.get(position) but gives wrong position using getTag.
mRecyclerView.addOnItemTouchListener(
new RecyclerItemClickListener(this, new RecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, int position) {
Log.e("onItemClick", "stringArrayList position: "+stringArrayList.get(position)+ " - view position: "+stringArrayList.get((int)view.getTag()));
}
}));
When I try to add data in the beginning of the list by following I am getting the exact issue.
// retain scroll position and gives wrong position onClick
adapter.notifyItemRangeInserted(0, stringArrayListTemp.size());
// does not retain scroll position, scroll to top & gives right position onClick
//adapter.notifyDataSetChanged();
This code work for me, so try some thing like that
public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
private final Typeface font;
public TextView mTitle, mDetail, mDate, mCategory;
public CheckBox mCheckBox;
public ImageButton mImageButton;
public ImageView mImage;
public View mview;
private SparseBooleanArray selectedItems = new SparseBooleanArray();
public MyViewHolder(View view) {
super(view);
mTitle = (TextView) view.findViewById(R.id.item_title);
mDetail = (TextView) view.findViewById(R.id.item_detail);
mImage = (ImageView) view.findViewById(R.id.item_thumbnail);
mDate = (TextView) view.findViewById(R.id.date);
mCategory = (TextView) view.findViewById(R.id.category);
mCheckBox = (CheckBox) view.findViewById(R.id.checkbox);
mImageButton = (ImageButton) view.findViewById(R.id.imageButton);
mview = view;
view.setOnClickListener(this);
view.setOnLongClickListener(this);
}
#Override
public void onClick(View v) {
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
CustomTabsIntent customTabsIntent = builder.build();
customTabsIntent.launchUrl(MainActivity.activity, Uri.parse(mDataSet.get(getAdapterPosition()).getPostLink()));
}
how i maintain the recyclerview position
public void setRecyclerViewLayoutManager(LayoutManagerType layoutManagerType) {
int scrollPosition = 0;
if (mRecyclerView.getLayoutManager() != null) {
scrollPosition = ((LinearLayoutManager) mRecyclerView.getLayoutManager())
.findFirstCompletelyVisibleItemPosition();
}
switch (layoutManagerType) {
case GRID_LAYOUT_MANAGER:
mLayoutManager = new GridLayoutManager(getActivity(), SPAN_COUNT);
mCurrentLayoutManagerType = LayoutManagerType.GRID_LAYOUT_MANAGER;
break;
case LINEAR_LAYOUT_MANAGER:
mLayoutManager = new LinearLayoutManager(getActivity());
mCurrentLayoutManagerType = LayoutManagerType.LINEAR_LAYOUT_MANAGER;
break;
default:
mLayoutManager = new LinearLayoutManager(getActivity());
mCurrentLayoutManagerType = LayoutManagerType.LINEAR_LAYOUT_MANAGER;
}
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.scrollToPosition(scrollPosition);
}
My adapter code:
public class BrandAdapter extends RecyclerView.Adapter<BrandAdapter.BrandViewHolder> {
private static final String TAG = BrandAdapter.class.getSimpleName();
private List<BrandItem> brands;
private Context context;
public BrandAdapter(Context context, List<BrandItem> data) {
this.context = context;
this.brands = data;
}
public void setData(List<BrandItem> dataDownload) {
this.brands = dataDownload;
}
#Override
public BrandViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_item_brand, null);
BrandViewHolder holder = new BrandViewHolder(view);
return holder;
}
#Override
public void onBindViewHolder(BrandViewHolder holder, int position) {
BrandItem brandItem = brands.get(position);
String name = brandItem.getName();
int count = brandItem.getCountArticles();
holder.tvName.setText(name);
if (count > 0) {
holder.tvCount.setText("" + count);
} else {
holder.tvCount.setVisibility(View.GONE);
}
}
#Override
public int getItemCount() {
return brands.size();
}
public static class BrandViewHolder extends RecyclerView.ViewHolder {
TextView tvName;
TextView tvCount;
public BrandViewHolder(View itemView) {
super(itemView);
tvName = (TextView) itemView.findViewById(R.id.tv_brand_name);
tvCount = (TextView) itemView.findViewById(R.id.tv_count_article);
}
}
}
Fragment code :
recyclerView = (RecyclerView) view.findViewById(R.id.recycleView);
linearLayoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(linearLayoutManager);
adapter = new BrandAdapter(getActivity(), brands);
recyclerView.setAdapter(adapter);
Data for brands is downloaded from server. After downloaded finished, I just set new data for adapter by this code :
brands = downloadedBrands();
adapter.setData(brands);
adapter.notifyDataSetChanged();
Everything is Ok when data loaded for first time after the download finish. But when I scroll down the recycleview and scroll up again, data for each item is wrong now, all textview tvCount is gone. I do not know why.
Is there any problem from my code ?
Greenrobo's answer is correct but here is an explanation as to WHY you are having this issue.
You are assuming that your view is always set to the default values in your onBindViewHolder method.
The RecyclerView re-uses views that have scrolled off screen and therefore the view you are binding to may have already been previously used (and changed).
You onBindViewHolder method should always set EVERYTHING up. i.e all views reset to the exact values you want and do not assume that because you default an item to visible, it will always be so.
Please make tvCount visible when setting a non-zero count.
if (count > 0) {
holder.tvCount.setText("" + count);
holder.tvCount.setVisibility(View.VISIBLE);
} else {
holder.tvCount.setVisibility(View.GONE);
}
See if this helps.
You told that if count is less than 0, hide the view. What if count is greater than zero ? You are not making the view visible again. So simply make the below changes in your if condition:
if (count > 0) {
holder.tvCount.setText("" + count);
holder.tvCount.setVisibility(View.VISIBLE);
} else {
holder.tvCount.setVisibility(View.GONE);
}