Behavior是CoordinatorLayout的一个内部类
1
|
public static abstract class Behavior<V extends View>
|
它只定义了一些抽象方法,其中最主要的当属下面两个(与本文相关):
1
2
3
4
5
6
7
|
public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
return
false
;
}
public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
return
false
;
}
|
CoordinatorLayout会在自己的onInterceptTouchEvent()方法中调用Behavior的
onInterceptTouchEvent:
1
|
b.onInterceptTouchEvent(
this
, child, ev);
|
把自己(this)、与此Behavior对象相关的子view(child)以及MotionEvent ev传递过去。这三个参数对于实现一个Behavior都至关重要。
CoordinatorLayout遍历子view,判断子view的mLayoutParam变量中是否有Behavior成员,如果有则调用Behavior的onInterceptTouchEvent和onTouchEvent方法。上面的代码中,swipeView通过
1
2
3
4
|
CoordinatorLayout.LayoutParams coordinatorParams =
(CoordinatorLayout.LayoutParams) swipeView.getLayoutParams();
coordinatorParams.setBehavior(swipe);
|
给自己设置了一个类型为SwipeDismissBehavior的Behavior,而且它又是CoordinatorLayout的子view,因此当CoordinatorLayout遍历到了这个cardview的时候,会尝试从这个swipeView获得Behavior:
1
2
|
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Behavior b = lp.getBehavior();
|
注:这里的LayoutParams是CoordinatorLayout.LayoutParams类,跟ViewGroup的还是有所区别,他是CoordinatorLayout的内部类。其实任何一个布局比如LinearLayout都有自己的LayoutParams类型,也都是定义在布局类的内部。
如果检测到这个Behavior不为空,就调用它的onInterceptTouchEvent和onTouchEvent方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
if
(!intercepted && b !=
null
) {
switch
(type) {
case
TYPE_ON_INTERCEPT:
intercepted = b.onInterceptTouchEvent(
this
, child, ev);
break
;
case
TYPE_ON_TOUCH:
intercepted = b.onTouchEvent(
this
, child, ev);
break
;
}
if
(intercepted) {
mBehaviorTouchView = child;
}
}
|
android.support.design.widget.AppBarLayout$ScrollingViewBehavior
,这个ScrollingViewBehavior
内部类指定的泛型是View
,所以理论上这个Behavior我们任何的View都可以使用,我们在自定义的时候,如果不是特殊的行为,也可以直接指定泛型View
。
在自定义Behavior的时候,我们需要关心的两组四个方法,为什么分为两组呢?看一下下面两种情况
某个view监听另一个view的状态变化,
例如大小、位置、显示状态等
某个view监听CoordinatorLayout里的滑动状态
对于第一种情况,我们关心的是:
layoutDependsOn
和onDependentViewChanged
方法,
对于第二种情况,我们关心的是:
onStartNestedScroll
和onNestedPreScroll
方法。
public class MyBehavior extends CoordinatorLayout.Behavior{
public MyBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
parseBehavior()
函数中直接反射调用这个构造函数,如果没有重写就无法加入自定义的属性
static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[] {
Context.class,
AttributeSet.class
};
final Class<Behavior> clazz = (Class<Behavior>) Class.forName(fullName, true,
context.getClassLoader());
c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
c.setAccessible(true);
constructors.put(fullName, c);
return c.newInstance(context, attrs)
app:layout_behavior
属性,
app:layout_behavior=“你的Behavior包含包名的类名”
@DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout {}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency.getId() == R.id.first;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
return true;
}

滑动
(一)自定义Behavior 实现CollapsingToolbarLayout 的效果
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return true;//这里返回true,才会接受到后续滑动事件。
}
(2) 设置滑动 轨迹
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
//进行滑动事件处理
}
(3)快速滚动优化处理
@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);
}
public class ScrollToTopBehavior extends CoordinatorLayout.Behavior<View>{
int offsetTotal = 0;
boolean scrolling = false;
public ScrollToTopBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return true;
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
offset(child, dyConsumed);
}
public void offset(View child,int dy){
int old = offsetTotal;
int top = offsetTotal - dy;
top = Math.max(top, -child.getHeight());
top = Math.min(top, 0);
offsetTotal = top;
if (old == offsetTotal){
scrolling = false;
return;
}
int delta = offsetTotal-old;
child.offsetTopAndBottom(delta);
scrolling = true;
}
}
xml中:
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="false"
tools:context=".MainActivity">
<android.support.v4.widget.NestedScrollView
android:id="@+id/second"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="128dp"
style="@style/TextAppearance.AppCompat.Display3"
android:text="A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM\nN\nO\nP\nQ\nR\nS\nT\nU\nV\nW\nX\nY\nZ"
android:background="@android:color/holo_red_light"/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
<View
android:id="@+id/first"
android:layout_width="match_parent"
android:layout_height="128dp"
app:layout_behavior=".ScrollToTopBehavior"
android:background="@android:color/holo_blue_light"/>
</android.support.design.widget.CoordinatorLayout>
当NestedScrollView滑动的时候,first也能跟着滑动。toolbar和fab的上滑隐藏都可以这样实现。

AppBarLayout的收缩原理分析
示例中给可滑动View设的Behavior是
@string/appbar_scrolling_view_behavior
(android.support.design.widget.AppBarLayout$ScrollingViewBehavior)。
唯一的作用是把自己放到AppBarLayout的下面,而且向上滑动可以从顶部穿透过去。所有View都能使用这个Behavior。
AppBarLayout自带一个Behivior(源码会自己去解析,而我们自定义的则是根据自己的类,利用反色去获取)。直接在源码里注解声明的。这个Behivior也只能用于AppBarLayout。
作用是让他根据CoordinatorLayout上的滚动手势进行一些效果(比如收缩)。在appBarLayout里面。
只不过只有某些可滑动View才会把滑动事件响应给CoordinatorLayout才能继而响应给AppBarLayout。
下面是相关代码:
(1)先自定义target这个属性。
<declare-styleable name="Follow">
<attr name="target" format="reference"/>
</declare-styleable>
(2)自定义Behavior
package com.intsig.camcard.discoverymodule.views;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.view.View;
import com.intsig.camcard.discoverymodule.R;
import com.intsig.camcard.discoverymodule.utils.Util;
/**
* Created by philos_lin on 2016/6/4.
*/
public class FollowBehavior extends CoordinatorLayout.Behavior {
private int targetId;
private Context context;
/*
* 必须重写这个构造方法(包含AttributeSet)
* */
public FollowBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Follow);
for (int i = 0; i < a.getIndexCount(); i++) {
int attr = a.getIndex(i);
if(a.getIndex(i) == R.styleable.Follow_target){
targetId = a.getResourceId(attr, -1);
}
}
a.recycle();
}
/*
* 当所依赖的View变动时会回调这个方法
* */
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
child.setY(dependency.getY()+dependency.getHeight());//这里可以通过设置X/Y的坐标关系,设置移动轨迹(抛物线之类的)- Util.dip2px(context,40f)
return true;
}
/*
* 确定依赖的View。
* child 是指应用behavior的View ,dependency 担任触发behavior的角色,并与child进行互动。
* */
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency.getId() == targetId;
}
}
(3)相关布局文件
<?xml version="1.0" encoding="utf-8"?>
<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:background="@color/color_light"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="0dp">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/collapsingToolbarLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<!-- 用LinearLayout包裹 头部 和 固定部分 上下分布-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- 头部 -->
<LinearLayout...>
<!-- 向上滚动的时候固定在顶部的部分 -->
<FrameLayout...>
</LinearLayout>
<!-- Toolbar 和 要固定部分 高度一致 -->
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="82dp"
android:fitsSystemWindows="true"
app:layout_collapseMode="pin" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<!-- 其他可以滚动的内容 -->
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:fastScrollEnabled="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:target="@id/top_spinned">
<android.support.v7.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 其他可以滚动的内容 -->
<TableLayout
android:id="@+id/tl_hot_navigation_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/color_white"
android:paddingBottom="5dp"
android:paddingTop="5dp"></TableLayout>
<LinearLayout
android:id="@+id/ll_items"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/window_margin"
android:orientation="vertical" />
</android.support.v7.widget.LinearLayoutCompat>
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
我们还可以干什么?
这种实现的效果
先看下我们的布局
<?xml version="1.0" encoding="utf-8"?>
<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">
<!--toolbar-->
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways" //让Toolbar支持隐藏
/>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_behavior"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!--底部操作栏-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@color/red"
android:orientation="horizontal"
android:padding="16dp"
android:gravity="center_vertical"
//2.自定义的behavior,包名需要全称
app:layout_behavior="com.example.lwp.design.behavior.FooterBehaviorDependAppBar"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="添加你的评论"
android:drawablePadding="5dp"
android:drawableLeft="@mipmap/ic_message"
android:textColor="@android:color/white" />
<ImageView
android:layout_marginLeft="29dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_favorite"/>
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>
布局很简单就三个内容 toolbar,RecyclerView,footer(LinearLayout)
app:layout_scrollFlags="scroll|enterAlways" 使用场景是在ToolBar标签中设置,与CoordinatorLayout配合对ToolBar进行滚动时隐藏。
查看文档可以知道,app:layout_scrollFlags是AppBarLayout的属性。
查看源码可以知道scroll|enterAlways 或操作 =5 ,即为可显示隐藏AppBarLayout is a vertical
LinearLayout
which implements many of the features of material designs app bar concept, namely scrolling gestures.Children should provide their desired scrolling behavior through
setScrollFlags(int)
and the associated layout xml attribute:app:layout_scrollFlags
.This view depends heavily on being used as a direct child within a
CoordinatorLayout
. If you use AppBarLayout within a differentViewGroup
, most of it‘s functionality will not work.
app:layout_behavior=”com.example.lwp.design.behavior.FooterBehavior”
FooterBehavior就是我们要自定义的behavior,让它和滑动交互,内容向上滑动时消失,向下滑动时显示
实现我们自己的Behavior其实很简单 ,就是几行代码的事,主要就是根据滑动距离来显示和隐藏footer
public class FooterBehavior extends CoordinatorLayout.Behavior<View> {
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
private int sinceDirectionChange;
public FooterBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
//1.判断滑动的方向 我们需要垂直滑动
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
//2.根据滑动的距离显示和隐藏footer view
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
if (dy > 0 && sinceDirectionChange < 0 || dy < 0 && sinceDirectionChange > 0) {
child.animate().cancel();
sinceDirectionChange = 0;
}
sinceDirectionChange += dy;
if (sinceDirectionChange > child.getHeight() && child.getVisibility() == View.VISIBLE) {
hide(child);
} else if (sinceDirectionChange < 0 && child.getVisibility() == View.GONE) {
show(child);
}
}
private void hide(final View view) {
ViewPropertyAnimator animator = view.animate().translationY(view.getHeight()).setInterpolator(INTERPOLATOR).setDuration(200);
animator.setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
view.setVisibility(View.GONE);
}
@Override
public void onAnimationCancel(Animator animator) {
show(view);
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
animator.start();
}
private void show(final View view) {
ViewPropertyAnimator animator = view.animate().translationY(0).setInterpolator(INTERPOLATOR).setDuration(200);
animator.setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
view.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationCancel(Animator animator) {
hide(view);
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
animator.start();
}
对个人详情页面的优化
利用Behavior实现toolbar背景色的渐变
(1)实现自定义的ToolbarAlphaBehavior
原理就是根据滑动的偏移值来设置对应的toolbar透明度,实现也就几行代码。
public class ToolbarAlphaBehavior extends CoordinatorLayout.Behavior<Toolbar> {
private static final String TAG = "ToolbarAlphaBehavior";
private int offset = 0;
private int startOffset = 0;
private int endOffset = 0;
private Context context;
public ToolbarAlphaBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, Toolbar child, View directTargetChild, View target, int nestedScrollAxes) {
return true;
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, Toolbar toolbar, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
startOffset = 0;
endOffset = context.getResources().getDimensionPixelOffset(R.dimen.cardview_card_height) - toolbar.getHeight();
offset += dyConsumed;
if (offset <= startOffset) { //alpha为0
toolbar.getBackground().setAlpha(0);
} else if (offset > startOffset && offset < endOffset) { //alpha为0到255
float precent = (float) (offset - startOffset) / endOffset;
int alpha = Math.round(precent * 255);
toolbar.getBackground().setAlpha(alpha);
} else if (offset >= endOffset) { //alpha为255
toolbar.getBackground().setAlpha(255);
}
}
}
没错就这么几行代码,主要代码都在onNestedScroll方法里面
3.配置Behavior
给Toolbar配置Behavior
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:minHeight="?attr/actionBarSize"
android:background="@color/color_212121"
app:layout_behavior="com.intsig.camcard.cardinfo.ToolbarAlphaBehavior"//添加配置
app:layout_collapseMode="pin"/>
因为一开始页面进来toolbar就是透明的 所以要初始化透明度
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.getBackground().setAlpha(0);//toolbar透明度初始化为0
最近在研究CoordinatorLayout与Behavior发现了有SwipeDismissBehavior这个东西,通过它可以实现侧滑删除。
app:layout_behavior="trs.com.swipedismissdemo.MySwipeDismissBehavior" 。先看效果。

SwipeDismissBehavior的用法
SwipeDismissBehavior的用法非常简单。
第一步:引入design库:
1
2
|
compile
'com.android.support:appcompat-v7:23.1.0'
compile
'com.android.support:design:23+'
|
第二步:把要滑动删除的View放在CoordinatorLayout中:
// SwipeDismissBehavior
View view= (View) findViewById(R.id.tv);
ViewGroup.LayoutParams params = tv.getLayoutParams();
if(params instanceof CoordinatorLayout.LayoutParams){
CoordinatorLayout.LayoutParams p= (CoordinatorLayout.LayoutParams) params;
CoordinatorLayout.Behavior behavior = p.getBehavior();
if(behavior instanceof SwipeDismissBehavior){
SwipeDismissBehavior sb= (SwipeDismissBehavior) behavior;
sb.setListener(new SwipeDismissBehavior.OnDismissListener() {
@Override
public void onDismiss(View view) {
Log.i("philos","onDismiss");
}
@Override
public void onDragStateChanged(int state) {
Log.i("philos","onDragStateChanged state="+state);
}
});
}
}
初步自定义
现在我们就来根据第一种情况尝试自定义一个Behavior,这里我们实现一个简单的效果,让一个View根据另一个View上下移动。
首先我们来自定义一个Behavior,起名为DependentBehavior
public class DependentBehavior extends CoordinatorLayout.Behavior {
public DependentBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return super.layoutDependsOn(parent, child, dependency);
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
ViewCompat.offsetLeftAndRight();
return super.onDependentViewChanged(parent, child, dependency);
}
}
如果只支持特定的view 比如TextView,那么layoutDependsOn
可以这么写,
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof TextView;
}
关键的还是获取dependency距离底部的距离,并且设置给child,很简单。
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
int offset = dependency.getTop() - child.getTop();
ViewCompat.offsetTopAndBottom(child, offset);
return true;
}
第二个TextView设置了app:layout_behavior="org.loader.mybehavior.DependentBehavior"
final TextView depentent = (TextView) findViewById(R.id.depentent);
depentent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ViewCompat.offsetTopAndBottom(v, 5);
}
});
效果:
Scroll Behavior
第二种情况-滑动。让一个ScrollView跟随另一个ScrollView滑动
从效果中我们可以看出,第二个ScrollView明显是是在跟随第一个进行滑动,现在就让我们用自定义Behavior的形式实现它。
创建一个BehaviZ喎�"http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vcqOsxvDD+73QU2Nyb2xsQmVoYXZpb3KjrDwvcD4NCjxwcmUgY2xhc3M9"brush:java;">public class ScrollBehavior extends CoordinatorLayout.Behavior { public ScrollBehavior(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) { return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); } @Override public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) { super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed); } @Override public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) { return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY); } }
和你想的一样,我们覆写了onStartNestedScroll
和onNestedPreScroll
方法,但是除了这两个方法外,我们还覆写了onNestedPreFling
方法,这个方法是干嘛的? 估计大家已经猜出来了,这里是处理fling动作的,你想想,我们在滑动松开手的时候,ScrollView是不是还继续滑动一会,那我们也需要让跟随的那个ScrollView也要继续滑动一会,这种效果,onNestedPreFling
就派上用场了。
好,接下来我们来实现代码,首先来看看onStartNestedScroll
,这里的返回值表明这次滑动我们要不要关心,我们要关心什么样的滑动?当然是y轴方向上的。
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
现在我们准备好了关心的滑动事件了,那如何让它滑动起来呢?还是要看onNestedPreScroll
的实现
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
int leftScrolled = target.getScrollY();
child.setScrollY(leftScrolled);
}
也很简单,让child的scrollY的值等于目标的scrollY的值就ok啦,那fling呢?更简单,
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY, boolean consumed) {
((NestedScrollView) child).fling((int)velocityY);
return true;
}
直接将现在的y轴上的速度传递传递给child,让他fling起来就ok了。
定义好了Behavior,就得在xml中使用了,使用方法和前面的一样。
回到滚动问题
NestedScrolling
机制
NestedScrolling
机制很好的解决滚动冲突的情况。
我们看看如何实现这个NestedScrolling
,首先有几个类(接口)我们需要关注一下
NestedScrollingChild
NestedScrollingParent
NestedScrollingChildHelper
NestedScrollingParentHelper
以上四个类都在
support-v4
包中提供,Lollipop的View默认实现了几种方法。
Behavior是CoordinatorLayout的一个内部类
1
|
public static abstract class Behavior<V extends View>
|
它只定义了一些抽象方法,其中最主要的当属下面两个(与本文相关):
1
2
3
4
5
6
7
|
public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
return
false
;
}
public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
return
false
;
}
|
CoordinatorLayout会在自己的onInterceptTouchEvent()方法中调用Behavior的
onInterceptTouchEvent:
1
|
b.onInterceptTouchEvent(
this
, child, ev);
|
把自己(this)、与此Behavior对象相关的子view(child)以及MotionEvent ev传递过去。这三个参数对于实现一个Behavior都至关重要。
CoordinatorLayout遍历子view,判断子view的mLayoutParam变量中是否有Behavior成员,如果有则调用Behavior的onInterceptTouchEvent和onTouchEvent方法。上面的代码中,swipeView通过
1
2
3
4
|
CoordinatorLayout.LayoutParams coordinatorParams =
(CoordinatorLayout.LayoutParams) swipeView.getLayoutParams();
coordinatorParams.setBehavior(swipe);
|
给自己设置了一个类型为SwipeDismissBehavior的Behavior,而且它又是CoordinatorLayout的子view,因此当CoordinatorLayout遍历到了这个cardview的时候,会尝试从这个swipeView获得Behavior:
1
2
|
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Behavior b = lp.getBehavior();
|
注:这里的LayoutParams是CoordinatorLayout.LayoutParams类,跟ViewGroup的还是有所区别,他是CoordinatorLayout的内部类。其实任何一个布局比如LinearLayout都有自己的LayoutParams类型,也都是定义在布局类的内部。
如果检测到这个Behavior不为空,就调用它的onInterceptTouchEvent和onTouchEvent方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
if
(!intercepted && b !=
null
) {
switch
(type) {
case
TYPE_ON_INTERCEPT:
intercepted = b.onInterceptTouchEvent(
this
, child, ev);
break
;
case
TYPE_ON_TOUCH:
intercepted = b.onTouchEvent(
this
, child, ev);
break
;
}
if
(intercepted) {
mBehaviorTouchView = child;
}
}
|
android.support.design.widget.AppBarLayout$ScrollingViewBehavior
,这个ScrollingViewBehavior
内部类指定的泛型是View
,所以理论上这个Behavior我们任何的View都可以使用,我们在自定义的时候,如果不是特殊的行为,也可以直接指定泛型View
。
在自定义Behavior的时候,我们需要关心的两组四个方法,为什么分为两组呢?看一下下面两种情况
某个view监听另一个view的状态变化,
例如大小、位置、显示状态等
某个view监听CoordinatorLayout里的滑动状态
对于第一种情况,我们关心的是:
layoutDependsOn
和onDependentViewChanged
方法,
对于第二种情况,我们关心的是:
onStartNestedScroll
和onNestedPreScroll
方法。
public class MyBehavior extends CoordinatorLayout.Behavior{
public MyBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
parseBehavior()
函数中直接反射调用这个构造函数,如果没有重写就无法加入自定义的属性
static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[] {
Context.class,
AttributeSet.class
};
final Class<Behavior> clazz = (Class<Behavior>) Class.forName(fullName, true,
context.getClassLoader());
c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
c.setAccessible(true);
constructors.put(fullName, c);
return c.newInstance(context, attrs)
app:layout_behavior
属性,
app:layout_behavior=“你的Behavior包含包名的类名”
@DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout {}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency.getId() == R.id.first;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
return true;
}

滑动
(一)自定义Behavior 实现CollapsingToolbarLayout 的效果
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return true;//这里返回true,才会接受到后续滑动事件。
}
(2) 设置滑动 轨迹
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
//进行滑动事件处理
}
(3)快速滚动优化处理
@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);
}
public class ScrollToTopBehavior extends CoordinatorLayout.Behavior<View>{
int offsetTotal = 0;
boolean scrolling = false;
public ScrollToTopBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return true;
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
offset(child, dyConsumed);
}
public void offset(View child,int dy){
int old = offsetTotal;
int top = offsetTotal - dy;
top = Math.max(top, -child.getHeight());
top = Math.min(top, 0);
offsetTotal = top;
if (old == offsetTotal){
scrolling = false;
return;
}
int delta = offsetTotal-old;
child.offsetTopAndBottom(delta);
scrolling = true;
}
}
xml中:
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="false"
tools:context=".MainActivity">
<android.support.v4.widget.NestedScrollView
android:id="@+id/second"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="128dp"
style="@style/TextAppearance.AppCompat.Display3"
android:text="A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM\nN\nO\nP\nQ\nR\nS\nT\nU\nV\nW\nX\nY\nZ"
android:background="@android:color/holo_red_light"/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
<View
android:id="@+id/first"
android:layout_width="match_parent"
android:layout_height="128dp"
app:layout_behavior=".ScrollToTopBehavior"
android:background="@android:color/holo_blue_light"/>
</android.support.design.widget.CoordinatorLayout>
当NestedScrollView滑动的时候,first也能跟着滑动。toolbar和fab的上滑隐藏都可以这样实现。

AppBarLayout的收缩原理分析
示例中给可滑动View设的Behavior是
@string/appbar_scrolling_view_behavior
(android.support.design.widget.AppBarLayout$ScrollingViewBehavior)。
唯一的作用是把自己放到AppBarLayout的下面,而且向上滑动可以从顶部穿透过去。所有View都能使用这个Behavior。
AppBarLayout自带一个Behivior(源码会自己去解析,而我们自定义的则是根据自己的类,利用反色去获取)。直接在源码里注解声明的。这个Behivior也只能用于AppBarLayout。
作用是让他根据CoordinatorLayout上的滚动手势进行一些效果(比如收缩)。在appBarLayout里面。
只不过只有某些可滑动View才会把滑动事件响应给CoordinatorLayout才能继而响应给AppBarLayout。
下面是相关代码:
(1)先自定义target这个属性。
<declare-styleable name="Follow">
<attr name="target" format="reference"/>
</declare-styleable>
(2)自定义Behavior
package com.intsig.camcard.discoverymodule.views;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.view.View;
import com.intsig.camcard.discoverymodule.R;
import com.intsig.camcard.discoverymodule.utils.Util;
/**
* Created by philos_lin on 2016/6/4.
*/
public class FollowBehavior extends CoordinatorLayout.Behavior {
private int targetId;
private Context context;
/*
* 必须重写这个构造方法(包含AttributeSet)
* */
public FollowBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Follow);
for (int i = 0; i < a.getIndexCount(); i++) {
int attr = a.getIndex(i);
if(a.getIndex(i) == R.styleable.Follow_target){
targetId = a.getResourceId(attr, -1);
}
}
a.recycle();
}
/*
* 当所依赖的View变动时会回调这个方法
* */
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
child.setY(dependency.getY()+dependency.getHeight());//这里可以通过设置X/Y的坐标关系,设置移动轨迹(抛物线之类的)- Util.dip2px(context,40f)
return true;
}
/*
* 确定依赖的View。
* child 是指应用behavior的View ,dependency 担任触发behavior的角色,并与child进行互动。
* */
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency.getId() == targetId;
}
}
(3)相关布局文件
<?xml version="1.0" encoding="utf-8"?>
<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:background="@color/color_light"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="0dp">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/collapsingToolbarLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<!-- 用LinearLayout包裹 头部 和 固定部分 上下分布-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- 头部 -->
<LinearLayout...>
<!-- 向上滚动的时候固定在顶部的部分 -->
<FrameLayout...>
</LinearLayout>
<!-- Toolbar 和 要固定部分 高度一致 -->
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="82dp"
android:fitsSystemWindows="true"
app:layout_collapseMode="pin" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<!-- 其他可以滚动的内容 -->
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:fastScrollEnabled="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:target="@id/top_spinned">
<android.support.v7.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 其他可以滚动的内容 -->
<TableLayout
android:id="@+id/tl_hot_navigation_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/color_white"
android:paddingBottom="5dp"
android:paddingTop="5dp"></TableLayout>
<LinearLayout
android:id="@+id/ll_items"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/window_margin"
android:orientation="vertical" />
</android.support.v7.widget.LinearLayoutCompat>
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
我们还可以干什么?
这种实现的效果
先看下我们的布局
<?xml version="1.0" encoding="utf-8"?>
<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">
<!--toolbar-->
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways" //让Toolbar支持隐藏
/>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_behavior"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!--底部操作栏-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@color/red"
android:orientation="horizontal"
android:padding="16dp"
android:gravity="center_vertical"
//2.自定义的behavior,包名需要全称
app:layout_behavior="com.example.lwp.design.behavior.FooterBehaviorDependAppBar"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="添加你的评论"
android:drawablePadding="5dp"
android:drawableLeft="@mipmap/ic_message"
android:textColor="@android:color/white" />
<ImageView
android:layout_marginLeft="29dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_favorite"/>
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>
布局很简单就三个内容 toolbar,RecyclerView,footer(LinearLayout)
app:layout_scrollFlags="scroll|enterAlways" 使用场景是在ToolBar标签中设置,与CoordinatorLayout配合对ToolBar进行滚动时隐藏。
查看文档可以知道,app:layout_scrollFlags是AppBarLayout的属性。
查看源码可以知道scroll|enterAlways 或操作 =5 ,即为可显示隐藏AppBarLayout is a vertical
LinearLayout
which implements many of the features of material designs app bar concept, namely scrolling gestures.Children should provide their desired scrolling behavior through
setScrollFlags(int)
and the associated layout xml attribute:app:layout_scrollFlags
.This view depends heavily on being used as a direct child within a
CoordinatorLayout
. If you use AppBarLayout within a differentViewGroup
, most of it‘s functionality will not work.
app:layout_behavior=”com.example.lwp.design.behavior.FooterBehavior”
FooterBehavior就是我们要自定义的behavior,让它和滑动交互,内容向上滑动时消失,向下滑动时显示
实现我们自己的Behavior其实很简单 ,就是几行代码的事,主要就是根据滑动距离来显示和隐藏footer
public class FooterBehavior extends CoordinatorLayout.Behavior<View> {
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
private int sinceDirectionChange;
public FooterBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
//1.判断滑动的方向 我们需要垂直滑动
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
//2.根据滑动的距离显示和隐藏footer view
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
if (dy > 0 && sinceDirectionChange < 0 || dy < 0 && sinceDirectionChange > 0) {
child.animate().cancel();
sinceDirectionChange = 0;
}
sinceDirectionChange += dy;
if (sinceDirectionChange > child.getHeight() && child.getVisibility() == View.VISIBLE) {
hide(child);
} else if (sinceDirectionChange < 0 && child.getVisibility() == View.GONE) {
show(child);
}
}
private void hide(final View view) {
ViewPropertyAnimator animator = view.animate().translationY(view.getHeight()).setInterpolator(INTERPOLATOR).setDuration(200);
animator.setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
view.setVisibility(View.GONE);
}
@Override
public void onAnimationCancel(Animator animator) {
show(view);
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
animator.start();
}
private void show(final View view) {
ViewPropertyAnimator animator = view.animate().translationY(0).setInterpolator(INTERPOLATOR).setDuration(200);
animator.setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
view.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationCancel(Animator animator) {
hide(view);
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
animator.start();
}
对个人详情页面的优化
利用Behavior实现toolbar背景色的渐变
(1)实现自定义的ToolbarAlphaBehavior
原理就是根据滑动的偏移值来设置对应的toolbar透明度,实现也就几行代码。
public class ToolbarAlphaBehavior extends CoordinatorLayout.Behavior<Toolbar> {
private static final String TAG = "ToolbarAlphaBehavior";
private int offset = 0;
private int startOffset = 0;
private int endOffset = 0;
private Context context;
public ToolbarAlphaBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, Toolbar child, View directTargetChild, View target, int nestedScrollAxes) {
return true;
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, Toolbar toolbar, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
startOffset = 0;
endOffset = context.getResources().getDimensionPixelOffset(R.dimen.cardview_card_height) - toolbar.getHeight();
offset += dyConsumed;
if (offset <= startOffset) { //alpha为0
toolbar.getBackground().setAlpha(0);
} else if (offset > startOffset && offset < endOffset) { //alpha为0到255
float precent = (float) (offset - startOffset) / endOffset;
int alpha = Math.round(precent * 255);
toolbar.getBackground().setAlpha(alpha);
} else if (offset >= endOffset) { //alpha为255
toolbar.getBackground().setAlpha(255);
}
}
}
没错就这么几行代码,主要代码都在onNestedScroll方法里面
3.配置Behavior
给Toolbar配置Behavior
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:minHeight="?attr/actionBarSize"
android:background="@color/color_212121"
app:layout_behavior="com.intsig.camcard.cardinfo.ToolbarAlphaBehavior"//添加配置
app:layout_collapseMode="pin"/>
因为一开始页面进来toolbar就是透明的 所以要初始化透明度
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.getBackground().setAlpha(0);//toolbar透明度初始化为0
最近在研究CoordinatorLayout与Behavior发现了有SwipeDismissBehavior这个东西,通过它可以实现侧滑删除。
app:layout_behavior="trs.com.swipedismissdemo.MySwipeDismissBehavior" 。先看效果。

SwipeDismissBehavior的用法
SwipeDismissBehavior的用法非常简单。
第一步:引入design库:
1
2
|
compile
'com.android.support:appcompat-v7:23.1.0'
compile
'com.android.support:design:23+'
|
第二步:把要滑动删除的View放在CoordinatorLayout中:
// SwipeDismissBehavior
View view= (View) findViewById(R.id.tv);
ViewGroup.LayoutParams params = tv.getLayoutParams();
if(params instanceof CoordinatorLayout.LayoutParams){
CoordinatorLayout.LayoutParams p= (CoordinatorLayout.LayoutParams) params;
CoordinatorLayout.Behavior behavior = p.getBehavior();
if(behavior instanceof SwipeDismissBehavior){
SwipeDismissBehavior sb= (SwipeDismissBehavior) behavior;
sb.setListener(new SwipeDismissBehavior.OnDismissListener() {
@Override
public void onDismiss(View view) {
Log.i("philos","onDismiss");
}
@Override
public void onDragStateChanged(int state) {
Log.i("philos","onDragStateChanged state="+state);
}
});
}
}
初步自定义
现在我们就来根据第一种情况尝试自定义一个Behavior,这里我们实现一个简单的效果,让一个View根据另一个View上下移动。
首先我们来自定义一个Behavior,起名为DependentBehavior
public class DependentBehavior extends CoordinatorLayout.Behavior {
public DependentBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return super.layoutDependsOn(parent, child, dependency);
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
ViewCompat.offsetLeftAndRight();
return super.onDependentViewChanged(parent, child, dependency);
}
}
如果只支持特定的view 比如TextView,那么layoutDependsOn
可以这么写,
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof TextView;
}
关键的还是获取dependency距离底部的距离,并且设置给child,很简单。
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
int offset = dependency.getTop() - child.getTop();
ViewCompat.offsetTopAndBottom(child, offset);
return true;
}
第二个TextView设置了app:layout_behavior="org.loader.mybehavior.DependentBehavior"
final TextView depentent = (TextView) findViewById(R.id.depentent);
depentent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ViewCompat.offsetTopAndBottom(v, 5);
}
});
效果:
Scroll Behavior
第二种情况-滑动。让一个ScrollView跟随另一个ScrollView滑动
从效果中我们可以看出,第二个ScrollView明显是是在跟随第一个进行滑动,现在就让我们用自定义Behavior的形式实现它。
创建一个BehaviZ喎�"http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vcqOsxvDD+73QU2Nyb2xsQmVoYXZpb3KjrDwvcD4NCjxwcmUgY2xhc3M9"brush:java;">public class ScrollBehavior extends CoordinatorLayout.Behavior { public ScrollBehavior(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) { return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); } @Override public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) { super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed); } @Override public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) { return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY); } }
和你想的一样,我们覆写了onStartNestedScroll
和onNestedPreScroll
方法,但是除了这两个方法外,我们还覆写了onNestedPreFling
方法,这个方法是干嘛的? 估计大家已经猜出来了,这里是处理fling动作的,你想想,我们在滑动松开手的时候,ScrollView是不是还继续滑动一会,那我们也需要让跟随的那个ScrollView也要继续滑动一会,这种效果,onNestedPreFling
就派上用场了。
好,接下来我们来实现代码,首先来看看onStartNestedScroll
,这里的返回值表明这次滑动我们要不要关心,我们要关心什么样的滑动?当然是y轴方向上的。
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
现在我们准备好了关心的滑动事件了,那如何让它滑动起来呢?还是要看onNestedPreScroll
的实现
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
int leftScrolled = target.getScrollY();
child.setScrollY(leftScrolled);
}
也很简单,让child的scrollY的值等于目标的scrollY的值就ok啦,那fling呢?更简单,
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY, boolean consumed) {
((NestedScrollView) child).fling((int)velocityY);
return true;
}
直接将现在的y轴上的速度传递传递给child,让他fling起来就ok了。
定义好了Behavior,就得在xml中使用了,使用方法和前面的一样。
回到滚动问题
NestedScrolling
机制
NestedScrolling
机制很好的解决滚动冲突的情况。
我们看看如何实现这个NestedScrolling
,首先有几个类(接口)我们需要关注一下
NestedScrollingChild
NestedScrollingParent
NestedScrollingChildHelper
NestedScrollingParentHelper
以上四个类都在
support-v4
包中提供,Lollipop的View默认实现了几种方法。