1. 程式人生 > >Android Material Design風格基本使用(實現簡易新聞APP)

Android Material Design風格基本使用(實現簡易新聞APP)

前言:Google推出Material Design已經很久了,我在很久之前就開始使用Material Design風格編寫APP,感覺還是挺好看的,風格比較統一。今天特意寫一個簡單的Demo來介紹一下Material Design(簡稱MD)的基本使用,由於Demo很簡單,編碼有點隨意,大佬勿噴!

首先上兩張Demo效果圖:

             

1、DrawerLayout(滑動選單-主體佈局)

首先介紹第一個控制元件就是DrawerLayout,是Material Design最基礎的控制元件,滑動選單就是將一些選單選項隱藏起來,而不是放在主螢幕上,然後可以通過滑動的方式將選單顯示出來。DrawerLayout的用法很簡單,首先它是一個佈局,允許在佈局中放入兩個直接子控制元件,第一個控制元件是主螢幕中顯示的內容,第二個則是隱藏的控制元件通過滑動來顯示內容,佈局檔案如下:

<?xml version="1.0" encoding="utf-8"?>
<!--定義DrawerLayout控制元件-->
<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/dl_activity_main"
    android:layout_width="match_parent"
android:layout_height="match_parent">

<!--定義第一個控制元件,直接顯示在主螢幕-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="顯示選單one"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="顯示選單two"/>
    </LinearLayout>

<!--定義第二個控制元件,隱藏選單,通過滑動顯示-->
<!—引數:android:layout_gravity="start" 指定滑動開啟的方向,start:從左向右 該屬性必須指定-->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"
         android:background="#669966"
        android:textColor="#FFFFFF"
        android:text="滑動選單,預設隱藏"/>

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

執行效果如下圖所示:

                                          

從效果圖可以發現,這樣看起來不是很明顯可以讓別人感受到你的應用有隱藏的滑動選單,所以,我們可以新增一個按鈕來提示使用者有隱藏的選單,當點選按鈕的時候彈出隱藏選單來引導使用者使用,程式碼實現如下所示:

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.dl_activity_main)
    DrawerLayout drawerLayout;

    private Unbinder unbinder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //獲取ActionBar物件
        ActionBar actionBar = getSupportActionBar();
        if(actionBar !=  null){
            //設定按鈕
            actionBar.setDisplayHomeAsUpEnabled(true);
            //更換按鈕圖示(預設是返回的箭頭)
            actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
        }

        unbinder = ButterKnife.bind(this);
    }
    //重寫該方法,箭頭選單按鈕的點選事件
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
    //該按鈕的Id已經在android的R檔案中定義,為 :android.R.id.home
        if(item.getItemId() == android.R.id.home){
            //彈出DrawerLayout選單,引數為彈出的方式
            drawerLayout.openDrawer(GravityCompat.START);
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbinder.unbind();
    }
}

  執行效果如下圖所示:

                         

現在的效果就是,點選按鈕,彈出隱藏選單。

2、Toolbar

從上面的執行效果中我們可以看出,當我們的滑動選單彈出的時候,ActionBar是沒有任何反應的,依然擋住了頂部的部分顯示空間,因此ActionBar十分的不靈活,故使用Toolbar來替換掉ActionBar,這也是Google希望的,Toolbar不僅擁有ActionBar的全部功能,而且還能更好的支援Material Design效果,現在我們更改一下佈局檔案,替換掉系統的ActionBar,在此之前,我們需要更改應用的預設主題,將主題指定為NoActionBar的,即去掉ActionBar,步驟:1.開啟AndroidManifest.xml檔案,2.開啟AppTheme主題資原始檔,3.修改AppTheme檔案,檔案預設配置如下:

<resources>

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

</resources>

修改為以下格式,指定為NoActionBar,引數解釋:colorPrimary標題欄顏色; colorPrimaryDark頂部狀態列顏色;colorAccent介面中其他元素的顏色,該顏色一般與上面的兩種顏色形成高反差。

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

</resources>

現在ActionBar已經被隱藏了,需要我們手動在佈局檔案中新增Toolbar,故現在的佈局檔案為:

<?xml version="1.0" encoding="utf-8"?>

<!--定義DrawerLayout控制元件-->
<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/dl_activity_main"
    android:layout_width="match_parent"
android:layout_height="match_parent">

<!--定義第一個控制元件,直接顯示在主螢幕-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
<!—自己定義的toolbar,引數解釋:background 背景指定為標題欄色-->
<!—引數解釋:android:theme將主題指定為深色,文字顯示為淺色-->
<!—引數解釋:app:theme將彈出選單主題指定為淺色,美觀,深色選單太醜-->
<android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@color/colorPrimary"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
        </android.support.v7.widget.Toolbar>
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="顯示選單one"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="顯示選單two"/>
    </LinearLayout>

<!--定義第二個控制元件,隱藏選單,通過滑動顯示-->
<!—引數:android:layout_gravity="start" 指定滑動開啟的方向,start:從左向右 該屬性必須指定-->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"
         android:background="#669966"
        android:textColor="#FFFFFF"
        android:text="滑動選單,預設隱藏"/>

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

執行效果如下圖所示:

            

現在,由於Toolbar過於單調,所以我們現在為Toolbar新增一些功能按鈕,簡單的分為三個步驟來實現為Toolbar新增選單按鈕,第一步:在res資料夾下建立一個menu的資料夾並在menu資料夾下建立toolbar.xml檔案,在toolbar.xml檔案中配置按鈕圖示文字顯示方式等資訊,配置如下所示:

<?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
<!—指定id-->
        android:id="@+id/backup"
<!—指定圖示-->
        android:icon="@drawable/ic_backup"
<!—指定標題-->
        android:title="備份"
<!—指定顯示風格 always:永遠顯示在toolbar中,空間不夠則不顯示;ifRoom:空間足夠時-->
<!—顯示在toolbar,不夠則在選單中顯示;never:永遠顯示在選單中。標題只會選單中顯示-->
        app:showAsAction="always"/>
    <item
        android:id="@+id/setting"
        android:icon="@drawable/ic_settings"
        android:title="設定"
        app:showAsAction="never"/>
</menu>

第二步,將配置好的xml檔案載入到toolbar,同樣很簡單,在Activity中重寫onCreateOptionsMenu方法,然後將佈局檔案載入到menu中即可,程式碼如下:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    //獲取menu的注入器(Inflater)並將我們配置的toolbar檔案載入到menu中即可
        getMenuInflater().inflate(R.menu.toolbar, menu);
        return true;
    }

第三步,為選單中的按鈕新增點選事件,在Activity中重寫onOptionsItemSelected方法即可,通過ID判斷哪個按鈕被點選:

@Override
    public boolean onOptionsItemSelected(MenuItem item) {
        //彈出左側滑動選單
if(item.getItemId() == android.R.id.home){
            drawerLayout.openDrawer(GravityCompat.START);
        //給指定按鈕新增點選事件
        }else if(item.getItemId() == R.id.backup){ 
            Toast.makeText(MainActivity.this, "正在上傳中...", Toast.LENGTH_SHORT).show();
        }
        return super.onOptionsItemSelected(item);
    }

至此,我們就完成了Toolbar的選單按鈕的新增,完成後的效果如下圖所示:

          

3、NavigationView(滑動選單-選單佈局)

目前我們已經可以實現簡單的滑動選單效果,但是效果還比較的簡陋,不過我們可以通過Google提供的NavigationView控制元件來為我們的滑動選單的選單佈局,將會達到更好的效果,不過在使用NavigationView之前,需要我們將NavigationView控制元件所在的庫新增進來,在app/build.gradle檔案中新增如下內容:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:26.+'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
    // butterknife
    compile 'com.jakewharton:butterknife:8.8.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
    // circleimageview依賴庫(圖片圓形化開源專案)
compile 'de.hdodenhof:circleimageview:2.1.0' 
// NavigationView依賴庫
    compile 'com.android.support:design:24.2.1'
}

新增完依賴以後,我們就可以動手了,一般MD風格的滑動選單欄分為兩個部分,上面一部分是頭下面一部分為功能選單,因此我們需要定義兩個佈局檔案在配置頭和選單,我們先來看看如何定義選單,很簡單,在res->menu下新建一個nav_menu.xml檔案,在xml檔案中定義我們自己所需的選單項即可,定義如下:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<!—用一個group來放item,checkableBehavior設定為single表示為一個組 -->  
    <group android:checkableBehavior="single">
        <item
            android:id="@+id/friends"
            android:icon="@drawable/nav_friends"
            android:title="我的好友"/>
        <item
            android:id="@+id/mail"
            android:icon="@drawable/nav_mail"
            android:title="我的郵件"/>
        <item
            android:id="@+id/location"
            android:icon="@drawable/nav_location"
            android:title="我的位置"/>
    </group>

</menu>

接下來我們來看看頭的佈局檔案,一般頭佈局中都是一個頭像加上一些簡單的資訊,在此處我們就用頭像、姓名和郵箱來佈局,佈局檔案如下所示:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
<!--一般頭佈局的高度為180dp-->
    android:layout_height="180dp"
    android:background="@color/colorPrimary"
    android:padding="10dp">
    <!-- CircleImageView 開源框架 將圖片裁剪為圓形-->
    <de.hdodenhof.circleimageview.CircleImageView
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:layout_centerInParent="true"
        android:src="@drawable/nav_icon"/>

    <TextView
        android:id="@+id/mail"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="Felix"
        android:textColor="#FFFFFF"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/mail"
        android:text="Felix"
        android:textColor="#FFFFFF"/>
</RelativeLayout>

可以看到,選單和頭佈局都非常的簡單,準備好選單和頭的佈局檔案以後我們就需要更改之前的佈局檔案了,將之前的簡單的TextView替換為NavigationView,更改後的佈局檔案如下:

<?xml version="1.0" encoding="utf-8"?>
<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/dl_activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

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

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="顯示選單one"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="顯示選單two"/>
    </LinearLayout>
<!—通過app:menu=””來指定選單檔案  通過app:headerLayout=””來指定頭佈局檔案-->
    <android.support.design.widget.NavigationView
        android:id="@+id/nav_activity_main"
        android:layout_gravity="start"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:menu="@menu/nav_menu"
        app:headerLayout="@layout/nav_head">

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

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

到這兒,基本的效果已經完成了,但是還需要給選單設定一下監聽事件,所以去到Activity檔案中進行修改,Activity中修改如下所示:

@BindView(R.id.dl_activity_main)
DrawerLayout drawerLayout;

@BindView(R.id.nav_activity_main)
NavigationView mNavigationView;
……
//設定預設選中的選單
mNavigationView.setCheckedItem(R.id.friends);
//給選單設定監聽
        mNavigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected( MenuItem item) {
                //關閉彈出選單
                drawerLayout.closeDrawers();
                return true;
            }
        });

接下來看看執行效果,如下圖所示:

    

4、FloatingActionButton + Sanckbar

該控制元件是Design Support庫中提供的,可以讓我們輕鬆實現懸浮按鈕的效果,該控制元件的使用非常簡單,只需要在佈局中新增FloatingActionButton標籤即可,xml佈局檔案如下所示(省略掉未更改的部分):

<android.support.design.widget.FloatingActionButton
            android:id="@+id/fab_activity_main"
            android:layout_width="wrap_content"
            android:layout_gravity="bottom|right"
            android:layout_margin="15dp"
            android:src="@drawable/ic_done"
            android:layout_height="wrap_content"/>

FloatingActionButton的點選事件的使用和普通的Button是相同的,我們在FloatingActionButton的點選事件中使用Sanckbar來提示使用者,Sanckbar的使用和Toast類似,只是除了提示以外,Sanckbar還可以執行一些簡單的邏輯,Activity程式碼如下:

@BindView(R.id.fab_activity_main)
FloatingActionButton mActionButton;
mActionButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //setAction來指定操作  第一個引數是操作名稱  第二個引數是點選事件的處理邏輯
                Snackbar.make(view, "刪除資料", Snackbar.LENGTH_SHORT).setAction("確定",
                        new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        Toast.makeText(MainActivity.this, "刪除成功", Toast.LENGTH_SHORT).show();
                    }
                }).show();
            }
        });

接下來看看執行效果,如下圖所示:

         

5、CoordinatorLayout

從上面第四步的截圖我們發現彈出的Snackbar遮擋住了我們的浮動按鈕,這是一種很不友好的使用者體驗,所以,Google為我們準備了CoordinatorLayout這個控制元件來解決這個問題,同樣是在Design支援庫中,我們只需要將滑動選單的預設顯示頁的最外層佈局指定為CoordinatorLayout即可,修改後的xml檔案如下所示:

<?xml version="1.0" encoding="utf-8"?>
<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/dl_activity_main"
    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="@color/colorPrimary"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
        </android.support.v7.widget.Toolbar>

        <android.support.design.widget.FloatingActionButton
            android:id="@+id/fab_activity_main"
            android:layout_width="wrap_content"
            android:layout_gravity="bottom|right"
            android:layout_margin="15dp"
            android:src="@drawable/ic_done"
            android:layout_height="wrap_content"/>
    </android.support.design.widget.CoordinatorLayout>

    <android.support.design.widget.NavigationView
        android:id="@+id/nav_activity_main"
        android:layout_gravity="start"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:menu="@menu/nav_menu"
        app:headerLayout="@layout/nav_head">

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

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

那麼,為什麼CoordinatorLayout可以實現讓浮動按鈕向上移動的效果呢?那是因為該控制元件會監聽它裡面所有控制元件的狀態,接下來看看使用CoordinatorLayout後的執行效果,如下圖所示:

     

到這裡,基本可以得到一個通用的結論那就是在Material Design中,外層的佈局方式基本為最外層為DrawerLayout滑動佈局,滑動佈局內部的主介面為CoordinatorLayout佈局,而滑動隱藏佈局為NavigationView,而使用者的主頁佈局只需在CoordinatorLayout新增佈局即可,而隱藏介面佈局則通過menu檔案和layout檔案指定給NavigationView即可基本框架如下xml檔案所示:

<?xml version="1.0" encoding="utf-8"?>
<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/dl_activity_main"
    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.CoordinatorLayout>

    <android.support.design.widget.NavigationView
        android:id="@+id/nav_activity_main"
        android:layout_gravity="start"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:menu="@menu/nav_menu"
        app:headerLayout="@layout/nav_head">
    </android.support.design.widget.NavigationView>

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

6、CardView(卡片式佈局)

CardView是實現卡片式佈局的重要控制元件,由appcompat-v7庫提供,需要在build.gradle檔案中宣告庫的依賴:compile ‘com.android.support:cardview-v7:26.+’, CardView的使用也很簡單,我們先來看看簡單的使用方法,如下xml佈局檔案所示:

<!-- app:cardCornerRadius:設定圓角弧度  app:cardElevation:設定投影高度-->
<android.support.v7.widget.CardView
            android:layout_margin="10dp"
            android:layout_width="match_parent"
            android:layout_height="50dp"
app:cardCornerRadius="5dp"
            app:cardElevation="8dp"
            >
            <TextView
                android:layout_width="wrap_content"
                android:layout_gravity="center"
                android:textSize="18sp"
                android:layout_height="wrap_content"
                android:text="CardView"/>
        </android.support.v7.widget.CardView>

從佈局檔案可以看到,CardView的使用很簡單,裡面就是一個TextView,CardView的特有屬性為app:cardCornerRadius和app:cardElevation,我們可以在CardView中巢狀其他ViewGroup來進行更為複雜的佈局,接下來將CardView新增到我們的CoordinatorLayout中,來看看效果,修改後的xml檔案如下所示:

<?xml version="1.0" encoding="utf-8"?>
<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/dl_activity_main"
    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="@color/colorPrimary"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
        </android.support.v7.widget.Toolbar>

        <android.support.v7.widget.CardView
            android:layout_margin="10dp"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            app:cardCornerRadius="5dp"
            app:cardElevation="8dp"
            >
            <TextView
                android:layout_width="wrap_content"
                android:layout_gravity="center"
                android:textSize="18sp"
                android:layout_height="wrap_content"
                android:text="CardView"/>
        </android.support.v7.widget.CardView>

        <android.support.design.widget.FloatingActionButton
            android:id="@+id/fab_activity_main"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|right"
            android:layout_margin="15dp"
            android:src="@drawable/ic_done"/>
    </android.support.design.widget.CoordinatorLayout>

    <android.support.design.widget.NavigationView
        android:id="@+id/nav_activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:headerLayout="@layout/nav_head"
        app:menu="@menu/nav_menu">

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

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

接下來看看執行效果,如下圖所示:

       

是不是感覺有什麼不對,是的,cardview遮擋住了我們的Toolbar,這很正常,因為CoordinatorLayout是FrameLayout的加強版,所以佈局方式和FrameLayout是相同的,所以都是一層覆蓋一層且是從左上角開始的,故Toolbar被遮擋住了,當然,肯定是有解決方法的,請往下看。

7、AppBarLayout

這個控制元件就是為了解決上面的遮擋問題的,實際上AppBarLayout是一個垂直方向的LinearLayout,但是,在它的內部還做了一些滾動事件的封裝,且用了Material Design的設計理念,接下來看看這個控制元件如何使用,很簡單,兩個步驟即可,第一步,在我們的Toolbar控制元件外層巢狀AppBarLayout,修改後的xml佈局檔案如下所示:

 <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="@color/colorPrimary"
                android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
            </android.support.v7.widget.Toolbar>
        </android.support.design.widget.AppBarLayout>

第一步就完成了,僅僅是在Toolbar的外部新增一個AppBarLayout而已,第二步在與Toolbar處於同級的佈局檔案中新增“app:layout_behavior="@string/appbar_scrolling_view_behavior”該屬性即可,當前佈局檔案中與Toolbar同級的控制元件為CardView,所以我們只需在CardView中新增該屬性即可,修改後的CardView佈局如下所示:

<android.support.v7.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_margin="10dp"
            app:cardCornerRadius="5dp"
            app:cardElevation="5dp"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            >

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:text="CardView"
                android:textSize="18sp"/>
        </android.support.v7.widget.CardView>

經過上面的兩個步驟,ToolBar就不會再被後面同級的控制元件遮擋住,app:layout_behavior="@string/

appbar_scrolling_view_behavior屬性由支援庫提供。同時我們可以給Toolbar設定app:layout_scrollFlags

="scroll|enterAlways|snap">屬性,該屬性是在Toolbar屬於AppBarLayout的子控制元件時才會生效,裡面的三個引數scroll代表當AppBarLayout下面的控制元件向上滾動(如:使用RecylerView的時候,Toolbar會跟著一起向上滾動並隱藏enterAlways是向下滾動會隨著滾動並重新顯示;snap則當Toolbar還沒完全隱藏或顯示的時候,會根據當前滾動距離,自動選擇隱藏還是顯示。

接下來我們將CardView替換成為RecylerView來模擬這種效果,並將RecylerView的佈局指定為CardView,這是修改後的activity_main.xml的佈局檔案:

<?xml version="1.0" encoding="utf-8"?>
<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/dl_activity_main"
    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="@color/colorPrimary"
                android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                app:layout_scrollFlags="scroll|enterAlways|snap"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
            </android.support.v7.widget.Toolbar>
        </android.support.design.widget.AppBarLayout>
        <!—將cardview替換為了RecyclerView ,因為RecyclerView 可以滾動-->
        <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.FloatingActionButton
            android:id="@+id/fab_activity_main"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|right"
            android:layout_margin="15dp"
            android:src="@drawable/ic_done"/>
    </android.support.design.widget.CoordinatorLayout>

    <android.support.design.widget.NavigationView
        android:id="@+id/nav_activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:headerLayout="@layout/nav_head"
        app:menu="@menu/nav_menu">

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

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

然後我們來編寫一下 RecyclerView的佈局檔案,外層使用CardView,裡面巢狀一個TextView,檔名稱為layout_item.xml佈局檔案如下所示:

<?xml version="1.0" encoding="utf-8"?>
<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="50dp"
    android:layout_margin="10dp"
    app:cardCornerRadius="5dp"
    app:cardElevation="5dp">

    <TextView
        android:id="@+id/tv_layout_item"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="CardView"
        android:textSize="18sp"/>

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

現在,我們去到Activity中給RecyclerView新增介面卡,Activity中和RecyclerView相關程式碼如下:

List<String> lists = new ArrayList<>();
        for(int i = 0; i < 20; i++){
            lists.add("模擬資料"+i);
        }
        //建立介面卡  並傳入模擬的資料
        MyAdapter adapter = new MyAdapter(lists);
        //設定顯示格式 2列
        GridLayoutManager layoutParams = new GridLayoutManager(this, 2);
        //將顯示格式傳給mRecyclerView
        mRecyclerView.setLayoutManager(layoutParams);
        //設定介面卡
        mRecyclerView.setAdapter(adapter);

MyAdapter.java檔案如下:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder>{
    private List<String> mLists;
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_item,
                parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.mTextView.setText(mLists.get(position));
    }

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

    static class ViewHolder extends RecyclerView.ViewHolder{
        private TextView mTextView;
        public ViewHolder(View itemView) {
            super(itemView);
            mTextView = itemView.findViewById(R.id.tv_layout_item);
        }
    }

    public MyAdapter(List<String> lists){
        mLists = lists;
    }

}

接下來看看執行效果,如下圖所示:

       

8、SwipeRefreshLayout(下拉重新整理)

到目前為止,基本的資料已經可以正常展示了,現在,我們將模擬的資料換成新聞資料,然後用Recycler

View來展示新聞,然後實現下拉重新整理新聞資料,說到下拉重新整理,Google也為我們封裝了一個適配MD風格的控制元件,就是SwipeRefreshLayout,SwipeRefreshLayout的使用很簡單,我們只需在之前的RecyclerView控制元件的外層加上SwipeRefreshLayout就行了,activity_main.xml佈局檔案修改如下:

<android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/srl_activity_main"
            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>

現在佈局檔案已經修改完畢,我們去Activity中新增獲取新聞的邏輯和下拉重新整理的邏輯,新聞是去知乎拉取的,用了第三方框架OkHttp、Glide和Gson,現在我們先來看看獲取資料的邏輯 ( 由於是Demo,程式碼和隨意,未任何優化,實際開發中不建議這樣寫哦~):

public void initData(){
        new Thread() {
            @Override
            public void run() {
                super.run();
                OkHttpClient client = new OkHttpClient();
                //新聞url
                String url = "http://news-at.zhihu.com/api/4/news/latest";
                Request request = new Request.Builder().url(url).build();
                Call call = client.newCall(request);
                //非同步獲取資料
                call.enqueue(new Callback() {
                    @Override
                    //獲取失敗的回撥
                    public void onFailure(Call call, IOException e) {
                        //關閉重新整理提示
                        mRefreshLayout.setRefreshing(false);
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(MainActivity.this, "無網路", Toast.LENGTH_SHORT).show();
                            }
                        });
                    }

                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                        try {
                            String string = response.body().string();
                            Log.i("felix", string);
                            //Gson解析獲取的資料
                            Gson gson = new Gson();
                            ResultBean resultBean = gson.fromJson(string, ResultBean.class);
                            //重置之前的List引用
                            lists = null;
                            //獲取Gson解析後的資料
                            lists = resultBean.getStories();
                            Log.i("felix", lists.size()+"");
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    //修改List引用物件
                                    adapter.updateLists(lists);
                                    //通知重新整理
                                    adapter.notifyDataSetChanged();
                                    //關閉重新整理提示
                                    mRefreshLayout.setRefreshing(false);
                                }
                            });
                        } catch (IOException e) {
                            e.printStackTrace();
                            mRefreshLayout.setRefreshing(false);
                            Toast.makeText(MainActivity.this, "無網路", Toast.LENGTH_SHORT).show();
                        }
                    }
                });
            }
        }.start();
    }

接下來看看用來進行Gson解析的ResultBean的定義:

public class ResultBean {
    private String date;//對應json資料中的date鍵
    private List<StoriesBean> stories; //對應json資料中的stories陣列
    public String getDate() {
        return date;
    }
    public void setDate(String date) {
        this.date = date;
    }
    public List<StoriesBean> getStories() {
        return stories;
    }
    public void setStories(List<StoriesBean> stories) {
        this.stories = stories;
    }
    public static class StoriesBean{
        private List<String> images;
        private String type;
        private String id;
        private String title;

        public List<String> getImages() {
            return images;
        }
        public void setImages(List<String> images) {
            this.images = images;
        }
        public String getType() {
            return type;
        }
        public void setType(String type) {
            this.type = type;
        }
        public String getId() {
            return id;
        }
        public void setId(String id) {
            this.id = id;
        }
        public String getTitle() {
            return title;
        }
        public void setTitle(String title) {
            this.title = title;
        }
    }
}

Gson小技巧:1、看到JSON結構裡面有{ }你就定義一個類,看到[ ]你就定義一個List或陣列即可,最後只剩下最簡單的如String、int等基本型別直接定義就好。2、內部巢狀的類,請使用public static class className { }。3、類內部的屬性名,必須與JSON串裡面的Key名稱保持一致。

接下來是實現下拉重新整理的邏輯,如下所示,在Activity中新增如下程式碼:

@BindView(R.id.srl_activity_main)
SwipeRefreshLayout mRefreshLayout;
//給SwipeRefreshLayout設定載入進度條的顏色和標題欄相同
mRefreshLayout.setColorSchemeResources(R.color.colorPrimary);
//設定下拉屬性監聽器
        mRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                //呼叫initData方法獲取資料
                initData();
            }
        });

接下來看看執行效果,如下圖所示:

         

9、CollapsingToolbarLayout

新聞展示介面我們已經做好了,現在就來進行新聞詳細顯示頁的佈局,用到的控制元件基本都是前面所學過的控制元件,不過這裡要新學習一個控制元件就是CollapsingToolbarLayout,該控制元件可以實現一個可摺疊式標題欄的效果,根據控制元件名我們就能猜出,該控制元件是作用於Toolbar之上的,而且該控制元件也是由Design支援庫提供的,並且該控制元件只能巢狀在AppBarLayout中使用,即xml佈局檔案應該是這種結構:

<android.support.design.widget.CoordinatorLayout
    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="match_parent">

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

        <android.support.design.widget.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:contentScrim="@color/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar_activity_info"
                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.design.widget.CoordinatorLayout>

即,最外層為CoordinatorLayout(防止遮擋,所有的非隱藏的MD風格佈局都使用這個控制元件作為最外層),CoordinatorLayout的下一層為AppBarLayout(也是防止遮擋,不過是防止Toolbar被遮擋,因為CoordinatorLayout是基於FrameLayout的),然後就是我們的可摺疊式標題欄CollapsingToolbarLayout,需要注意的是,我們來看看我們給CollapsingToolbarLayout設定的屬性theme為深色系,因為這樣文字才會顯示為白色,app:contentScrim="@color/colorPrimary" 主體顏色和標題欄顏色相同,最後一個屬性不是第一次見了,滑動時的反應,scroll表示摺疊式標題欄會隨著下面的可滾動的內容的滾動而滾動,exitUntilCollapsed 則表示隨著滾動CollapsingToolbarLayout摺疊完成之後把Toolbar留在頂部。

接下來看看執行效果,我已將應用名改為“簡新聞”執行結果如下圖所示,第一張為我們點選新聞進去的介面,第二張為向上滑動摺疊式標題欄摺疊以後的效果:

       

現在,又發現了新的問題,就是第一張截圖的狀態列特別扎眼,所以我們需要修改一下屬性來使狀態列透明化,要讓狀態列透明化很簡單,給CollapsingToolbarLayout及其CollapsingToolbarLayout外層的所有控制元件加上這個屬性就可以使狀態列透明化“android:fitsSystemWindows="true"”然而加上以後發現好像並沒有效果,這是因為我們還差一步,需要在主題檔案中給狀態列的顏色指定為透明色,現在我們就專門為這個頁面編寫一個主題檔案,在res下新建values-v21資料夾,在資料夾下面新建名為styles.xml檔案,在裡面編寫如下的主題檔案資訊:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- name:主題名, parent:繼承自那個主題 -->
<style name="InfoActivityTheme" parent="AppTheme">
    <!-- android:statusBarColor:指定狀態列的顏色為transparent即透明 -->
    <item name="android:statusBarColor">@android:color/transparent</item>
    </style>
</resources>

然後我們去AndroidManifast.xml修改新聞詳情頁的主題為InfoActivityTheme即可,修改後的顯示截圖如下所示

       

10、NestedScrollView

現在,可摺疊式標題欄也完成了,就差顯示新聞詳情的介面了,在這兒我們用NestedScrollView來作為顯示新聞的最外層的佈局,裡面巢狀一個線性佈局(注意:NestedScrollView佈局內只能有一個佈局,所以我們巢狀線性佈局來進行裡面的佈局),裡面的佈局也很簡單,線上性佈局內巢狀CardView,在CardView的裡面放一個TextView就ok了,完整的佈局檔案如下所示,包括上面的可摺疊式標題欄的佈局:

<?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: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="250dp"
        android:fitsSystemWindows="true"
        >

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

            <ImageView
                android:id="@+id/img_activity_info"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_gravity="center"
                android:fitsSystemWindows="true"
                android:scaleType="centerCrop"
                app:layout_collapseMode="parallax"/>
            <View
                android:background="#22000000"
                android:fitsSystemWindows="true"
                android:layout_width="match_parent"
                android:layout_height="80dp"
                app:layout_collapseMode="pin"/>

            <TextView
                android:id="@+id/title_activity_info"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom"
                android:layout_margin="20dp"
                android:textSize="25sp"
                android:textStyle="bold"
                android:textColor="#FFFFFF"
                android:text=""/>

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar_activity_info"
                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:layout_width="match_parent"
            android:layout_height="match_parent">

            <android.support.v7.widget.CardView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_margin="10dp"
                app:cardCornerRadius="5dp"
                app:cardElevation="5dp">

                <TextView
                    android:id="@+id/tv_activity_info"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:padding="10dp"
                    android:text="CardView"
                    android:textSize="15sp"/>
            </android.support.v7.widget.CardView>

        </LinearLayout>

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

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

現在,我們去到InfoActivity中編寫獲取新聞資料的邏輯,我們在MainActivity的RecyclerView中,當我們點選相應的新聞標題的時候,就把新聞的ID通過Intent傳遞到InfoActivity中,在InfoActivity中拿到對應的ID去知乎伺服器獲取詳細的新聞資料,然後Gson解析,將解析後的資料展示到相應的位置即可,需要注意的是,伺服器返回的詳細內容是html格式的,我們需要使用TextView的圖文混排功能來展示,完整的InfoActivity程式碼如下:

public class InfoActivity extends AppCompatActivity {

    @BindView(R.id.tv_activity_info)
    TextView mTextView;

    @BindView(R.id.toolbar_activity_info)
    Toolbar mToolbar;

    @BindView(R.id.img_activity_info)
    ImageView mImageView;

    @BindView(R.id.cool_toolbar)
    CollapsingToolbarLayout mCTL;

    @BindView(R.id.title_activity_info)
    TextView mTitle;

    ActionBar actionBar;

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

        ButterKnife.bind(this);

        setSupportActionBar(mToolbar);


        mTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                MyLog.i("textview");
                Log.i("felix","1111");
            }
        });

        mTextView.setMovementMethod(LinkMovementMethod.getInstance());

        actionBar = getSupportActionBar();
        if(actionBar != null){
            actionBar.setDisplayHomeAsUpEnabled(true);
        }

        //讓Toolbar的標題固定在上面
        mCTL.setTitleEnabled(false);

        Intent intent = getIntent();
        String id = intent.getStringExtra("id");
        String url = "http://news-at.zhihu.com/api/4/news/"+id;
        OkHttpClient client = new OkHttpClient();
        final Request request = new Request.Builder().url(url).build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                final String string = response.body().string();
                MyLog.i(string);
                Gson gson = new Gson();
                final ContentBean bean = gson.fromJson(string, ContentBean.class);
                
                Log.i("felix", "當前執行緒ID:"+Thread.currentThread().getId());

                final Spanned spanned = Html.fromHtml(bean.getBody(), new UrlImageGetter(
                        InfoActivity.this), null);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {

                        mTextView.setText(spanned);

                        Glide.with(InfoActivity.this).load(bean.getImage()).into(mImageView);

                        mTitle.setText(bean.getTitle());
                    }
                });

            }
        });
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()){
            case android.R.i