Thank you for suggested links with solutions!
Unfortunately, these methods doesn't solve the problem with scroll stuttering in recycler.
And when new portions of data inserted into the first recycler, everything work very slow, cause scrollView recalculate height of every child item when, its contents are changing (the same problem was in NestedScrollView).
And all RecyclerView efficiency is killed by inlining it inside ScrollView or NestedScrollView.
I solved this in another way:
I used one RecyclerView with sections inside of it. Achieved this with custom ViewHolder and custom adapter behavior.
With that approach my recycler works very smooth, and fast.
So my items structure in RecyclerView:
- First TextView header
- First dynamicly populated items section
- Second TextView header
- Second items section
- TextView footer
For headers i declared custom ViewHolder:
protected class HeaderViewHolder extends BaseListAdapter.ViewHolder implements View.OnClickListener {
View view;
TextView textView;
protected HeaderViewHolder(View v) {
super(v);
this.view = v;
textView = this.view.findViewById(R.id.header_text);
v.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (itemClickListener != null)
itemClickListener.onItemClick(v, this.getLayoutPosition());
}
public View getView() {
return this.view;
}
// Set custom header text
public void setTitle(String title) {
textView.setText(title);
}
// Hide or show title, if items section don't have any items
public void setVisibility(int visibility) {
textView.setVisibility(visibility);
}
}
Layout for header ViewHolder:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/search_header"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
CustomListAdapter to fill and manage recycler sections:
public class CustomListAdapter extends BaseListAdapter<RecyclerItem> {
private static final int FOOTER_TYPE = 10;
private static final int FIRST_HEADER_TYPE = 25;
private static final int SECOND_HEADER_TYPE = 50;
private static final int SECOND_ITEMS_TYPE = 100;
private List<RecyclerItem> secondRecyclerItems = new ArrayList<>();
public CustomListAdapter(List<RecyclerItem> items, Context context) {
super(items, R.layout.first_recycler_item, context);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case FIRST_HEADER_TYPE:
case SECOND_HEADER_TYPE:
HeaderViewHolder holder = new HeaderViewHolder(View.inflate(parent.getContext(), R.layout.header_layout, null));
holder.setTitle(parent.getResources().getString(viewType == FIRST_HEADER_TYPE ? R.string.first_header_title : R.string.second_header_title));
return holder;
case FOOTER_TYPE:
return new HeaderViewHolder(View.inflate(parent.getContext(), R.layout.footer_layout, null));
default:
return super.onCreateViewHolder(parent, viewType);
}
}
@Override
public void onBindViewHolder(BaseListAdapter.ViewHolder holder, int position) {
if (holder instanceof HeaderViewHolder) {
if (position == 0) {
((HeaderViewHolder) holder).setVisibility(items.size() == 0 ? View.GONE : View.VISIBLE);
} else if (position == super.getItemCount() + 1 || position == getItemCount() - 1) {
((HeaderViewHolder) holder).setVisibility(secondRecyclerItems.size() == 0 ? View.GONE : View.VISIBLE);
}
return;
}
RecyclerItem item = position <= items.size()
? items.get(position - 1)
: secondRecyclerItems.get(position - (items.size() + 2));
// Here you populate your items with data, load images, etc...
// TextView title = holder.itemView.findViewById(R.id.first_recycler_item_title);
}
public void clearFirstRecyclerItems() {
items.clear();
notifyDataSetChanged();
}
public void clearSecondRecyclerItems() {
secondRecyclerItems.clear();
notifyDataSetChanged();
}
// Define separate collection for second recycler items. First recycler will be populated by default base adapter
public void swapSecondRecyclerItems(List<RecyclerItem> list) {
if (secondRecyclerItems != null) {
secondRecyclerItems.clear();
secondRecyclerItems.addAll(list);
} else {
secondRecyclerItems = new ArrayList<>();
secondRecyclerItems.addAll(list);
}
notifyDataSetChanged();
}
// Handle items click, and skip clicks on headers
public RecyclerItem getClickedItem(int position) {
if (position == super.getItemCount() + 1 || position == 0 || position == getItemCount() - 1)
return null;
return position <= items.size()
? items.get(position - 1)
: secondRecyclerItems.get(position - (items.size() + 2));
}
// That's the core method to split recycler items into sections, depending on their position
@Override
public int getItemViewType(int position) {
if (position == 0) {
return FIRST_HEADER_TYPE;
} else if (position == super.getItemCount() + 1) {
return SECOND_HEADER_TYPE;
} else if (position == getItemCount() - 1) {
return FOOTER_TYPE;
} else if (position > super.getItemCount() + 1) {
return SECOND_ITEMS_TYPE;
}
return super.getItemViewType(position);
}
// Redefine overall items quantity - super.getItemCount() (base recyclerAdapter items) + secondRecyclerItems + 3 (2 headers and 1 footer)
@Override
public int getItemCount() {
return super.getItemCount() + secondRecyclerItems.size() + 3;
}
public int getFirstRecyclerItemsCount() {
return super.getItemCount() + 1;
}
}
And for completeness BaseListAdapter:
public abstract class BaseListAdapter<T> extends RecyclerView.Adapter<BaseListAdapter.ViewHolder> {
protected List<T> items;
protected @android.support.annotation.LayoutRes int layout;
protected Context context;
protected OnItemClickListener itemClickListener;
public BaseListAdapter(List<T> items, @android.support.annotation.LayoutRes int layout, Context context) {
this.items = items;
this.layout = layout;
this.context = context;
}
public T getItem(int position) {
return items.get(position);
}
public List<T> getItems() {
return items;
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.itemClickListener = onItemClickListener;
}
// Create new views (invoked by the layout manager)
@Override
public BaseListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(layout, parent, false);
return new ViewHolder(v);
}
// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
return items.size();
}
/**
* Update the recycler view with a new dataset.
* */
public void swap(List<T> list){
if(items != null) {
items.clear();
items.addAll(list);
}
else {
items = new ArrayList<>();
items.addAll(list);
}
notifyDataSetChanged();
}
public void add(List<T> list){
items.addAll(list);
notifyDataSetChanged();
}
protected class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private View view;
protected ViewHolder(View v) {
super(v);
this.view = v;
v.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if(itemClickListener != null)
itemClickListener.onItemClick(v, this.getLayoutPosition());
}
public View getView() {
return this.view;
}
}
public interface OnItemClickListener
{
void onItemClick(View v, int position);
}
}
Hope it could help someone.