Recycler view inside coordinator layout is not scrolling programmatically - android

I have following XML layout. I am using recycler inside Coordinator layout. Although when I scroll manually, it works as expected. But when I am calling
mRecyclerView.getLayoutManager().scrollToPosition(selectedPosition); or mRecyclerView.scrollToPosition(selectedPosition); to scroll the recycler view programmatically its not scrolling. Below is my XML code
<android.support.design.widget.AppBarLayout
android:id="#+id/tab_appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/backgroundBlue">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|enterAlways">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="#dimen/standard_layout_margin"
android:layout_marginRight="#dimen/standard_layout_margin"
app:cardElevation="#dimen/small_layout_margin"
app:cardUseCompatPadding="true">
<include
android:id="#+id/details"
layout="#layout/detail_layout" />
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="#dimen/standard_layout_margin"
android:layout_marginRight="#dimen/standard_layout_margin"
app:cardElevation="#dimen/small_layout_margin"
app:cardUseCompatPadding="true">
<include layout="#layout/store_layout" />
</android.support.v7.widget.CardView>
</LinearLayout>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/collapsing_toolbar"
android:layout_marginLeft="#dimen/standard_layout_margin"
android:layout_marginRight="#dimen/standard_layout_margin"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>
At particular instance, I want my particular item in the recycler view to be on top of screen. I have below java code,
if (selectedPosition != 0) {
AppBarLayout appBarLayout = (AppBarLayout) mView.findViewById(R.id.tab_appbar);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
if(behavior!=null) {
behavior.onNestedPreScroll(mCoordinatorLayout, appBarLayout, mRecyclerView, 0, (int) mRecyclerView.getY(), new int[]{0, 0});
mRecyclerView.getLayoutManager().scrollToPosition(selectedPosition);
mRecyclerView.scrollToPosition(selectedPosition);
}
}
any idea, how to pull an item of recycler view to the top of screen?

CoordinatorLayout.Behaviour is works with only NestedScroll event. When you try to scroll RecyclerView programmatically it is treat as normal scroll.
Write below line to inform RecyclerView start NesteadScroll, With ViewCompat.SCROLL_AXIS_VERTICAL and ViewCompat.TYPE_NON_TOUCH
ViewCompat.SCROLL_AXIS_VERTICAL: Indicates scrolling along the vertical axis.
ViewCompat.TYPE_NON_TOUCH: Indicates that the input type for the gesture is caused by something which is not a user touching a screen. This is usually from a fling which is settling.
recycler_view.startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH)
recycler_view.smoothScrollBy(0,200)

Related

How to make content draw behind a non full width Toolbar (Like new Gmail or Google App)

What I'm trying to implement is a Toolbar that is not full width (has a margin on all sides of 16dp) like the following:
Gmail - Please note that the RecyclerView can be seen scrolling behind the toolbar
Google App - Same thing, the cards can be seen behind the toolbar.
Additionally, these toolbars hide when scrolling down and appear when scrolling up.
The content of the toolbar is not what I'm worried about right now.
I'm assuming this is done using a Coordinator Layout so this is the skeleton I have:
Coordinator Layout
AppBarLayout
Toolbar
NestedScrollView (appbar_scrolling_view_behavior)
ConstraintLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
android:animateLayoutChanges="true"
tools:context=".MainContentFragment">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_margin="32dp"
android:background="#android:color/transparent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#android:color/holo_red_dark"
app:layout_scrollFlags="scroll|enterAlways">
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
...
This is the output before scrolling
And after scrolling down
As you can see the space outside the toolbar is not transparent but has that grey background, since the content is going under it and cannot be seen.
The idea behind showing the content behind the toolbar:
Setting a negative margin to the scrolling view (NestedScrollView/RecyclerView) which equals to what you want the size of the blank area when all the content are scrolled up (all the stuff below the toolbar, nothing behind); it's assumed -100dp in the below demo
Reverse back this negative margin on the child of the NestedScrollView (in layout), or the 1st child of the RecyclerView (programmatically)
Put the scrolling view on top of the AppBarLayout so that the background of the AppBarLayout don't obscure the scrolling content to let them appear in the back of the toolbar.
Cosmetics:
Remove the elevation of the AppBarLayout to 0 to make it as not existing; just the ToolBar is there.
Set the background color of the AppBarLayout to transparent, so now it takes the background of the root layout.
Demo:
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-100dp"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="100dp"
android:text="#string/longText" />
</androidx.core.widget.NestedScrollView>
<com.google.android.material.appbar.AppBarLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#android:color/transparent"
app:elevation="0dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:background="#drawable/rounded_toolbar"
app:layout_scrollFlags="scroll|enterAlways">
<com.google.android.material.appbar.MaterialToolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
For the RecyclerView, you can't control its children with layouts, so reversing the margin can either by
Having 2 layouts one with the 100dp top margin for the first child, another without margins for the rest children. And decide which layout in the adapter's onCreateViewHolder()
Adding the margin in adapter's onBindViewHolder():
#Override
public void onBindViewHolder(#NonNull CustomViewHolder holder, final int position) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) holder.itemView.getLayoutParams();
if (position == 0) {
Resources resources = holder.itemView.getContext().getResources();
float dip = 100f;
float px = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dip,
resources.getDisplayMetrics()
);
params.topMargin = (int) px;
} else
params.topMargin = 0;
holder.itemView.setLayoutParams(params);
//...... rest of code
}
You should just wrap all widgets inside CoordinatorLayout with FrameLayout and change AppBarLayout position to the end:
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
android:animateLayoutChanges="true"
tools:context=".MainContentFragment">
<FrameLayout android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
...
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_margin="32dp"
android:background="#android:color/transparent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#android:color/holo_red_dark"
app:layout_scrollFlags="scroll|enterAlways">
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
</FrameLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout

Prevent scrolls in Bottom Sheet ancestor view

I have a CoordinatorLayout containing two NestedScrollViews, One has ScrollingViewBehavior and the other has BottomSheetBehavior and plays BottomSheet role, The problem is when I drag the BottomSheet, The first NestedScrollView scrolls too :
What I expect:
When I scroll BottomSheet, Another NestedScrollView should not scroll
What I tried:
I created a custom behavior and override onInterceptTouchEvent, On event that belongs to BottomSheet returned true and invoked dispatchTouchEvent on BottomSheet, It prevents the other NestedScrollView scrolls but BottomSheet can't scroll itself and click events on BottomSheet children did not work anymore.
Here's my layout :
<android.support.design.widget.CoordinatorLayout
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:id="#+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
tools:ignore="UnusedAttribute">
<include
layout="#layout/toolbar"/>
<android.support.v4.widget.NestedScrollView
android:id="#+id/nested"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="?actionBarSize"
android:background="#ff0"
android:fillViewport="true"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<LinearLayout
android:id="#+id/ll1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="24dp">
<Button
android:id="#+id/button_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:background="#android:color/holo_green_dark"
android:padding="16dp"
android:text="Button 1"
android:textColor="#android:color/white"/>
<Button
android:id="#+id/button_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:background="#android:color/holo_blue_light"
android:padding="16dp"
android:text="Button 2"
android:textColor="#android:color/white"/>
<Button
android:id="#+id/button_3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:background="#android:color/holo_red_dark"
android:padding="16dp"
android:text="Button 3"
android:textColor="#android:color/white"/>
<android.support.v4.widget.Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Bottom..."/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
<android.support.v4.widget.NestedScrollView
android:id="#+id/bottom_sheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/holo_orange_light"
android:clipToPadding="false"
android:elevation="4dp"
android:fillViewport="true"
app:behavior_peekHeight="60dp"
app:layout_behavior=".Behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:elevation="6dp"
android:focusable="true"
android:padding="16dp"
android:text="#string/ipsum"
android:textSize="16sp"/>
<RecyclerView.../>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
and my toolbar.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="320dp"
android:fitsSystemWindows="true">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="#drawable/lily_lake"
app:layout_collapseMode="parallax"/>
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:layout_gravity="bottom"
app:layout_collapseMode="pin"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
Updated with simpler solution.
The solution to your question can be divided into two sections:
How to raise the bottom sheet without scrolling the appbar?
How to lower the bottom sheet once raised.
To raise the bottom sheet without effected the appbar, we need to detect when the bottom sheet is responsible for the scroll and let the appbar refuse the nested scroll so it won't receive subsequent nested scroll events. This is accomplished in the onStartNestedScroll method of AppBarLayout.Behavior. (See code for MyAppBarBehavior below.)
We can now drag up the bottom sheet just like an independent sliding panel.
Now that we have dragged the bottom sheet up, can we drag it back down? Yes, and no. If the bottom sheet is scrolled to the top of its content, then we can drag the bottom sheet down if we touch the bottom sheet below the appbar. If we touch the bottom sheet over the appbar (which, of course, is behind the bottom sheet), then we cannot drag the bottom sheet down. This issue is present in the underlying code without our modifications. I think that it is just unusual for a bottom sheet to overlay an appbar.
To demonstrate this notice, in this video, that the appbar opens up behind the bottom sheet when the bottom sheet is dragged.
To change this behavior, we will override the onInterceptTouchEvent of the AppBarLayout.Behavior to return false if the bottom sheet is being touched.
Here is the new AppBarLayout.Behavior:
MyAppBarBehavior
class MyAppBarBehavior extends AppBarLayout.Behavior {
private boolean mIsSheetTouched = false;
#Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
View directTargetChild, View target, int axes, int type) {
// Set flag if the bottom sheet is responsible for the nested scroll.
mIsSheetTouched = target.getId() == R.id.bottom_sheet;
// Only consider starting a nested scroll if the bottom sheet is not touched; otherwise,
// we will let the other views do the scrolling.
return !mIsSheetTouched
&& super.onStartNestedScroll(coordinatorLayout, child, directTargetChild,
target, axes, type);
}
#Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
// Don't accept touch stream here if the bottom sheet is touched. This will permit the
// bottom sheet to be dragged down without interaction with the appBar. Reset on cancel.
if (ev.getActionMasked() == MotionEvent.ACTION_CANCEL) {
mIsSheetTouched = false;
}
return !mIsSheetTouched && super.onInterceptTouchEvent(parent, child, ev);
}
}
(I shouldn't have identified the bottom sheet with a specific id, but this is just a demonstration.)
Add the custom behavior to the appbar:
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="320dp"
android:fitsSystemWindows="true"
app:layout_behavior=".MyAppBarBehavior"
app:expanded="true">
Here is the final result:
There may be other ways to accomplish this. I would probably use an existing sliding panel library such as AndroidSlidingUpPanel instead of placing a bottom sheet in a nested scrolling environment where we must then undo the effects of the environment.

Persistent BottomSheet below ActionBar

I have an app layout with a custom toolbar and a persistent BottomSheet - both inside of a CoordinatorLayout.
On a button click I want to show the BottomSheet. Right now the sheet is displayed fullscreen and overlays the toolbar. By setting the app theme to Theme.AppCompat.Light.DarkActionBar the BottomSheet stays below the ActionBar, but the bar cannot be customized.
Is there a way to limit the height of the persitent BottomSheet to fullscreen - ActionBar height?
This is my code in activity_main.xml
<android.support.design.widget.CoordinatorLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:attrs="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="com.test.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
app:elevation="20dp"
android:elevation="20dp"
android:layout_height="?attr/actionBarSize">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
app:elevation="20dp"
android:elevation="20dp"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"/>
</android.support.design.widget.AppBarLayout>
</LinearLayout>
<include layout="#layout/bottom_sheet_additem"/>
</CoordinatorLayout>
Here is the code of sheet_bottom.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/bottomSheetLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorAccent"
app:behavior_hideable="true"
app:behavior_peekHeight="0dp"
android:fitsSystemWindows="true"
app:layout_behavior="#string/bottom_sheet_behavior">
<TextView
android:id="#+id/bottomsheet_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="Lorem Ipsum Dolor..."
android:textColor="#FFFFFF" />
</RelativeLayout>
The image on the left hand side shows the BottomSheet which stops below the Toolbar - which is not working with my current code. Currently it looks like the picture on the right.
I had the same problem... I don't know if it's the best solution, but for now, worked for me.
Try to put your include inside another CoordinatorLayout in your activity_main.xml, with a marginTop like this:
<android.support.design.widget.CoordinatorLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:attrs="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="com.test.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
app:elevation="20dp"
android:elevation="20dp"
android:layout_height="?attr/actionBarSize">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
app:elevation="20dp"
android:elevation="20dp"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"/>
</android.support.design.widget.AppBarLayout>
</LinearLayout>
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="56dp">
<include layout="#layout/bottom_sheet_additem"/>
</android.support.design.widget.CoordinatorLayout>
</CoordinatorLayout>
I hope it helps.
We can use app:layout_behavior instead of fixed height
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<include layout="#layout/bottom_sheet_additem"/>
</android.support.design.widget.CoordinatorLayout>
Your sheet_bottom should look like this
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
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"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:showIn="#layout/activity_main">
<RelativeLayout
android:id="#+id/bottom_sheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:behavior_hideable="true"
app:behavior_peekHeight="?android:attr/actionBarSize"
app:elevation="#dimen/size_5dp"
app:layout_behavior="#string/bottom_sheet_behavior">
</RelativeLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
The accepted answer works when you expand the bottom sheet to the full screen, but when collapsing it, it adds additional margin that makes a part of the collapsing layout hidden below the screen, so I decided to put the margin programmatically by listening to the collapse/hidden status of the BottomSheet.
First add the BottomSheet within a CoordintorLayout in xml
And add below callback listener.
mBottomSheetBehavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
#Override
public void onStateChanged(#NonNull View bottomSheet, int newState) {
CoordinatorLayout bottomSheet = findViewById(..); // inflate the bottom sheet
CoordinatorLayout.LayoutParams layoutParams =
(CoordinatorLayout.LayoutParams) bottomSheet.getLayoutParams();
if (newState == BottomSheetBehavior.STATE_COLLAPSED)
layoutParams.setMargins(0, 0, 0, 0); // remove top margin
else if (newState == BottomSheetBehavior.STATE_EXPANDED) {
layoutParams.setMargins(0, 100, 0, 0); // add top margin
bottomSheet.setLayoutParams(layoutParams);
}
#Override
public void onSlide(#NonNull View bottomSheet, float slideOffset) {
}
});
+1 for updating the margin programmatically. While the hidden part of the BottomSheet can be offset by increasing the peek height, using just the original solution above means the margin projects onto the underlying UI elements so the scroll action area projects off the actual BottomSheet onto the other UI.
Using the onStateChanged method in the BottomSheetCallback means acting on the event that the BottomSheet has already expanded or collapsed. Adding or removing the margin at this stage can result in seeing 'jerky' behaviour where for example the sheet momentarily reaches the fully expanded state covering the appbar before the margin is then programmatically applied to shift the UI components down resulting in a 'flash' as this applies.
Instead I used the onSlide method to detect when the BottomSheet was being slid up or down and added or removed the margins only once the sheet was half way through the transition. If the margins are applied too early in the slide motion then again the user can see the BottomSheet UI jumping up or down not long after initiating the action (they are less likely to notice this at the half way point if they have done a 'fling' up or down motion.
Also I found it worked best to fetch the height of the AppBar and the status bar and use those to set the required padding value for accurate placement in expanded mode.
This challenge can be avoided altogether by using a widget to trigger the BottomSheet state change programmatically.
#Override
public void onSlide(View bottomSheet, float slideOffset) {
boolean inRangeExpanding = oldOffSet < slideOffset;
boolean inRangeCollapsing = oldOffSet > slideOffset;
oldOffSet = slideOffset;
if (inRangeCollapsing && slideOffset < 0.5f) {
//reset padding on top of bottomsheet so there is no padding/overlap onto underlying sheet (which overlaps underlying sheet and so interfers with scrolling behaviour
bSheetView.setPadding(0,10,0,0);
Log.d(TAG,"onSlide STATE_COLLAPSING");
} else if(inRangeExpanding && slideOffset > 0.5f){
//reset padding on top of bottomsheet so there is padding/overlap onto underlying sheet so it does not write over the top of the menu appbar
bSheetView.setPadding(0, topMargin,0,0);
Log.d(TAG,"onSlide STATE_EXPANDING");
}
}

RecyclerView and a view collapsed on tap causes RecyclerView to "jump" on scroll

I have a RecyclerView which has children that expand on tap. I also have a CollapsingToolbarLayout at the top of this screen that collapses when the child of the RecyclerView expands. This is intended however when I then scroll on the RecyclerView, the collapsed view will suddenly expand to full size.
I assume this is happening because when the child is expanded and the toolbar collapses, it thinks the user has collapsed it on scroll. But because no touch was involved, when I subsequently scroll myself the RecyclerView still believes the view was where it was.
Any idea how to either stop the automatic collapsing or get the RecylerView to update with the position of the collapsed view?
Code, if necessary:
<android.support.design.widget.CoordinatorLayout
android:id="#+id/lytChanges"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="89dp"
android:background="#color/colorPrimary"
android:fitsSystemWindows="true">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll">
<reroo.reroo.CustomViews.FontText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="#dimen/itinerary_changes_margin_top"
android:background="#android:color/transparent"
android:text="Changes:"
android:textAlignment="center"
android:textColor="#B3ffffff"
android:textSize="16sp"
app:layout_collapseMode="pin"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<!--</android.support.percent.PercentRelativeLayout>-->
<android.support.v7.widget.RecyclerView
android:id="#+id/my_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:background="#color/grey"
android:focusable="false"
android:foreground="?android:attr/selectableItemBackground"
android:scrollbars="vertical"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
EDIT 1: I have tried adding enterAlways, enterAlwaysCollapsed and exitUntilCollapsed to my xml but I believe this is an issue with the RecyclerView not tracking its position after it has animated with the child expanding.
EDIT 2: Using notifyItemChanged(position) after child tap causes the RecyclerView to animate the expansion and collapse, and then subsequently animate the un-collapsing of the view without any user input.
Preferred behaviour:
animate and collapse. Stay. or
don't animate and collapse, but collapse on scroll

Recycleview in coordinatorlayout

I am trying to create RelativeLayout that has CoordinatorLayout and LinearLayout at bottom and found some strange behavior that I can't resolve. This is my layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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.support.design.widget.CoordinatorLayout
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#+id/sender"
android:background="#android:color/white"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:background="?colorPrimary"
app:layout_scrollFlags="scroll|enterAlways" />
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/messages_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>
<LinearLayout
android:id="#+id/sender"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:gravity="center_vertical"
android:background="#android:color/white">
<EditText
android:id="#+id/inputText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:inputType="text" />
<Button
android:fontFamily="sans-serif"
style="?android:attr/borderlessButtonStyle"
android:textAppearance="?android:textAppearanceButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/send"
android:textColor="?colorPrimary"
android:id="#+id/send"/>
</LinearLayout>
</RelativeLayout>
After changing data in adapter I trying to scroll to last element ( for example by recyclerView.smoothScrollToPosition(size); )and all I see is part of last view (not full size). If recycleview not nest to CoordinatorLayout - all works as expecting - I see full sized last element view. How can I change layout to works it all correctly ?
Issue seems to be that smoothScrollToPosition() will silently scroll RecyclerView without letting CoordinatorLayout about the scroll being happening. This is what I came up with. Nice thing about it is that it should only scroll AppBarLayout if you have enough items in Adapter.
final AppBarLayout layout = (AppBarLayout) findViewById(R.id.appbar);
layout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
lastVerticalOffset = verticalOffset;
}
});
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (tryCollapseAppbarOnNextScroll && lastVerticalOffset != -layout.getTotalScrollRange()) {
layout.setExpanded(false);
tryCollapseAppbarOnNextScroll = false;
}
}
});
Now whenever you send a message, do this:
tryCollapseAppbarOnNextScroll = true;
recyclerView.smoothScrollToPosition(adapter.getItemCount()-1);
in your code remove app:layout_scrollFlags="scroll|enterAlways" from Toolbar and instead add it to AppBarLayout so it should be like this
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:background="?colorPrimary"/>
</android.support.design.widget.AppBarLayout>
Try this way:
<RelativeLayout 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:layout_marginBottom="16dp">
The main point is to use to add android:layout_marginBottom to your activity
The last element is clipped because the RecyclerView is not entirely visible on the screen. It is being pushed down by the expanded AppBar. Note that when scrolling manually, the AppBar will shrink as you reach the last element.
What worked best in my case was to simply collapse the AppBar before scrolling:
AppBarLayout appBarLayout = (AppBarLayout) getActivity().findViewById(R.id.appbar);
appBarLayout.setExpanded(false, false);
recyclerView.smoothScrollToPosition(position);
I imagine you could improve on this solution by collapsing it only when needed.
If collapsing the AppBar is not desired, then you could add bottom padding of the same height as the expanded AppBar. However, there will be other glitches (e.g. when scrolling to a position which is already in the RecyclerView, but just outside of the screen)

Categories

Resources