协调布局CoordinatorLayout能够让内部的子控件互相配合着移动,这跟以往孤立的控件运动截然不同,协调运动的结果,意味着App画面更加流畅、自然,也更符合日常生活中的动作场景。如果你对CoordinatorLayout的用法还不太了解的话,建议先浏览这篇博文熟悉一下《Android开发笔记(一百三十四)协调布局CoordinatorLayout》。
一般我们使用CoordinatorLayout,都是结合悬浮按钮FloatingActionButton、应用栏布局AppBarLayout或者可折叠工具栏布局CollapsingToolbarLayout,但这不表示CoordinatorLayout只能与这少数几个控件一起使用,事实上,只要定义了两个控件之间的互动行为,即使是TextView、ImageView这些普通控件,也能实现自动协调运动的效果。先上个图,说明一下自定义行为所支持的画面:
从这张女孩照镜子的动画可以看到,当拖动左边女孩头像时,右边镜子里的头像也随之靠近或者远离,颇似现实生活中的镜像运动。如果按照普通的实现方式,此种照镜子的动画效果,得给两个视图分别注册监听器,然后在A视图移动之际,同时触发B视图的移动行为。如此一来,两个视图之间的联系变得很紧密了,不但要分别改造这两个视图,而且还无法给其他视图复用。
正因为存在以上问题,所以MaterialDesign库专门设计了CoordinatorLayout,用来协调内部视图互相的行为,具体的行为定义模板叫做CoordinatorLayout.Behavior。也许读者对Behavior类还有些陌生,不过之前的几篇博文已经涉及到了部分知识,比如在博文《Android开发笔记(一百三十五)应用栏布局AppBarLayout》中,就提到给主页面的视图节点添加属性app:layout_behavior="@string/appbar_scrolling_view_behavior",表示通知AppBarLayout捕捉RecyclerView的滚动操作。而字符串appbar_scrolling_view_behavior指向android.support.design.widget.AppBarLayout$ScrollingViewBehavior,它便是规定AppBarLayout滚动操作的自定义行为。
另外一个Design库的例子,则是悬浮按钮FloatingActionButton配合提示条Snackbar,博文《Android开发笔记(一百三十四)协调布局CoordinatorLayout》提到,Snackbar出现之时,FloatingActionButton会随着往上挪动;而Snackbar关闭的时候,FloatingActionButton也会随着向下移动。具体的动图如下所示:
查看FloatingActionButton的源码,发现该控件内部也有个Behavior类,下面是与互动行为有关的代码:
1、两个函数都有三个参数,分别是做为父布局的CoordinatorLayout、做为子控件的FloatingActionButton、做为子控件依赖者的View。其中parent是页面布局文件的根节点,child是跟随运动的控件,dependency是带头运动的视图。dependency好比是广播操的指挥者,与之相对的child是广播操的表演者,dependency口里喊道“一二一一二一”,child就跟着踏步走。换句话说,child的任何运动,都得跟dependency一一配合;只有dependency动了,child才能跟着动。
2、两个函数中,layoutDependsOn用来判断当前的几个视图是否存在依赖关系,返回true表示存在依赖,反之则不存在依赖;也就是说,只有存在依赖关系的两个视图才会夫唱妇随,缺一不可。而onDependentViewChanged定义了依赖运动的具体对应规则,即dependency做某个动作时,child应该配合着做什么动作。
FloatingActionButton中的Behavior类定义的便是它跟依赖视图,即SnackbarLayout的运动关系。首先在layoutDependsOn方法中判断依赖视图是否为SnackbarLayout的示例,表示悬浮按钮会跟着提示条一块运动。然后在onDependentViewChanged方法中规定悬浮按钮配合提示条的运动行为,即提示条出现之时,悬浮按钮往上挪动;提示条消失之际,悬浮按钮向下移动。
只要明确了协调行为的原理与实现,自定义Behavior的过程就有章可循了。比如AppBarLayout节点的layout_behavior属性,便定义了AppBarLayout跟随主页面视图如RecyclerView的运动行为。再比如本文开头给的女孩照镜子动图,镜子里的头像会跟着女孩一齐靠近镜面,也一齐远离镜面;在该例子中,镜像就依赖于女孩,一旦女孩动了,镜像也跟着动。女孩照镜子是在水平方向上协调运动的例子,生活中还有在垂直方向上协调运动的例子,比如使用定滑轮吊起重物,滑轮一端绳子吊着重物,另一端绳子连着人力;人力拉动绳子,重物就被吊上来,人力松开绳子,重物就会掉下来。定滑轮升降的动图如下所示:
具体实现之时,则需做三处修改:
1、首先自定义一个图像控件,通过手势可以拖动该控件;
2、其次自定义一个Behavior,指定存在依赖关系的两种视图,在layoutDependsOn方法中规定两种视图的类型,在onDependentViewChanged方法中定义重物视图配合人力拉曳时的运动行为;
3、在布局文件中放置定滑轮、人力视图、重物视图,并给重物视图指定layout_behavior属性,说明重物视图的协调动作;
下面是可拖动图像控件的代码例子:
下面是自定义滑动行为的代码例子:
下面是定滑轮升降的布局例子:
点此查看Android开发笔记的完整目录
一般我们使用CoordinatorLayout,都是结合悬浮按钮FloatingActionButton、应用栏布局AppBarLayout或者可折叠工具栏布局CollapsingToolbarLayout,但这不表示CoordinatorLayout只能与这少数几个控件一起使用,事实上,只要定义了两个控件之间的互动行为,即使是TextView、ImageView这些普通控件,也能实现自动协调运动的效果。先上个图,说明一下自定义行为所支持的画面:
从这张女孩照镜子的动画可以看到,当拖动左边女孩头像时,右边镜子里的头像也随之靠近或者远离,颇似现实生活中的镜像运动。如果按照普通的实现方式,此种照镜子的动画效果,得给两个视图分别注册监听器,然后在A视图移动之际,同时触发B视图的移动行为。如此一来,两个视图之间的联系变得很紧密了,不但要分别改造这两个视图,而且还无法给其他视图复用。
正因为存在以上问题,所以MaterialDesign库专门设计了CoordinatorLayout,用来协调内部视图互相的行为,具体的行为定义模板叫做CoordinatorLayout.Behavior。也许读者对Behavior类还有些陌生,不过之前的几篇博文已经涉及到了部分知识,比如在博文《Android开发笔记(一百三十五)应用栏布局AppBarLayout》中,就提到给主页面的视图节点添加属性app:layout_behavior="@string/appbar_scrolling_view_behavior",表示通知AppBarLayout捕捉RecyclerView的滚动操作。而字符串appbar_scrolling_view_behavior指向android.support.design.widget.AppBarLayout$ScrollingViewBehavior,它便是规定AppBarLayout滚动操作的自定义行为。
另外一个Design库的例子,则是悬浮按钮FloatingActionButton配合提示条Snackbar,博文《Android开发笔记(一百三十四)协调布局CoordinatorLayout》提到,Snackbar出现之时,FloatingActionButton会随着往上挪动;而Snackbar关闭的时候,FloatingActionButton也会随着向下移动。具体的动图如下所示:
查看FloatingActionButton的源码,发现该控件内部也有个Behavior类,下面是与互动行为有关的代码:
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) { return ((SNACKBAR_BEHAVIOR_ENABLED) && (dependency instanceof Snackbar.SnackbarLayout)); } public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) { if (dependency instanceof Snackbar.SnackbarLayout) updateFabTranslationForSnackbar(parent, child, dependency); else if (dependency instanceof AppBarLayout) { updateFabVisibility(parent, (AppBarLayout) dependency, child); } return false; }上述代码主要包含了两种意思:
1、两个函数都有三个参数,分别是做为父布局的CoordinatorLayout、做为子控件的FloatingActionButton、做为子控件依赖者的View。其中parent是页面布局文件的根节点,child是跟随运动的控件,dependency是带头运动的视图。dependency好比是广播操的指挥者,与之相对的child是广播操的表演者,dependency口里喊道“一二一一二一”,child就跟着踏步走。换句话说,child的任何运动,都得跟dependency一一配合;只有dependency动了,child才能跟着动。
2、两个函数中,layoutDependsOn用来判断当前的几个视图是否存在依赖关系,返回true表示存在依赖,反之则不存在依赖;也就是说,只有存在依赖关系的两个视图才会夫唱妇随,缺一不可。而onDependentViewChanged定义了依赖运动的具体对应规则,即dependency做某个动作时,child应该配合着做什么动作。
FloatingActionButton中的Behavior类定义的便是它跟依赖视图,即SnackbarLayout的运动关系。首先在layoutDependsOn方法中判断依赖视图是否为SnackbarLayout的示例,表示悬浮按钮会跟着提示条一块运动。然后在onDependentViewChanged方法中规定悬浮按钮配合提示条的运动行为,即提示条出现之时,悬浮按钮往上挪动;提示条消失之际,悬浮按钮向下移动。
只要明确了协调行为的原理与实现,自定义Behavior的过程就有章可循了。比如AppBarLayout节点的layout_behavior属性,便定义了AppBarLayout跟随主页面视图如RecyclerView的运动行为。再比如本文开头给的女孩照镜子动图,镜子里的头像会跟着女孩一齐靠近镜面,也一齐远离镜面;在该例子中,镜像就依赖于女孩,一旦女孩动了,镜像也跟着动。女孩照镜子是在水平方向上协调运动的例子,生活中还有在垂直方向上协调运动的例子,比如使用定滑轮吊起重物,滑轮一端绳子吊着重物,另一端绳子连着人力;人力拉动绳子,重物就被吊上来,人力松开绳子,重物就会掉下来。定滑轮升降的动图如下所示:
具体实现之时,则需做三处修改:
1、首先自定义一个图像控件,通过手势可以拖动该控件;
2、其次自定义一个Behavior,指定存在依赖关系的两种视图,在layoutDependsOn方法中规定两种视图的类型,在onDependentViewChanged方法中定义重物视图配合人力拉曳时的运动行为;
3、在布局文件中放置定滑轮、人力视图、重物视图,并给重物视图指定layout_behavior属性,说明重物视图的协调动作;
下面是可拖动图像控件的代码例子:
public class CoordinatorImageView extends ImageView { private final static String TAG = "CoordinatorImageView"; private int mViewWidth, mViewHeight; private int mLastXPos, mLastYPos; private int mOrientation = LinearLayout.HORIZONTAL; public static final int STRETCH_NONE = 0; public static final int STRETCH_LEFT = 1; public static final int STRETCH_TOP = 2; public static final int STRETCH_RIGHT = 3; public static final int STRETCH_BOTTOM = 4; private int mStretchDirection = STRETCH_NONE; private boolean mReverser = false; public CoordinatorImageView(Context context) { this(context, null); } public CoordinatorImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CoordinatorImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public void setOrientation(int orientation) { mOrientation = orientation; } public int getOrientation() { return mOrientation; } public void setStretchDirection(int direction) { mStretchDirection = direction; } public int getStretchDirection() { return mStretchDirection; } public void setReverser(boolean reverser) { mReverser = reverser; } public boolean getReverser() { return mReverser; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mViewWidth = getMeasuredWidth(); mViewHeight = getMeasuredHeight(); } @Override public boolean onTouchEvent(MotionEvent event) { int xPos = (int) event.getRawX(); int yPos = (int) event.getRawY(); if (event.getAction() == MotionEvent.ACTION_MOVE) { CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) getLayoutParams(); int left = params.leftMargin + xPos - mLastXPos; int top = params.topMargin + yPos - mLastYPos; int right = params.rightMargin - (xPos - mLastXPos); int bottom = params.bottomMargin - (yPos - mLastYPos); if (mOrientation == LinearLayout.HORIZONTAL) { if (mStretchDirection == STRETCH_NONE) { params.leftMargin = left; } else { if (mStretchDirection == STRETCH_LEFT) { params.leftMargin = left; } else if (mStretchDirection == STRETCH_RIGHT) { params.rightMargin = right; } int tempWidth = mViewWidth + xPos - mLastXPos; if (getMinimumWidth() == 0 || tempWidth > getMinimumWidth()) { params.width = tempWidth; } } } else if (mOrientation == LinearLayout.VERTICAL) { if (mStretchDirection == STRETCH_NONE) { params.topMargin = top; } else { if (mStretchDirection == STRETCH_TOP) { params.topMargin = top; } else if (mStretchDirection == STRETCH_BOTTOM) { params.bottomMargin = bottom; } int tempHeight = mViewHeight + yPos - mLastYPos; if (getMinimumHeight() == 0 || tempHeight > getMinimumHeight()) { params.height = tempHeight; } } } setLayoutParams(params); requestLayout(); } mLastXPos = xPos; mLastYPos = yPos; return true; } }
下面是自定义滑动行为的代码例子:
public class ImageViewBehavior extends CoordinatorLayout.Behavior<ImageView> { private final static String TAG = "ImageViewBehavior"; private int mScreenWidth, mScreenHeight; public ImageViewBehavior(Context context, AttributeSet attrs) { super(context, attrs); mScreenWidth = DisplayUtil.getSreenWidth(context); mScreenHeight = DisplayUtil.getSreenHeight(context); } @Override public boolean layoutDependsOn(CoordinatorLayout parent, ImageView child, View dependency) { return dependency instanceof CoordinatorImageView; } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, ImageView child, View dependency) { if (dependency instanceof CoordinatorImageView) { CoordinatorImageView depend = (CoordinatorImageView) dependency; int left = depend.getLeft(); int top = depend.getTop(); int right = depend.getRight(); int bottom = depend.getBottom(); int orientation = depend.getOrientation(); int direction = depend.getStretchDirection(); boolean reverser = depend.getReverser(); CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) child.getLayoutParams(); if (orientation == LinearLayout.HORIZONTAL) { params.width = depend.getMeasuredWidth(); if (direction == CoordinatorImageView.STRETCH_NONE || direction == CoordinatorImageView.STRETCH_LEFT) { if (reverser) { params.rightMargin = left; } else { params.leftMargin = left; } } else if (direction == CoordinatorImageView.STRETCH_RIGHT) { if (reverser) { int minWidth = child.getMinimumWidth(); int tempWidth = mScreenWidth - left- depend.getMeasuredWidth() + depend.getMinimumWidth() - 50; if (minWidth == 0 || tempWidth > minWidth) { params.width = tempWidth; } else { params.width = minWidth; } } else { params.rightMargin = left; } } } else if (orientation == LinearLayout.VERTICAL) { params.height = depend.getMeasuredHeight(); if (direction == CoordinatorImageView.STRETCH_NONE || direction == CoordinatorImageView.STRETCH_TOP) { if (reverser) { params.bottomMargin = top; } else { params.topMargin = top; } } else if (direction == CoordinatorImageView.STRETCH_BOTTOM) { if (reverser) { int minHeight = child.getMinimumHeight(); int tempHeight = mScreenHeight - top - depend.getMeasuredHeight() + depend.getMinimumHeight() - 50; if (minHeight == 0 || tempHeight > minHeight) { params.height = tempHeight; } else { params.height = minHeight; } } else { params.bottomMargin = top; } } } child.setLayoutParams(params); } return true; } }
下面是定滑轮升降的布局例子:
<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" > <ImageView android:layout_width="200dp" android:layout_height="200dp" android:layout_gravity="top|center" android:layout_marginTop="0dp" android:scaleType="fitCenter" android:src="@drawable/pulley_top" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top|right" android:layout_marginRight="119dp" android:layout_marginTop="150dp" android:minHeight="140dp" android:background="@drawable/pulley_right" app:layout_behavior="com.example.exmbehavior.widget.ImageViewBehavior" /> <com.example.exmbehavior.widget.CoordinatorImageView android:id="@+id/civ_pulley" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top|left" android:layout_marginLeft="122dp" android:layout_marginTop="150dp" android:minHeight="140dp" android:background="@drawable/pulley_left" /> </android.support.design.widget.CoordinatorLayout>
点此查看Android开发笔记的完整目录
作者:aqi00 发表于2017/3/6 15:39:13 原文链接
阅读:35 评论:0 查看评论