Material Design之 AppbarLayout 开发实践总结

前言

本文同步发布到个人简书专栏Material Design之 AppbarLayout 开发实践总结html

前一篇文章是Material Design 系列的第一篇文章,讲了Toolbar 的使用,《Material Design 之 Toolbar 开发实践总结》,还没看过的同窗能够去看一下,这篇是Material Design 系列的第二篇文章,此次咱们讲AppbarLayout。一说到AppbarLayout,那么咱们必然会说到另外两个与AppbarLayout常常一块儿使用的View,那就是 CoordinatorLayout和CollapsingToolbarLayout。这三个View在一块儿使用的功能很是强大,能够实现不少炫酷的UI和动画效果。本篇文章将分别介绍三个view的一些功能和属性,最将三个View结合在一块儿使用实现炫酷的UI效果。java

正文

1, CoordinatorLayout

第一次接触CoordinatorLayout 你可能有这些疑问,CoordinatorLayout 究竟是个什么玩意儿呢?它到底能帮咱们作什么?咱们要了解它,确定是先看官方文档了。文档的第一句话就很是醒目: CoordinatorLayout is a super-powered FrameLayout ,很是明了,CoordinatorLayout 继承于ViewGroup,它就是一个超级强大Framelayout。CoordinatorLayout的做用就是协调子View。它有两种使用场景:android

1,做为 一个应用顶层的装饰布局,也就是一个Activity Layout 的最外一层布局。
2,As a container for a specific interaction with one or more child views,做为一个或多个有特定响应动做的容器。git

CoordinatorLayout 能够协调子View,而这些子View 的具体响应动做是经过 behavior 来指定的。若是你有特定的需求,你就须要本身定义一个特定的 Behavior,Google 也给咱们定义了一些经常使用的Behavior,如后面要用的到的 appbar_scrolling_view_behavior ,用于协调 AppbarLayout 与 ScrollView 滑动的Behavior,以下:github

<android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="18dp"
            android:text="@string/large_text"/>
    </android.support.v4.widget.NestedScrollView>复制代码

经过 layout_behavior 属性来指定 Behavior。本篇文章不会过多的去介绍Behavior,后面会单独拿出来介绍,咱们只要知道CoordinatorLayout 的做用就好了,能够经过Behavior 协调子View 。在网上也看到了一篇不错的专门介绍CoordinatorLayout 的文章,CoordinatorLayout的使用如此简单,须要的能够去了解一下。app

2,AppbarLayout 的使用

AppbarLayout继承自LinearLayout,它就是一个垂直方向的LinearLayout,在LinearLayout的基础上添加了一些材料设计的概念和特性,即滑动手势。它可让你定制在某个可滑动的View(如:ScrollView ,ListView ,RecyclerView 等)滑动手势发生改变时,内部的子View 该作什么动做。子View应该提供滑动时他们指望的响应的动做Behavior,经过setScrollFlags(int)),或者xml 中使用属性:ide

app:layout_scrollFlags复制代码

注意:AppbarLayout 严重依赖于CoordinatorLayout,必须用于CoordinatorLayout 的直接子View,若是你将AppbarLayout 放在其余的ViewGroup 里面,那么它的这些功能是无效的。oop

2.1 AppbarLayout 子View 的几种动做

上面说了 AppbarLayout 能够定制当某个可滑动的View滑动手势改变时内部子View的动做,经过app:layout_scrollFlags来指定,那么如今咱们就看一下layout_scrollFlags有哪几种动做。layout_scrollFlags有5种动做,分别是 scroll,enterAlways,enterAlwaysCollapsed,exitUntilCollapsed,snap。咱们来分别看一下这五种动做的含义和效果。布局

1, scroll ,子View 添加layout_scrollFlags属性 的值scroll 时,这个View将会随着可滚动View(如:ScrollView,如下都会用ScrollView 来代替可滚动的View )一块儿滚动,就好像子View 是属于ScrollView的一部分同样。动画

布局代码以下:

<android.support.v7.widget.Toolbar
                   android:layout_width="match_parent"
                   android:layout_height="?attr/actionBarSize"
                   app:title="AppbarLayout"
                   app:titleTextColor="@color/white"
                   app:layout_scrollFlags="scroll"
                   >

               </android.support.v7.widget.Toolbar>复制代码

效果以下:

2, enterAlways ,子View 添加layout_scrollFlags属性 的值有enterAlways 时, 当ScrollView 向下滑动时,子View 将直接向下滑动,而无论ScrollView 是否在滑动。注意:要与scroll 搭配使用,否者是不能滑动的。
代码以下:

<android.support.v7.widget.Toolbar
                   android:layout_width="match_parent"
                   android:layout_height="?attr/actionBarSize"
                   app:title="AppbarLayout"
                   app:titleTextColor="@color/white"
                   app:layout_scrollFlags="scroll|enterAlways"
                   />复制代码

效果以下:


3, enterAlwaysCollapsed , enterAlwaysCollapsed 是对enterAlways 的补充,当ScrollView 向下滑动的时候,滑动View(也就是设置了enterAlwaysCollapsed 的View)下滑至折叠的高度,当ScrollView 到达滑动范围的结束值的时候,滑动View剩下的部分开始滑动。这个折叠的高度是经过View的minimum height (最小高度)指定的。

补充说明:要配合scroll|enterAlways 一块儿使用

代码以下:

<android.support.v7.widget.Toolbar
                   android:layout_width="match_parent"
                   android:layout_height="200dp"
                   android:minHeight="?attr/actionBarSize"
                   app:title="AppbarLayout"
                   android:gravity="bottom"
                   android:layout_marginBottom="25dp"
                   app:titleTextColor="@color/white"
                   app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
                   />复制代码

效果图以下:

4,exitUntilCollapsed, 当ScrollView 滑出屏幕时(也就时向上滑动时),滑动View先响应滑动事件,滑动至折叠高度,也就是经过minimum height 设置的最小高度后,就固定不动了,再把滑动事件交给 scrollview 继续滑动。

代码以下:

<android.support.v7.widget.Toolbar
                   android:layout_width="match_parent"
                   android:layout_height="200dp"
                   android:minHeight="?attr/actionBarSize"
                   app:title="AppbarLayout"
                   android:gravity="bottom"
                   app:titleTextColor="@color/white"
                   app:layout_scrollFlags="scroll|exitUntilCollapsed"
                   />复制代码

效果图以下:

appbar_layout4.gif

5, snap,意思是:在滚动结束后,若是view只是部分可见,它将滑动到最近的边界。好比,若是view的底部只有25%可见,它将滚动离开屏幕,而若是底部有75%可见,它将滚动到彻底显示。

解释:可能这段话有点难懂,解释一下,就是说,好比在屏幕的顶部有个View ,高度200dp,我向上滑动40%后中止,也就 40% 滑出了屏幕,剩下的60%留在屏幕,那么这个属性就会自动将屏幕外的40% 滑回屏幕,结果的整个View都留在屏幕上,相反,若是我向上将60%的部分滑出屏幕,而后中止滑动,那么这个属性会将剩下的40% 也自动滑出屏幕,结果是整个View都在屏幕以外。这就是上面所说的滑动到最近的边界。

代码以下:

<android.support.v7.widget.Toolbar
                   android:layout_width="match_parent"
                   android:layout_height="200dp"
                   android:minHeight="?attr/actionBarSize"
                   app:title="AppbarLayout"
                   android:gravity="bottom"
                   app:titleTextColor="@color/white"
                   app:layout_scrollFlags="scroll|snap"
                   />复制代码

效果图以下:

appbar_layout5.gif

2.2 AppbarLayout 的几个重要方法

介绍一下AppbarLayout几个经常使用且重要的方法

  • addOnOffsetChangedListener 当AppbarLayout 的偏移发生改变的时候回调,也就是子View滑动。

  • getTotalScrollRange 返回AppbarLayout 全部子View的滑动范围

  • removeOnOffsetChangedListener 移除监听器

  • setExpanded (boolean expanded, boolean animate)设置AppbarLayout 是展开状态仍是折叠状态,animate 参数控制切换到新的状态时是否须要动画

  • setExpanded (boolean expanded) 设置AppbarLayout 是展开状态仍是折叠状态,默认有动画

3,CollapsingToolbarLayout 的使用

CollapsingToolbarLayout 是对Toolbar的包装而且实现了折叠app bar效果,使用时,要做为 AppbarLayout 的直接子View。CollapsingToolbarLayout有如下特性:

(1) Collapsing title(折叠标题) 当布局所有可见的时候,title 是最大的,当布局开始滑出屏幕,title 将变得愈来愈小,你能够经过setTitle(CharSequence) 来设置要显示的标题。

注意:Toolbar 和CollapsingToolbarLayout 同时设置了title时,不会显示Toolbartitle而是显示CollapsingToolbarLayout 的title,若是要显示Toolbar 的title,你可一在代码中添加以下代码:

collapsingToolbarLayout.setTitle("");复制代码

(2)Content scrim(内容纱布) 当CollapsingToolbarLayout滑动到一个肯定的阀值时将显示或者隐藏内容纱布,能够经过setContentScrim(Drawable)来设置纱布的图片。

提醒:纱布能够是图片也能够是颜色色值,若是要显示颜色,在xml 布局文件中用contentScrim属性添加,代码以下:

app:contentScrim="@color/colorPrimary"复制代码

(3)Status bar scrim(状态栏纱布) 当CollapsingToolbarLayout滑动到一个肯定的阀值时,状态栏显示或隐藏纱布,你能够经过setStatusBarScrim(Drawable)来设置纱布图片。

提醒:同内容纱布同样,状态栏纱布能够是图片也能够是一个颜色值,若是要显示颜色值,在xml 中用statusBarScrim 属性指定。

(4)Parallax scrolling children(有视差地滚动子View) 让CollapsingToolbarLayout 的子View 能够有视差的滚动,须要在xml中用 添加以下代码:

app:layout_collapseMode="parallax"复制代码

(5)Pinned position children(固定子View的位置)子View能够固定在全局空间内,这对于实现了折叠而且容许经过滚动布局来固定Toolbar 这种状况很是有用。在xml 中将collapseMode设为pin,代码以下:

app:layout_collapseMode="pin"复制代码

以上就是CollapsingToolbarLayout的一些特性,有了CollapsingToolbarLayout,配合AppbarLayout咱们就能够很轻松的作出这种折叠header的效果了,请看效果图:

Coll_toolbar1.gif

咱们再来看一下设置了Content scrim(内容纱布) 的效果:

Coll_toolbar2.gif

4,总结

以上就是对CollapsingToolbarLayout 、AppbarLayout、CoordinatorLayout 3个View的使用介绍,总的来讲,CoordinatorLayout 是协调子View的,经过Behavior指定子View动做。AppbarLayout就是一个竖直方向的LinearLayout,只不过它添加了一些材料的概念和特性,能够定制子View的滑动。CollapsingToolbarLayout 是对Toolbar 的包装,它有5个特性,Collapsing title、Content scrim、Status bar scrim、Parallax scrolling children、Pinned position children。这个三个View配合使用,能够作出一些很炫酷的UI效果。须要注意的是: AppbarLayout 要做为CoordinatorLayout 的直接子View使用,而CollapsingToolbarLayout 要做为AppbarLayout 的直接子View 使用,不然,上面将的特性是没有效果的。

5,AppbarLayout 实践之仿简书首页效果

前文讲了CollapsingToolbarLayout 、AppbarLayout、CoordinatorLayout 3个View的使用方法,最后就经过一个实例来结束本文,使用AppbarLayout 实现简书首页效果。简书首页效果以下:

jianshu.gif

实现效果以下:

jianshu_2.gif

布局文件以下:

<?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:orientation="vertical"
    android:background="@color/white"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

  <android.support.design.widget.AppBarLayout
      android:id="@+id/jianshu_appbar_layout"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:background="@color/white"
      app:elevation="0dp"
      >
    <com.bigkoo.convenientbanner.ConvenientBanner
        android:id="@+id/banner"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        app:canLoop="true"
        app:layout_scrollFlags="scroll"
        />
     <HorizontalScrollView
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         app:layout_scrollFlags="scroll"
         android:scrollbars="none"
         android:layout_marginLeft="15dp"
         android:layout_marginTop="10dp"
         >
       <LinearLayout
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:orientation="horizontal"
           >
         <TextView
             android:id="@+id/item_label1"
             android:layout_width="130dp"
             android:layout_height="50dp"
             android:textColor="@color/white"
             android:textSize="15sp"
             android:text="小说精选"
             android:gravity="center"
             android:background="@drawable/label_shape"
             />
         <TextView
             android:id="@+id/item_label2"
             android:layout_width="130dp"
             android:layout_height="50dp"
             android:textColor="@color/white"
             android:textSize="15sp"
             android:layout_marginLeft="5dp"
             android:text="摄影游记"
             android:gravity="center"
             android:background="@drawable/label_shape2"
             />
         <TextView
             android:id="@+id/item_label3"
             android:layout_width="130dp"
             android:layout_height="50dp"
             android:textColor="@color/white"
             android:textSize="15sp"
             android:text="漫画手绘"
             android:layout_marginLeft="5dp"
             android:gravity="center"
             android:background="@drawable/label_shape3"
             />
         <TextView
             android:id="@+id/item_label4"
             android:layout_width="130dp"
             android:layout_height="50dp"
             android:textColor="@color/white"
             android:textSize="15sp"
             android:text="签约做者"
             android:layout_marginLeft="5dp"
             android:gravity="center"
             android:background="@drawable/label_shape4"
             />
       </LinearLayout>
     </HorizontalScrollView>
    <EditText
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:layout_marginLeft="15dp"
        android:layout_marginTop="10dp"
        android:layout_marginRight="15dp"
        android:hint="搜索简书的内容和朋友"
        android:gravity="center"
        android:background="@drawable/edti_text_shape"
        android:layout_marginBottom="5dp"
        />
    <View
        android:id="@+id/line_divider"
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:background="@android:color/darker_gray"
        android:layout_marginBottom="10dp"
        android:visibility="gone"
        />
  </android.support.design.widget.AppBarLayout>

  <android.support.v7.widget.RecyclerView
      android:id="@+id/vertical_recyclerView"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:background="@color/white"
      app:layout_behavior="@string/appbar_scrolling_view_behavior"
      />

</android.support.design.widget.CoordinatorLayout>复制代码

Activity 代码以下:

**
 *
 * 仿简书 首页效果
 * Created by zhouwei on 16/12/8.
 */

public class JanshuActivity extends AppCompatActivity {
    private ConvenientBanner mConvenientBanner;
    private RecyclerView mRecyclerView;
    private AppBarLayout mAppBarLayout;
    private View mLine;
    private String[] images = {"http://img2.imgtn.bdimg.com/it/u=3093785514,1341050958&fm=21&gp=0.jpg",
            "http://img2.3lian.com/2014/f2/37/d/40.jpg",
            "http://d.3987.com/sqmy_131219/001.jpg",
            "http://img2.3lian.com/2014/f2/37/d/39.jpg",
            "http://www.8kmm.com/UploadFiles/2012/8/201208140920132659.jpg",
            "http://f.hiphotos.baidu.com/image/h%3D200/sign=1478eb74d5a20cf45990f9df460b4b0c/d058ccbf6c81800a5422e5fdb43533fa838b4779.jpg",
            "http://f.hiphotos.baidu.com/image/pic/item/09fa513d269759ee50f1971ab6fb43166c22dfba.jpg"
    };
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.janshu_activity_layout);
        initView();
    }

    private void initView() {
        mAppBarLayout = (AppBarLayout) findViewById(R.id.jianshu_appbar_layout);
        mLine = findViewById(R.id.line_divider);
        mConvenientBanner = (ConvenientBanner) findViewById(R.id.banner);
        mRecyclerView = (RecyclerView) findViewById(R.id.vertical_recyclerView);
        LinearLayoutManager manager = new LinearLayoutManager(this);
        manager.setOrientation(LinearLayoutManager.VERTICAL);
        mRecyclerView.setLayoutManager(manager);
        MyAdapter myAdapter = new MyAdapter();
        mRecyclerView.setAdapter(myAdapter);
        myAdapter.setData(mockData());
        myAdapter.notifyDataSetChanged();

        mConvenientBanner.setPages(new CBViewHolderCreator<NetworkImageHolderView>() {
            @Override
            public NetworkImageHolderView createHolder() {
                return new NetworkImageHolderView();
            }
        }, Arrays.asList(images));

        mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
            @Override
            public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                if(Math.abs(verticalOffset) >= mAppBarLayout.getTotalScrollRange()){
                    mLine.setVisibility(View.VISIBLE);
                }else{
                    mLine.setVisibility(View.GONE);
                }
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        mConvenientBanner.startTurning(2000);// 2s 换一张
    }

    @Override
    protected void onPause() {
        super.onPause();
        mConvenientBanner.stopTurning();
    }

    /** * 模拟首页数据 * @return */
    private List<JsEntry> mockData(){
        List<JsEntry> data = new ArrayList<>();
        JsEntry jsEntry = new JsEntry();
        jsEntry.comment = 50;
        jsEntry.award = 3;
        jsEntry.like = 460;
        jsEntry.seek = 12504;
        jsEntry.time = "15小时前";
        jsEntry.title = "这些情商的技巧,你是否是都掌握了?";
        jsEntry.authorName = "JayChou";
        jsEntry.label = "心理";
        jsEntry.cover ="http://upload-images.jianshu.io/upload_images/2785318-5306a632b46a8c27.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/1020/q/80";
        JsEntry jsEntry2 = new JsEntry();
        jsEntry2.comment = 150;
        jsEntry2.award = 33;
        jsEntry2.like = 1460;
        jsEntry2.seek = 170444;
        jsEntry2.time = "10小时前";
        jsEntry2.title = "除了阴谋,《锦绣未央》里还有哪些温情?";
        jsEntry2.authorName = "菇凉似梦";
        jsEntry2.label = "文化.艺术";
        jsEntry2.cover = "http://upload-images.jianshu.io/upload_images/2881988-b217e714eb05f88e.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/1020/q/80";
        for (int i=0;i<100;i++){
           if(i % 2 == 0){
               data.add(jsEntry);
           }else{
               data.add(jsEntry2);
           }
        }
        return data;
    }
    public static class NetworkImageHolderView implements CBPageAdapter.Holder<String>{
         private ImageView imageView;
        @Override
        public View createView(Context context) {
            imageView = new ImageView(context);
            imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
            return imageView;
        }

        @Override
        public void UpdateUI(Context context, int position, String data) {
            ImageLoader.getInstance().displayImage(data,imageView);
        }
    }

    public static class MyAdapter extends RecyclerView.Adapter{
        private List<JsEntry> mData;

        public void setData(List<JsEntry> data) {
            mData = data;
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.jianshu_label_item,null));
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            MyViewHolder viewHolder = (MyViewHolder) holder;
            JsEntry jsEntry = mData.get(position);
            viewHolder.title.setText(jsEntry.title);
            viewHolder.name.setText(jsEntry.authorName);
            viewHolder.label.setText(jsEntry.label);
            viewHolder.time.setText(jsEntry.time);
            ImageLoader.getInstance().displayImage(jsEntry.cover,viewHolder.cover);
            viewHolder.comment.setText(String.format(viewHolder.comment.getContext().getResources().getString(R.string.js_comment),jsEntry.seek,jsEntry.comment,jsEntry.like,jsEntry.award));
        }

        @Override
        public int getItemCount() {
            return mData == null ? 0:mData.size();
        }
    }

    public static class MyViewHolder extends RecyclerView.ViewHolder{
        private TextView title;
        private TextView time;
        private TextView comment;
        private TextView label;
        private TextView name;
        private ImageView cover;
        public MyViewHolder(View itemView) {
            super(itemView);
            title = (TextView) itemView.findViewById(R.id.item_content);
            time = (TextView) itemView.findViewById(R.id.publish_time);
            comment = (TextView) itemView.findViewById(R.id.js_comment);
            label = (TextView) itemView.findViewById(R.id.js_label);
            name = (TextView) itemView.findViewById(R.id.author_name);
            cover = (ImageView) itemView.findViewById(R.id.cover);
        }
    }
}复制代码

Demo 源码请看GithubMaterialDesignSamples

好了,以上就是关于AppbarLayout 、CollapsingToolbarLayout、CollapsingToolbarLayout的使用介绍,若有什么问题,欢迎评论区指正,Good night。