1. 程式人生 > >Material Design之 AppbarLayout 開發實踐總結

Material Design之 AppbarLayout 開發實踐總結

原文連結:https://www.jianshu.com/p/ac56f11e7ce1

前一篇文章是Material Design 系列的第一篇文章,講了Toolbar 的使用,《Material Design 之 Toolbar 開發實踐總結》,還沒看過的同學可以去看一下,這篇是Material Design 系列的第二篇文章,這次我們講AppbarLayout。一說到AppbarLayout,那麼我們必然會說到另外兩個與AppbarLayout經常一起使用的View,那就是 CoordinatorLayout和CollapsingToolbarLayout。這三個View在一起使用的功能非常強大,可以實現很多炫酷的UI和動畫效果。本篇文章將分別介紹三個view的一些功能和屬性,最將三個View結合在一起使用實現炫酷的UI效果。

正文

1, CoordinatorLayout

第一次接觸CoordinatorLayout 你可能有這些疑問,CoordinatorLayout 到底是個什麼玩意兒呢?它到底能幫我們做什麼?我們要了解它,肯定是先看官方文件了。文件的第一句話就非常醒目:** CoordinatorLayout is a super-powered FrameLayout **,非常明瞭,CoordinatorLayout 繼承於ViewGroup,它就是一個超級強大Framelayout。CoordinatorLayout的作用就是協調子View。它有兩種使用場景:

1,作為 一個應用頂層的裝飾佈局,也就是一個Activity Layout 的最外一層佈局。
2,As a container for a specific interaction with one or more child views,作為一個或多個有特定響應動作的容器。

CoordinatorLayout 可以協調子View,而這些子View 的具體響應動作是通過 behavior 來指定的。如果你有特定的需求,你就需要自己定義一個特定的 Behavior,Google 也給我們定義了一些常用的Behavior,如後面要用的到的 appbar_scrolling_view_behavior ,用於協調 AppbarLayout 與 ScrollView 滑動的Behavior,如下:

<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的使用如此簡單,需要的可以去了解一下。

2,AppbarLayout 的使用

AppbarLayout繼承自LinearLayout,它就是一個垂直方向的LinearLayout,在LinearLayout的基礎上添加了一些材料設計的概念和特性,即滑動手勢。它可以讓你定製在某個可滑動的View(如:ScrollView ,ListView ,RecyclerView 等)滑動手勢發生改變時,內部的子View 該做什麼動作。子View應該提供滑動時他們期望的響應的動作Behavior,通過setScrollFlags(int),或者xml 中使用屬性:

app:layout_scrollFlags

注意:AppbarLayout 嚴重依賴於CoordinatorLayout,必須用於CoordinatorLayout 的直接子View,如果你將AppbarLayout 放在其他的ViewGroup 裡面,那麼它的這些功能是無效的。

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>

效果如下:

appbar_layout1.gif

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"
                   />

效果如下:

appbar_layout2.gif

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"
                   />

效果圖如下:

 

appbar_layout3.gif

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 程式碼如下:
```java
**
 *
 * 仿簡書 首頁效果
 * 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**