I have RecyclerView which its height is set to wrap_content. Now I need to implement OnLoadMore to it but there is a problem.
I used,
RecyclerView.OnScrollListener.onScrolled(RecyclerView recyclerView, int dx, int dy)
But it doesn't get invoked because my RecyclerView doesn't scroll. Its height is wrap_content.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/root_rtl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white">
<include
android:id="#+id/tool_bar"
layout="#layout/toolbar" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#id/tool_bar">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context="com.yarima.msn.Activities.ProfileActivity">
<FrameLayout
android:id="#+id/FRAGMENT_PLACEHOLDER"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/white">
<!-- Some Content That I want to scroll with recyclerview -->
</FrameLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview_posts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:bellow="#id/FRAGMENT_PLACEHOLDER"/>
</RelativeLayout>
</ScrollView>
</RelativeLayout>
So I need to use another approach for loading more pages to RecyclerView.
I think the best way to do this, is calling onLoadMore event when the last item of RecyclerView become visible. I already tried to do this from onBindViewHolder method in adapter, but all pages loaded altogether.
if(getItemCount()-position == 1 && onLoadMoreListener != null){
if (recyclerView != null) {
visibleItemCount = recyclerView.getChildCount();
totalItemCount = recyclerView.getLayoutManager().getItemCount();
firstVisibleItem = ((LinearLayoutManager)recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading && (totalItemCount - visibleItemCount)
<= (firstVisibleItem + visibleThreshold)) {
loading = true;
onLoadMoreListener.onLoadMore();
}
}
}
What is the alternative way to implement onLoadMore without using scroll events?
Update:
The RecyclerView works perfectly with android:layout_height:"wrap_content" and my ScrollView scrolls smoothly.
Update 2:
My problem is when your RecyclerView height is wrap_content, scroll events of RecyclerView cannot be invoked. So I need an alternative way to find out when my RecyclerView reaches to end of its list and implement OnLoadMore event that way.
Update 3
I simplified xml before I wrote it in question... In real xml, there is ViewPager instead of the RecyclerView. And I have 4 tabs in that ViewPager that each tab contains a RecyclerView with different contents.
Above of this ViewPager I have some information about user and I want to scroll all of them together. So I put this header and ViewPager in a ScrollView and set the height of RecyclerView to wrap_content.
You can take a look at profile page of instagram. I want to this page works like that.
It's not possible to show this information in header of RecyclerView because in this way, I should add this information in each RecyclerView in every tabs.
Edited
Could look for scroll events in scrollview
https://developer.android.com/reference/android/view/View.html#onScrollChanged(int, int, int, int)
You need to keep your ViewPager and your RecyclerView inside a NestedScrollView. The final xml should look like this.
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ViewPager/>
<RecyclerView/>
</android.support.v4.widget.NestedScrollView>
And set the height of your RecyclerView to match_parent.
You'll face another problem here. The page will automatically scroll to bottom when the RecyclerView will be loaded. But there's a solution to it too.
Adding android:descendantFocusability="blocksDescendants" to the child layout in NestedScrollView which will prevent the automatic scrolling to the bottom.
If the above doesn't work, try setting nestedScrollView.scrollTo(0, 0); from your code after the RecyclerView is loaded with items.
Related
How to trigger a recyclerview scroll listener when it is inside a scroll view?
Here the scroll view listener is alone triggering
1. Set nested scrolling enabled false of recycler view.
recyclerView.setNestedScrollingEnabled(false);
2. Add scroll listner to nested scrollview.
mScrollView.getViewTreeObserver().addOnScrollChangedListener(new
ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged()
{
View view = (View)mScrollView.getChildAt(mScrollView.getChildCount() - 1);
int diff = (view.getBottom() - (mScrollView.getHeight() + mScrollView
.getScrollY()));
if (diff == 0) {
// your pagination code
}
}
});
Try with NestedScrollView instead of ScrollView.
NestedScrollView is just like ScrollView, but it supports acting as both a nested scrolling parent and child on both new and old versions of Android. Nested scrolling is enabled by default.
Note: You have to set setNestedScrollingEnable(false) to your recylerview.
in xml add android:nestedScrollingEnabled="false" in recyclerview
OR
programmatically yourRecylerview.setNestedScrollingEnabled(false);
See this article.
You can try this one, but still I assuming you may have the same code like mine as you didnot post your needed code into your question.
Try this:
<?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="match_parent"
android:orientation="vertical">
<android.support.v4.widget.NestedScrollView
android:id="#+id/nestedScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</LinearLayout>
Use NestedScrollView with <android.support.v7.widget.RecyclerView as I have mention above.
Its ScrollListener will not work as all the rows gets layed when recyclerview is inside scrollview whole purpose of recyclerview gets destroyed when you put it inside scroll view.
For Example as the recyclerview will stretch to full fill its parent ,when you will put it inside scroll view , in case of scroll view it will stretch to its fullest. if there are 1000 items in recyclerview all it will draw 1000 different rows which is also a performance issue.
You should remove it from a scroll view to make its scroll listener working
I have a RecyclerView with a FAB on top at the right bottom.
When scrolling I would like to allow the user to scroll further up such that the last item is fully visible and not covered by the FAB. It would be ok to allow an additional 48px at the bottom showing the background.
I tried adding a transparent footer item which does the trick. However, after adding sorting to the list, the transparent footer item creates some UI glitches during sorting (the divider is shown below the normal items).
I tried adding margin, but then the space is wasted all the time. It is ok that the FAB covers the last item when user views the first items at the top. Only when that last item is important than the item must not be covered.
This is the layout xml:
<android.support.design.widget.CoordinatorLayout
android:id="#+id/coordinator_layout"
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="match_parent"
android:layout_gravity="center"
android:clipToPadding="false"
android:scrollbars="vertical"/>
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
style="#style/floating_action_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="#dimen/fab_margin"
android:src="#drawable/ic_add_white_24dp"
app:layout_anchorGravity="bottom|center"/>
</android.support.design.widget.CoordinatorLayout>
Update
Using item decoration is the correct answer here.
I think better solution should calculate scroll range and hide the fab, if content was scroll down.
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener(){
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy){
if (dy > 0) {
fab.hide();
return;
}
if (dy < 0) {
fab.show();
}
}
});
But if you really want to add margin at bottom you should use ItemDecoratorion.
I am using RecyclerView inside NestedScrollView and it works. But when I use RecyclerView inside LinearLayout or something, it scroll in various speed depending on gesture. The scroll listen to gesture and if I slide up only a bit, then it scroll a little bit while if I slide up really fast, then it scroll really fast. Now my problem is that RecyclerView inside NestedScrollView certainly scroll but fast scroll does not work. However I slide up fast or slow, RecyclerView or NestedScrollView only scroll a little bit.
How can I make my NestedScrollView or RecyclerView inside that scroll view scroll in various speed?
try
recyclerView.setNestedScrollingEnabled(false);
By default setNestedScrollingEnabled works only after API-21.
You can use ViewCompat.setNestedScrollingEnabled(recyclerView, false); to disable nested scrolling for before and after API-21(Lollipop). Link to documentation.
I was working on android 16 where this was not possible to use setNestedSCrollEnabled method,
What I end up doing to stop RecyclerView from handling Scrolls.
Like in LinerLayoutManager i made canScrollHorizontally, canScrollVertically to return false by default.
myRecyclerView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false){
#Override
public boolean canScrollHorizontally() {
return false;
}
#Override
public boolean canScrollVertically() {
return false;
}
});
After several iterations, I came up with a solution.
If you are using RecyclerView, then:
recyclerView.setNestedScrollingEnabled(false);
If you are using LinearLayout inside NestedScrollingView, take the LinearLayout inside a normal ScrollView and then set its scrolling to
scrollView.setNestedScrollingEnabled(false);
android:overScrollMode="never
<android.support.v4.widget.NestedScrollView
android:id="#+id/nestedScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
You can use ScrollView with ExtendRecyclerView class that overrides the onMeasure method. That works for me!
#Override
protected void onMeasure(int widthSpec, int heightSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthSpec, expandSpec);
}
recyclerView.setNestedScrollingEnabled(false);
Will be useful sometimes.But it is not advisable for all the times.because it disables view recycling feature in recylcer view.
Alternatives:
Try CollapsiveToolbarLayout with Recycler view.
put other views in collapsiveTollbar layout.
I also met this problem.
And upgrade to 26.1.0 fix it.
In My Case i placed all images in drawable folder insted of drawable-xxxhdpi folder thats why my screen UI is lagging.
This is WAI. The NestedScrollView measures its children with the Spec "Unspecified". The child can grow as much as it wants too.
This essentially equates the height of NSV and RV. So as far as the RV is concerned, it believes that it is completely displayed.
Wrap your RV with an LL and give your RV a height. The LL would not set the measure spec to be UNSPECIFIED so the RV would correctly scroll within its set height of whatever DPs you provide.
The only downside of this method is that you will not be able to do a match parent on your RV.
You should wrap recycler view in any layout like LinearLayout and set RecyclerView size to constant, like 800dp. This will enable smooth scroll and recycler view will still recycler views during scroll.
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="800dp"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
</LinearLayout>
I'm trying to implement a CoordinatorLayout with a ToolBar that collapses. I already have a SwipeRefreshLayout with a RecyclerView inside. This recycler view also has an onScrollListener to load more content and a custom adapter to show a loading ViewHolder when it's loading more content.
Everything was working fine before I tried adding the CoordinatorLayout. Now I have two problems:
When loading items for the first time, the loading ViewHolder shows up and it's well placed (below the ToolBar). When it finishes, the loading is removed and the items are added. The problem is that the first item is hidden. It's like the second item is actually the first one. It's completely impossible to see the first item even when it is bigger than the ToolBar. But when I use swipe to refresh, the item gets placed properly. I have no idea why this happens.
When I'm using swipe to refresh, it loads items two times. The first time is the normal load and the second is a load for more items because of the onScrollListener. However the scroll is still on the top of the list. The recycler view items stay invisible until I scroll (I think this is because I only notify the adapter of the new items but we are still at the top of the list).
However, I don't know what to change in the listener to fix this.
Here's the listener:
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if(!adapter.isLoading()) {
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
//position starts at 0
if (layoutManager.findLastCompletelyVisibleItemPosition() >= layoutManager.getItemCount() - 2) {
loadSubmissions(false);
}
}
}
});
My activity layout:
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include
android:id="#+id/toolbar"
layout="#layout/toolbar" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/contentView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="6dp"
android:paddingTop="6dp"
android:scrollbars="vertical"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</android.support.v4.widget.SwipeRefreshLayout>
</android.support.design.widget.CoordinatorLayout>
My toolbar:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/colorPrimary"
android:elevation="4dp"
app:layout_scrollFlags="scroll|enterAlways">
</android.support.v7.widget.Toolbar>
Both problems are fixed. For the first one, when I was removing the loading ViewHolder I was actually doing notifyItemInserted instead of notifyItemRemoved.
For the second problem it's because I'm calling notifyDatasetChanged when I'm using swipe to refresh.
#Override public void onRefresh() {
adapter.clear();
adapter.notifyDataSetChanged();
loadSubmissions(true);
}
When I clear the adapter with the swipe to refresh, I'm swiping so it means that the onScrollListener will be called.
I changed the listener and added a verification to don't load more if there are no items.
mRecyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener(linearLayoutManager) {
#Override
public void onLoadMore(int current_page) {
// Doesnt load multiples times with the same scroll and doesnt load if there are no items
if(!adapter.isLoading() && adapter.getItemCount() > 0) {
loadSubmissions(false);
}
}
});
I'm trying to use the setHideOnContentScrollEnabled and setHideOffset in the new L API. However, none of the mentioned functions seem to have any effect. Anyone else encountered the same issue?
My Activity's layout is a ScrollView with a TextView displaying a large amount of text, so there are def scrolling. I have also, as required by the documentation, added FEATURE_ACTION_BAR_OVERLAY
getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
setContentView(R.layout.main_activity);
getActionBar().setHideOnContentScrollEnabled(true);
getActionBar().setHideOffset(40);
Notice that:
If enabled, the action bar will scroll out of sight along with a
nested scrolling child view's content.
View.setNestedScrollingEnabled(boolean)
I was facing the same problem, using a RecyclerView, a Toolbar and trying to support API10+. I just could not get setHideOffset() or setHideOnContentScrollEnabled() on my SupportActionBar to work.
After a lot of different manual approaches on scrolling the toolbar, this is my current workaround:
I use a ScrollView only for my Toolbar. My Recycler handles its own scrolling which is being listened to.
my_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!--The Recycler is in a RefreshLayout. This is optional.-->
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/swipe"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:layout_alignParentTop="true">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:scrollbars="vertical" />
</android.support.v4.widget.SwipeRefreshLayout>
<!--Draw the Recycler _below_ the Toolbar-->
<!--by defining it _before_ everything else.-->
<ScrollView
android:id="#+id/scroll_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_alignParentTop="true"
android:scrollbars="none">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
<!--Add a transparent View below the Toolbar
to give the ScrollView "something to scroll".
Make sure it is _not_ clickable.-->
<View
android:layout_width="match_parent"
android:layout_height="128dp"
android:clickable="false" />
</RelativeLayout>
</ScrollView>
In myActivity.class
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_room_list);
mToolbarScroller = (ScrollView) findViewById(R.id.scroll_toolbar);
mRecycler = (RecyclerView) findViewById(R.id.recycler_rooms);
// [...]
// Do not forget to give your Recycler a Layout before listening to scroll events.
mRecycler.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// Only handle scrolling further if there is at least one child element in the list.
if (recyclerView.getChildCount() == 0) {
mSwipeLayout.setEnabled(true);
return;
}
final boolean didReachTop = recyclerView.getChildAt(0).getTop() >= 0;
if (mToolbarScroller == null) return;
// Simply let the Toolbar follow the scrolling Recycler
// by passing on the scroll-down (positive) values of dy.
if (dy > 2) mToolbarScroller.scrollBy(0, dy);
// Let the Toolbar reappear immediately
// when scrolling up a bit or if the top has been reached.
else if (dy < -4 || didReachTop) mToolbarScroller.scrollTo(0, 0);
}
});
This leads to your Toolbar always overlapping the first element in your Recycler. If you want to avoid this, add an invisible View to your item layouts that has the size of the Toolbar. In your Adapter you simply set it to VISIBLE, if it is the first element in the list, or to GONE if it is any other element:
In myRecyclerItemAdapter.java (optional):
#Override
public void onBindViewHolder(RoomViewHolder viewHolder, Cursor cursor) {
// To compensate for the overlaying toolbar,
// offset the first element by making its spacer visible.
if (cursor.isFirst()) viewHolder.mSpacer.setVisibility(View.VISIBLE);
else viewHolder.mSpacer.setVisibility(View.GONE);
I am probably going to tweak the threshold dy values in the OnScrollListener. They are supposed to filter jittery scroll values, such as a rapid succession of -1, +1, -1, +1 that sometimes happen.
If anyone has a better way or thinks I am making huge mistakes, please let me know! I am always looking for better solutions.