如何在 android 中使用滚动视图实现 'quick return'

How to implement 'quick return' with scrollview in android

我想用 ScrollView 实现 'Quick return header'。我的页眉不是工具栏,是FrameLayout..

基本上你需要使用Nested Scrolling(添加在API 21)。此功能允许子级(滚动的)与其父级(通常是 CoordinatorLayout)通信。但是,为了拥有嵌套滚动,您需要:

  1. 父视图必须实现 NestedScrollingParent 接口
  2. 子视图必须实现 NestedScrollingChild 接口

CoordinatorLayout实现了NestedScrollingParent,但不幸的是,ScrollView没有实现NestedScrollingChild接口。

因此,您无法使用 SCROLLVIEW 进行嵌套滚动(在 API 21 之前:查看底部的编辑部分)。但是,您可以使用基本上是实现 NestedScrollingChild 接口的 ScrollViewNestedScrollView

之后,您需要创建自定义 CoordinatorLayout.Behavior 并覆盖方法 onStartNestedScrollonNestedPreScrollonNestedScroll。您可以在下面看到符合您需要的代码(我从 this example 中获取它并进行了一些更改)。

假设您查看的是 FramLayout 您可以在您的布局中做这样的事情:

<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
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
        </LinearLayout>
    </android.support.v4.widget.NestedScrollView>
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="84dp"
        android:background="#FF0000"
        app:layout_behavior=".QuickHideBehavior">
    </FrameLayout>
</android.support.design.widget.CoordinatorLayout>

如您所见,自定义行为已附加到 FrameLayout。那么你需要这样修改上面的QuickHideBehavior例子:

public class QuickHideBehavior extends CoordinatorLayout.Behavior<View> {

    private static final int DIRECTION_UP = 1;
    private static final int DIRECTION_DOWN = -1;

    /* Tracking direction of user motion */
    private int mScrollingDirection;
    /* Tracking last threshold crossed */
    private int mScrollTrigger;

    /* Accumulated scroll distance */
    private int mScrollDistance;
    /* Distance threshold to trigger animation */
    private int mScrollThreshold;


    private ObjectAnimator mAnimator;

    //Required to instantiate as a default behavior
    public QuickHideBehavior() {
    }

    //Required to attach behavior via XML
    public QuickHideBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.getTheme()
                .obtainStyledAttributes(new int[] {R.attr.actionBarSize});
        //Use half the standard action bar height
        mScrollThreshold = a.getDimensionPixelSize(0, 0) / 2;
        a.recycle();
    }

    //Called before a nested scroll event. Return true to declare interest
    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
                                       View child, View directTargetChild, View target,
                                       int nestedScrollAxes) {
        //We have to declare interest in the scroll to receive further events
        return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }

    //Called before the scrolling child consumes the event
    // We can steal all/part of the event by filling in the consumed array
    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout,
                                  View child, View target,
                                  int dx, int dy,
                                  int[] consumed) {
        //Determine direction changes here
        if (dy > 0 && mScrollingDirection != DIRECTION_UP) {
            mScrollingDirection = DIRECTION_UP;
            mScrollDistance = 0;
        } else if (dy < 0 && mScrollingDirection != DIRECTION_DOWN) {
            mScrollingDirection = DIRECTION_DOWN;
            mScrollDistance = 0;
        }
    }

    //Called after the scrolling child consumes the event, with amount consumed
    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout,
                               View child, View target,
                               int dxConsumed, int dyConsumed,
                               int dxUnconsumed, int dyUnconsumed) {
        //Consumed distance is the actual distance traveled by the scrolling view
        mScrollDistance += dyConsumed;
        if (mScrollDistance > mScrollThreshold
                && mScrollTrigger != DIRECTION_UP) {
            //Hide the target view
            mScrollTrigger = DIRECTION_UP;
            restartAnimator(child, getTargetHideValue(coordinatorLayout, child));
        } else if (mScrollDistance < -mScrollThreshold
                && mScrollTrigger != DIRECTION_DOWN) {
            //Return the target view
            mScrollTrigger = DIRECTION_DOWN;
            restartAnimator(child, 0f);
        }
    }

    //Called after the scrolling child handles the fling
    @Override
    public boolean onNestedFling(CoordinatorLayout coordinatorLayout,
                                 View child, View target,
                                 float velocityX, float velocityY,
                                 boolean consumed) {
        //We only care when the target view is already handling the fling
        if (consumed) {
            if (velocityY > 0 && mScrollTrigger != DIRECTION_UP) {
                mScrollTrigger = DIRECTION_UP;
                restartAnimator(child, getTargetHideValue(coordinatorLayout, child));
            } else if (velocityY < 0 && mScrollTrigger != DIRECTION_DOWN) {
                mScrollTrigger = DIRECTION_DOWN;
                restartAnimator(child, 0f);
            }
        }

        return false;
    }

    /* Helper Methods */

    //Helper to trigger hide/show animation
    private void restartAnimator(View target, float value) {
        if (mAnimator != null) {
            mAnimator.cancel();
            mAnimator = null;
        }

        mAnimator = ObjectAnimator
                .ofFloat(target, View.TRANSLATION_Y, value)
                .setDuration(250);

        /*mAnimator = ObjectAnimator.ofFloat(target, "alpha", 0f, 1f).setDuration(250);*/
        mAnimator.start();
    }

    private float getTargetHideValue(ViewGroup parent, View target) {
        if(target instanceof FrameLayout)
            return -target.getHeight();

        return 0f;
    }
}

大功告成。

我建议你看一下 here for more examples or this video 两个 Dave Smith

编辑: 实际上,自API 21,ScrollView(或ListView)支持嵌套滚动,但您需要启用它:

scrollView.setNestedScrollingEnabled(true);

你当然需要设置

minSdkVersion > 20