一篇文章学会Coordinatorlayout+AppbarLayout

现如今,折叠式布局在App中相当常见,给人一种科技感,充满良好的用户体验。Coordinatorlayout+AppbarLayout+CollapsingToolbarLayout这三个臭皮匠联合起来用千变万化,啊,我重来没有见过如此超凡脱俗之效果。
网上大多来不来就将这仨揉在一起,布局也是直接全部嵌套完成搬上来,但是你真的理解它们之间的协作关系吗?相互联动的原理是什么呢?一个个控件都没整明白写出这个功能也没有意义呀。那我就一个一个拆开来讲,分别来个功能,再一个接一个拼接。接下来,让我们一起走进它们的内心世界。

github代码直通车: https://github.com/18380438200/CoordinatorlayoutFull
先上效果图:

giphy的副本.gif

博客讲解demo地址:https://github.com/18380438200/MDView

ToolBar(因为涉及到,也一并讲解)

从Android3.0后出现ActionBar,但是这效果,谁用谁知道啊。颜色不好看不说,布局也是无法订制,都不如自定义ActionBar的好。可见我的另一篇自定义[Actionbar] http://www.jianshu.com/p/43b51e1062f1
使用方式:
1.首先在Activity主题里面将默认Actionbar改为NoActionbar

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">

2.绑定toolbar ,setSupportActionBar(toolbar) 设置toolbar为标题栏
3.设置常用属性:

    toolbar.setNavigationIcon(int resId);
    toolbar.setLogo(int resId);
    toolbar.setTitle("");
    toolbar.setSubtitle("");
    toolbar.setOnMenuItemClickListener(Toolbar.OnMenuItemClickListener listener);

4.引用菜单

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        //引入options菜单
        getMenuInflater().inflate(R.menu.menu,menu);
        return true;
    }

5.在menu文件夹中设置菜单

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item android:id="@+id/menu_1"
          android:title="菜单1"
          android:icon="@mipmap/make_music_voice_changer_female"
          app:showAsAction="collapseActionView"/>

    <item android:id="@+id/menu_2"
        android:title="菜单2"
        android:icon="@mipmap/make_music_voice_changer_female"
        app:showAsAction="collapseActionView"/>

    <item android:id="@+id/menu_3"
        android:title="菜单3"
        android:icon="@mipmap/make_music_voice_changer_female"
        app:showAsAction="collapseActionView"/>

    <item android:id="@+id/menu_4"
        android:title="菜单4"
        android:icon="@mipmap/make_music_voice_changer_female"
        app:showAsAction="collapseActionView"/>
</menu>

或者直接在布局中添加子view使用

            <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/ThemeOverlay.AppCompat.Light" >

                <TextView
                    android:id="@+id/tv1"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="返回"
                    android:textSize="13sp"
                    android:textColor="@android:color/white" />

                <TextView
                    android:id="@+id/tv2"
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout_gravity="right"
                    android:layout_centerHorizontal="true"
                    android:layout_marginRight="6dp"
                    android:gravity="center"
                    android:padding="4dp"
                    android:textColor="#fff"
                    android:textSize="14sp"
                    android:text="菜单"/>
            </android.support.v7.widget.Toolbar>
showAsAction属性
  1. ifRoom 会显示在Item中,空间不足会将后面item收起来,如果已经有4个或者4个以上的Item时会隐藏在溢出列表中。
  2. never 永远不会显示。只会在藏出列表中显示,而且只显示标题,所以在定义item的时候,最好把标题都带上。
  3. always 无论是否超出空间,总会显示。
  4. withText withText值示意Action bar要显示文本标题。Action bar会尽可能的显示这个标题,但是,如果图标有效并且受到Action bar空间的限制,文本标题有可能显示不全。
  5. collapseActionView 声明了这个操作视窗应该被折叠到一个按钮中,当用户选择这个按钮时,这个操作视窗展开。否则,这个操作视窗在默认的情况下是可见的,并且即便在用于不适用的时候,也要占据操作栏的有效空间。
    例如效果:


    ifroom的效果

    collapseActionView的效果

Coordinatorlayout :

定义:is a super-powered Framelayout
是一个超级有力量的爸爸,官方给的定义就足以证明它的强大。
作用:协调子view的相互关系,比如位置、大小,就像有几个调皮孩子的爸爸,要管管孩子的行为。

Behavior:


Behavior的来源

打开Coordinatorlayout看,Behavior是CoordinatorLayout的一个泛型抽象内部类(这么长累不累呀),所以给子view添加layout_behavior属性是来自于它。

我写了一个例子来理解CoordinatorLayout的工作原理:


这是一个大叔跟随女孩的故事
<android.support.design.widget.CoordinatorLayout xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/coordinatorLayout"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context="com.example.md.mdview.CoordinatorLayoutActivity">

    <View
        android:id="@+id/view_girl"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:layout_marginLeft="200dp"
        android:background="@mipmap/make_music_voice_changer_female" />

    <View
        android:id="@+id/view_uncle"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@mipmap/make_music_voice_changer_uncle"
        app:layout_behavior="com.example.md.mdview.RunBehavior"/>
</android.support.design.widget.CoordinatorLayout>

布局:两个子view,操作viewgirl,viewuncle也会相应跟着走,这就要写一个联动关系,用自定义Behavior实现

public class RunBehavior extends CoordinatorLayout.Behavior<View>{

    public RunBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        int top = dependency.getTop();
        int left = dependency.getLeft();

        ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
        params.topMargin = top - 400;
        params.leftMargin = left;
        child.setLayoutParams(params);
        return true;
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return true;
    }
}

public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) 方法:
根据条件过滤判断返回值,返回true联动,返回flase不联动,即behavior不生效

public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency)
当 dependency这个哥哥发生变化时, 另一个child弟弟也要跟着去玩
一个view根据另一个view的变化而变化, dependency被 child监听
功能是child的y值永远比dependency大400像素(废话,还用说吗)

app:layout_behavior="com.example.md.mdview.RunBehavior"

这里一定要写上带参数的构造方法,因为coordinatorlayout是根据反射(所以是包名.类名路径)获取这个behavior,是从这个构造方法获得对象的,否则会报


image.png
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                params.leftMargin = (int) (event.getX() - viewGirl.getMeasuredWidth() / 2);
                params.topMargin = (int) (event.getY() - viewGirl.getMeasuredHeight() / 2);
                viewGirl.setLayoutParams(params);
                break;
            case MotionEvent.ACTION_MOVE:
                params.leftMargin = (int) (event.getX() - viewGirl.getMeasuredWidth() / 2);
                params.topMargin = (int) (event.getY() - viewGirl.getMeasuredHeight() / 2);
                viewGirl.setLayoutParams(params);
                break;
        }
        return true;
    }

最后是在界面监听手指的位置,给viewGirl设置手指的位置,viewgril变化了,viewuncle也就随之变化了。

好,在会了Coordinatorlayout的用法,最外层父布局有了,该添加两个子view了。这里里面分别加入AppbarLayout和NestedScrollView作子view,给NestedScrollView加上behavior,就可以让AppbarLayout跟随NestedScrollView的Behavior联动。Android已经自带了app:layout_behavior="@string/appbar_scrolling_view_behavior",只要滚动发生,就会给自己的子view(if
instance of Appbarlayout)添加滚动事件。不明白这俩控件紧接着看后面讲解。

当前布局变为:

<android.support.design.widget.CoordinatorLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:background="#0e932e"
                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="wrap_content"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:textColor="#000"
            android:padding="10dp"
            android:text=""/>
    </android.support.v4.widget.NestedScrollView>

</android.support.design.widget.CoordinatorLayout>

NestedScrollView (viewgirl的角色)

NestedScrolling机制能够让父View和子View在滚动式进行配合,其基本流程如下:

当子view开始滚动之前,可以通知父View,让其先于自己进行滚动;
子View自己进行滚动;子view滚动之后,还可以通知父view继续滚动。
而要实现这样的交互机制,首先父view要实现NestedScrollingParent接口,而子View需要实现NestedScrollingChild接口,在这套机制中子View是发起者,父view是接受回调并做出响应的。
以下是几个关键的类和接口

/**
 * NestedScrollView is just like {@link android.widget.ScrollView}, but it supports acting
 * as both a nested scrolling parent and child on both new and old versions of Android.
 * Nested scrolling is enabled by default.
 */
public class NestedScrollView extends FrameLayout implements NestedScrollingParent,
        NestedScrollingChild, ScrollingView {
    static final int ANIMATED_SCROLL_GAP = 250;

    static final float MAX_SCROLL_FACTOR = 0.5f;

    private static final String TAG = "NestedScrollView";

    /**
     * Interface definition for a callback to be invoked when the scroll
     * X or Y positions of a view change.
     *
     * <p>This version of the interface works on all versions of Android, back to API v4.</p>
     *
     * @see #setOnScrollChangeListener(OnScrollChangeListener)
     */
    public interface OnScrollChangeListener {
        /**
         * Called when the scroll position of a view changes.
         *
         * @param v The view whose scroll position has changed.
         * @param scrollX Current horizontal scroll origin.
         * @param scrollY Current vertical scroll origin.
         * @param oldScrollX Previous horizontal scroll origin.
         * @param oldScrollY Previous vertical scroll origin.
         */
        void onScrollChange(NestedScrollView v, int scrollX, int scrollY,
                int oldScrollX, int oldScrollY);
    }

    private long mLastScroll;

    private final Rect mTempRect = new Rect();
    private OverScroller mScroller;
    private EdgeEffect mEdgeGlowTop;
    private EdgeEffect mEdgeGlowBottom;
    ······

//主要接口
NestedScrollingChild
NestedScrollingParent
//帮助类
NestedScrollingChildHelper
NestedScrollingParentHelper

AppbarLayout (viewuncle的角色)

继承自Linearlayout,且方向是vertical,它可以让你定制当某个可滚动View的滚动手势发生变化时,其内部的子View实现何种动作。

AppBarLayout子View的动作

内部的子View通过在布局中加app:layout_scrollFlags设置执行的动作

·scroll :子view会跟随滚动事件一起滚动,相当于添加到scrollview头部

·enterAlways :只要屏幕下滑,view就会立即拉下出来。

·snap :这个属性让控件变得有弹性,如果控件下拉了75%的高度,就会自动展开,如果只有25%显示,就会反弹回去关闭。(去试试支付宝首页吧,就是加了弹性这个效果)

·exitUntilCollapsed :当scrollview滑到订部,再将子view折叠起来

·enterAlwaysCollapsed :当scrollview滑到底,再将子view展开

可以给ViewPager设置行为,就不需要使用NestedScrollView的滑动,实现与AppBarLayout联动。
app:layout_behavior="@string/appbar_scrolling_view_behavior"

setExpande(boolean ) 设置展开和关闭状态,默认有开关动画

使用示例:


app:layout_scrollFlags="scroll"
app:layout_scrollFlags="scroll|enterAlways"

CollapsingToolbarLayout

CollapsingToolbarLayout作用是提供了一个可以折叠的Toolbar,它继承自FrameLayout。
CollapsingToolbarLayout属性 含义
app:title 设置标题
app:collapsedTitleGravity="center" 设置标题位置
app:contentScrim 设置折叠时toolbar的颜色,默认是colorPrimary的色值
app:statusBarScrim 设置折叠时状态栏的颜色 ,默认是colorPrimaryDark的色值
app:layout_collapseParallaxMultiplier 设置视差
app:layout_collapseMode="parallax" 视差模式,在折叠的时候会有个视差折叠的效果
app:layout_collapseMode="pin" 固定模式,在折叠的时候最后固定在顶端

使用示例:让图片折叠,让toolbar固定

<android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <android.support.design.widget.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:background="@mipmap/bg"
                app:layout_collapseMode="parallax"/>

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:minHeight="50dp"
                android:background="#000"
                app:layout_collapseMode="pin"/>
        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>
图片折叠,固定toolbar

添加flags可以设置系统状态栏为透明,如果最顶上是背景这样用效果更佳

getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
setContentView(R.layout.activity_main);

实现toolbar渐变颜色:AppbarLayout提供了滑动偏移监听,偏移量除以appbar总高度可以得到当前滑动百分比。注意:这个verticalOffset是0或者负数,需要转绝对值。

        appbarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
            @Override
            public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                //verticalOffset始终为0以下的负数
                float percent = (Math.abs(verticalOffset * 1.0f)/appBarLayout.getTotalScrollRange());
            }
        });

这Matial Design的设计真好,但是这名取得,一个个儿的也忒长了吧,google什么时候把名字精简了啊?
好了,以后会持续更新的,喜欢我就点我吧!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • CoordinatorLayout与滚动的处理 CoordinatorLayout实现了多种Material De...
    cxm11阅读 6,579评论 1 15
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,711评论 2 59
  • 本文转自:泡在网上的日子 http://www.jcodecraeer.com/a/anzhuokaifa/and...
    JCboy阅读 1,595评论 0 1
  • 5CoordinatorLayout与AppBarLayout--嵌套滑动 上文我们说了AppBarLayout的...
    chefish阅读 6,174评论 4 19
  • 突然想发个微博,但又不知道说些啥。2012年的十一月份来杭州的,到现在差不多整整四年了,四年时间别人可以用来读完大...
    邰风先生阅读 286评论 0 1