Open bottom sheet when scrolling sister comes to the end?

Is there a way to “forward” scroll events from one scroll view to my bottom sheet so that my bottom sheet starts expanding when I reprogram the first scroll view?

Consider this tiny application:

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); int peekHeight = getResources().getDimensionPixelSize(R.dimen.bottom_sheet_peek_height); // 96dp View bottomSheet = findViewById(R.id.bottomSheet); BottomSheetBehavior<View> behavior = BottomSheetBehavior.from(bottomSheet); behavior.setPeekHeight(peekHeight); } } 
 <android.support.design.widget.CoordinatorLayout 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.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent"> <!-- LinearLayout holding children to scroll through --> </android.support.v4.widget.NestedScrollView> <View android:id="@+id/bottomSheet" android:layout_width="300dp" android:layout_height="400dp" android:layout_gravity="center_horizontal" app:layout_behavior="android.support.design.widget.BottomSheetBehavior"/> </android.support.design.widget.CoordinatorLayout> 

Out of the box, this works great. I see 96 dp of my bottom sheet, and I can hold it up and down, as usual. In addition, I can see my scrollable content, and I can scroll it up and down, as usual.

enter image description here enter image description here enter image description here

Suppose I am in the state shown in the second image. My NestedScrollView scrolls to the lowest level and my bottom sheet is collapsed. I would like to be able to scroll up on the NestedScrollView (and not on the bottom sheet), and since it cannot scroll further, let this gesture gesture be sent to the bottom sheet so that it starts to expand. Basically, the application behaves as if my gesture was executed on the bottom sheet, and not as a scroll.

My first thought was to look at the NestedScrollView.OnScrollChangeListener , but I couldn’t make it work, since it stops running at the borders of the scrollable content (in the end, it listens for scroll changes and nothing changes when you're around the edges )

I also considered creating my own subclass of BottomSheetBehavior and trying to override onInterceptTouchEvent() , but ran into problems in two places. Firstly, I only want to capture events when the sibling scroll view is at the bottom, and I could do it, but now I captured all events (which made it impossible to scroll back). Secondly, the private mIgnoreEvents field inside the BottomSheetBehavior blocked the bottom sheet from the actual extension. I can use reflection to access this field and prevent it from being blocked, but it seems evil.

Edit: I spent some more time searching for AppBarLayout.ScrollingViewBehavior since it seemed pretty close to what I wanted (it converts swipes from one view to resized on another), but it looks like a manually set offset pixel per pixel and bottom sheets do not quite behave this way.

+5
source share
1 answer

This is an update with a more general solution. Now it handles the hide and “skips the crash” of the standard behavior of the lower view.

The following solution uses a custom BottomSheetBehavior . Here is a short video of a small application based on your hosted application with custom behavior:

enter image description here

MyBottomSheetBehavior extends BottomSheetBehavior and makes a heavy lift for the desired behavior. MyBottomSheetBehavior is passive until the NestedScrollView reaches the bottom scroll limit. onNestedScroll() determines that the limit has been reached and shifts the bottom sheet by the amount of scrolling until the offset for the fully expanded bottom sheet is reached. This is expansion logic.

As soon as the bottom sheet is released from the bottom, the bottom sheet is considered “captured” until the user lifts a finger from the screen. While the bottom sheet is captured, onNestPreScroll() handles the movement of the bottom sheet at the bottom of the screen. This is collapsing logic.

BottomSheetBehavior provides no means to manipulate the bottom sheet except to collapse or expand it completely. Other functionalities that are needed are blocked in the individually-private functions of the underlying behavior. To get around this, I created a new class called BottomSheetBehaviorAccessors that shares the package ( android.support.design.widget ) with the stock behavior. This class provides access to some private packages that are used in the new behavior.

MyBottomSheetBehavior also supports BottomSheetBehavior.BottomSheetCallback and other common functions.

MyBottomSheetBehavior.java

 public class MyBottomSheetBehavior<V extends View> extends BottomSheetBehaviorAccessors<V> { // The bottom sheet that interests us. private View mBottomSheet; // Offset when sheet is expanded. private int mMinOffset; // Offset when sheet is collapsed. private int mMaxOffset; // This is the bottom of the bottom sheet parent. private int mParentBottom; // True if the bottom sheet is being moved through nested scrolls from NestedScrollView. private boolean mSheetCaptured = false; // True if the bottom sheet is touched directly and being dragged. private boolean mIsheetTouched = false; // Set to true on ACTION_DOWN on the NestedScrollView private boolean mScrollStarted = false; @SuppressWarnings("unused") public MyBottomSheetBehavior() { } @SuppressWarnings("unused") public MyBottomSheetBehavior(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) { if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { mSheetCaptured = false; mIsheetTouched = parent.isPointInChildBounds(child, (int) ev.getX(), (int) ev.getY()); mScrollStarted = !mIsheetTouched; } return super.onInterceptTouchEvent(parent, child, ev); } @Override public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) { mMinOffset = Math.max(0, parent.getHeight() - child.getHeight()); mMaxOffset = Math.max(parent.getHeight() - getPeekHeight(), mMinOffset); mBottomSheet = child; mParentBottom = parent.getBottom(); return super.onLayoutChild(parent, child, layoutDirection); } @Override public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) { if (dy >= 0 || !mSheetCaptured || type != ViewCompat.TYPE_TOUCH || !(target instanceof NestedScrollView)) { super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type); return; } // Pointer moving downward (dy < 0: scrolling toward top of data) if (child.getTop() - dy <= mMaxOffset) { // Dragging... ViewCompat.offsetTopAndBottom(child, -dy); setStateInternalAccessor(STATE_DRAGGING); consumed[1] = dy; } else if (isHideable()) { // Hide... ViewCompat.offsetTopAndBottom(child, Math.min(-dy, mParentBottom - child.getTop())); consumed[1] = dy; } else if (mMaxOffset - child.getTop() > 0) { // Collapsed... ViewCompat.offsetTopAndBottom(child, mMaxOffset - child.getTop()); consumed[1] = dy; } if (consumed[1] != 0) { dispatchOnSlideAccessor(child.getTop()); } } @Override public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) { if (dyUnconsumed <= 0 || !(target instanceof NestedScrollView) || type != ViewCompat.TYPE_TOUCH || getState() == STATE_HIDDEN) { mSheetCaptured = false; } else if (!mSheetCaptured) { // Capture the bottom sheet only if it is at its collapsed height. mSheetCaptured = isSheetCollapsed(); } if (!mSheetCaptured) { super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type); return; } /* If the pointer is moving upward (dyUnconsumed > 0) and the scroll view isn't consuming scroll (dyConsumed == 0) then the scroll view must be at the end of its scroll. */ if (child.getTop() - dyUnconsumed < mMinOffset) { // Expanded... ViewCompat.offsetTopAndBottom(child, mMinOffset - child.getTop()); } else { // Dragging... ViewCompat.offsetTopAndBottom(child, -dyUnconsumed); setStateInternalAccessor(STATE_DRAGGING); } dispatchOnSlideAccessor(child.getTop()); } @Override public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) { if (mScrollStarted) { // Ignore initial call to this method before anything has happened. mScrollStarted = false; } else if (!mIsheetTouched) { snapBottomSheet(); } super.onStopNestedScroll(coordinatorLayout, child, target); } private void snapBottomSheet() { if ((mMaxOffset - mBottomSheet.getTop()) > (mMaxOffset - mMinOffset) / 2) { setState(BottomSheetBehavior.STATE_EXPANDED); } else if (shouldHideAccessor(mBottomSheet, 0)) { setState(BottomSheetBehavior.STATE_HIDDEN); } else { setState(BottomSheetBehavior.STATE_COLLAPSED); } } private boolean isSheetCollapsed() { return mBottomSheet.getTop() == mMaxOffset; } @SuppressWarnings("unused") private static final String TAG = "MyBottomSheetBehavior"; } 

BottomSheetBehaviorAccessors

 package android.support.design.widget; // important! // A "friend" class to provide access to some package-private methods in `BottomSheetBehavior`. public class BottomSheetBehaviorAccessors<V extends View> extends BottomSheetBehavior<V> { @SuppressWarnings("unused") protected BottomSheetBehaviorAccessors() { } @SuppressWarnings("unused") public BottomSheetBehaviorAccessors(Context context, AttributeSet attrs) { super(context, attrs); } protected void setStateInternalAccessor(int state) { super.setStateInternal(state); } protected void dispatchOnSlideAccessor(int top) { super.dispatchOnSlide(top); } protected boolean shouldHideAccessor(View child, float yvel) { return mHideable && super.shouldHide(child, yvel); } @SuppressWarnings("unused") private static final String TAG = "BehaviorAccessor"; } 

MainActivity.java

 public class MainActivity extends AppCompatActivity{ private View mBottomSheet; MyBottomSheetBehavior<View> mBehavior; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); getSupportActionBar().setDisplayShowTitleEnabled(false); int peekHeight = getResources().getDimensionPixelSize(R.dimen.bottom_sheet_peek_height); // 96dp mBottomSheet = findViewById(R.id.bottomSheet); mBehavior = (MyBottomSheetBehavior) MyBottomSheetBehavior.from(mBottomSheet); mBehavior.setPeekHeight(peekHeight); } } 

activity_main.xml

 <android.support.design.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <android.support.design.widget.AppBarLayout android:id="@+id/appBar" android:layout_width="match_parent" android:layout_height="wrap_content" android:stateListAnimator="@null" android:theme="@style/AppTheme.AppBarOverlay" app:expanded="false" app:layout_behavior="android.support.design.widget.AppBarLayout$Behavior"> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/collapsingToolbarLayout" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_scrollFlags="scroll|exitUntilCollapsed" app:statusBarScrim="?attr/colorPrimaryDark"> <ImageView android:layout_width="match_parent" android:layout_height="250dp" android:layout_marginTop="?attr/actionBarSize" android:scaleType="centerCrop" android:src="@drawable/seascape1" app:layout_collapseMode="parallax" app:layout_collapseParallaxMultiplier="1.0" tools:ignore="ContentDescription" /> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin" /> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <com.example.bottomsheetoverscroll.MyNestedScrollView android:id="@+id/nestedScrollView" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <View android:layout_width="match_parent" android:layout_height="100dp" android:background="@android:color/holo_blue_light" /> <View android:layout_width="match_parent" android:layout_height="100dp" android:background="@android:color/holo_red_light" /> <View android:layout_width="match_parent" android:layout_height="100dp" android:background="@android:color/holo_blue_light" /> <View android:layout_width="match_parent" android:layout_height="100dp" android:background="@android:color/holo_red_light" /> <View android:layout_width="match_parent" android:layout_height="100dp" android:background="@android:color/holo_blue_light" /> <View android:layout_width="match_parent" android:layout_height="100dp" android:background="@android:color/holo_red_light" /> <View android:layout_width="match_parent" android:layout_height="100dp" android:background="@android:color/holo_green_light" /> </LinearLayout> </com.example.bottomsheetoverscroll.MyNestedScrollView> <TextView android:id="@+id/bottomSheet" android:layout_width="300dp" android:layout_height="400dp" android:layout_gravity="center_horizontal" android:background="@android:color/white" android:text="Bottom Sheet" android:textAlignment="center" android:textSize="24sp" android:textStyle="bold" app:layout_behavior="com.example.bottomsheetoverscroll.MyBottomSheetBehavior" /> <!--app:layout_behavior="android.support.design.widget.BottomSheetBehavior" />--> </android.support.design.widget.CoordinatorLayout> 
+5
source

Source: https://habr.com/ru/post/1275525/


All Articles