12(下) 最佳的UI體驗(yàn)---Material Design實(shí)戰(zhàn)

本章的主要的知識(shí)點(diǎn):

  • 卡片式布局
    1. CardView
    2. AppBarLayout
  • 下拉刷新
  • 可折疊式標(biāo)題欄
    1. CollapsingToolbarLayout
    2. 充分利用系統(tǒng)狀態(tài)欄空間
高冷女.jpg

12.5 卡片式布局

卡片式布局也是Material Design中提出的一個(gè)新的概念,它可以讓頁(yè)面中的元素看起來(lái)就像在卡片中一樣邪狞,并且還能擁有圓角和投影。

12.5.1 CardView

CardView是用于實(shí)現(xiàn)卡片式布局效果的重要控件雪猪,有appcompat-v7庫(kù)提供乏奥,實(shí)際上,CardView也是一個(gè)FrameLayout,只是額外提供了圓角和陰影等效果贾铝,看上去會(huì)有立體的感覺(jué)。

<android.support.v7.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:cardCornerRadius="4dp"
    app:elevation="5dp">
    
    <TextView
        android:id="@+id/info_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</android.support.v7.widget.CardView>

這里定義了一個(gè)CardView布局,我們可以通過(guò)app:cardCornerRadius屬性指定卡片圓角的弧度垢揩,數(shù)值越大玖绿,圓角的弧度也越大。另外還可以通過(guò)app:elevation屬性指定卡片的高度叁巨,高度值越大斑匪,投影范圍也越大,但是投影效果越淡锋勺,高度值越小蚀瘸,投影范圍也越小揪胃,但是投影效果越濃响牛。這一點(diǎn)和FloatingActionButton是一致的碎乃。

添加依賴

    compile 'com.android.support:cardview-v7:25.1.1'
    compile 'com.android.support:recyclerview-v7:25.1.1'
    compile 'com.github.bumptech.glide:glide:3.7.0'

這里添加了一個(gè)Glide庫(kù)的依賴族阅。Glide是一個(gè)超級(jí)強(qiáng)大的圖片加載庫(kù)当宴,它不僅可以用于加載本地圖片铭若,還可以加載網(wǎng)絡(luò)圖片冰沙,GIF圖片哑芹,甚至是本地視頻布近。最重要的是垫释,Glide的用法非常簡(jiǎn)單,只需一行代碼就能輕松實(shí)現(xiàn)復(fù)雜的圖片加載功能撑瞧。

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
        </android.support.v7.widget.Toolbar>
        
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            
        </android.support.v7.widget.RecyclerView>

        <android.support.design.widget.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:layout_margin="16dp"
            android:src="@drawable/done"
            app:elevation="8dp"/>
    </android.support.design.widget.CoordinatorLayout>

    ``

</android.support.v4.widget.DrawerLayout>

這里我們?cè)?code>CoordinatorLayout中添加了一個(gè)RecyclerView,給它指定了一個(gè)id棵譬,然后將寬度和高度都設(shè)置為match_parent,這樣RecyclerView也就占滿了整個(gè)布局的空間。

定義一個(gè)實(shí)體類Fruit

public class Fruit
{
    private String name;
    
    private int imageId;

    public Fruit(String name, int imageId)
    {
        this.name = name;
        this.imageId = imageId;
    }

    public String getName()
    {
        return name;
    }

    public int getImageId()
    {
        return imageId;
    }
}

Fruit類中只有兩個(gè)字段预伺,name表示水果的名字订咸,imageId表示水果對(duì)應(yīng)圖片的資源id
新建fruit_item.xml

<android.support.v7.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:cardCornerRadius="4dp"
    android:layout_margin="5dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/fruit_image"
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:scaleType="centerCrop"/>

        <TextView
            android:id="@+id/fruit_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_margin="5dp"
            android:textSize="16sp"/>

    </LinearLayout>

</android.support.v7.widget.CardView>

這里使用了CardView來(lái)作為子項(xiàng)的最外層布局酬诀,從而使得RecyclerView中的每個(gè)元素都是在卡片當(dāng)中的脏嚷。CardView由于是一個(gè)FrameLayout,因此它沒(méi)有什么方便的定位方式瞒御,這里我們只好在CardView中再嵌套一個(gè)LinearLayout父叙,然后在LinearLayout中放置具體的內(nèi)容。

注意在ImageView中我們使用了一個(gè)scaleType屬性肴裙,這個(gè)屬性可以指定圖片的縮放模式趾唱。由于各張水果圖片的長(zhǎng)寬比例可能不一致,為了讓所有的圖片都能填充滿整個(gè)ImageView蜻懦,這里使用了centerCrop模式甜癞,它可以讓圖片保持原有比例填充滿ImageView,并將超出屏幕的部分裁減掉宛乃。

新建FruitAdapter類悠咱,讓這個(gè)類繼承自RecyclerView.Adapter,并將泛型指定為FruitAdapter.ViewHolder

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder>
{

    private Context mContext;
    
    private List<Fruit> mFruitList;

    public FruitAdapter(Context context, List<Fruit> mFruitList)
    {
        this.mContext = context;
        this.mFruitList = mFruitList;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
    {
        if (mContext != null)
        {
            mContext = parent.getContext();
        }
        View view = LayoutInflater.from(mContext).inflate(R.layout.fruit_item,parent,false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position)
    {
        Fruit fruit = mFruitList.get(position);
        holder.fruitName.setText(fruit.getName());
        Glide.with(mContext).load(fruit.getImageId()).into(holder.fruitImage);
    }

    @Override
    public int getItemCount()
    {
        return mFruitList.size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder
    {
        CardView cardView;
        ImageView fruitImage;
        TextView fruitName;
        
        public ViewHolder(View itemView)
        {
            super(itemView);
            cardView = (CardView) itemView;
            fruitImage = (ImageView) itemView.findViewById(R.id.fruit_image);
            fruitName = (TextView) itemView.findViewById(R.id.fruit_name);
        }
    }
}

首先調(diào)用Glide.with()方法并傳入一個(gè)Context蒸辆,ActivityFragment參數(shù),然后調(diào)用load()方法去加載圖片乔煞,可以是一個(gè)URL地址吁朦,也可以是一個(gè)本地路徑,或則是一個(gè)資源id渡贾,最后調(diào)用into()方法將圖片設(shè)置到具體一個(gè)ImageView中就可以了逗宜。

這次我從網(wǎng)上找的這些水果圖片像素都非常高,如果不進(jìn)行壓縮就直接展示的話空骚,很容易就會(huì)引起內(nèi)存溢出纺讲。而使用Glide就完全不需要擔(dān)心這回事,因?yàn)?code>Glide在內(nèi)部做了許多非常復(fù)雜的邏輯操作囤屹,其中就包括了圖片壓縮熬甚,我們只需要安心按照Glide的標(biāo)準(zhǔn)用法去加載圖片就可以了。

public class MainActivity extends AppCompatActivity {
    private Fruit[] fruits = {
            new Fruit("Apple",R.drawable.apple),
            new Fruit("Banana",R.drawable.banana),
            new Fruit("Orange",R.drawable.orange),
            new Fruit("Watermelon",R.drawable.watermelon),
            new Fruit("Pear",R.drawable.pear),
            new Fruit("Grape",R.drawable.grape),
            new Fruit("Pineapple",R.drawable.pineapple),
            new Fruit("Strawberry",R.drawable.strawberry),
            new Fruit("Cherry",R.drawable.cherry),
            new Fruit("Mango",R.drawable.mango)};

    private List<Fruit> fruitList = new ArrayList<>();

    private FruitAdapter adapter;

    private RecyclerView recyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        ``
        initFruits();
        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        GridLayoutManager layoutManager = new GridLayoutManager(this,2);
        recyclerView.setLayoutManager(layoutManager);
        adapter = new FruitAdapter(this,fruitList);
        recyclerView.setAdapter(adapter);
    }

    public void initFruits()
    {
        fruitList.clear();
        for (int i = 0; i < 50; i++)
        {
            Random random = new Random();
            int index = random.nextInt(fruits.length);
            fruitList.add(fruits[index]);
        }
    }
}

MainActivity中我們首先定義了一個(gè)數(shù)組肋坚,數(shù)組里面存放了很多個(gè)Fruit的實(shí)例乡括,每個(gè)實(shí)例都代表著一種水果。然后在initFruit()方法中智厌,先是清空一下fruitList中的數(shù)據(jù)诲泌,接著使用一個(gè)隨機(jī)函數(shù),從剛才定義的Fruit數(shù)組中隨機(jī)挑選一個(gè)水果放入fruitList當(dāng)中铣鹏,這樣每次打開程序看到的水果數(shù)據(jù)都是不同的敷扫。

卡片式布局效果

仔細(xì)觀察可以看到ToolbarRecyclerView擋住了,這就需要借助AppBarLayout

12.5.2 AppBarLayout

由于RecyclerViewToolbar都是放置在CoordinatorLayout中的诚卸,CoordinatorLayout就是一個(gè)加強(qiáng)版的FrameLayout葵第。那么FrameLayout中的所有控件在不進(jìn)行明確定位的情況下,默認(rèn)都會(huì)排放在左上角合溺,從而也就產(chǎn)生了遮擋的現(xiàn)象卒密。

傳統(tǒng)情況下使用偏移是唯一的解決辦法,則讓RecyclerView向下偏移一個(gè)Toolbar的高度棠赛,從而保證到不會(huì)遮擋到Toolbar哮奇。

這里我準(zhǔn)備使用Design Support庫(kù)中提供的另外一個(gè)工具---AppBarLayoutAppBarLayout實(shí)際上是一個(gè)垂直方向上的LinearLayout恭朗,它在內(nèi)部做了很多滾動(dòng)事件的封裝,并應(yīng)用了一些Material Design的設(shè)計(jì)理念依疼。

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        
        <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="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
            </android.support.v7.widget.Toolbar>
            
        </android.support.design.widget.AppBarLayout>
        
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

        </android.support.v7.widget.RecyclerView>

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

我們首先定義了一個(gè)AppBarLayout痰腮,并將Toolbar放置在了AppBarLayout里面,然后在RecyclerView中使用app:layout_behavior屬性指定了一個(gè)布局行為律罢,其中appbar_scrolling_view_behavior這個(gè)字符串也是由Design Support庫(kù)提供的膀值。

事實(shí)上棍丐,當(dāng)RecyclerView滾動(dòng)的時(shí)候就已經(jīng)將滾動(dòng)事件都通知給AppStoreBarLayout了只是我們還沒(méi)進(jìn)行處理而已。

當(dāng)AppBarLayout接收到滾動(dòng)事件的時(shí)候沧踏,它內(nèi)部的子控件是可以指定如何去影響這些事件的歌逢,通過(guò)app:layout_scrollFlags屬性就能實(shí)現(xiàn)。

<android.support.v7.widget.Toolbar
     android:id="@+id/toolbar"
     android:layout_width="match_parent"
     android:layout_height="?attr/actionBarSize"
     android:background="?attr/colorPrimary"
     android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
     app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
     app:layout_scrollFlags="scroll|enterAlways|snap">
</android.support.v7.widget.Toolbar>

這里在Toolbar中添加了一個(gè)app:layout_scrollFlags屬性翘狱,并將這個(gè)屬性的值指定成了scroll|enterAlways|snap秘案。其中,scroll表示當(dāng)RecyclerView向上滾動(dòng)的時(shí)候潦匈,Toolbar會(huì)跟著一起向上滾動(dòng)并實(shí)現(xiàn)隱藏阱高;enterAlways表示當(dāng)RecyclerView向下滾動(dòng)的時(shí)候,Toolbar會(huì)跟著一起向下滾動(dòng)并重新現(xiàn)實(shí)茬缩,snap表示Toolbar還沒(méi)有完全隱藏或顯示的時(shí)候赤惊,會(huì)根據(jù)當(dāng)前滾動(dòng)的距離,自動(dòng)選擇是隱藏還是顯示凰锡。

12.6 下拉刷新

谷歌為了讓Android的下拉刷新風(fēng)格能有一個(gè)統(tǒng)一的標(biāo)準(zhǔn)未舟,于是在Material Design中制定了一個(gè)官方的設(shè)計(jì)規(guī)范。

SwipeRefreshLayout就是用于實(shí)現(xiàn)下拉刷新功能的核心類掂为,它是由support-v4庫(kù)提供的裕膀。我們把想要實(shí)現(xiàn)下拉刷新功能的控件放置到SwipeRefreshLayout中,就可以迅速讓這個(gè)控件支持下拉刷新菩掏。

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

        ``

        <android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/swipe_refresh"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <android.support.v7.widget.RecyclerView
                android:id="@+id/recycler_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

            </android.support.v7.widget.RecyclerView>

        </android.support.v4.widget.SwipeRefreshLayout>

        ``

我們?cè)?code>RecyclerView的外面又嵌套了一層SwipeRefreshLayout魂角,這樣RecyclerView就自動(dòng)擁有下拉刷新功能了。另外需要注意智绸,由于RecyclerView現(xiàn)在變成了SwipeRefreshLayout的子控件野揪,因此之前使用app:layout_behavior聲明的布局行為現(xiàn)在也要移到SwipeRefreshLayout中才行。

雖然RecyclerView已經(jīng)支持下拉刷新功能了瞧栗,但是我們還要在代碼中處理具體的刷新邏輯才行斯稳。

public class MainActivity extends AppCompatActivity
{
    ······
    private SwipeRefreshLayout swipeRefreshLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);

       swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
        swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary);
        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener()
        {
            @Override
            public void onRefresh()
            {
                refreshFruits();
            }
        });
    }
   
   ······

    private void refreshFruits()
    {
        new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                try
                {
                    Thread.sleep(2000);
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                runOnUiThread(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        initFruits();;
                        adapter.notifyDataSetChanged();
                        swipeRefreshLayout.setRefreshing(false);
                    }
                });
            }
        }).start();
    }

}

首先通過(guò)findViewById()方法拿到SwipeRefreshLayout的實(shí)例,然后調(diào)用setColorSchemeResources()方法來(lái)設(shè)置下拉刷新進(jìn)度條的顏色迹恐,這里我們使用主題中的colorPrimary作為進(jìn)度條的顏色了挣惰。接著調(diào)用setOnRefreshListener()方法來(lái)設(shè)置一個(gè)下拉刷新的監(jiān)聽器,當(dāng)觸發(fā)了下拉刷新操作的時(shí)候就會(huì)回調(diào)這個(gè)監(jiān)聽器的onRefresh()方法殴边,然后我們?cè)谶@里去處理具體的刷新邏輯就行了憎茂。

通常情況下,onRefresh()方法中應(yīng)該是去網(wǎng)絡(luò)上請(qǐng)求最新的數(shù)據(jù)锤岸,然后再將這些數(shù)據(jù)展示出來(lái)竖幔。refreshFruits()方法中先是開啟了一個(gè)線程,然后將線程沉睡兩秒鐘是偷。之所以這麼做拳氢,是因?yàn)楸镜厮⑿虏僮魉俣确浅募逞?欤绻粚⒕€程沉睡的話馋评,刷新立刻就結(jié)束了放接,從而看不到刷新的過(guò)程。沉睡結(jié)束之后留特,這里使用了runOnUiThread()方法將線程切換回主線程纠脾,然后調(diào)用initFruits()方法重新生成數(shù)據(jù),接著再調(diào)用FruitAdapternotifyDataSetChanged()方法通知數(shù)據(jù)發(fā)生了變化磕秤,最后調(diào)用SwipeRefreshLayoutsetRefershing()方法并傳入false乳乌,用于表示刷新事件結(jié)束,并隱藏刷新進(jìn)度條市咆。

12.7 可折疊式標(biāo)題欄

雖說(shuō)我們現(xiàn)在的標(biāo)題欄是使用Toolbar來(lái)編寫的汉操,不過(guò)它看上去和傳統(tǒng)的ActionBar其實(shí)沒(méi)什么兩樣,只不過(guò)可以響應(yīng)RecyclerView的滾動(dòng)事件來(lái)進(jìn)行隱藏和顯示蒙兰。而Material Design中并沒(méi)有限定標(biāo)題欄必須是長(zhǎng)這樣子的磷瘤,事實(shí)上,我們可以根據(jù)自己的喜好隨意定制標(biāo)題欄的樣式搜变。

12.7.1 CollapsingToolbarLayout

CollapsingToolbarLayout是一個(gè)作用于Toolbar基礎(chǔ)之上的布局采缚,它也是由Design Support庫(kù)提供的。CollapsingToolbarLayout可以讓Toolbar的效果變得更加豐富挠他,不僅僅是展示一個(gè)標(biāo)題欄扳抽,而是能夠?qū)崿F(xiàn)非常華麗的效果。

CollapsingToolbarLayout是不能獨(dú)立存在的殖侵,它在設(shè)計(jì)的時(shí)候就被限定只能作為AppBarLayout的直接子布局來(lái)使用贸呢。而AppBarLayout又必須是CoordinatorLayout的子布局。

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <android.support.design.widget.AppBarLayout
        android:id="@+id/appBar"
        android:layout_width="match_parent"
        android:layout_height="250dp">
        
        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">
            
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                app:layout_collapseMode="parallax"/>
            
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin">
                
            </android.support.v7.widget.Toolbar>
            
        </android.support.design.widget.CollapsingToolbarLayout>
        
    </android.support.design.widget.AppBarLayout>
    
    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">
        
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            
            <android.support.v7.widget.CardView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="15dp"
                android:layout_marginLeft="15dp"
                android:layout_marginRight="15dp"
                android:layout_marginTop="35dp"
                app:cardCornerRadius="4dp">
                
                <TextView
                    android:id="@+id/fruit_content_text"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_margin="10dp"/>
            </android.support.v7.widget.CardView>
        </LinearLayout>
        
    </android.support.v4.widget.NestedScrollView>
    
    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:src="@drawable/done"
        app:layout_anchor="@id/appBar"
        app:layout_anchorGravity="bottom|end"/>

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

1.我們使用了新的布局CollapsingToolbarLayout拢军,android:theme屬性指定了一個(gè)ThemeOverlay.AppCompat.Dark.ActionBar的主題楞陷,app:contentScrim屬性用于指定CollapsingToolbarLayout在趨于折疊狀態(tài)以及折疊之后的背景色,其實(shí)CollapsingToolbarLayout在折疊之后就是一個(gè)普通的Toolbar茉唉,那么背景色肯定應(yīng)該是colorPrimary了固蛾。app:layout_scrollFlags屬性我們也是見過(guò)的,只不過(guò)之前是給Toolbar指定的度陆,現(xiàn)在也移到外面來(lái)了艾凯。其中scroll表示CollapsingToolbarLayout會(huì)隨著水果內(nèi)容詳情的滾動(dòng)一起滾動(dòng),exitUntilCollapsed表示當(dāng)CollapsingToolbarLayout隨著滾動(dòng)完成折疊之后就保留在界面上懂傀,不再移出屏幕趾诗。

2.我們?cè)贑ollapsingToolbarLayout中定義了一個(gè)ImageView和Toolbar。app:layout_collapseMode鸿竖,它用于指定當(dāng)前控件在CollapsingToolbarLayout折疊過(guò)程中的折疊模式沧竟,其中Toolbar指定成pin,表示在折疊過(guò)程中位置始終保持不變,ImageView指定成parallax,表示會(huì)在折疊的過(guò)程中產(chǎn)生一定的錯(cuò)位便宜缚忧,這種模式的視覺(jué)效果會(huì)非常好悟泵。

3.水果內(nèi)容詳情的最外層布局使用了一個(gè)NestedScrollView,注意它和AppBarLayout是平級(jí)的。我們之前學(xué)過(guò)ScrollView的用法闪水,它允許使用滾動(dòng)的方式來(lái)查看屏幕以外的數(shù)據(jù)糕非,而NestedScrollView在此基礎(chǔ)上還增加了嵌套響應(yīng)滾動(dòng)事件的功能。由于CoordinatorLayout本身已經(jīng)可以響應(yīng)滾動(dòng)事件了球榆,因此我們?cè)谒膬?nèi)部就需要使用NestedScrollView或RecyclerView這樣的布局朽肥。另外,這里還通過(guò)app:layout_behavior屬性指定了一個(gè)布局行為持钉,這和之前在RecyclerView中的用法是一模一樣的衡招。

不管是ScrollView還是NestedScrollView,他們的內(nèi)部都只允許存在一個(gè)直接子布局每强。因此始腾,如果我們想要在里面放入很多東西的話,通常都會(huì)先嵌套一個(gè)LinearLayout空执,然后再在LinearLayout中放入具體的內(nèi)容就可以了浪箭。

3.需要注意的是,這里為了讓界面更加美觀辨绊,我在CardView和TextView上都加一些邊距奶栖。其中CardView的marginTop加了35dp的邊距,這是為下面要編寫的東西留出空間门坷。

4.這里加入了一個(gè)FloatingActionButton宣鄙,它和AppBarLayout以及NestedScrollView是平級(jí)的,F(xiàn)loatingActionButton中使用app:layout_anchor屬性指定了一個(gè)錨點(diǎn)拜鹤,我們將錨點(diǎn)設(shè)置為AppBarLayout框冀,這樣懸浮按鈕就會(huì)出現(xiàn)在水果標(biāo)題欄的區(qū)域內(nèi),接著又使用app:layout_anchorGravity屬性將懸浮按鈕定位在標(biāo)題欄區(qū)域的右下角敏簿。

public class FruitActivity extends AppCompatActivity
{

    public static final String FRUIT_NAME = "fruit_name";

    public static final String FRUIT_IMAGE_ID = "fruit_image_id";

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fruit);

        //獲取跳轉(zhuǎn)過(guò)來(lái)的數(shù)據(jù)
        Intent intent = getIntent();
        String fruitName = intent.getStringExtra(FRUIT_NAME);
        int fruitImageId = intent.getIntExtra(FRUIT_IMAGE_ID,0);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        CollapsingToolbarLayout collapsingToolbar = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
        ImageView fruitImageView = (ImageView) findViewById(R.id.fruit_image_view);
        TextView fruitContentText = (TextView) findViewById(R.id.fruit_content_text);
        setSupportActionBar(toolbar);
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null)
        {
            actionBar.setDisplayHomeAsUpEnabled(true);
        }
        collapsingToolbar.setTitle(fruitName);
        Glide.with(this).load(fruitImageId).into(fruitImageView);
        String fruitContent = generateFruitContent(fruitName);
        fruitContentText.setText(fruitContent);
    }

    //500個(gè)水果名去
    private String generateFruitContent(String fruitName)
    {
        StringBuilder fruitContent = new StringBuilder();
        for (int i = 0; i < 500; i++)
        {
            fruitContent.append(fruitName);
        }
        return fruitContent.toString();
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
        switch (item.getItemId())
        {
            case android.R.id.home:
                finish();
                break;
        }
        return super.onOptionsItemSelected(item);
    }
}

通過(guò)Intent獲取到傳入的水果名和水果圖片的資源id明也,然后通過(guò)findViewById()方法拿到剛才在布局文件中定義的各個(gè)控件的實(shí)例。接著就是使用了Toolbar的標(biāo)準(zhǔn)用法惯裕,將它作為ActionBar顯示温数,并啟用HomeAsUp按鈕。HomeAsUp按鈕的默認(rèn)圖標(biāo)就是一個(gè)返回箭頭蜻势。

接下來(lái)開始填充界面上的內(nèi)容撑刺,調(diào)用CollapsingToolbarLayout的setTitle()方法將水果名設(shè)置成當(dāng)前界面的標(biāo)題,然后使用Glide加載傳入的水果圖片握玛,并設(shè)置到標(biāo)題欄的ImageView上面够傍。使用了一個(gè)generateFruitContent()方法將水果名循環(huán)拼接500次甫菠,從而生成了一個(gè)比較長(zhǎng)的字符串,將它設(shè)置到了TextView上面冕屯。

我們?cè)趏nOptionsItemSelected()方法中處理了HomeAsUp按鈕的點(diǎn)擊事件寂诱,當(dāng)點(diǎn)擊了這個(gè)按鈕時(shí),就調(diào)用finish()方法關(guān)閉當(dāng)前的活動(dòng)安聘,從而返回上一個(gè)活動(dòng)痰洒。

@Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
    {
        if (mContext != null)
        {
            mContext = parent.getContext();
        }
        View view = LayoutInflater.from(mContext).inflate(R.layout.fruit_item,parent,false);

        final ViewHolder holder = new ViewHolder(view);
        holder.cardView.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                int position = holder.getAdapterPosition();
                Fruit fruit = mFruitList.get(position);
                Intent intent = new Intent(mContext,FruitActivity.class);
                intent.putExtra(FruitActivity.FRUIT_NAME,fruit.getName());
                intent.putExtra(FruitActivity.FRUIT_IMAGE_ID,fruit.getImageId());
                mContext.startActivity(intent);
            }
        });

        return holder;
    }

這里我們給CardView注冊(cè)了一個(gè)點(diǎn)擊事件監(jiān)聽器,然后在點(diǎn)擊事件中獲取當(dāng)前點(diǎn)擊項(xiàng)的水果名和水果圖片資源id,把他們傳入到Intent中浴韭,最后調(diào)用startActivity()方法啟動(dòng)FruitActivity丘喻。

水果的詳情展示界面

這個(gè)界面上的內(nèi)容分為3部分,水果標(biāo)題欄念颈,水果內(nèi)容詳情和懸浮按鈕泉粉。Toolbar和水果背景圖完美地融合到了一起,既保證了圖片的展示空間榴芳,又不影響Toolbar的任何功能搀继,那個(gè)向左的箭頭就是用來(lái)返回上一活動(dòng)的。

12.7.2 充分利用系統(tǒng)狀態(tài)欄空間

你會(huì)發(fā)現(xiàn)水果的背景圖片和系統(tǒng)的狀態(tài)欄總有一些不搭的感覺(jué)翠语。

只不過(guò)很可惜的是叽躯,在Android5.0系統(tǒng)之前,我們是無(wú)法對(duì)狀態(tài)欄的背景或顏色進(jìn)行操作的肌括,那個(gè)時(shí)候也還沒(méi)有Material Design的概念点骑。但是Android5.0及之后的系統(tǒng)都是支持這個(gè)功能的,在Android5.0及之后的系統(tǒng)中谍夭,使用背景圖和狀態(tài)欄融合的模式黑滴,在之前的系統(tǒng)中使用普通的模式。

想要讓背景圖能夠和系統(tǒng)狀態(tài)欄融合紧索,需要借助android:fitsSystemWindows這個(gè)屬性來(lái)實(shí)現(xiàn)袁辈。在CoordinatorLayout,AppBarLayout,CollapsingToolbarLayout這種嵌套結(jié)構(gòu)的布局中珠漂,將控件的android:fitsSystemWindows屬性指定成true晚缩,就表示該控件會(huì)出現(xiàn)在系統(tǒng)狀態(tài)欄里。對(duì)應(yīng)到我們的程序媳危,那就是水果標(biāo)題欄中的ImageView應(yīng)該設(shè)置這個(gè)屬性了荞彼。不過(guò)只給ImageView設(shè)置這個(gè)屬性是沒(méi)有用的,我們必須將ImageView布局結(jié)構(gòu)中的所有父布局都設(shè)置上這個(gè)屬性才可以待笑。

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context="com.example.materialtest.FruitActivity"
    android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appBar"
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:fitsSystemWindows="true">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            android:fitsSystemWindows="true">

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

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

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

·················

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

但是即使我們將android:fitsSystemWindows屬性都設(shè)置好了還是沒(méi)有用的鸣皂,因?yàn)檫€必須在程序的主題中將狀態(tài)欄顏色指定成透明色才行。指定成透明色的方法很簡(jiǎn)單,在主題中將android:statusBarColor這個(gè)屬性的值指定成@android:color/transparent就可以了寞缝。但問(wèn)題在于癌压,android:statusBarColor這個(gè)屬性是從API21,也就是Android5.0系統(tǒng)開始才有的荆陆,之前的系統(tǒng)無(wú)法指定這個(gè)屬性措拇。那么,系統(tǒng)差異性的功能實(shí)現(xiàn)就要從這里開始了慎宾。

右擊res目錄--->New--->Directory,創(chuàng)建一個(gè)values-v21目錄浅悉,然后右擊values-v21目錄--->New--->Values resource file,創(chuàng)建一個(gè)styles.xml文件趟据。

<resources>

    <style name="FruitActivityTheme" parent="AppTheme">
        <item name="android:statusBarColor">@android:color/transparent</item>
    </style>

</resources>

這里我們定義了一個(gè)FruitActivityTheme主題,它是專門給FruitActivity使用的术健。FruitActivityTheme的parent主題是AppTheme汹碱,也就是說(shuō),它繼承了AppTheme中的所有特性荞估。然后我們?cè)贔ruitActivityTheme中將狀態(tài)欄中的顏色指定成透明色咳促,由于values-v21目錄是只有Android5.0及以上的系統(tǒng)才回去讀取的,因此這么生命是沒(méi)有問(wèn)題的勘伺。

但是Android5.0之前的系統(tǒng)卻無(wú)法識(shí)別FruitActivityTheme這個(gè)主題跪腹,因此我們還需要對(duì)values/styles.xml文件進(jìn)行修改。

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <style name="FruitActivityTheme" parent="AppTheme">

    </style>

</resources>

可以看到飞醉,這里也定義了一個(gè)FruitActivityTheme主題冲茸,并且parent主題也是AppTheme,但是它的內(nèi)部是空的缅帘。因?yàn)锳ndroid5.0之前的系統(tǒng)無(wú)法指定狀態(tài)欄的顏色轴术,因此這里什么都不用做就可以了。

<activity
            android:name=".FruitActivity"
            android:theme="@style/FruitActivityTheme">
        </activity>

這里使用android:theme屬性單獨(dú)給FruitActivity指定了FruitActivityTheme這個(gè)主題钦无,這樣我們就大功告成了逗栽。

效果圖
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市失暂,隨后出現(xiàn)的幾起案子彼宠,更是在濱河造成了極大的恐慌,老刑警劉巖弟塞,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兵志,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡宣肚,警方通過(guò)查閱死者的電腦和手機(jī)想罕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人按价,你說(shuō)我怎么就攤上這事惭适。” “怎么了楼镐?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵癞志,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我框产,道長(zhǎng)凄杯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任秉宿,我火速辦了婚禮戒突,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘描睦。我一直安慰自己膊存,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布忱叭。 她就那樣靜靜地躺著隔崎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪韵丑。 梳的紋絲不亂的頭發(fā)上爵卒,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音撵彻,去河邊找鬼技潘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛千康,可吹牛的內(nèi)容都是我干的享幽。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼拾弃,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼值桩!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起豪椿,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤奔坟,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后搭盾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體咳秉,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年鸯隅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了澜建。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片向挖。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖炕舵,靈堂內(nèi)的尸體忽然破棺而出何之,到底是詐尸還是另有隱情,我是刑警寧澤咽筋,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布溶推,位于F島的核電站,受9級(jí)特大地震影響奸攻,放射性物質(zhì)發(fā)生泄漏蒜危。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一睹耐、第九天 我趴在偏房一處隱蔽的房頂上張望辐赞。 院中可真熱鬧,春花似錦疏橄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至表牢,卻和暖如春窄绒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背崔兴。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工彰导, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人敲茄。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓位谋,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親堰燎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子掏父,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容