Android鬼点子-Coordinatorlayout与它的关系户们 | GreendaMi'Blog
Coordinatorlayout是Material风格的重要组件,是Design Support Library中重要一部分,它的作用是协调(Coordinate)其他组件, 实现联动.
使用Coordinatorlayout可很容易的实现这样的动画。
Coordinatorlayout其实就是一个强化版的FrameLayout。使用Coordinatorlayout需要引入
compile 'com.android.support:design:22.2.1'我们都知道,当一个View响应了触摸事件之后,这个事件就不会被和它同行级别的其他View接受到。也就是说像上面那张图那样,如果ListView接收到了事件,那么和它同级别的TitleBar就不会收到这个事件了。但是Coordinatorlayout就会很好的解决这个问题。所有的事件都是首先由Coordinatorlayout来进行处理和分发,然后再由其他子View做出响应。下面我用一个例子来详细的说明。

上布局!!!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:textFloatingActionButton="http://schemas.android.com/apk/res-auto" android:id="@+id/img2" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/transparent" app:layout_behavior="ui.FilmInfoBehavior" > <RelativeLayout android:id="@+id/top" android:layout_width="match_parent" android:layout_height="match_parent"> <RelativeLayout android:id="@+id/topR" android:layout_width="match_parent" android:layout_height="350dp"> ... </RelativeLayout> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/topR" android:background="@color/transparent" android:paddingBottom="10dp" android:paddingTop="10dp" > <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" > ... </RelativeLayout> </RelativeLayout> </android.support.v4.widget.NestedScrollView> </RelativeLayout> </android.support.design.widget.AppBarLayout> <ui.TextFloatingActionButton android:id="@+id/play" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="16dp" android:clickable="true" app:backgroundTint="@color/DarkFontColor" app:elevation="6dp" app:layout_anchor="@id/img1" app:layout_anchorGravity="bottom|right" app:pressedTranslationZ="12dp" app:rippleColor="#33728dff" textFloatingActionButton:textFloatingActionButtonText="@string/播放" textFloatingActionButton:textFloatingActionButtonTextColor="@color/FontColor" /> </android.support.design.widget.CoordinatorLayout> |
最外层是CoordinatorLayout,让后CoordinatorLayout的两个子View是AppBarLayout和FloatingActionButton,这里的TextFloatingActionButton是我自己优化了一下,可以显示文字。AppBarLayout里面有个很重要的是NestedScrollView。
关系户一:FloatingActionButton简称FAB
就是那个圆圆的播放键。FloatingActionButton已经要是CoordinatorLayout的直接子View,layout_anchor这个锚点信息才会有作用。anchor可以控制FAB的位置。
1、app:borderWidth=””——————边框宽度,通常设置为0 ,用于解决Android 5.X设备上阴影无法正常显示的问题
2、app:backgroundTint=””—————按钮的背景颜色,不设置,默认使用theme中colorAccent的颜色
3、app:rippleColor=””——————–点击的边缘阴影颜色
4、app:elevation=””———————-边缘阴影的宽度
5、app:pressedTranslationZ=”16dp”—–点击按钮时,按钮边缘阴影的宽度,通常设置比elevation的数值大
对了,FloatingActionButton的原形是ImageView。下面是我自己改的可以显示文字的FloatingActionButton。因为我只显示一个字,所以文字位置的计算可能不具有通用性,使用的时候需要修改一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | public class TextFloatingActionButton extends FloatingActionButton { Typeface mtypeface; String text; int textcolor; public TextFloatingActionButton(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs , R.styleable.TextFloatingActionButton); text = a.getString(R.styleable.TextFloatingActionButton_textFloatingActionButtonText); a.recycle(); mtypeface = Typeface.createFromAsset(context.getAssets(), "iconfont/iconfont.ttf"); } protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint paint=new Paint(); // paint.setTextAlign(Paint.Align.CENTER); paint.setColor(Color.WHITE); paint.setTypeface(mtypeface); paint.setTextSize(getWidth() * 0.6f); Rect rect = new Rect(); //返回包围整个字符串的最小的一个Rect区域 paint.getTextBounds(text, 0, 1, rect); //拿到字符串的宽度 float stringWidth = rect.width(); float stringHeight = rect.height(); float x =(getWidth()* 0.9f-stringWidth)/2; canvas.drawText(text, x ,(getHeight()* 0.9f + stringHeight)/2 ,paint); } } |
attr.xml
1 2 3 4 5 6 | <resources> <declare-styleable name="TextFloatingActionButton"> <attr name="textFloatingActionButtonText" format="string"></attr> <attr name="textFloatingActionButtonTextColor" format="color"></attr> </declare-styleable> </resources> |
关系户二:Behavior
当A做了XX,B就XX。这里B做的动作,就是在Behavior中实现的。Behavior中有两个很重要的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /** * 判断child的布局是否依赖dependency */ public boolean layoutDependsOn(CoordinatorLayout parent, T child, View dependency) { boolean rs; //根据逻辑判断rs的取值 //返回false表示child不依赖dependency,ture表示依赖 return rs; } /** * 当dependency发生改变时(位置、宽高等),执行这个函数 * 返回true表示child的位置或者是宽高要发生改变,否则就返回false */ public boolean onDependentViewChanged(CoordinatorLayout parent, T child, View dependency) { //child要执行的具体动作 return true; } |
dependency就是A,child就B。注意,这里的Child一定要是Coordinatorlayout的子View。Behavior写好了,是这样使用的。使用这个属性的View是Child。
app:layout_behavior="ui.FilmInfoBehavior"关系户三:NestedScrollView
NestedScrollView收到了触摸事件,然后让它的父控件滚动,这是我上面那张图的效果。做出响应的是Child,需要设置Behavior,就是布局中的AppBarLayout。下面是我写的Behavior。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | public class FilmInfoBehavior extends AppBarLayout.ScrollingViewBehavior { final AlphaAnimation InAnimation = new AlphaAnimation(0, 1); int totle; int sum = 0; public FilmInfoBehavior(Context context, AttributeSet attrs) { super(context, attrs); } public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) { totle = target.getTop() * 6 / 7; Log.d("FilmInfoBehavior", "target.getTop()/7:" + (target.getTop() * 6 / 7)); return true; } public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { View back = coordinatorLayout.findViewById(R.id.backBt); View top = coordinatorLayout.findViewById(R.id.top); View cover = coordinatorLayout.findViewById(R.id.cover); if(dyUnconsumed > 0 && sum + dyUnconsumed < totle ){ top.offsetTopAndBottom(- dyUnconsumed); back.offsetTopAndBottom(dyUnconsumed); sum = sum + dyUnconsumed; } if(dyUnconsumed < 0 && sum + dyUnconsumed > 0 ){ sum = sum + dyUnconsumed; top.offsetTopAndBottom(- dyUnconsumed); back.offsetTopAndBottom(dyUnconsumed); } cover.setAlpha(((float)(sum)/totle)); } } |
NestedScrollView实现了NestedScrollingParent,NestedScrollingChild接口,才会在事件响应之前被拦截,然后在onStartNestedScroll和 onNestedScroll中做出响应。dyUnconsumed是手指滑动的距离。
完整源码:Github