『Material Design入门学习笔记』CollapsingToolbarLayout与AppBarLayout(附demo)

这篇文章中,介绍到的其它组件有TextInputLayout,FloatingActionButton,Snackbar,CoordinatorLayout。
该专题的其它文章:
『Material Design入门学习笔记』前言
『Material Design入门学习笔记』动画(含demo)
『Material Design 入门学习笔记』主题与 AppCompatActivity(附 demo)
『Material Design入门学习笔记』RecyclerView与CardView(附demo)
demo下载

组件介绍

CoordinatorLayout

这一篇讲的组件有些多,所以要从头说起,头是哪呢?就是布局文件,布局文件的头是哪呢?就是CoordinatorLayout。
CoordinatorLayout被誉为超级FrameLayout。
主要是实现两个功能:

  • 作为顶层布局
  • 调度协调子布局
    比如说,接下来会讲到的Snackbar,就只有依靠CoordinatorLayout才能实现一些特殊操作,这个后面会讲到。
    CoordinatorLayout功能强大,主要是依靠一个Behavior对象。CoordinatorLayout自己并不控制View,所有的控制权都在Behavior。
    这个Behavior可以通过自定义的方式来实现自己的逻辑。写到这里,我突然发现后面没法介绍了,因为介绍Behavior的功能需要依赖其它组件,那就放在后面再介绍Behavior吧。
    这里强调一下,接下来介绍的所有组件,都会用到这个库:
compile 'com.android.support:design:22.2.1'

AppBarLayout

AppBarLayout继承自LinearLayout,布局方向为垂直方向。但是它内部封装了一些手势变换的动画。
首先它需要依赖CoordinatorLayout作为父容器,同时也要求一个具有可以独立滚动的子View。
他的子View会有一个设置参数 app:layout_scrollFlags有一下几种设置:

  • scroll:设为scroll的View会跟随滚动事件一起发生移动, 所有想滚动出屏幕的view都需要设置这个flag,没有设置这个flag的view将被固定在屏幕顶部。
  • enterAlways:设为enterAlways的View,当ScrollView往下滚动时,该View会直接往下滚动,而不用考虑ScrollView是否在滚动,这个flag让任意向下的滚动都会导致该view变为可见,如启用快速“返回模式”。
  • exitUntilCollapsed:值设为exitUntilCollapsed的View,当这个View要往上逐渐“消逝”时,会一直往上滑动,直到剩下的的高度达到它的最小高度后,再响应ScrollView的内部滑动事件。简而言之,滚动退出屏幕,最后折叠在顶端。
  • enterAlwaysCollapsed:是enterAlways的附加选项,一般跟enterAlways一起使用,它是指,View在往下“出现”的时候,首先是enterAlways效果,当View的高度达到最小高度时(注意你的view需要设置minHeight属性),View就暂时不去往下滚动,直到ScrollView滑动到顶部不再滑动时,View再继续往下滑动,直到滑到View的顶部结束。
    需要注意的是,后面两种模式基本只有在CollapsingToolbarLayout才有用。

CollapsingToolbarLayout

CollapsingToolbarLayout继承自FrameLayout,作用是为Toolbar提供了折叠功能。
下面介绍一些参数:
app:contentScrim这个参数可以让CollapsingToolbarLayout在收缩的时候,背景图片消失的时候,指定一个颜色。
app:collapsedTitleGravity 指定折叠状态的标题如何放置,可选值:top、bottom等
app:collapsedTitleTextAppearance指定折叠状态标题文字的样貌
app:expandedTitleTextAppearance指定展开状态标题文字的样貌
app:expandedTitleGravity 展开状态的标题如何放置
app:titleEnabled指定是否显示标题文本
app:toolbarId指定与之关联的ToolBar,如果未指定则默认使用第一个被发现的ToolBar子View
app:expandedTitleMarginStart指定展开状态标题距离开始位置的高度
app:expandedTitleMarginBottom指定展开状态标题距离底部的距离
app:expandedTitleMarginEnd指定展开状态标题距离结束位置的高度
app:layout_collapseParallaxMultiplier="0.7"设置视差的系数,介于0.0-1.0之间。
app:layout_collapseMode“pin”:固定模式,在折叠的时候最后固定在顶端;“parallax”:视差模式,在折叠的时候会有个视差折叠的效果。
app:layout_anchor``app:layout_anchorGravity两个属性连同一起,与某一个AppBarLayout控件相关联,确定位置。

TextInputLayout

TextInputLayout控件和LinearLayout完全一样,它只是一个容器。跟ScrollView一样,TextInputLayout只接受一个子元素。子元素需要是一个EditText元素。
EditText中有一个参数hint,这个大家都不陌生,但是如果EditText放在TextInputLayout中,则会让hint变成一个在EditText上方的浮动标签,同时还包括一个material动画。

FloatingActionButton

FloatingActionButton是一个圆形的按钮,跟Button一样会有点击事件。不同的是,可以设置阴影,可以设置反馈动画。而且如果你设置的背景图片是一个方形的,会在圆形按钮中间显示。

布局文件

上面介绍了一些基本属性,现在看一下整体的布局文件

<?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"
                                                 android:id="@+id/main_content"
                                                 android:layout_width="match_parent"
                                                 android:layout_height="match_parent"
                                                 android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="256dp"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        android:fitsSystemWindows="true">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:expandedTitleMarginStart="48dp"
            app:expandedTitleMarginEnd="64dp">

            <ImageView
                android:id="@+id/backdrop"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:fitsSystemWindows="true"
                android:src="@drawable/logo"
                app:layout_collapseMode="parallax"
                />

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                app:layout_collapseMode="pin" />

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

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



    <android.support.design.widget.TextInputLayout
        android:id="@+id/inputwrapper"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <EditText
            android:id="@+id/input"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="输入内容"/>

    </android.support.design.widget.TextInputLayout>
    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_anchor="@id/appbar"
        app:layout_anchorGravity="bottom|right|end"
        android:src="@mipmap/ic_launcher"
        android:id="@+id/btn_ok"
        />

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

在该布局文件中使用了Toolbar,关于它的相关属性,可以参考我之前的文章,这里不再重复介绍。
TextInputLayout有人会问为什么TextInputLayout使用app:layout_behavior="@string/appbar_scrolling_view_behavior",其实为了适配好位置,最好在外面加一层NestedScrollView,因为涉及到滑动嘛,但是这里暂时不用,因为那是下一篇文章的内容。

代码

下面贴出相关的代码:

public class CollapsingActivity extends AppCompatActivity {
    private FloatingActionButton okBtn;
    private EditText editText;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_collapsing);
        initToolBar();
        TextInputLayout inputWrapper = (TextInputLayout) findViewById(R.id.inputwrapper);
        editText = (EditText)findViewById(R.id.input);
        okBtn = (FloatingActionButton)findViewById(R.id.btn_ok);
        okBtn.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                hideKeyboard();
                Snackbar mySnackbar = Snackbar.make(findViewById(R.id.main_content),editText.getText().toString(),Snackbar.LENGTH_SHORT);
                mySnackbar.setAction(editText.getText().toString(), new OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        Toast.makeText(CollapsingActivity.this,"click Snackbar",Toast.LENGTH_LONG).show();
                    }
                });
                mySnackbar.show();

            }
        });
        okBtn.setCompatElevation(0);
        inputWrapper.setHintAnimationEnabled(true);
        inputWrapper.setHint("请输入内容");
    }
    private void hideKeyboard() {
        View view = getCurrentFocus();
        if (view != null) {
            ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).
                hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
        }
    }
    private void initToolBar(){
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        setTitle("CollapsingActivity");//设置标题
        toolbar.setNavigationIcon(R.mipmap.ic_launcher_round);//设置返回键,我这里没有,就有icon代替吧
        toolbar.setNavigationOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                finish();
            }
        });//返回监听
        toolbar.setSubtitle("by deep");//设置副标题
    }
}

简单说一下代码的逻辑,在界面上有个输入框,输入内容,点击FloatingActionButton,会将内容做一个Snackbar的展示,这是在底部会有一个Snackbar,Snackbar可以通过setAction方法设置点击事件,它的第一个参数是按钮显示的文字,第二个参数是点击事件,我这里是点击Snackbar会再弹出一个Toast。
关于Snackbar的初始化,可以使用make方法,这里需要注意它的参数:

  • 第一个参数是一个view,我试过,可以设置界面任何一个View,但是最好设置为父容器CoordinatorLayout,这样的话,会有一个滑动消除的手势。
  • 第二个参数是显示的内容,第三个参数是显示时长。

自定义behavior

在上文中提到过CoordinatorLayout功能如此强大,全依赖于Behavior对象。
我们先看一下源码,Behavior是一个抽象类,public static abstract class Behavior<V extends View>,我们可以根据View随意去实现。
源码中的实现这个抽象类的有以下几个:

  1. AppBarLayout.Behavior;
  2. AppBarLayout.ScrollingViewBehavior;
  3. FloatingActionButton.Behavior;
  4. Snackbar.Behavior;
  5. BottomSheetBehaviro;
  6. SwipeDismissBehavior;
  7. HeaderBehavior;
  8. ViewOffsetBehavior;
  9. HeaderScrollingViewBehavior;
    其中也有抽象类。
    上面我们用到的就有ScrollingViewBehavior和FloatingActionButton.Behavior,由于FloatingActionButton.Behavior默认就是设置好的,所以我们没有在布局文件中写。
    那么我们现在可以重新写一个FloatingActionButton的Behavior。
    在写之前最好去读一下Behavior这个抽象类,其中很多概念和关联需要弄明白。
    其中重要的两个概念
  • child 它是一个View, 是该Behavior的关联对象,也即Behavior所要操作的对象
  • dependency ,也是个View,是 child的依赖对象,同时也是Behavior对child进行操作的根据
    例如我们上面那个布局文件中child就是FloatingActionButton,dependency就是AppBarLayout。
    还有一些方法:
    layoutDependsOn用来确定依赖关系,原文是:

Determine whether the supplied child view has another specific sibling view as a layout dependency.
意思是,这个view有没有兄弟view。

onDependentViewChanged

Respond to a change in a child's dependent view
当我们的 dependency 发生改变的时候,这个方法会调用,而我们在 onDependentViewChanged 方法里根据需求做相应的界面处理即可。
我们简单写一个FloatingActionButton跟随AppBarLayout移动的例子:

public class CustomBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {

    public CustomBehavior() {
    }

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

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

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
        float translationY = Math.abs(dependency.getTop());
        Log.e("xxxxxx ","translationY="+translationY);
        child.setY(800-translationY);
        return true;
    }

}

然后设置到布局文件的对应位置:

  <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_anchor="@id/appbar"
        app:layout_anchorGravity="bottom|right|end"
        android:src="@mipmap/ic_launcher"
        android:id="@+id/btn_ok"
        app:layout_behavior="deep.testmaterial.coordinator.RotateBehavior"
        />

自定义Behavior的方式就介绍到这。

总结

本来想在文章中插图,但是由于动图过大,上传总是失败,所以,还是请感兴趣的朋友运行demo看一下效果即可。后面的文章还是对本文的延伸,增添了一些新的组件,敬请关注。
有问题可以给我留言,或者关注我的公众号留言。


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

推荐阅读更多精彩内容