In nested recyclerview, child recyclerview's GridLayoutManager.getChildCount() gives total item count - android

I am using a RecyclerView inside SwipeRefreshLayout.The RecyclerView has another 2 RecyclerView (for now; it may increase). In my second RecyclerView i am trying to implement infinite scrolling. But my RecyclerView.getItemCount() and RecyclerView.getChildCount() are giving same value. Also the 2nd re has GridLayoutManager and GridlayoutManager.findFirstVisibleItemPosition() always gives 0 and GridLayoutManager.findLastVisibleItemPosition() always gives list size - 1 in OnScrolled of the RecyclerView. What is causing this and what should i do to implement the infinite scrolling.
fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/swipe_container"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/main_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingTop="#dimen/padding_standard"
android:paddingBottom="#dimen/padding_standard">
</android.support.v7.widget.RecyclerView>
</android.support.v4.widget.SwipeRefreshLayout>
parent_recyclerview.xml
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_marginBottom="#dimen/padding_standard"
android:orientation="vertical">
<TextView
android:id="#+id/section_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="#dimen/padding_standard"
android:layout_marginBottom="#dimen/margin_standard"
android:textColor="#color/label_text"
android:textSize="#dimen/text_size_standard"
android:textStyle="bold"
tools:text="MOments"/>
<android.support.v7.widget.RecyclerView
android:id="#+id/section_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"/>
<ProgressBar
android:id="#+id/events_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:visibility="gone"/>
</LinearLayout>
onScrollListener for child recycler view
RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
GridLayoutManager manager = (GridLayoutManager) recyclerView.getLayoutManager();
int itemSize = manager.getItemCount();
int firstVisibleItem = manager.findFirstVisibleItemPosition();
int visibleChIldCount = manager.getChildCount();
Logger.e(TAG,"=============== START =====================");
Logger.e(TAG, "itemSize: " + itemSize);
Logger.e(TAG, "firstVisibleitem: " + firstVisibleItem);
Logger.e(TAG, "visibleChIldCount: " + visibleChIldCount);
Logger.e(TAG,"mLayoutManager.firstCOmpletely: "+ manager.findFirstCompletelyVisibleItemPosition());
Logger.e(TAG,"mLayoutManager. lastcompletey: "+ manager.findLastCompletelyVisibleItemPosition());
Logger.e(TAG,"mLayoutManager.findLastVisible: "+ manager.findLastVisibleItemPosition());
Logger.e(TAG,"=================END ================");
if (itemSize >= firstVisibleItem + visibleChIldCount){
Logger.e("", "loading");
mLoadMoreListener.loadMore();
} else {
Logger.e(TAG, "not Loading");
}
}
};

Sorry for the late reply. But i will post my solution here if in case someone else is looking for them.
In the adapter of the parent recycler view i have set tag for the view
#Override
public SectionRowHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mLayoutInflater.inflate(R.layout.view_section, parent, false);
view.setTag(viewType);
return new SectionRowHolder(view);
}
The SectionRowHolder is simple ViewHolder with a RecyclerView.OnScrollListener property with getter and setter.
public class SectionRowHolder extends RecyclerView.ViewHolder {
protected RecyclerView recyclerView;
RecyclerView.OnScrollListener mOnScrollListener;
public SectionRowHolder(View view) {
super(view);
this.recyclerView = (RecyclerView) view.findViewById(R.id.section_list);
}
public RecyclerView.OnScrollListener getCustomScrollListener() {
return mOnScrollListener;
}
public void setCustomScrollListener(RecyclerView.OnScrollListener mOnScrollListener) {
this.mOnScrollListener = mOnScrollListener;
}
}
Then in the onBindViewHolder for the child with infinite scrolling I have implemented the load more logic in scroll listener and set to the child RecyclerView.
RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {
boolean loadEnable = false;
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
mTotalScrolled += dy;
if ((mTotalScrolled + LOAD_MORE_ENABLE_HEIGHT) > recyclerView.getHeight() && loadEnable) {
loadEnable = false;
mLoadMoreListener.loadMore();
} else {
loadEnable = true;
}
}
};
holder.setCustomScrollListener(scrollListener);
holder.recyclerView.addOnScrollListener(scrollListener);
Here LOAD_MORE_ENABLE_HEIGHT is offset from bottom of the child recycler view to initialize the loadmore() logic and mLoadMoreListener is callback to the fragment or activity.
Finally for passing the scoll listener from parent recycler view to the child recycler vew, in my parent RecyclerView's onScrollListener
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
View v = mRecyclerView.findViewWithTag(CHILD_RECYCLERVIEW_TAG);
SectionedLifeAtAdapter.SectionRowHolder viewHolder =
(SectionedLifeAtAdapter.SectionRowHolder) mRecyclerView
.findContainingViewHolder(v);
if (viewHolder.getCustomScrollListener() != null)
viewHolder.getCustomScrollListener().onScrolled((RecyclerView) v
.findViewById(R.id.section_list), dx, dy);
Logger.e(TAG, ">>> call to on scrolled listener >>>");
}
});
Here CHILD_RECYCLERVIEW_TAG is the view type that you set in the onCreateViewHolder of the parent Adapter.
It kind of look like messy but it did the job for me without any issues.

Related

How to show the view from bottom while recyclerview scrolling

I am having 15 to 30 items in my recyclerview. At the End of the recyclerview I want to show the Image/Layout at bottom. This image will slowly come to top while scroll the recylerview to top. When the list end the image/layout will fully shown. If we scroll down the recyclerview the image/layout should go down. If I stop the scroll at middle the image/layout will show partially. For example the Image/Layout height will be 100 dp. it will be placed in the bottom. It will not visible at first time. When we scroll the Recyclerview that view will be slowly appear. Please give me any idea to achieve this. Sorry for my bad English.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical" />
<RelativeLayout
android:id="#+id/bottomView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Will show while Scroll"
android:textSize="30sp"
/>
</RelativeLayout>
</RelativeLayout>
Scrolling Recyclerview
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) {
footerHeight = +10;
bottomView.setTranslationY(footerHeight);
Log.i("Test","...Scrolling up");
} else {
footerHeight = -10;
bottomView.setTranslationY(footerHeight);
Log.i("Test","...Scrolling down");
}
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
switch (newState) {
case RecyclerView.SCROLL_STATE_IDLE:
Log.i("Test","...The RecyclerView is not scrolling");
break;
case RecyclerView.SCROLL_STATE_DRAGGING:
Log.i("Test","...Scrolling now");
break;
case RecyclerView.SCROLL_STATE_SETTLING:
Log.i("Test","...Scroll Settling");
break;
}
}
});
Here I just increase/decrease the bottomX view while scrolling. But still I am missing something.
OP:
In this image bottom view is showing always. But initially it want view should be hidden state. While scroll up Bottom view slowly come up. If I scroll down Bottom view should slowly goes down.
Start a new project and try this:
MainActivity.java:
public class MainActivity extends AppCompatActivity {
private static final int DATA_LIST_SIZE = 50;
RecyclerView recyclerView;
TextView footer;
ArrayList<SampleData> dataArrayList;
LinearLayoutManager linearLayoutManager;
int totalHeight = -1;
int invisibleHeight = -1;
int scrolledHeight = -1;
int childHeight = -1;
int footerHeight = -1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recycler_view);
footer = findViewById(R.id.text_view_footer);
dataArrayList = genSampleDataList();
CustomRecyclerViewAdapter adapter = new CustomRecyclerViewAdapter(dataArrayList);
linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setAdapter(adapter);
footer.measure( View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
footerHeight = footer.getMeasuredHeight();
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
View firstVisibleView = recyclerView.getChildAt(0);
if (invisibleHeight == -1) {
childHeight = linearLayoutManager.getDecoratedMeasuredHeight(firstVisibleView);
totalHeight = childHeight * DATA_LIST_SIZE;
invisibleHeight = totalHeight - recyclerView.getHeight() + footerHeight;
}
scrolledHeight = linearLayoutManager.findFirstVisibleItemPosition() * childHeight +
recyclerView.getTop() - firstVisibleView.getTop();
int newRecyclerViewHeight = totalHeight - invisibleHeight + footerHeight -
scrolledHeight * footerHeight / invisibleHeight;
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, newRecyclerViewHeight);
recyclerView.setLayoutParams(params);
footer.setBackgroundColor(Color.rgb(255 * (invisibleHeight - scrolledHeight) / invisibleHeight,
255 * scrolledHeight / invisibleHeight, 0));
}
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
}
});
}
private ArrayList<SampleData> genSampleDataList() {
ArrayList<SampleData> tmpList = new ArrayList<>();
for (int i = 0; i < DATA_LIST_SIZE; i++) {
tmpList.add(new SampleData("Item " + (i + 1), "Description " + (i + 1)));
}
return tmpList;
}
}
SampleData.java:
public class SampleData {
String name;
String description;
public SampleData(String name, String description) {
this.name = name;
this.description = description;
}
}
CustomRecyclerViewAdapter.java:
public class CustomRecyclerViewAdapter extends RecyclerView.Adapter<CustomRecyclerViewAdapter.ViewHolder> {
ArrayList<SampleData> dataList;
public CustomRecyclerViewAdapter(ArrayList<SampleData> dataList) {
this.dataList = dataList;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_view, null);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
SampleData sampleData = dataList.get(position);
holder.textViewName.setText(sampleData.name);
holder.textViewDescription.setText(sampleData.description);
}
#Override
public int getItemCount() {
return dataList.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
TextView textViewName;
TextView textViewDescription;
public ViewHolder(#NonNull View itemView) {
super(itemView);
textViewName = itemView.findViewById(R.id.text_view_name);
textViewDescription = itemView.findViewById(R.id.text_view_description);
}
}
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:scrollbars="vertical" />
<TextView
android:id="#+id/text_view_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/recycler_view"
android:gravity="center"
android:text="Will show while Scroll"
android:textSize="30sp" />
</RelativeLayout>
item_view.xml:
<TextView
android:id="#+id/text_view_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="25sp"
android:textStyle="bold" />
<TextView
android:id="#+id/text_view_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp" />
</LinearLayout>
One solution if I've read your question correctly is in your model class to include link or Uri of ImageView in a String.
Then in your RecyclerView adapter do some boolean checking to see if item added has a link to it and if it has load it with library called Picasso for example. Picasso is simple just one line of code. If you are using image from phone you might just add uri to image.
And when items are added on last item add link to image or set it yourself.

Hidden view above RecyclerView

I want to make some hidden filters options above recyclerview (for example like in old versions of spotify):
How to do that? I use AppBar above my recycler.
You can use this code to do what you want
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) {
//You should HIDE filter view here
} else if (dy < 0) {
System.out.println("Scrolled Upwards");
if (mLayoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
//this is the top of the RecyclerView
System.out.println("==================================== >>>> Detect top of the item List");
//You should visible filter view here
} else {
//You should HIDE filter view here
}
} else {
System.out.println("No Vertical Scrolled");
//You should HIDE filter view here
}
}
});
You can do it as mentioned in answer above (with changing visibility of searching view so when it is visibility is View.GONE the recycleView will move up, using ContraintLayout). Or to add aditional ViewHolder to yours adapter (like TopSearchViewHolder which contains yours searching view).
Like this (in getItemViewType you would decide when to display particular viewType) :
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val orderViewHolder = OrderViewHolder.create(parent).apply {
listener = this#OrdersAdapter.listener
}
return when (viewType) {
R.layout.list_item1 -> orderViewHolder
R.layout.list_item2 -> NetworkStateViewHolder.create(parent, retryCallback)
else -> throw IllegalAccessException("Unknown view type $viewType")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (getItemViewType(position)) {
R.layout.list_item1 -> (holder as OrderViewHolder).bind(getItem(position))
R.layout.list_item2 -> (holder as NetworkStateViewHolder).bind(getItem(position))
}
}
override fun getItemViewType(position: Int): Int {
return if (hasExtraRow() && position == itemCount - 1) {
R.layout.list_item1
} else {
R.layout.list_item2
}
}
Use the RecyclerView scroll listener as I mentioned in the comment like this
private static int firstVisibleInListview;
firstVisibleInListview = yourLayoutManager.findFirstVisibleItemPosition();
#Override
public void onScrollStateChanged(RecyclerView recyclerView,int newState){
super.onScrollStateChanged(recyclerView,newState);
}
#Override
public void onScrolled(RecyclerView recyclerView,int dx,int dy){
super.onScrolled(recyclerView,dx,dy);
int currentFirstVisible =
yourLayoutManager.findFirstVisibleItemPosition();
if(currentFirstVisible > firstVisibleInListview){
image.setVisibility(View.VISIBLE);
}else{
filterView.setVisibvility(View.GONE);
}
}
}
});
XML for RecyclerView layout
<LinearLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/White"
android:orientation="vertical"
android:layout_gravity="center"
xmlns:android="http://schemas.android.com/apk/res/android">
//use FilterView here
//hide this view initially.
<Filter-view
android:visibility="gone"
android:id="#+id/filterView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/moviesRecyclerView"
android:layout_marginBottom="?attr/actionBarSize"/>
<TextView
android:visibility="gone"
android:layout_marginTop="#dimen/dimen_200dp"
android:id="#+id/noMoviesMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:padding="#dimen/dimen_10dp"
android:fontFamily="#font/noirden_bold"
android:textColor="#color/Black"
android:textSize="#dimen/text_18sp"
android:text="#string/the_are_no_movies_showing_right_now_please_check_later"/>
</LinearLayout>
let me know after you try this
My solution for now:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
int dy = 0;
Handler handler = new Handler();
#Override
public void onScrollStateChanged(RecyclerView recyclerView, final int newState) {
super.onScrollStateChanged(recyclerView, newState);
final boolean isOnTop = layoutManager.findFirstCompletelyVisibleItemPosition() == 0;
if (isOnTop && newState == 1)
handler.postDelayed(new Runnable() {
#Override
public void run() {
if (dy <= 0) {
KLog.d("show filter");
}
}
}, 20);
else if (newState == 1 && dy > 0)
KLog.e("gone filter");
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
this.dy = dy;
}
});
Not super elegant and still need to find a way to animate hidden view.

Make View scrolls along RecyclerView

Well, I'm trying to make a top bar like the one that is on Youtube's app. It works on almost all cases but when I use the ScrollListener from RecyclerView I get a problem.
As you can see the View doesn't change its position at certain moment when scrolled.
Here is my code:
rvTop.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
LinearLayoutManager layoutManager = (LinearLayoutManager) layoutManagerTop;
if(filterTopAdapter.lastSelected >= layoutManager.findFirstVisibleItemPosition() && filterTopAdapter.lastSelected <= layoutManager.findLastVisibleItemPosition()) {
selectionLine.setVisibility(View.VISIBLE);
final View view = rvTop.findViewHolderForAdapterPosition(filterTopAdapter.lastSelected).itemView;
selectionLine.setTranslationX(view.getLeft() - rvTop.getScrollX());
Log.d("Scroll", String.valueOf(view.getLeft() - rvTop.getScrollX()));
}
else {
selectionLine.setVisibility(View.GONE);
}
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
LinearLayoutManager layoutManager = (LinearLayoutManager) layoutManagerTop;
if(filterTopAdapter.lastSelected >= layoutManager.findFirstVisibleItemPosition() && filterTopAdapter.lastSelected <= layoutManager.findLastVisibleItemPosition()) {
selectionLine.setVisibility(View.VISIBLE);
final View view = rvTop.findViewHolderForAdapterPosition(filterTopAdapter.lastSelected).itemView;
selectionLine.setTranslationX(view.getLeft() - rvTop.getScrollX());
Log.d("Scroll", String.valueOf(view.getLeft() - rvTop.getScrollX()));
}
else {
selectionLine.setVisibility(View.GONE);
}
}
});
You should try TabLayout with ViewPager
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="fill_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="stellar.kade_c.com.MainActivity">
<android.support.design.widget.TabLayout
android:id="#+id/sliding_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="scrollable" />
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
You should use TabLayout with ViewPager for this

How to sync scrolling of all horizontal nested RecyclerViews within a vertical RecyclerViews?

Background
Suppose I have a vertical RecyclerView, where each row is a horizontal RecyclerView.
What I'd like to do is that no matter which horizontal RecyclerViews you scroll, all of the others will scroll accordingly, and always be synced with the exact same scroll X coordinate
The problem
I actually did ok for the basic operation :
It works by having a scrolling listener that all horizontal RecyclerViews have, yet when one starts to scroll, it is the only one that will have it, while it also affects the others to scroll with it.
However, I have 2 main issues with what I did:
In some (horizontal) scrolling operations (maybe some gestures, like fling), the scrolling of the multiple RecyclerViews is out of sync, so some are in X coordinate that is different from the others.
When scrolling vertically, I couldn't succeed setting the X coordinate correctly. Not only that, but onBindViewHolder of the vertical RecyclerView doesn't get called when I expected it to be called (called when I scroll a lot, and not just when I see a used one being re-shown).
What I've tried
Here's the current code:
MainActivity.java
public class MainActivity extends AppCompatActivity {
int mCurX = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final RecyclerView mainRecyclerView = (RecyclerView) findViewById(R.id.activity_main);
final LinearLayoutManager verticalLinearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mainRecyclerView.setLayoutManager(verticalLinearLayoutManager);
final LayoutInflater layoutInflater = LayoutInflater.from(this);
final OnScrollListener masterOnScrollListener = new OnScrollListener() {
RecyclerView masterRecyclerView = null;
#Override
public void onScrollStateChanged(final RecyclerView recyclerView, final int newState) {
super.onScrollStateChanged(recyclerView, newState);
switch (newState) {
case RecyclerView.SCROLL_STATE_IDLE:
if (masterRecyclerView != null) {
masterRecyclerView = null;
final int firstVisibleItemPosition = verticalLinearLayoutManager.findFirstVisibleItemPosition();
final int lastVisibleItemPosition = verticalLinearLayoutManager.findLastVisibleItemPosition();
for (int i = firstVisibleItemPosition; i <= lastVisibleItemPosition; ++i) {
RecyclerView horizontalRecyclerView = (RecyclerView) mainRecyclerView.findViewHolderForAdapterPosition(i).itemView;
if (horizontalRecyclerView != recyclerView)
horizontalRecyclerView.addOnScrollListener(this);
}
}
break;
case RecyclerView.SCROLL_STATE_SETTLING:
//TODO fix out-of-sync scrolling issues, probably here
case RecyclerView.SCROLL_STATE_DRAGGING:
if (masterRecyclerView == null) {
masterRecyclerView = recyclerView;
final int firstVisibleItemPosition = verticalLinearLayoutManager.findFirstVisibleItemPosition();
final int lastVisibleItemPosition = verticalLinearLayoutManager.findLastVisibleItemPosition();
for (int i = firstVisibleItemPosition; i <= lastVisibleItemPosition; ++i) {
RecyclerView horizontalRecyclerView = (RecyclerView) mainRecyclerView.findViewHolderForAdapterPosition(i).itemView;
if (horizontalRecyclerView != recyclerView)
horizontalRecyclerView.removeOnScrollListener(this);
}
}
}
}
#Override
public void onScrolled(final RecyclerView recyclerView, final int dx, final int dy) {
super.onScrolled(recyclerView, dx, dy);
mCurX += dx;
final int firstVisibleItemPosition = verticalLinearLayoutManager.findFirstVisibleItemPosition();
final int lastVisibleItemPosition = verticalLinearLayoutManager.findLastVisibleItemPosition();
for (int i = firstVisibleItemPosition; i <= lastVisibleItemPosition; ++i) {
RecyclerView horizontalRecyclerView = (RecyclerView) mainRecyclerView.findViewHolderForAdapterPosition(i).itemView;
if (horizontalRecyclerView != recyclerView)
horizontalRecyclerView.scrollBy(dx, dy);
}
}
};
mainRecyclerView.setAdapter(new Adapter() {
#Override
public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
RecyclerView horizontalRecyclerView = (RecyclerView) layoutInflater.inflate(R.layout.horizontal_recycler_view, parent, false);
horizontalRecyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this, LinearLayoutManager.HORIZONTAL, false));
horizontalRecyclerView.addOnScrollListener(masterOnScrollListener);
final ViewHolder horizontalViewHolder = new ViewHolder(horizontalRecyclerView) {
};
horizontalRecyclerView.setAdapter(new Adapter() {
#Override
public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
return new ViewHolder(layoutInflater.inflate(R.layout.single_item, parent, false)) {
};
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
((TextView) holder.itemView).setText("horizontalRecyclerView:" + horizontalViewHolder.getAdapterPosition() + "\nitem:" + position);
}
#Override
public int getItemCount() {
return 100;
}
});
return horizontalViewHolder;
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
//TODO check why this isn't called for some cases
RecyclerView recyclerView = (RecyclerView) holder.itemView;
recyclerView.removeOnScrollListener(masterOnScrollListener);
//TODO scroll to correct location here. The below code doesn't seem to work at all
recyclerView.scrollToPosition(0);
recyclerView.scrollBy(mCurX,0);
recyclerView.addOnScrollListener(masterOnScrollListener);
recyclerView.getAdapter().notifyDataSetChanged();
}
#Override
public int getItemCount() {
return 40;
}
});
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
android:id="#+id/activity_main"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="lb.com.nestedallscrollingrecyclerviewtest.MainActivity"/>
horizontal_recycler_view.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="100dp"/>
single_item.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="10dp"/>
The questions
What is wrong in the code that causes it to be out-of-scrolling sync?
Is it possible I also don't get a hold of all the RecyclerViews that I should?
How come the onBindViewHolder of the vertical RecyclerView doesn't get called when I expect it to?
How do I set the x-coordinate scrolling of a horizontal RecyclerView to be as the others, in onBindViewHolder of the vertical one?
I'm not sure if this could be a problem, but what should I do in case each item in each horizontal RecyclerView could be with a different width than the others ?
A bit late to the party but just putting it here in case anyone else stumbles upon the same issue. Please Note that this solution is written in Kotlin and you might have to convert it to Java if that is your language of choice.
Solution
There are a couple of issues that need to be taken into account.
Synchronise scrolling of horizontal recycler views
Retain offset when scrolling vertical recycler view
Add this code in the Adapter for your vertical recycler view
var horizontalRecyclerViews = mutableListOf<RecyclerView>()
var absoluteOffset: Int? = null //Used to solve issue number 2
// matchOffset is used to synchronise the offset of each horizontal recyclerview.
// It is called when a horizontal recyclerview is scrolled with that recyclerview's
// offset. It is also called when the vertical recycler view is scrolled but without
// an offset value (in which case, it uses the absoluteOffset which is set when
// the horizontal scrolling is stopped)
fun matchOffset(offset: Int? = absoluteOffset) {
offset?.let { offsetValue ->
horizontalRecyclerViews.forEach { recyclerView ->
val currentOffset = recyclerView.computeHorizontalScrollOffset()
if (offsetValue != currentOffset) {
recyclerView.scrollBy(offsetValue-currentOffset, 0)
}
}
}
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
...
...
...
val onTouchListener = object: RecyclerView.OnItemTouchListener {
override fun onTouchEvent(p0: RecyclerView, p1: MotionEvent) {
}
override fun onInterceptTouchEvent(p0: RecyclerView, p1: MotionEvent): Boolean {
if (p1.action == MotionEvent.ACTION_UP) {
// This value is used by the vertical recycler view
absoluteOffset = p0.computeHorizontalScrollOffset()
// Disable the fling scroll to make life easier
return true
}
return false
}
override fun onRequestDisallowInterceptTouchEvent(p0: Boolean) {
}
}
val onScrollListener = object: RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val value = recyclerView.computeHorizontalScrollOffset()
matchOffset(value)
}
}
...
...
...
//Clear scroll listeners on each bind to stop them from accumulating
horizontalRecyclerView.clearOnScrollListeners()
//Add touch and scroll listeners to horizontalRecyclerView
horizontalRecyclerView.addOnItemTouchListener(onTouchListener)
horizontalRecyclerView.addOnScrollListener(onScrollListener)
//Add each horizontal recyclerView into the mutableList
horizontalRecyclerViews.add(horizontalRecyclerView)
...
...
...
}
To wrap it up for the resolution of issue number 2, add the following scroll listener to your vertical recycler view
val onScrollListener = object: RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
//Cast the Adapter to access the matchOffset method
(recyclerView.adapter as? Adapter)?.matchOffset()
}
}
verticalRecyclerView.addOnScrollListener(onScrollListener)

Android how to keep RecyclerView recycling inside ScrollView

I have three recyclerview inside a nestedscrollview and I need that the third one keep recycling like if he was out of the nestedscrollview.
It all goes great except the recycling feature is missing.
Here is my xml :
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/scroll_discovery"
android:fillViewport="true"
android:fitsSystemWindows="true"
>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/category_recycler"/>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/category_recycler"
android:id="#+id/user_recycler"/>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/user_recycler"
android:id="#+id/experiences"/>
</RelativeLayout>
</android.support.v4.widget.NestedScrollView>
and here is the java part
experienceRecycler=(RecyclerView)findViewById(R.id.experiences);
experienceRecycler.setHasFixedSize(true);
mLayoutManager =new LinearLayoutManager(this) {
#Override
public boolean canScrollVertically() {
return false;
}
};
page= 1;
experienceRecycler.setLayoutManager(mLayoutManager);
experienceAdapter=new FeedCardAdapter(experiences,this,currentUser);
experienceRecycler.setAdapter(experienceAdapter);
/*experienceRecycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
feedCardAdapter.setScrolled(true);
}
});*/
categoryRecycler=(RecyclerView)findViewById(R.id.category_recycler);
categoryRecycler.setLayoutManager(new LinearLayoutManager(this){
#Override
public boolean canScrollVertically() {
return false;
}
});
categoryAdapter=new CategoryAdapter(this,categories);
categoryRecycler.setHasFixedSize(true);
categoryRecycler.setAdapter(categoryAdapter);
userRecycler=(RecyclerView)findViewById(R.id.user_recycler);
userRecycler.setLayoutManager(new LinearLayoutManager(this){
#Override
public boolean canScrollVertically() {
return false;
}
});
userAdapter=new UserAdapter(this,users);
userRecycler.setAdapter(userAdapter);
userRecycler.setHasFixedSize(true);
I finaly find the solution by myself.
I just had a height to the recyclerview I want to keep recycling.

Categories

Resources