当同级滚动到达末尾时打开底页? [英] Open bottom sheet when sibling scrolling reaches the end?

查看:59
本文介绍了当同级滚动到达末尾时打开底页?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否可以将滚动事件从一个滚动视图转发"到我的底部页面,以便在我过度滚动第一个滚动视图时底部页面开始扩展?

Is there any way to "forward" scroll events from one scrolling view to my bottom sheet, so that my bottom sheet begins to expand when I over-scroll the first scrolling view?

考虑这个小应用程序:

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>

开箱即用,就可以了.我看到底片的价值为96dp,可以像往常一样上下滑动.此外,我可以看到滚动内容,也可以像往常一样上下滚动.

Out of the box, this works just fine. I see 96dp worth of my bottom sheet, and I can swipe it up and down as normal. Additionally, I can see my scrolling content, and I can scroll it up and down as normal.

让我们假设我处于第二张图片所示的状态.我的NestedScrollView一直滚动到底部,并且我的底部工作表已折叠.我希望能够在NestedScrollView(底部的 not )上向上滑动,并且由于它无法进一步滚动,所以可以使用滑动手势 被发送到底部工作表,以便开始扩展.基本上,让应用程序表现得好像我的手势是在底部工作表上执行的,而不是在滚动视图上完成的.

Let's assume I'm at the state shown in the second image. My NestedScrollView is scrolled all the way to the bottom and my bottom sheet is collapsed. I'd like to be able to swipe upwards on the NestedScrollView (not on the bottom sheet) and, since it can't scroll any farther, have that swipe gesture instead be sent to the bottom sheet, so that it begins to expand. Basically, have the app behave as though my gesture had been performed on the bottom sheet, not the scroll view.

我的第一个想法是看NestedScrollView.OnScrollChangeListener,但是我无法使它起作用,因为它不再在滚动内容的边界处触发(毕竟,它监听滚动更改 ,并且当您处于边缘时,什么都没有改变.

My first thought was to look at NestedScrollView.OnScrollChangeListener, but I couldn't get that to work since it stops being triggered at the boundaries of the scrolling content (after all, it listens for scroll changes, and nothing's changing when you're at the edges).

我还研究了如何创建自己的BottomSheetBehavior子类并尝试覆盖onInterceptTouchEvent(),但是在两个地方遇到了麻烦.首先,我只想在同级滚动视图位于底部时捕获事件,我可以这样做,但是现在我捕获了 all 事件(无法向上滚动同级).其次,BottomSheetBehavior内的private字段mIgnoreEvents阻止了底部工作表的实际扩展.我可以使用反射来访问此字段并阻止它阻止我,但这感觉很邪恶.

I also took a look at creating my own subclass of BottomSheetBehavior and trying to override onInterceptTouchEvent(), but ran into trouble in two places. First, I only want to capture events when the sibling scroll view is at the bottom, and I could do that, but I was now capturing all events (making it impossible to scroll the sibling back up). Second, the private field mIgnoreEvents inside BottomSheetBehavior was blocking the bottom sheet from actually expanding. I can use reflection to access this field and prevent it from blocking me, but that feels evil.

我花了更多时间研究AppBarLayout.ScrollingViewBehavior,因为这似乎与我想要的非常接近(它将一个视图上的滑动转换为另一视图上的尺寸调整),但是这似乎是通过以下方式手动设置偏移像素的:像素和底页的行为不太一样.

I spent some more time looking into AppBarLayout.ScrollingViewBehavior, since that seemed to be pretty close to what I wanted (it converts swipes on one view into resizing on another), but that appears to manually set the offset pixel by pixel, and bottom sheets don't quite behave that way.

推荐答案

这是具有更通用解决方案的更新.现在,它可以处理标准底视图行为的隐藏和跳过折叠".

以下解决方案使用自定义的BottomSheetBehavior.这是一个基于您已发布的应用程序以及适当的自定义行为的小型应用程序的快速视频:

The following solution uses a custom BottomSheetBehavior. Here is a quick video of a small app based upon your posted app with the custom behavior in place:

MyBottomSheetBehavior扩展BottomSheetBehavior并进行繁重的操作以实现所需的行为. MyBottomSheetBehavior是被动的,直到NestedScrollView达到其底部滚动限制. onNestedScroll()标识已达到限制,并将底页偏移滚动量,直到达到完全展开的底页的偏移为止.这是扩展逻辑.

MyBottomSheetBehavior extends BottomSheetBehavior and does the heavy lifting for the desired behavior. MyBottomSheetBehavior is passive until the NestedScrollView reaches its bottom scroll limit. onNestedScroll() identifies that the limit has been reached and offsets the bottom sheet by the amount of the scroll until the offset for the fully expanded bottom sheet is reached. This is the expansion logic.

一旦从底部松开底部薄片,则将底部薄片视为已捕获",直到用户从屏幕上抬起手指为止.捕获底页时,onNestPreScroll()处理将底页移向屏幕底部.这就是崩溃的逻辑.

Once 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 moving the bottom sheet toward the bottom of the screen. This is the collapsing logic.

BottomSheetBehavior除了完全折叠或展开底板之外,没有提供其他方法来操纵底板.所需的其他功能已锁定在基本行为的程序包专用功能中.为了解决这个问题,我创建了一个名为BottomSheetBehaviorAccessors的新类,该类与库存行为共享一个包(android.support.design.widget).此类提供对新行为中使用的某些程序包专用方法的访问.

BottomSheetBehavior doesn't provide a means to manipulate the bottom sheet other than to completely collapse or expand it. Other functionality that is needed is locked up in package-private functions of the base behavior. To get around this, I created a new class called BottomSheetBehaviorAccessors that shares a package (android.support.design.widget) with the stock behavior. This class provides access to some package-private methods that are used in the new behavior.

MyBottomSheetBehavior还可以容纳BottomSheetBehavior.BottomSheetCallback的回调和其他常规功能.

MyBottomSheetBehavior also accommodates the callbacks of BottomSheetBehavior.BottomSheetCallback and other general functionality.

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's 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>

这篇关于当同级滚动到达末尾时打开底页?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆