自定义Behavior实现快速返回效果

Behavior是Android Design包中出现的一个概念,Android Design包中很多控件的动画效果都是使用Behavior实现的,所以想要更好的实现Material Design风格的应用就有必要弄清楚Behavior。这篇文章从简单开始,介绍如何自定义Behavior以实现快速返回的效果。

先看最终的实现效果
这里写图片描述

介绍

先看官方介绍https://developer.android.google.cn/reference/android/support/design/widget/CoordinatorLayout.Behavior.html

Interaction behavior plugin for child views of CoordinatorLayout.

A Behavior implements one or more interactions that a user can take on a child view. These interactions may include drags, swipes, flings, or any other gestures.

上面的介绍说Behavior是CoordinatorLayout子视图的一个交互插件,它可以为子视图实现一个或多个交互,这些交互包括拖拽,滑动或其他的手势操作。

通过上面的介绍我们知道Behavior是作用于CoordinatorLayout子视图的,而CoordinatorLayout我们可以把它看做一个FrameLayout。

根据我的理解来说Behavior其实就是一系列手势操作行为的回调,通过这些回调来处理CoordinatorLayout子视图的手势操作。

使用

用过Android Design库中AppBarLayout与NestedScrollView这两个类的同学应该知道,这两个类一起使用会产生很漂亮的滑动效果,这也是Android库中对Behavior的一个很典型的应用。而对于Behavior的使用,也可以参考这两个类的两种使用方式:
1. 为CoordinatorLayout的直接子View设置app:layout_behavior=“behavior完全类名”
2. 为CoordinatorLayout的某个子View设置默认Behavior,设置方式是在该View的类声明上添加注解@CoordinatorLayout.DefaultBehavior(Behavior类.class)

大家看上面代码NestedScrollView的布局部分,可以发现会有一个app:layout_behavior属性,该属性就是设置NestedScrollView这个子视图的Behavior插件,所以当我们自定义实现了Behavior后,只需要在CoordinatorLayout的子View中为其添加app:layout_behavior属性即可。

Behavior相关方法

public class BackTopBehavior extends CoordinatorLayout.Behavior {
    public MyBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
        //必须实现此构造方法,因为CoordinatorLayout中初始化Behavior时是通过反射调用此构造来进行初始化的
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {

        return super.layoutDependsOn(parent, child, dependency);
        //判断视图child进行layout布局时是否依赖于某个特定的View dependency
        //child是指应用此Behavior的View,dependency是触发执行此Behavior的视图并与child进行相关交互,也就是上面所说的是child的依赖
        //此方法在CoordinatorLayout进行request layout时至少会调用一次
        //如果返回true,CoordinatorLayout会总是在依赖视图dependency layout完成之后对child视图进行layout布局
        //同时如果依赖视图dependency的layout或position发生变化,CoordinatorLayout会调用onDependentViewChanged
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {

        return super.onDependentViewChanged(parent, child, dependency);
        //此方法的调用时机参考上面的方法layoutDependsOn的说明
        //当child的依赖视图dependency发生layout变化后,如果想对child布局(child's size or position)做出相应变化则返回true否则返回false,具体对child如何update则需要在onLayoutChild中进行实现
    }

    @Override
    public void onDependentViewRemoved(CoordinatorLayout parent, View child, View dependency) {

        super.onDependentViewRemoved(parent, child, dependency);
        //当child的依赖视图dependency从其parent中remove掉后会调用此方法
    }

    @Override
    public boolean onMeasureChild(CoordinatorLayout parent, View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {

        return super.onMeasureChild(parent, child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
        //当测量CoordinatorLayout的子视图child时调用此方法
    }

    @Override
    public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {

        return super.onLayoutChild(parent, child, layoutDirection);
        //当对CoordinatorLayout的子视图child进行layout布局时会调用此方法
        //当child的依赖视图layout结束之后,会调用此方法对child进行layout布局
        //如果onDependentViewChanged中返回了true,则需要在此方法中对child视图进行update
    }

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {

        return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
        //滑动开始调用,返回true表示此Behavior接收此滑动,才会有后续的滑动处理
    }

    @Override
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {

        super.onStopNestedScroll(coordinatorLayout, child, target);
        //滑动结束调用
    }

    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {

        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
        //滑动过程中调用
    }

    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {

        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
       //滑动过程中,在child自身消费掉此次滑动的distance之前调用此方法
       //onNestedPreScroll is called each time the nested scroll is updated by the nested scrolling child
       // before the nested scrolling child has consumed the scroll distance itself
    }

    @Override
    public boolean onNestedFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY, boolean consumed) {

        return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
        //快速滑动时调用此方法
    }
}

上面就是Behavior中比较常用比较重要的一些方法。

自定义Behavior

我要实现的是在在列表上滑时,显示快速返回按钮,列表下滑时隐藏快速返回按钮,当快速返回按钮显示时,点击该按钮,列表会自动滑动到顶部。

快速返回按钮我用的是Android Design中的FloatingActionButton,其实在FloatingActionButton中设置了默认的Behavior,但是这个默认的Behavior是与SnackBar结合使用的,因此我可以直接继承FloatingActionButton.Behavior复写其中的相关方法实现我们所要的效果,这样可以减少很多工作

public class BackTopBehavior extends FloatingActionButton.Behavior {
    private static final String TAG = "BackTopBehavior";

    public BackTopBehavior(Context context, AttributeSet attrs) {
        super();
    }

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {

        return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;//垂直方向滑动
    }

    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {

        if (dyConsumed > 0 && dyUnconsumed == 0) {
            Log.d(TAG, "上滑中。。。");
        }
        if (dyConsumed == 0 && dyUnconsumed > 0) {
            Log.d(TAG, "到边界了还在上滑。。。");
        }
        if (dyConsumed < 0 && dyUnconsumed == 0) {
            Log.d(TAG, "下滑中。。。");
        }
        if (dyConsumed == 0 && dyUnconsumed < 0) {
            Log.d(TAG, "到边界了,还在下滑。。。");
        }

        if((dyConsumed > 0 && dyUnconsumed == 0) || (dyConsumed == 0 && dyUnconsumed > 0) && child.getVisibility() != View.VISIBLE) {
            child.show();//上滑的时候显示按钮
        } else if((dyConsumed < 0 && dyUnconsumed == 0) || (dyConsumed == 0 && dyUnconsumed < 0) && child.getVisibility() != View.GONE) {
            child.hide();//下滑的时候因此按钮
        }
    }
}

这样实现起来是不是感觉很简单,如果不用Behavior的话,需要自己自定义View,并对滑动事件进行处理,实现起来肯定比Behavior方式要费劲。

剩下的布局和Activity代码如下:

public class BackTopBehaviorActivity extends AppCompatActivity {
    CommonRecyclerView mRecyclerView;
    LinearLayoutManager mLayoutManager;
    FloatingActionButton mFloatingActionButton;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_backtop_behavior);
        mFloatingActionButton = (FloatingActionButton) findViewById(R.id.floatingActionButton);
        mFloatingActionButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mLayoutManager.smoothScrollToPosition(mRecyclerView, null , 0);
            }
        });
        initRecyclerView();
    }

    private void initRecyclerView() {

        mRecyclerView = (CommonRecyclerView) findViewById(R.id.recyclerView);
        mLayoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.addItemDecoration(new SpacesItemDecoration(5));

        List<String> data = new ArrayList<>();
        for (int i = 0; i < 30; i ++) {
            data.add("数据" + (i + 1));
        }

        StringListAdapter mAdapter = new StringListAdapter(this, data);
        mRecyclerView.setAdapter(mAdapter);
    }
}
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            app:title="@string/app_name"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:titleTextColor="@android:color/white"/>
    </android.support.design.widget.AppBarLayout>

    <cn.ittiger.demo.ui.CommonRecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        app:layout_collapseMode="pin"/>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/floatingActionButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/d_15"
        android:src="@android:drawable/stat_sys_upload_done"
        app:layout_behavior="cn.ittiger.demo.behavior.BackTopBehavior"
        app:layout_scrollFlags="scroll|enterAlways|snap"
        android:visibility="gone"/>

</android.support.design.widget.CoordinatorLayout>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值