CoordinatorLayout与Behavior 的使用总结




写在前面

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;
     }
}
自此CoordinatorLayout的任务完成,接下来该怎么处理就交给我们设置的behavior。
以上过程基本都在CoordinatorLayout的performIntercept(MotionEvent ev, final int type)方法里。
Behavior  有个泛型是可以用来指定Behavior的View的类型,例如appbar_scrolling_view_behavior对应的字符串其实是android.support.design.widget.AppBarLayout$ScrollingViewBehavior,这个ScrollingViewBehavior内部类指定的泛型是View,所以理论上这个Behavior我们任何的View都可以使用,我们在自定义的时候,如果不是特殊的行为,也可以直接指定泛型View

在自定义Behavior的时候,我们需要关心的两组四个方法,为什么分为两组呢?看一下下面两种情况

某个view监听另一个view的状态变化,
         例如大小、位置、显示状态等
某个view监听CoordinatorLayout里的滑动状态

对于第一种情况,我们关心的是:
layoutDependsOnonDependentViewChanged方法,
对于第二种情况,我们关心的是:
onStartNestedScrollonNestedPreScroll方法。


依赖

(1)自定义一个Behavior
    public class MyBehavior extends CoordinatorLayout.Behavior{
        public MyBehavior(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    }
一定要重写这个构造函数。因为CoordinatorLayout源码中 parseBehavior()函数中直接反射调用这个构造函数,如果没有重写就无法加入自定义的属性
static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[] {
        Context.class,
        AttributeSet.class
};
源代码是通过反射生成Behavior实例的。而在实例化CoordinatorLayout.LayoutParams时:
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)


第一种方法:在任意View中xml文件添加  app:layout_behavior 属性,
app:layout_behavior=“你的Behavior包含包名的类名”
然后CoordinatorLayout就会反射生成你的Behavior。
另外一种方法:在你的自定义View类上添加注解@DefaultBehavior(你的Behavior.class)
你的View就默认使用这个Behavior。就像AppBarLayout一样也是通过注解的方式,默认选定了系统的behavior。

@DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout {}


(2)确定依赖关系。重写Behavior的这个方法来确定你依赖哪些View。

@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
    return dependency.getId() == R.id.first;
}
child 是指应用behavior的View ,dependency 担任触发behavior的角色,并与child进行互动。
确定你是否依赖于这个View。CoordinatorLayout会将自己所有View遍历判断。

(3) 回调方法 , 允许一起变动
如果确定依赖。这个方法很重要。当所依赖的View变动时会回调这个方法。
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
    return true;
}


效果是不管first怎么移动。second都会在他下面。

01.gif

滑动


Behavior最大的用处在于对滑动事件的处理。就像CollapsingToolbarLayout的那个酷炫效果一样,利用的就是系统的behavior。

(一)自定义Behavior 实现CollapsingToolbarLayout 的效果

主要是这3个方法,所依赖对象的滑动事件都将通知进来:

(1)允许一起滑动
@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);
}
(4) 实现 NestedScrollingChild接口
注意被依赖的View只有实现了 NestedScrollingChild接口的才可以将事件传递给CoordinatorLayout。
但注意这个滑动事件是对于CoordinatorLayout的。所以只要CoordinatorLayout有NestedScrollingChild就会滑动,他滑动就会触发上面这几个回调。无论你是否依赖了那个View。


下面就是一个简单的View跟随ScrollView滑入滑出屏幕的例子。可以是Toolbar或其他任何View(因为泛型为view)。
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的上滑隐藏都可以这样实现。


02.gif

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的属性。

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 different ViewGroup, most of it‘s functionality will not work.

查看源码可以知道scroll|enterAlways 或操作 =5 ,即为可显示隐藏



Appbar appbar = (Appbar)findById(R.id.appbar); AppBarLayout.LayoutParams mParams = (AppBarLayout.LayoutParams) appBar.getChildAt(0).getLayoutParams(); mParams.setScrollFlags(0); mParams.setScrollFlags(0);的时候AppBarLayout下的toolbar就不会随着滚动条折叠 mParams.setScrollFlags(5);的时候AppBarLayout下的toolbar会随着滚动条折叠

注意底部操作栏最外层的 LinearLayout我们加上了


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中:

xml代码:
<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="trs.com.swipedismissdemo.MainActivity">

    <TextView
        android:id="@+id/tv"
        app:layout_behavior="trs.com.swipedismissdemo.MySwipeDismissBehavior"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@android:color/holo_blue_dark"
        android:gravity="center"
        android:text="Hello World!"
        android:textColor="@android:color/white"
        android:textSize="50sp" />
</android.support.design.widget.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); } }

和你想的一样,我们覆写了onStartNestedScrollonNestedPreScroll方法,但是除了这两个方法外,我们还覆写了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;
     }
}
自此CoordinatorLayout的任务完成,接下来该怎么处理就交给我们设置的behavior。
以上过程基本都在CoordinatorLayout的performIntercept(MotionEvent ev, final int type)方法里。
Behavior  有个泛型是可以用来指定Behavior的View的类型,例如appbar_scrolling_view_behavior对应的字符串其实是android.support.design.widget.AppBarLayout$ScrollingViewBehavior,这个ScrollingViewBehavior内部类指定的泛型是View,所以理论上这个Behavior我们任何的View都可以使用,我们在自定义的时候,如果不是特殊的行为,也可以直接指定泛型View

在自定义Behavior的时候,我们需要关心的两组四个方法,为什么分为两组呢?看一下下面两种情况

某个view监听另一个view的状态变化,
         例如大小、位置、显示状态等
某个view监听CoordinatorLayout里的滑动状态

对于第一种情况,我们关心的是:
layoutDependsOnonDependentViewChanged方法,
对于第二种情况,我们关心的是:
onStartNestedScrollonNestedPreScroll方法。


依赖

(1)自定义一个Behavior
    public class MyBehavior extends CoordinatorLayout.Behavior{
        public MyBehavior(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    }
一定要重写这个构造函数。因为CoordinatorLayout源码中 parseBehavior()函数中直接反射调用这个构造函数,如果没有重写就无法加入自定义的属性
static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[] {
        Context.class,
        AttributeSet.class
};
源代码是通过反射生成Behavior实例的。而在实例化CoordinatorLayout.LayoutParams时:
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)


第一种方法:在任意View中xml文件添加  app:layout_behavior 属性,
app:layout_behavior=“你的Behavior包含包名的类名”
然后CoordinatorLayout就会反射生成你的Behavior。
另外一种方法:在你的自定义View类上添加注解@DefaultBehavior(你的Behavior.class)
你的View就默认使用这个Behavior。就像AppBarLayout一样也是通过注解的方式,默认选定了系统的behavior。

@DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout {}


(2)确定依赖关系。重写Behavior的这个方法来确定你依赖哪些View。

@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
    return dependency.getId() == R.id.first;
}
child 是指应用behavior的View ,dependency 担任触发behavior的角色,并与child进行互动。
确定你是否依赖于这个View。CoordinatorLayout会将自己所有View遍历判断。

(3) 回调方法 , 允许一起变动
如果确定依赖。这个方法很重要。当所依赖的View变动时会回调这个方法。
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
    return true;
}


效果是不管first怎么移动。second都会在他下面。

01.gif

滑动


Behavior最大的用处在于对滑动事件的处理。就像CollapsingToolbarLayout的那个酷炫效果一样,利用的就是系统的behavior。

(一)自定义Behavior 实现CollapsingToolbarLayout 的效果

主要是这3个方法,所依赖对象的滑动事件都将通知进来:

(1)允许一起滑动
@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);
}
(4) 实现 NestedScrollingChild接口
注意被依赖的View只有实现了 NestedScrollingChild接口的才可以将事件传递给CoordinatorLayout。
但注意这个滑动事件是对于CoordinatorLayout的。所以只要CoordinatorLayout有NestedScrollingChild就会滑动,他滑动就会触发上面这几个回调。无论你是否依赖了那个View。


下面就是一个简单的View跟随ScrollView滑入滑出屏幕的例子。可以是Toolbar或其他任何View(因为泛型为view)。
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的上滑隐藏都可以这样实现。


02.gif

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的属性。

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 different ViewGroup, most of it‘s functionality will not work.

查看源码可以知道scroll|enterAlways 或操作 =5 ,即为可显示隐藏



Appbar appbar = (Appbar)findById(R.id.appbar); AppBarLayout.LayoutParams mParams = (AppBarLayout.LayoutParams) appBar.getChildAt(0).getLayoutParams(); mParams.setScrollFlags(0); mParams.setScrollFlags(0);的时候AppBarLayout下的toolbar就不会随着滚动条折叠 mParams.setScrollFlags(5);的时候AppBarLayout下的toolbar会随着滚动条折叠

注意底部操作栏最外层的 LinearLayout我们加上了


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中:

xml代码:
<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="trs.com.swipedismissdemo.MainActivity">

    <TextView
        android:id="@+id/tv"
        app:layout_behavior="trs.com.swipedismissdemo.MySwipeDismissBehavior"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@android:color/holo_blue_dark"
        android:gravity="center"
        android:text="Hello World!"
        android:textColor="@android:color/white"
        android:textSize="50sp" />
</android.support.design.widget.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); } }

和你想的一样,我们覆写了onStartNestedScrollonNestedPreScroll方法,但是除了这两个方法外,我们还覆写了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默认实现了几种方法。


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值