最近一直忙着写各种的文档啊报告啊~ 搞得劳资都以为自己是项目经理了。终于抽出时间学习了CoordinatorLayout自定义behavior,建议先阅读之前的一篇博客:
在上一篇博客中说到,MaterialDesign中最核心的布局就是CoordinatorLayout,使用这个布局可以很轻松的控制其内部的组建进行交互,产生一种联动的感觉。其中要产生这种效果主要用到了app:layout_behavior这个属性,那么这一篇我们试试自己自定义Behavior来创造自己的联动效果。
先上效果
,、、
图1-TV2随着TV1改变位置自己也改变位置
图2-左边的ScrollView滑动,右边的ScrollView也随之滑动
图3-自定义FloatingActionButton的旋转Behavior
图4-自定义头部随下滑隐藏上滑显示的Behavior
正文
友情提示:记得导入Design包(AS玩家添加依赖),否则无法继续~
先看前两种效果,都是以一个控件作为主动改变的控件(或者叫做被监听控件),而另一个控件会随着这个被监听控件发生改变自己随之改变。
图中的两种效果就是自定义Behavior的两种基本类型:
- View监听另一个View的状态变化而自身发生变化 (即监听状态)
- View监听到另一个View的滑动自身也发生变化 (即监听滑动)
监听状态:
上一篇博客我们已经提到,通过给布局文件的控件设置layout_behavior属性来控制这个控件的行为,而这一切都是在CoordinatorLayout中进行的,所以Xml文件的根布局要设置为CoordinatorLayout, 并且,CoordinatorLayout要获取控件的Behavior是根据其包名+类名使用反射来找到这个Behavior的,在源码里CoordinatorLayout最终调用了Behavior的
public Behavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
两个参数的构造方法来实例化这个Behavior.所以在自定义Behavior的时候一定要写这个两个参数的构造器,否则会报下面的错误:
此外,重写layoutDependsOn和onDependentViewChanged来控制控件的效果~
说了这么多,还是来看亲切的代码更有效果:
自定义SimpleBehavior1:
package com.chan.coordinatorbehaviordemo.behavior;
import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
/**
* Created by Iwfu on 2016/6/12.
*
* 简单的Behavior:一个TextView随着另一个TextView移动而移动
*
* (让View监听另一个View的状态变化)
*/
public class SimpleBehavior1 extends CoordinatorLayout.Behavior {
public SimpleBehavior1 () {
}
/**
* 必须重写带两个参数的构造方法,因为Coordinator里利用反射去找到这个behavior
*
* @param context
* @param attrs
*/
public SimpleBehavior1(Context context, AttributeSet attrs) {
super(context, attrs);
Log.d("tag", "创建了" + "SimpleBehavior1");
}
/*
* 判断使用这个Behavior的View是基于哪一个View的(即要监听哪一个View)
* 当监听的这个dependency状态改变了,则调用onDependentViewChanged
*/
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child,
View dependency) {
Log.d("tag", "获取了dependency");
return dependency instanceof TextView;
}
/*
* 在被监听的View的大小或者位置发生改变时调用, 这个被监听的View是由layoutDependsOn的返回值决定的,
* 也可以是设置的其他锚点:anchor, 如果这个Behavior改变了child(使用者)的状态,返回true
*/
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
View dependency) {
Log.d("tag", "child状态改变");
int offset = dependency.getTop() - child.getTop();
ViewCompat.offsetTopAndBottom(child, offset);
return true;
}
}
其中的关键地方注释已经写出来了,主要就是在layoutDependsOn中确定被监听的控件,在onDependentViewChanged中对主动监听的那个控件进行操作和变化状态。对于其中参数很容易理解:
- CoordinatorLayout parent : 外层的CoordinatorLayout
- View child : 主动监听的控件,就是那个要设置layout_behavior属性的这个控件
- View dependency : 被监听的控件,child根据这个控件来进行变化。
写好了Behavior,那么如何使用呢~
使用SimpleBehavior1:
在XML文件里:
<xml version="1.0" encoding="utf-8">
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="com.chan.coordinatorbehaviordemo.Demo1Activity">
<TextView
android:id="@+id/tv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#ffffff"
android:textSize="30sp"
android:padding="20dp"
android:background="#0000ff"
android:text="TV1"/>
<TextView
android:id="@+id/tv2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#ffffff"
android:textSize="30sp"
android:padding="20dp"
app:layout_behavior="com.chan.coordinatorbehaviordemo.behavior.SimpleBehavior1"
android:layout_gravity="right|top"
android:background="#00ff00"
android:text="TV2"/>
</android.support.design.widget.CoordinatorLayout>
要实现图1的那种效果,让tv2随着tv1的下滑同时改变自己的位置。就在tv2里加上
app:layout_behavior=”XXXXX.SimpleBehavior1”
其中的值是自己SimpleBehavior1的完整包名
这样就实现了一个监听状态的自定义Behavior~~~~掌声~
监听滑动:
第二种是监听滑动,即监听到指定的控件发生了滑动,自己随之进行变化。主要是重写这三个方法:
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return true;//这里返回true,才会接受到后续滑动事件。
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
//进行滑动事件处理
}
@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);
}
自定义SimpleBehavior2:
package com.chan.coordinatorbehaviordemo.behavior;
import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.NestedScrollView;
import android.util.AttributeSet;
import android.view.View;
/**
* Created by Iwfu on 2016/6/12.
*
* 让一个ScrollView随着另一个ScrollView滑动而滑动
*/
public class SimpleBehavior2 extends CoordinatorLayout.Behavior {
public SimpleBehavior2(Context context, AttributeSet attrs) {
super(context, attrs);
}
/*
* 返回值表明这次滑动我们要不要关心,我们要关心什么样的滑动?当然是y轴方向上的。
*/
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
View child, View directTargetChild, View target,
int nestedScrollAxes) {
// 垂直方向滑动
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
/*
* 在被监听控件滑动时会调用这个~参数: dx ,dy表示水平和竖直方向的滑动距离
* consumed表示水平和竖直方向上滑动被消耗了多少,unconsumed表示未消耗的
*/
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child,
View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
int dyUnconsumed) {
int scrollY = target.getScrollY();
child.setScrollY(scrollY);
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed,
dyConsumed, dxUnconsumed, dyUnconsumed);
}
/*
* 模拟快速滑动,直接把Y方向的速度交给child,让他fling~~
*/
@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout,
View child, View target, float velocityX, float velocityY) {
((NestedScrollView) child).fling((int) velocityY);
return super.onNestedPreFling(coordinatorLayout, child, target,
velocityX, velocityY);
}
}
其中,onStartNestedScroll:确定监听的方向,onNestedScroll:确定监听到滑动后的处理,onNestedPreFling:实现瞬滑的效果
讲到滑动处理,就不得不说到Design包下的NestedScrolling(嵌入式滑动)滑动机制,这里参考:
注意被依赖的(被监听的)View只有实现了NestedScrollingChild接口的才可以将事件传递给CoordinatorLayout。
但注意这个滑动事件是对于CoordinatorLayout的。所以只要CoordinatorLayout有NestedScrollingChild就会滑动,他滑动就会触发这几个回调。无论你是否依赖了(监听了)那个View。
同理在xml里使用这个behavior
使用SimpleBehavior2:
<xml version="1.0" encoding="utf-8">
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.chan.coordinatorbehaviordemo.Demo2Activity">
<android.support.v4.widget.NestedScrollView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="left"
android:background="#FF00FF00">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="50dp"
android:paddingTop="50dp"
android:text="contentLeft"
android:textColor="@android:color/white"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="50dp"
android:paddingTop="50dp"
android:text="contentLeft"
android:textColor="@android:color/white"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="50dp"
android:paddingTop="50dp"
android:text="contentLeft"
android:textColor="@android:color/white"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="50dp"
android:paddingTop="50dp"
android:text="contentLeft"
android:textColor="@android:color/white"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="50dp"
android:paddingTop="50dp"
android:text="contentLeft"
android:textColor="@android:color/white"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="50dp"
android:paddingTop="50dp"
android:text="contentLeft"
android:textColor="@android:color/white"/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
<android.support.v4.widget.NestedScrollView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="right"
android:background="#FFFF0000"
app:layout_behavior="com.chan.coordinatorbehaviordemo.behavior.SimpleBehavior2">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="50dp"
android:paddingTop="50dp"
android:text="contentRight"
android:textColor="@android:color/white"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="50dp"
android:paddingTop="50dp"
android:text="contentRight"
android:textColor="@android:color/white"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="50dp"
android:paddingTop="50dp"
android:text="contentRight"
android:textColor="@android:color/white"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="50dp"
android:paddingTop="50dp"
android:text="contentRight"
android:textColor="@android:color/white"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="50dp"
android:paddingTop="50dp"
android:text="contentRight"
android:textColor="@android:color/white"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="50dp"
android:paddingTop="50dp"
android:text="contentRight"
android:textColor="@android:color/white"/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
到这里自定义Behavior的两种最基本的实现方式就到这里,通过这两种基本的behavior监听可以实现茫茫多界面的炫酷交互效果。下面是我实现的两个:
自定义FAB旋转Behavior:
这个是在一个项目里修改的,无视里面的命名~
public class NewsDetailBehavior
extends
CoordinatorLayout.Behavior<FloatingActionButton> {
private static float degress=10;
public NewsDetailBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
/**
* 参数1-CoordinatorLayout布局
* 2-传入的泛型,即具体要用到这个Behavior的控件
* 3-View dependency:当前用到这个Behavior的控件是依附于哪个View的
*
* 返回值:一般写成 dependency instanceof XXX;为布尔值
*/
public boolean layoutDependsOn(CoordinatorLayout parent,
FloatingActionButton child, View dependency) {
return dependency instanceof AppBarLayout;
}
@Override
/**
* 如果依赖的控件的位置或者大小发生改变,使用Viewcompat操控使用这个Behavior的控件进行对应的变化
*/
public boolean onDependentViewChanged(CoordinatorLayout parent,
FloatingActionButton child, View dependency) {
degress+=10;
ViewCompat.setRotation (child,degress);
return super.onDependentViewChanged(parent, child, dependency);
}
}
布局文件:
<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="com.chan.iwfu_md_news.activity.NewsDetailActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="256dp"
android:fitsSystemWindows="true"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:collapsedTitleGravity="center"
app:contentScrim="@color/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:id="@+id/iv_news_detail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@mipmap/shibai"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.5"/>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar_newsDetail"
android:layout_width="match_parent"
android:layout_height="attr/actionBarSize"
android:background="#00000000"
app:layout_collapseMode="pin"
app:popupTheme="@style/Theme.AppCompat.Light"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.design.widget.FloatingActionButton
android:layout_height="wrap_content"
android:layout_width="wrap_content"
app:layout_anchor="@id/appbar"
app:layout_anchorGravity="bottom|right|end"
app:layout_behavior="com.chan.iwfu_md_news.utils.NewsDetailBehavior"
android:src="@mipmap/ic_launcher"
android:layout_margin="20dp"
android:clickable="true"/>
</android.support.design.widget.CoordinatorLayout>
效果图:
自定义滑动隐藏头部Behavior:
这个就像现在很多软件都有的下滑隐藏head头部,上滑而不用滑到顶就可以显示头部。
/**
* Created by Iwfu on 2016/6/12.
*/
public class HideHeadBehavior extends CoordinatorLayout.Behavior {
int offsetTotal = 0;
boolean scrolling = false;
public HideHeadBehavior(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文件:
<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:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="false"
tools:context="com.chan.coordinatorbehaviordemo.Demo3Activity">
<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="com.chan.coordinatorbehaviordemo.behavior.HideHeadBehavior"
android:background="@android:color/holo_blue_light"/>
</android.support.design.widget.CoordinatorLayout>
效果图:
以上就是本篇的全部内容,主要包括自定义Behavior的两种方式,开动脑筋实现更多特效~
附:
源码链接
https://github.com/ImWaitingForU/CoordinatorBehaviorDemo
本文部分内容参考其他大神文章:
最后感谢你能耐心看到这里,不足之处还望指正。
完结撒花~~
- 顶
- 0
- 踩
- 0