本文主要是介绍针对 CoordinatorLayout 及 Behavior 的一次细节较真,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
我认真不是为了输赢,我就是认真。– 罗永浩
我一直对 Material Design 很感兴趣,每次在官网上阅读它的相关文档时,我总会有更进一步的体会。当然,Material Design 并不是仅仅针对 Android 而言的,它其实是一套普遍性的设计规范。而对于 Android 开发人员而言,我们涉及的往往是它的实现。也就是一个个个性鲜明的类。比如 RecyclerView 、CardView、Palette 等等。并且为了让开发者更轻松地开发出符合 Material Design 设计规范的界面,Google 开发人员直接提供了一个兼容包,它就是 Android Support Design Library。
引用这个包需要在 build.gradle 中添加依赖。
compile 'com.android.support:design:25.0.1'
在这个包中,最核心的一个类就是 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"android:fitsSystemWindows="true"tools:context="com.frank.supportdesigndemo.ScrollingActivity"><android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"android:layout_width="match_parent"android:layout_height="@dimen/app_bar_height"android:layout_marginTop="-28dp"android:fitsSystemWindows="true"android:theme="@style/AppTheme.AppBarOverlay"><android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"android:layout_width="match_parent"android:layout_height="match_parent"android:fitsSystemWindows="true"app:contentScrim="?attr/colorPrimary"app:layout_scrollFlags="scroll|exitUntilCollapsed"><ImageView
android:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="centerCrop"android:src="@drawable/test"app:layout_collapseMode="parallax"app:layout_collapseParallaxMultiplier="0.7"/><android.support.v7.widget.Toolbar
android:id="@+id/toolbar"android:layout_width="match_parent"android:layout_height="?attr/actionBarSize"app:layout_collapseMode="pin"app:popupTheme="@style/AppTheme.PopupOverlay" /></android.support.design.widget.CollapsingToolbarLayout></android.support.design.widget.AppBarLayout><include layout="@layout/content_scrolling" /><android.support.design.widget.FloatingActionButton
android:id="@+id/fab"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_margin="@dimen/fab_margin"app:layout_anchor="@id/app_bar"app:layout_anchorGravity="bottom|end"app:srcCompat="@android:drawable/ic_dialog_email" /></android.support.design.widget.CoordinatorLayout>
我照着完成了,效果也达到了,但是感觉有些虚。我不得劲,或者说我内心纠结吧。内心千头万绪,上面的布局文件中,除了 Toolbar 我认识外,其它的控件,我一个都不熟悉。
我不喜欢这种感觉。因为我有许许多多的疑惑。
CoordinatorLayout 是什么?
CoordinatorLayout 有什么作用?
AppBarLayout 是什么?
AppBarLayout 有什么作用?
……
接下来的文章篇幅会比较长,大家仔细阅读就好。如果时间不够,可以直接拖动到文章最后总结的那一节。不明白的地方再到文章中间部分阅读相关内容就可以了。但我希望读者还是顺序方式阅读,因为我相信如果你有许多疑惑,我的学习过程也许可以给你一些提示或者启迪。
更多的真相
在编程领域,学习一个陌生的事物,最好的途径可能就是阅读它的官方文档或者是源代码。带着心中的困惑,我前往 Android 官网,直接挑最显眼的 CoordinatorLayout 来进行研究。所以这篇文章我主讲 CoordinatorLayout。
官网解释 CoordinatorLayout 是一个超级 FrameLayout,然后可以作为一个容器指定与 child 的一些交互规则。
public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent {}
这是 CoordinatorLayout 的声明。它本质上就是一个 ViewGroup,注意的是它并没有继承自 FrameLayout,然后实现了 NestedScrollingParent 接口,我们先不管这个 NestedScrollingParent,NestedScrolliingParent 在文章后面适当的地方我会给出解释。
官网又说通过给CoordinaotrLayout 中的 child 指定 Behavior,就可以和 child 进行交互,或者是 child 之间互相进行相关的交互。并且自定义 View 时,可以通过 DefaultBehavior 这个注解来指定它关联的 Behavior。这里出现了新的名词:Behavior。于是,中断对 CoordinatorLayout 的跟踪,转到 Behavior 细节上来。
Behavior 其实是 CoordinatorLayout 中的一个静态内部类,并且是个泛型,接受任何 View 类型。官方文档真是惜字如金,更多的细节需要去阅读代码,也就是要靠猜测。这点很不爽的。好吧,官方文档说 Behavior 是针对 CoordinatorLayout 中 child 的交互插件。记住这个词:插件。插件也就代表如果一个 child 需要某种交互,它就需要加载对应的 Behavior,否则它就是不具备这种交互能力的。而 Behavior 本身是一个抽象类,它的实现类都是为了能够让用户作用在一个 View 上进行拖拽、滑动、快速滑动等手势。如果自己要定制某个交互动作,就需要自己实现一个 Behavior。
但是,对于我们而言,我们要实现一个 Behavior,我们用来干嘛呢?
是的,问问自己吧,我们如果自定义一个 Behavior,我们想干嘛?
前面内容有讲过,CoordinatorLayout 可以定义与它 child 的交互或者是某些 child 之间的交互。
我们先看看 Behavior 的代码细节,代码有精简。
public static abstract class Behavior<V extends View> {public Behavior() { }public Behavior(Context context, AttributeSet attrs) {}public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) { return false; }public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) { return false; }public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {}public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,V child, View directTargetChild, View target, int nestedScrollAxes) {return false;}public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, V child,View directTargetChild, View target, int nestedScrollAxes) {// Do nothing}public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {// Do nothing}public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target,int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {// Do nothing}public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target,int dx, int dy, int[] consumed) {// Do nothing}public boolean onNestedFling(CoordinatorLayout coordinatorLayout, V child, View target,float velocityX, float velocityY, boolean consumed) {return false;}public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target,float velocityX, float velocityY) {return false;}
}
一般我们自定义一个 Behavior,目的有两个,一个是根据某些依赖的 View 的位置进行相应的操作。另外一个就是响应 CoordinatorLayout 中某些组件的滑动事件。
我们先看第一种情况。
两个 View 之间的依赖关系
如果一个 View 依赖于另外一个 View。那么它可能需要操作下面 3 个 API:
public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) { return false; }public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) { return false; }public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {}
确定一个 View 对另外一个 View 是否依赖的时候,是通过 layoutDependsOn() 这个方法。注意参数,child 是要判断的主角,而 dependency 是宾角,如果 return true,表示依赖成立,反之不成立。当然,你可以复写这个方法对 dependency 进行类型判断否则是其它条件判断,然后再决定是否依赖。只有在 layoutDependsOn() 返回为 true 时,后面的 onDependentViewChanged() 和 onDependentViewRemoved() 才会被调用。
当依赖的那个 View 发生变化时,这个变化代码注释有解释,指的是 dependency 的尺寸和位置发生的变化,当有变化时 Behavior 的 onDependentViewChanged() 方法会被调用。如果复写这个方法时,改变了 child 的尺寸和位置参数,则需要返回 true,默认情况是返回 false。
onDependentView() 被调用时一般是指 dependency 被它的 parent 移除,或者是 child 设定了新的 anchor。
有了上面 3 个 API,我们就能应付在 CoordinatorLayout 中一个子 View 对别个一个子 View 的依赖情景了。
可能会有同学不明白,依赖是为何?或者说是何种依赖。为了避免概念过于空洞抽象。下面,我们用一个简单的例子来让大家感受一下,加深理解。
为了演示效果,我首先在屏幕上定义一个能够响应拖动的自定义 View,我叫它 DependencyView 好了。
它的代码很简单,主要是继承一个 TextView,然后在触摸事件中对自身位置进行位移。
public class DependencyView extends TextView {private final int mSlop;private float mLastX;private float mLastY;public DependencyView(Context context) {this(context,null);}public DependencyView(Context context, AttributeSet attrs) {this(context, attrs,0);}public DependencyView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);setClickable(true);mSlop = ViewConfiguration.getTouchSlop();}@Overridepublic boolean onTouchEvent(MotionEvent event) {// return super.onTouchEvent(event);int action = event.getAction();switch (action){case MotionEvent.ACTION_DOWN:mLastX = event.getX();mLastY = event.getY();break;case MotionEvent.ACTION_MOVE:int deltax = (int) (event.getX() - mLastX);int deltay = (int) (event.getY() - mLastY);if (Math.abs(deltax) > mSlop || Math.abs(deltay) > mSlop) {ViewCompat.offsetTopAndBottom(this,deltay);ViewCompat.offsetLeftAndRight(this,deltax);mLastX = event.getX();mLastY = event.getY();}break;case MotionEvent.ACTION_UP:mLastX = event.getX();mLastY = event.getY();break;default:break;}return true;}
}
前置条件已经确定好了,现在我们要向目标 Behavior 出发了。
做一个跟屁虫
实现一个 Behavior,让它支配一个 View 去紧紧跟随所依赖的 View。在这
这篇关于针对 CoordinatorLayout 及 Behavior 的一次细节较真的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!