自定义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)`

Behavior相关方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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复写其中的相关方法实现我们所要的效果,这样可以减少很多工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?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>

Demo的完整代码戳这里


原创文章,转载请出处注明。

下面是我的个人公众号,欢迎关注交流

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×