Material Design实战

1、Toolbar

ActionBar由于其设计的原因, 被限定只能位于活动的顶部, 从而不能实现一些Material Design的效果, 因此官方现在已经不再建议使用ActionBar, 而是推荐使用ToolBar。ToolBar不仅继承了ActionBar的所有功能, 而且灵活性高, 可以配合其他控件来完成一些Material Design效果。 接下来添加一些action 按钮来让ToolBar 更丰富一些, 创建一个 menu文件夹 及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
        android:id="@+id/backup"
        android:icon="@drawable/ic_backup"
        android:title="Backup"
        app:showAsAction="always" />

    <item
        android:id="@+id/delete"
        android:icon="@drawable/ic_delete"
        android:title="Delete"
        app:showAsAction="ifRoom"

        />

    <item
        android:id="@+id/settings"
        android:icon="@drawable/ic_settings"
        android:title="Settings"
        app:showAsAction="never"
        />
</menu>

 可以看到, 此处通过<item>标签来定义action按钮。app:showAsAction来指定按钮的显示位置, never表示永远显示在菜单中, always表示永远显示在toolbar。

在onCreateOptionsMenu()方法中加载toolbar.xml这个菜单文件, 在onOptionsItemSelected()方法中处理各个按钮的点击事件。

2、滑动菜单

(1)、DrawerLayout

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/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    
    <FrameLayout
        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>

    </FrameLayout>


    <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent" 
            android:layout_gravity="start"
            android:text="This is menu"
            android:textSize="30sp"
            android:background="#FFF"
            />
        
 </android.support.v4.widget.DrawerLayout>

第一个子控件是FrameLayout, 用于显示主屏幕中显示的内容, 里面有我们刚定义的toolbar,第二个子控件TextView 其实是有什么都可以, DrawerLayout 并没有限制只能使用固定的控件。看看效果:


另外我们可以添加导航浏览按钮, 点击也可以使菜单打开, 修改主活动代码:

 
 
public class MainActivity extends AppCompatActivity {

    private DrawerLayout drawer_layout;  //

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

        android.support.v7.widget.Toolbar toolbar = (android.support.v7.widget.Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        drawer_layout = (DrawerLayout) findViewById(R.id.drawer_layout);//
        ActionBar actionBar = getSupportActionBar();  //
        if (actionBar != null){
            actionBar.setDisplayHomeAsUpEnabled(true);
            actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);

        }


    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.toolbar, menu);

        return true;

    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()){   //
            case R.id.home:
                drawer_layout.openDrawer(GravityCompat.START);
                break;

            case R.id.backup:
                Toast.makeText(this, "Backup",Toast.LENGTH_SHORT).show();
                break;

            case R.id.delete:
                Toast.makeText(this,"Delete", Toast.LENGTH_SHORT).show();
                break;

            case R.id.settings:
                Toast.makeText(this, "Setting", Toast.LENGTH_SHORT).show();

                break;
                default:
        }
    return true;
    }

}

得到DrawerLayout的实例后调用 getSupportActionBar()方法得到了 ActionBar 的实例, 虽然这个ActionBar 实现是由Toolbar完成的 , 接着调用 ActionBar 的setDisplayHomeAsUpEnabled() 方法让导航按钮显示出来, 又调用了 setHomeAsUpIndicator() 方法来设置一个导航栏按钮图标。 实际上ToolBar 最左侧的这个按钮就叫做 HomeAsUp 按钮, 它的默认图标是一个返回的箭头, 这里将他的默认的样式和作用都修改了。

  接着对HomeAsUp按钮的点击事件进行处理, HomeAsUp按钮的id 永远都是android.R.id.home。 然后调用openDrawer() 方法将滑动菜单展示出来, 为了保证这里的行为和XML 中定义的一致, 传入GravityCompat.START。 


(2)、NavigationView

NavigationView是 Design Support库中提供的一个控件, 它不仅严格按照Material Design 的要求来进行设计, 而且还可以讲话的菜单页面的实现变得非常简单。 应用前,首先将这个库引入到项目中:

dependencies {
 
    implementation 'com.android.support:design:26.1.0'
    implementation 'de.hdodenhof:circleimageview:2.1.0'

}

   第一行是 Design Support 库, 第二行是一个开源项目 CircleImageView ,它可以实现图片圆形化的功能。 在开始使用 NavigationView 之前, 需要准备好 menu 和 headerlayout。 menu用来在NavigationView显示具体菜单项 ,headerlayout 用来在NavigationView显示头部布局。

   先来准备menu, 在menu文件夹创建 nav_menu.xml文件:


<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <group android:checkableBehavior="single" >

        <item
            android:id="@+id/nav_call"
            android:title="Call"
            android:icon="@drawable/nav_call"/>

        <item
            android:id="@+id/nav_friends"
            android:title="Friends"
            android:icon="@drawable/nav_friends"/>

        <item
            android:id="@+id/nav_location"
            android:title="Location"
            android:icon="@drawable/nav_location"/>

        <item
            android:id="@+id/nav_mail"
            android:title="Mail"
            android:icon="@drawable/nav_mail"/>

        <item
            android:id="@+id/nav_task"
            android:title="Tasks"
            android:icon="@drawable/nav_task"/>
     </group>


</menu>

<menu> 中嵌套了一个<group> 标签, 将checkableBehavior 属性指定为single。 group表示一个组, checkableBehavior 表示所有菜单项只能单选。

 menu就这样准备好了。。。接下来应该喜欢被headerLayout!!!我们就在headerLayout 中放置头像, 用户名, 邮箱地址这3项内容吧。 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="180dp"
    android:padding="10dp"
    android:background="?attr/colorPrimary"
    >

    <de.hdodenhof.circleimageview.CircleImageView
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:id="@+id/icon_image"
        android:src="@drawable/nav_icon"
        android:layout_centerInParent="true" />
    
    <TextView
        android:id="@+id/mail"
        android:layout_alignParentBottom="true"
        android:text="[email protected]"
        android:textSize="14sp"
        android:textColor="#FFF"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/username"
        android:layout_above="@+id/mail"
        android:text="Tony"
        android:textSize="14sp"
        android:textColor="#FFF"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
 </RelativeLayout>

   现在menu 和 headerLayout 都准备好了, NavigationView 随时准备着, 修改主活动布局:

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

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


    <android.support.design.widget.NavigationView   //
        android:id="@+id/nav_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"

        app:menu = "@menu/nav_menu"
        app:headerLayout="@layout/nav_header"
        />                     //

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

  通过app:menu 和 app:headerLayout 属性将我们刚才准备好的menu 和 headeLayout 设置进去, 这样NavigationView 就这样定义完成了。 

   

  NavigationView 定义好了之后, 我们还需要去处理菜单项的点击事件才行。 修改主活动:

public class MainActivity extends AppCompatActivity {

    private DrawerLayout drawer_layout;

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

        android.support.v7.widget.Toolbar toolbar = (android.support.v7.widget.Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        NavigationView nav_view = (NavigationView) findViewById(R.id.nav_view);  //
        drawer_layout = (DrawerLayout) findViewById(R.id.drawer_layout);
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null){
            actionBar.setDisplayHomeAsUpEnabled(true);
            actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
           }

        nav_view.setCheckedItem(R.id.nav_call);    //
        nav_view.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                drawer_layout.closeDrawers();
                return true;
            }
        });   //
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.toolbar, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()){
            case android.R.id.home:
                drawer_layout.openDrawer(GravityCompat.START);
                break;

            case R.id.backup:
                Toast.makeText(this, "Backup",Toast.LENGTH_SHORT).show();
                break;

            case R.id.delete:
                Toast.makeText(this,"Delete", Toast.LENGTH_SHORT).show();
                break;

            case R.id.settings:
                Toast.makeText(this, "Setting", Toast.LENGTH_SHORT).show();

                break;
                default:
        }
    return true;
    }

}

 获得NavigationView 实例后调用它的setCheckedItem()方法 将Call菜单设置为默认选中。  接着调用 setNavigationItemSelectedListener()方法 来设置一个菜单项选中事件的监听器, 当用户点击任意菜单项时, 就会回调到onNavigationItemSelected() 方法中。 可以在这个方法中写出相应的逻辑处理, 此处只是将滑动菜单关闭。


3、悬浮按钮和可交互提示

   立体设计是Material Design 中一条非常重要的设计思想, 这种按钮不属于主界面平面的一部分, 而是位于另外一个维度的, 因此给人以悬浮感。 关于提示工具, 我们经常使用Toset, 但是Toast不能与用户交互, 只是起到有个通知的作用。

  (1)、FloatingActionButton

修改主活动布局:

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

    <FrameLayout
        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.design.widget.FloatingActionButton   //
            android:id="@+id/fab"
            android:layout_gravity="bottom|end"
            android:layout_margin="16dp"
            android:src="@drawable/ic_done"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" 
            />
    </FrameLayout>


    <android.support.design.widget.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"

        app:menu = "@menu/nav_menu"
        app:headerLayout="@layout/nav_header"
        />

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

layout_gravity属性指定将这个控件放置于屏幕的右下角, end的原理即 如果系统语言是从左到右, 那么end就表示右边。 

public class MainActivity extends AppCompatActivity {

    private DrawerLayout drawer_layout;

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

        android.support.v7.widget.Toolbar toolbar = (android.support.v7.widget.Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        NavigationView nav_view = (NavigationView) findViewById(R.id.nav_view);
        drawer_layout = (DrawerLayout) findViewById(R.id.drawer_layout);
        ActionBar actionBar = getSupportActionBar();
        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); //

       fab.setOnClickListener(new View.OnClickListener() {   //
           @Override
           public void onClick(View v) {
               Toast.makeText(MainActivity.this, "FAB clicked", Toast.LENGTH_SHORT).show();
           }
       });


        ......

}

 FloatingActionBar 其实和普通Button 差不多, 都是调用setOnClickListener()方法来注册一个监听器。


   (2)、Snackbar

Design Support 库提供的更加先进的提示工具----Snackbar。 Snackbar并不是Toast的替代品, Snackbar允许在提示当中鸡加入一个可交互按钮, 当用户点击按钮时可以执行一些额外的逻辑操作。 比如当我们删除一些数据时, 添加一个Undo 按钮, 就相当于给用户提供了一种弥补措施, 从而提示了用户的使用体验。

  Snackbar用法与Toast 相似, 只不过可以额外增加一个按钮的点击事件。 修改主活动:

fab.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
               
               Snackbar.make(v, "Data delted", Snackbar.LENGTH_SHORT)
                       .setAction("Undo", new View.OnClickListener() {

                           @Override
                           public void onClick(View v) {
                               Toast.makeText(MainActivity.this, "Data restored", Toast.LENGTH_SHORT).show();
                           }
                       }).show();
               
           }
       });

这里调用了Snackbar 的 make()方法来创建一个Snackbar对象, make() 方法的第一个参数需要传入一个View, 只要是当前界面布局的任意一个View都可以, Snackbar 会使用这个View 来自动来查找最外层的布局, 用于展示Snackbar。 第二个参数就是Snackbar 中显示的内容, 第三个参数是Snackbar 显示的时长。   接着调用setAction() 从而让Snackbar 不仅仅是一个提示, 而是可以和用户进行交互的。 最后调用show() 让Snackbar显示出来。

  

 注意此处有一个bug, Snackbar竟然将悬浮按钮挡住了, 此时我们可以借助 CoordinatorLayout 就可以轻松解决。 

 (3)、CoordinatorLayout

CoordinatorLayout 可以说是一个加强版的FrameLayout, 也是由Design Support库提供的, 其必然就一些Material Design的魔力。 事实上 CoordinatorLayout 可以监听其所有子控件的各种事件, 然后帮我们做出合理的响应。 接下来我们使用以下CoordinatorLayout 。 只需要将原来的 FrameLayout 替换一下即可, 修改主活动布局:

<?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/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.design.widget.FloatingActionButton
            android:id="@+id/fab"
            android:layout_gravity="bottom|end"
            android:layout_margin="16dp"
            android:src="@drawable/ic_done"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            />
        
    </android.support.design.widget.CoordinatorLayout>


    <android.support.design.widget.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"

        app:menu = "@menu/nav_menu"
        app:headerLayout="@layout/nav_header"
        />

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

 

可以看到悬浮按钮随着Snackbar上下浮动了, 解决了上面的bug。

  

CoordinatorLAyout可以监听其所有子控件的各种事件, 但是Snackbar 并不是其控件, 但它为啥会被监听的呢? 其道

理是我们在Snackbar 的 make()方法中传入的第一个参数, 这个参数是用来指定Snackbar是基于那个View来触发的, 刚才传入的是FloatingActionButton 本身, 而FloatingActionButton 是 CoordinatorLayout 中的子控件, 因此这个事件 就能被监听到了。 

4、卡片式布局

  为了让主体内容也Material化, 准备在这放一些水果图片, 本节中我们将会学习如实现卡片式布局的效果。  卡片式布局也是

Materials Design中提到的一个新的概念, 它可以让页面中的元素看起来就像在卡片中一样, 并且还能拥有圆角和投影。

  (1)、CardView

   CardView是实现卡片式布局的重要控件, 由v7库提供。 实际上, CardView也是一个FrameLayout,  只是额外提供了圆角和阴影等效果, 看上去有立体的效果。

   接下来我们使用RecyclerView 来填充项目的主界面,  由于我们需要用到RecyclerView, CardView 这几个控件, 因此必须在gradle中声明这些库的依赖:

    

implementation 'com.android.support:recyclerview-v7:26.1.0'
    implementation 'com.android.support:cardview-v7:26.1.0'
    implementation 'com.github.bumptech.glide:glide:3.7.0'

  最后一行添加了 Glide库。 Glide是一个超级强大的图片加载库, 他不仅可以用于加载本地图片, 还可以加载网络图片、 GIF图片、 甚至是本地视频。 最重要的是, Glide的用法非常简单, 只需要一行代码就能实现复杂图片的加载。 

  接下来开始具体的代码实现, 修改主活动布局:

<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.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_gravity="bottom|end"
            android:layout_margin="16dp"
            android:src="@drawable/ic_done"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            />

    </android.support.design.widget.CoordinatorLayout>
    <android.support.design.widget.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"

        app:menu = "@menu/nav_menu"
        app:headerLayout="@layout/nav_header"
        />

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

      这里我们在CoordinatorLayout 中添加了一个RecyclerView, 给它指定一个id, 然后将宽高占满布局的空间。

  接着定义一个实体类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;
    }
}

name代表水果名字, imageId 代表图片资源id。 然后需要为RecyclerView 的子项指定一个我们自定义的布局, 在layout目录下新建 fruit_item.xml:

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

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

        <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_gravity="center_horizontal"
            android:layout_margin="5dp"
            android:textSize="16sp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>

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

   CardView来作为子项的最外层布局, 从而使得RecyclerView 中的每个元素都是在卡片中的。 CardView 由于是一个FrameLayout, 因此他没有什么方便地定位方式, 在这我们在其嵌套一个LinearLayout。 注意ImageView, 这里使用了centerCrop 模式, 它可以让图片保持原有比例充满 ImageView , 并将超出部分裁减掉。

    接下来为 RecyclerView 准备一个适配器, 新建一个FruitAdapter 类, 让这个适配器去继承RecyclerView.Adapter, 并将泛型指定为 FruitAdapter.ViewHolder, 其中,ViewHolder是我们在FruitAdapter 中定义的一个内部类:

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

    private Context mContext;

    private  List<Fruit> 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.fruit_name.setText(fruit.getName());
        Glide.with(mContext).load(fruit.getImageId()).into(holder.fruit_image);
     }

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

    public class ViewHolder extends RecyclerView.ViewHolder {

        CardView cardView;
        ImageView fruit_image;

        TextView fruit_name;

        public ViewHolder(View itemView) {
            super(itemView);
            cardView = (CardView) itemView;
            fruit_name = (TextView) itemView.findViewById(R.id.fruit_name);
            fruit_image = (ImageView) itemView.findViewById(R.id.fruit_image);
        }
    }
        public FruitAdapter(List<Fruit> fruitList) {

            mFruitList = fruitList;

        }
    
}

   代码虽长, 但并不难理解, 这里首先自定义了一个内部类 ViewHolder, ViewHolder 要继承自RecyclerView.ViewHolder。 然后ViewHolder 的构造函数中要传入一个View参数, 这个参数就是RecyclerView 子项的最外层布局, 那么我们就可以提供findViewById()方法获取布局中的ImageView 和 TextView 的实例。

    接着看FruitAdapter 中也有一个构造函数, 这个方法用于把要展示的数据源传进来, 并赋值给一个全局变量mFruitList, 后续的操作都将在这个数据源的基础上进行。

   继续看, 由于FruitAdapter 是继承自RecyclerView.Adapter 的, 那么就必须重写onCreateViewHolder()、 onBindViewHolder()、 和 getItemCount() 这3个方法。 onCreateViewHolder() 方法是用来创建ViewHolder实例的, 在这个方法中将 fruit_item 布局加载进来, 然后创建一个ViewHolder实例, 并把加载的布局传入到 构造函数中, 最后返回ViewHolder实例,    onBindViewHolder() 方法是用于对RecyclerView子项数据进行赋值的,  在每个子项被滚到屏幕是被执行, 通过position 参数得到当前Fruit, 然后将数据设置到 ViewHolder 的 ImageView 和 TextView当中即可。 getItemCount() 直接返回数据源的长度。

  在onBindViewHolder()方法中我们使用了Glide 来加载图片。 其用法为 Glide.with()方法传入一个 Context, Activity或 Fragment 参数, 然后调用 load()方法去加载图片, 可以是本地地址, 也可以是一个URL 地址, 或者是一个资源id, 最后调用into() 将图片设置到一个具体的ImageView 中即可。 

  Glide可以将高清图片进行压缩, 不会造成内存溢出比传统方式更好。

 如此, 适配器就创建完成, 最后修改主活动:

public class MainActivity extends AppCompatActivity {

    private DrawerLayout drawer_layout;

    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;


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

        android.support.v7.widget.Toolbar toolbar = (android.support.v7.widget.Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        initFruits();
        RecyclerView recycler_view = (RecyclerView) findViewById(R.id.recycler_view);
        GridLayoutManager layoutManager = new GridLayoutManager(this, 2);
        recycler_view.setLayoutManager(layoutManager);
        adapter = new FruitAdapter(fruitList);
        recycler_view.setAdapter(adapter);

        NavigationView nav_view = (NavigationView) findViewById(R.id.nav_view);
        drawer_layout = (DrawerLayout) findViewById(R.id.drawer_layout);
        ActionBar actionBar = getSupportActionBar();
        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);

       fab.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {

               Snackbar.make(v, "Data delted", Snackbar.LENGTH_SHORT)
                       .setAction("Undo", new View.OnClickListener() {

                           @Override
                           public void onClick(View v) {
                               Toast.makeText(MainActivity.this, "Data restored", Toast.LENGTH_SHORT).show();
                           }
                       }).show();

           }
       });


        if (actionBar != null){
            actionBar.setDisplayHomeAsUpEnabled(true);
            actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
           }

        nav_view.setCheckedItem(R.id.nav_call);
        nav_view.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                drawer_layout.closeDrawers();
                return true;
            }
        });
    }

    private 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]);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.toolbar, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()){
            case android.R.id.home:
                drawer_layout.openDrawer(GravityCompat.START);
                break;

            case R.id.backup:
                Toast.makeText(this, "Backup",Toast.LENGTH_SHORT).show();
                break;

            case R.id.delete:
                Toast.makeText(this,"Delete", Toast.LENGTH_SHORT).show();
                break;

            case R.id.settings:
                Toast.makeText(this, "Setting", Toast.LENGTH_SHORT).show();

                break;
                default:
        }
    return true;
    }

}

在数组中存放了多个Fruit 实例, 然后在initFruits() 方法中, 先是清空了一下 fruitList 中的数据, 接着使用一个随机函数 从数组中挑一个水果放入fruitList中, 为了让数据更丰富 使用了一个循环, 随机挑选50个水果。

GridLayoutManager布局方式的用法也没什么特别之处,它的构造函数接收两个参数, 第一个是Context, 第二个是列数, 这里我们希望每行中有两列数据。 

这里可以发现, ToolBar 被RecyclerView挡住了,  需要使用 AppBarLayout 解决这个问题。

(3)、AppBarLayout

   RecyclerView 和 Toolbar 都是放置在CoordinatorLayout 中的, 由于 CoordinatorLayout是一个加强版的 FrameLayout , 那么FrameLayout 中所有的控件在不进行明确定位的情况下, 默认都摆放在左上角, 从而产生了遮挡。 这就是Toolbar 被 RecyclerView 遮挡的原因。

  传统的情况下我们会使 RecyclerView 向下偏移一个ToolBar 的高度, 然而我们 的是CoordinatorLayout , 我们可以抛弃传统方式, 而使用 Design Support 库中提供的另外一个工具---AppBarLayout。 AppBarLayout实际上是一个垂直方向的 LinearLayout , 它在内部做了很多滚动事件的封装, 并应用的 Material Design的设计理念。 

   解决覆盖问题, 只需要两步, 第一 将Toolbar 嵌套到 AppBarLayout中, 第二 给 RecyclerView 指定一个布局行为。 修改主活动布局:

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

 接下来我们来看看 AppBarLayout 到底能实现什么样的 Material Design效果。 接收到滚动事件的时候, 它内部的子控件其实是可以指定如何去影响这些事件的, 通过 app:layout_scrollFlags 属性就能实现。 修改主活动布局:

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

此处给ToolB中添加了app:layout_scrollFlags" 属性, 并赋值scroll|enterAlways|snap, 其中 scroll 表示当 RecyclerView 向上滚动的时候 Toolbar 会一起向上滚动并实现隐藏, enterAlways 表示当RecyclerView 向下滚动时, Toolbar 会一起向下滚动 并重新显示, snap 表示当Toolbar 还没有完全隐藏或显示 的时候, 会根据当前滚动的距离, 自动选择显示还是隐藏。


 像这种功能, 是ActionBar无法实现的, ToolBar为我们提供了更多可能。

5、下拉刷新

 谷歌为了让Android的下拉刷新风格能有一个统一的标准, 于是在Material Design 中定制了一个官方的设计规范, 谷歌早就提供了现成的控件, 我们只需在项目中直接引用即可。 

  SwipRefreshLayout 就是用于实现下拉刷新的核心类, 由 support-v4 库提供的。 我们把控件放置到SwipRefreshLayout 中, 就可以迅速让这个控件实现下拉刷新。 在这个Test中, 支持下拉刷新的控件自然就是 RecyclerView 了, 修改主活动的布局:

...
 <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"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            >
        </android.support.v7.widget.RecyclerView>

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

  如此RecyclerView 就具有下拉刷新功能了, 另外注意, 由于RecyclerView 现在变成了SwipRefreshLayout 的子控件, 因此之前使用app:layout_behavior 声明的布局行为现在也要移到 SwipRefreshLayout 中才行。

  接下来需要在代码中实现具体的刷新逻辑。 修改主活动代码:

  

public class MainActivity extends AppCompatActivity {

    private DrawerLayout drawer_layout;

    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 SwipeRefreshLayout swipeRefreshLayout;//
    private SwipeRefreshLayout swipe_refresh;


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

        android.support.v7.widget.Toolbar toolbar = (android.support.v7.widget.Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        initFruits();
        RecyclerView recycler_view = (RecyclerView) findViewById(R.id.recycler_view);
        GridLayoutManager layoutManager = new GridLayoutManager(this, 2);
        recycler_view.setLayoutManager(layoutManager);
        adapter = new FruitAdapter(fruitList);
        recycler_view.setAdapter(adapter);

        NavigationView nav_view = (NavigationView) findViewById(R.id.nav_view);
        drawer_layout = (DrawerLayout) findViewById(R.id.drawer_layout);

        swipe_refresh = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
        swipe_refresh.setColorSchemeResources(R.color.colorPrimary);
        swipe_refresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                refreshFruits();
            }
        });
        
        ActionBar actionBar = getSupportActionBar();
        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);

        fab.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {

               Snackbar.make(v, "Data delted", Snackbar.LENGTH_SHORT)
                       .setAction("Undo", new View.OnClickListener() {

                           @Override
                           public void onClick(View v) {
                               Toast.makeText(MainActivity.this, "Data restored", Toast.LENGTH_SHORT).show();
                           }
                       }).show();

           }
       });


        if (actionBar != null){
            actionBar.setDisplayHomeAsUpEnabled(true);
            actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
           }

        nav_view.setCheckedItem(R.id.nav_call);
        nav_view.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                drawer_layout.closeDrawers();
                return true;
            }
        });
    }

    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();
                        swipe_refresh.setRefreshing(false);
                        
                    }
                });
            }
        }).start();
    }

...
}

   此处通过 findViewById() 方法获得实例, 然后调用 setColorSchemaResources() 方法来设置进度条颜色, setOnRefreshListener() 方法设置一个下拉刷新的监听器, 触发刷新时就会回调这个监听器 的onRefresh() 方法, 在这里可以去处理刷新逻辑。通常在是去网络请求最新数据,再将数据展示出来。 我们这就展示一个刷新本地图片操作, refreshFruits() 方法中 开启一个线程, 然后将线程沉睡两秒, 沉睡结束后 使用runOnUiThread() 方法将线程切换到主线程, 然后调用 initFruits() 重新生成数据, 接着调用FruitAdapter 的 notifyDataSetChanged() 方法通知数据发送了变化, 最后调用 SwipRefreshLayout 的 setRefreshing() 方法并传入false , 用于表示刷新结束, 并隐藏进度条。

   


6、可折叠式标题栏

  在Material Design中可以根据我们的喜好指定标题栏的样式, 比如折叠式标题栏。 需要借助 CollapsingToolbarLayout 这个工具。

(1)、CollapsingToolbarLayout 

   CollapsingToolbarLayout 是一个可以作用于 ToolBar 基础之上的布局, 由Design Support 库 CollapsingToolbarLayout 
 可以让Toolbar 的效果更佳丰富。 不过它是不可以独立存在的, 在设计时就被限定只能作为 AppBarLayout 的直接子布局来使用。 而 AppBarLayout 又必须是 CoordinatorLayout  的子布局, 因此让我们这里的项目是个布局综合性蛮强的操作。 

  创建一个FruiteActivity, 开始编写水果内容详情, 我们来一步步实现。

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

  注意始终定义一个 xmlns:app的命名空间, 在Material Design 的开发会经常用到它。 

  接着在CoordinatorLayout 中嵌套一个AppBarLayout, 如下:

<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:id="@+id/appBar"
        android:layout_width="match_parent"
        android:layout_height="250dp">
    </android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>

接着在AppBarLayout 中再嵌套一个CollapsingToolbarLayout, 如下所示:

...
<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:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:contentScrim="?attr/colorPrimary"
            >
            
        </android.support.design.widget.CollapsingToolbarLayout>

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

  这里我们使用了新布局CollapsingToolbarLayout, android:theme 属性用于指定 CollapsingToolbarLayout 在趋于折叠状态以及折叠之后的背景色, 其实CollapsingToolbarLayout 折叠后就是一个普通的 Toolbar , 背景肯定是 colorPrimary 了。 

app:layout_scrollFlags 属性我们也是见过的, 之前 Toolbar 见过,  scroll 表示CollapsingToolbarLayout 会随着水果内容详情的滚动一起滚动, exitUnitilCollapsed 表示当CollapsingToolbarLayout 随着滚动完成折叠之后就保留在界面,不在移除屏幕。 

   接下来, 我们在 CollapsingToolbarLayout 中定义标题栏内容, 如下:

...
<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:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:contentScrim="?attr/colorPrimary"
            >
            
            <ImageView
                android:id="@+id/fruit_image_view"
                android:scaleType="centerCrop"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                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>
...

此处, 我们在 CollapsingToolbarLayout  中定义了一个 ImageView 和一个 Toolbar, 也就意味着, 这个高级版的标题将是

由普通标题栏 加上图片组成而成的。 这里有个app:layout_collapseMode 比较陌生。 它用于指定当前控件在 CollapsingToolbarLayout 折叠过程中的折叠模式, 其中Toolbar 指定为pin ,表示在折叠过程中位置始终保持不变, ImageViwe 指定成 parallax, 表示会在折叠的过程产生一定的错位漂移, 这种模式视觉效果好。

  这样水果标题栏就完成了, 下面编写水果内容详情部分:

<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:id="@+id/appBar"
        android:layout_width="match_parent"
        android:layout_height="250dp">

       ......

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

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

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

   水果内容详情的最外层布局使用了一个 NestedScrollView, 注意它和AppBarLayout 是平级的。  ScrollView 的用法, 它允许

使用滚动的方式来查看屏幕外的数据, 而NestedView 在此基础之上还增加了嵌套响应滚动时间的功能。 由于CoordinatorLayout 本身已近可以响应滚动事件了, 因此我们在它的内部就需要使用 NestedScrollView 或 RecyclerView 这样的布局。 另外, 这里还通过 app: layout_behavior 属性指定了一个布局行为, 这和之前在 RecyclerView 中的用法是一模一样的。

   不管是ScrollerView 还是 NestedScrollerView, 它们的内部都只允许一个直接子布局。 因此我们想要在里面放很多东西的话, 通常都会嵌套一个 LinearLayout , 然后在 LinearLayout 中放入具体的内容即可。在这里我们将准备使用一个 TextView来显示水果的内容详情, 并将 TextView 放在一个卡片式布局当中:

   

...
<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"
              >
              
          </android.support.v7.widget.CardView>  
         </LinearLayout>

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

  在CardView 和 TextView 上都加了一些边距。 CardView的 marginTop 加了35dp的边距, 为了下面要编写的东西留出空间。

  

<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: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:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:contentScrim="?attr/colorPrimary"
            >

            <ImageView
                android:id="@+id/fruit_image_view"
                android:scaleType="centerCrop"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                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"
            >

        </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/ic_comment"
        app:layout_anchor="@id/appBar"
        app:layout_anchorGravity="bottom|end"
        />
       
</android.support.design.widget.CoordinatorLayout>

  FloatingActionButton, 和 AppBarLayout、 NestedScrollView 是平级的。 其中, app:layout_anchorGravity 属性将悬浮按钮 定位到标题栏 区域的右下角。 

  界面完成之后, 接下来开始编写功能逻辑, 修改FruitActivity:

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

        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 collapsing_toolbar = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
        ImageView fruit_image_view = (ImageView) findViewById(R.id.fruit_image_view);
        TextView fruit_content_text = (TextView) findViewById(R.id.fruit_content_text);

        setSupportActionBar(toolbar);

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

        collapsing_toolbar.setTitle(fruitName);
        Glide.with(this).load(fruitImageId).into(fruit_image_view);
        String fruitContent = generateFruitContent(fruitName);
        fruit_content_text.setText(fruitContent);
    }

    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();
                return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

   在onCreate() 方法中, 我们通过Intent 获取到传入水果的名 和水果图片的资源ID, 然后获得布局文件中的各个空间实例。  接着就用了 Toolbar的标准用法, 将它作为ActionBar 显示, 并启用HomeAsUp 按钮。 由于HomeAsUp  按钮的默认图标是一个返回按钮, 这正是我们希望的, 因此无需设置别的图标了。

    接下来开始填充界面上的内容, 调用CollapsingToolbarLayout 的 setTitle() 将水果名设置为标题, 然后使用Glide 加载传入的水果图片, 并设置到标题栏的 ImageView 上面。 此处填充了500此水果名。

  接着, 我们在onOptionsItemSelected() 方法中处理了 HomeAsUp 按钮的点击事件, 当点击按钮 就调用finish() 方法关闭此活动, 从而返回上一个活动。

 最后一步, 处理RecyclerView 的点击事件, 不然就无法打开FruitActivity, 修改FruitAdapter:

  

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

    private Context mContext;

    private  List<Fruit> 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);
        
        
        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;
    }

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

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

    public class ViewHolder extends RecyclerView.ViewHolder {

        CardView cardView;
        ImageView fruit_image;

        TextView fruit_name;

        public ViewHolder(View itemView) {
            super(itemView);
            cardView = (CardView) itemView;
            fruit_name = (TextView) itemView.findViewById(R.id.fruit_name);
            fruit_image = (ImageView) itemView.findViewById(R.id.fruit_image);
        }
    }
        public FruitAdapter(List<Fruit> fruitList) {

            mFruitList = fruitList;

        }

}

  在这里, 我们给CardView 注册了一个点击事件监听器, 然后在点击事件当前点击项的水果名和图片资源id,把他们传入到intent 中, 最后调用startActivity() 启动FruitActivity。

 (2)、充分利用系统状态栏空间

 虽说水果详情页展示界面的效果已经非常华丽的, 但这并不代表我们不能再进一步地提高升。 其实这里我们能将背景图和状态栏融合到一起, 那这个视觉体验绝对提升几个档次。 但是再Android5.0之前, 是无法对状态栏的背景或颜色进行操作的, 那个时候也没有 Material Design 的概念。 因此在Android 5.0 系统之后我们完全可以 使用背景图和状态栏融合的模式。 

  此时需要借助 android:fitsSystemWindows 这个属性来将其属性设置为true , 这就表示控件会出现在系统状态栏里。在这里 就是水果标题栏中的 ImageView 应该设置这个属性了。 但是之给 ImageView设置此属性是不够的, 必须将ImageView 布局结构中的所有父布局都设置上这个属性才行, 修改acticity_fruit.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: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:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:contentScrim="?attr/colorPrimary"
            android:fitsSystemWindows="true"
            >

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

   但是, 只设置android:fitsSystemWindows 属性都设置好了是没有的, 因为还必须在程序的主题中将状态指定为透明色才行。 只要将主题将 android: statusBarColor 属性的值指定成 @android:color/transparent 就可以了。 但问题是, android:statusBar 这个属性是从API21, 也就是Android5.0 系统开始才有的, 之前的系统无法指定这个属性。 

 那么。。。

系统差异型功能实现就要从这里开始了。。。

  右击res目录, 新建一个value-v21目录, 创建一个sytle.xml 文件:

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

    在这里我们定义了一个FruitActivityTheme 主题, 它是专门给 FruitActivity使用的。 FruitActivityTheme 的 parent 主题是AppTheme, 也就是说它继承了 AppTheme的所有特性。 然后我们在 FruitActitityTheme主题 将状态栏颜色设置为透明, 且只有 Android5.0 及以上系统才会去读取。   

  但是再Android5.0 之前的系统时无法对其进行识别的, 因此我们还需要对 values/styles.xml文件进行修改:

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

 可以看到这里定义了一个 FruitActivityTheme 主题,并且parent 主题也是 AppTheme, 但是它的内部是空的。 因为 Android5.0 之前的系统无法指定系统状态栏的颜色, 因此这里什么都不用做就可以了。

  最后,我们还需要让 FruitActivity 使用这个主题才可以, 修改清单:

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

 这里使用android:theme 属性单独给FruitActivity 指定了FruitActivityTheme 这个主题, 如此即大功告成。

 现在只要是在Android5.0 及以上系统运行 MaterialTest 程序, 水果详情展示界面的效果就会如图:



结束!!!



  


  

  



   



猜你喜欢

转载自blog.csdn.net/qq_41405257/article/details/80201109