Material Design基本使用

1、Toolbar

1.1 修改样式#####

首先要知道,任何一个新建项目默认都是ActionBar,这个ActionBar就是根据项目中指定的主题来显示,打开AndroidManifest.xml文件看一下,如下所示:

<application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >
...
</application>

可以看到,这里使用 android:theme属性指定一个AppTheme的主题,该属性定义在res/values/styles.xml文件中,如下所示:

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

这里定义了一个叫AppTheme的主题,然后指定他的parent主题是Theme.AppCompat.Light.DarkActionBar。这个DarkActionBar是一个深色的ActionBar主题。而现在我们使用Toolbar代替ActionBar,因此需要指定一个不带ActionBar的主题,通常有两种主题可选

  • Theme.AppCompat.NoActionBar 表示深色主题
  • Theme.AppCompat.Light.NoActionBar 表示淡色主题

这里我们选用淡色主题,如下所示:

<resources>
    <!-- Base application theme. -->
    <!-- 淡色主题   parent="Theme.AppCompat.Light.NoActionBar"-->
    <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>

这里重写了colorPrimary、colorPrimaryDark、colorAccent 3个属性。
用下面的图来解释每个属性代表的含义:

 

唯独colorAccent属性需要强调一下:它不只是指定这个按钮的颜色,而是表达强调的的意思,比如一些控件的选中状态也会使用colorAccent的颜色。

1.2 修改成Toolbar#####

当在res/values/styles.xml文件中将 parent属性设置为"Theme.AppCompat.Light.NoActionBar"后,ActionBar就隐藏起来了。
现在用Toolbar代替ActionBar:
修改activity_main.xml

<FrameLayout 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.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:layout_scrollFlags="scroll|enterAlways|snap"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

</FrameLayout>
  1. Toolbar控件是appcompat-v7库提供的,高度设置为actionBar的高度。
  2. Toolbar上面的元素默认使用深色系,为了让Toolbar和界面风格一致,将样式调整为android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"。
  3. 如果Toolbar有菜单按钮,指定淡色主题
    app:popupTheme="@style/ThemeOverlay.AppCompat.Light" 。
    app:popupTheme属性是Android5.0 系统新增的,使用app:popupTheme可以兼容以下版本。
    同时修改MainActivity
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
    }
}

同时给activity增加了一个android:label属性,用于指定在Toolbar中显示文字的内容,如果没有指定默认使用application中指定label内容。

 <activity
            android:name=".MainActivity"
            android:label="材料设计">
            ...
 </activity>

效果如下:

1.3 在Toolbar上加入Action按钮#####

在res目录下新建toolbar.xml的menu文件夹,代码如下:

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

app:showAsAction属性用来指定按钮显示位置,showAsAction有如下几种值可选:

  • always :永远显示在Toolbar中
  • ifRoom :屏幕空间足够的情况下显示在Toolbar中,不够的话显示在菜单中
  • never :永远显示在菜单中
    ** 注意:Toolbar中的action按钮只会显示图片,菜单中的action按钮只会显示文字 **
    修改MainActivity:
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
    }

    @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.backup:
                Toast.makeText(this, "You clicked Backup", Toast.LENGTH_SHORT).show();
                break;
            case R.id.delete:
                Toast.makeText(this, "You clicked Delete", Toast.LENGTH_SHORT).show();
                break;
            case R.id.settings:
                Toast.makeText(this, "You clicked Settings", Toast.LENGTH_SHORT).show();
                break;
        }
        return true;
    }
}

在onCreateOptionsMenu方法中加载toolbar.xml菜单文件,然后在onOptionsItemSelected方法处理按钮的点击事件

2、滑动菜单DrawerLayout

DrawerLayout是个布局,在布局中允许放两个直接子控件,第一个子控件是主屏幕中显示的内容,第二个子控件是滑动菜单中显示的内容。
修改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/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:layout_scrollFlags="scroll|enterAlways|snap"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

    </FrameLayout>

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

这里最外层的控件使用了DrawerLayout,这个控件是support-4库提供。DrawerLayout中放置了两个直接子控件,第一个子控件是FrameLayout,用于作为主屏幕中显示的内容;第二个子控件使用了TextView,用于作为滑动菜单中显示的内容。

第二个子控件有一点特别要注意,android:layout_gravity="start"必须要指定。因为要告诉DrawerLayout滑动菜单是在屏幕的左边还是右边。指定left表示滑动菜单在左边,right表示滑动菜单在右边。这里使用过了start,表示会根据系统语言进行判断。如果系统语言是从左往右的,比如汉语、英语,滑动菜单就在左边。

Material Design建议在Toolbar的最左边加入一个导航按钮,点击按钮后也会将滑动菜单内容展示出来。
准备一张ic_menu.png的图片后,修改MainActivity后如下:

public class MainActivity extends AppCompatActivity {
    private DrawerLayout mDrawerLayout;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...
        mDrawerLayout = (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 android.R.id.home:
                mDrawerLayout.openDrawer(GravityCompat.START);
                break;
          ...
        }
        return true;
    }
}

首先获得DrawerLayout的实例,然后调用getSupportActionBar()方法得到ActionBar的实例,虽然这个ActionBar的具体实现是由Toolbar完成的。

接着调用ActionBar的setDisplayHomeAsUpEnabled()方法让导航按钮显示出来,又调用了setHomeAsUpIndicator()方法设置一个导航按钮图标。实际上,Toolbar最左侧这个按钮叫做HomeAsUp按钮,它默认的图标是一个返回的箭头,含义是返回上一个活动。

在onOptionsItemSelected()方法中对HomeAsUp按钮的点击事件进行处理,** HomeAsUp按钮的id永远都是android.R.id.home **。

然后调用DrawerLayout的openDrawer()方法将滑动菜单显示出来。注意:openDrawer()方法要求传入一个Gravity参数,为了保证这里的行为和XML定义的一致,这里传入了GravityCompat.START。

效果如下:

 

3、NavigationView

通过NavigationView可以在滑动菜单页面定制任意布局。NavigationView是Design Support库中提供的。需要在app/builde.gradle文件中加入依赖

dependencies {
   ...
    compile 'com.android.support:design:25.0.1'
    compile 'de.hdodenhof:circleimageview:2.1.0'
    ...
}

在使用NavigationView之前,需要提供menu和headerLayout。

  • menu是用来在NavigationView中显示具体的菜单项。
  • headerLayout是用来在NavigationView中显示头部布局。

在menu文件夹中加入nav_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <group android:checkableBehavior="single">
        <item
            android:id="@+id/nav_call"
            android:icon="@drawable/nav_call"
            android:title="Call" />
        <item
            android:id="@+id/nav_friends"
            android:icon="@drawable/nav_friends"
            android:title="Friends" />
        <item
            android:id="@+id/nav_location"
            android:icon="@drawable/nav_location"
            android:title="Location" />
        <item
            android:id="@+id/nav_mail"
            android:icon="@drawable/nav_mail"
            android:title="Mail" />
        <item
            android:id="@+id/nav_task"
            android:icon="@drawable/nav_task"
            android:title="Tasks" />
    </group>
</menu>

在menu中嵌套了一个<group>标签,group表示一个组,android:checkableBehavior指定为single表示组中的所有菜单项都是单选。

在layout文件夹中加入nav_header.xml

<?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:background="?attr/colorPrimary"
    android:padding="10dp">

    <TextView
        android:id="@+id/username"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="tonygreendev@gmail.com"
        android:textColor="#FFF"
        android:textSize="14sp" />

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

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/icon_image"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:src="@drawable/nav_icon" />

</RelativeLayout>

修改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/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:layout_scrollFlags="scroll|enterAlways|snap"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

    </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:headerLayout="@layout/nav_header"
        app:menu="@menu/nav_menu" />
</android.support.v4.widget.DrawerLayout>

这里把之前的TextView换成了NavigationView,然后通过app:headerLayout和app:menu属性把刚才准备好的menu和headerLayout设置进去了。

同时也要处理菜单项的点击事件才行
修改MainActivity如下:

public class MainActivity extends AppCompatActivity {
    private DrawerLayout mDrawerLayout;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...
        NavigationView navView = (NavigationView) findViewById(R.id.nav_view);
        navView.setCheckedItem(R.id.nav_call);
        navView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                mDrawerLayout.closeDrawers();
                return true;
            }
        });
    }

效果如下:

4、FloatingActionButton

FloatingActionButton是Design Support库提供,可以实现悬浮按钮。默认使用colorAccent作为按钮的颜色,可以指定一个图标来表示这个按钮的作用。
修改activity_main.xml如下:

<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:layout_scrollFlags="scroll|enterAlways|snap"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

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

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

修改MainActivity如下:

public class MainActivity extends AppCompatActivity {
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...
        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
               Toast.makeText(MainActivity.this,"点击悬浮按钮了",Toast.LENGTH_LONG).show();
            }
        });
    }
    ...
}

显示效果如下:

5、Snackbar

Snackbar是一个提示工具,在Design Support库中。
Snackbar并不是Toast的替代品,两者之间有着不同的应用场景。Toast的作用是告诉用户现在发生了什么事,但同时用户只能被动接收这个事情。而Snackbar则进行了扩展,在提示中加入了可交互按钮,当用户点击按钮时可以执行额外的逻辑操作。

修改MainActivity如下:

public class MainActivity extends AppCompatActivity {
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...
        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "是否删除", Snackbar.LENGTH_INDEFINITE)
                        .setAction("撤销", new View.OnClickListener() {
                            @Override
                            public void onClick(View view) {
                                Toast.makeText(MainActivity.this, "数据已恢复", Toast.LENGTH_SHORT).show();
                            }
                        })
                        .show();
            }
        });
    }
}
 ...

这里调用了Snackbar的make()方法创建一个Snackbar对象,该方法第一个参数需要传入一个view,只要是当前界面布局的任意一个View都可以,Snackbar会使用这个View来自动查找最外层的布局,用于展示Snackbar。第二个和第三个参数不用解释。

接着调用setAction()方法来设置一个动作,从而让Snackbar不仅仅是一个提示,而是可以和用户交互的。

效果如下:

可以看到Snackbar从屏幕底部出现了,但是这个Snackbar竟然把悬浮按钮遮挡住了,尼玛啊!只需借助CoordinatorLayout就可以轻松搞定。

6、CoordinatorLayout

CoordinatorLayout是一个加强版的FrameLayout,在Design Support库中。CoordinatorLayout可以监听其所有子控件的各种事件,然后自动帮助我们做出最为合理的响应。

举个例子,刚才弹出的Snackbar提示将悬浮按钮遮挡了,如果能让CoordinatorLayout监听到Snackbar的弹出事件,那么它会自动将内部的FloatingActionBar向上偏移,从而确保不会被Snackbar遮挡住。

至于如何使用,只要把原来的FrameLayout替换一下就可以了。
修改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/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:layout_scrollFlags="scroll|enterAlways|snap"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

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

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

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

效果如下:

刚才说的是CoordinatorLayout可以监听其所有子控件的各种事件,但是Snackbar貌似并不是CoordinatorLayout的子控件啊,为什么它却可以监听到呢?

原因:在Snackbar的make()方法中传入第一个参数view,这个参数就是用来指定Snackbar是基于哪个View来触发的,刚才我们传入的是FloatingActionBar本身,而FloatingActionBar是CoordinatorLayout中的子控件,因此这个事件就理所当然能被监听到了。假如传一个DrawerLayout,那么Snackbar就会再次遮挡悬浮按钮,因为DrawerLayout不是CoordinatorLayout的子控件,CoordinatorLayout也就无法监听到Snackbar的弹出和隐藏事件了。so easy~~~

7、CardView 卡片式布局

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

<android.support.v7.widget.CardView 
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:elevation="5dp"
    app:cardCornerRadius="4dp">
   <TextView
            android:id="@+id/fruit_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_margin="5dp"
            android:textSize="16sp" />
</android.support.v7.widget.CardView>

app:cardCornerRadius属性用于指定卡片圆角的弧度,数值越大,圆角的弧度也越大。app:elevation属性指定卡片的高度,高度越大,投影范围也越大。

然后在CardView里放置了一个TextView,那么这个TextView就会显示在一张卡片当中了。

下面来实现水果列表,用到了RecyelerView、CardView。在app/build.gradle文件中声明这些依赖库才行:

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

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

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

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

在CoordinatorLayout中添加一个RecycleView,这样RecycleView就会占满这个布局。

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

}

在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"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="5dp"
    app:cardCornerRadius="4dp">

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

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

        <TextView
            android:id="@+id/fruit_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_margin="5dp"
            android:textSize="16sp" />
    </LinearLayout>
</android.support.v7.widget.CardView>

接下来准备一个适配器

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
    private Context mContext;
    private List<Fruit> mFruitList;

    public FruitAdapter(List<Fruit> mFruitList) {
        this.mFruitList = mFruitList;
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        private CardView cardView;
        private ImageView fruitImage;
        private TextView fruitName;

        public ViewHolder(View view) {
            super(view);
            cardView = (CardView) view;
            fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
            fruitName = (TextView) view.findViewById(R.id.fruit_name);
        }
    }

    @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);
        return holder;
    }

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

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

}

修改MainActivity

public class MainActivity extends AppCompatActivity {
    private DrawerLayout mDrawerLayout;
    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 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
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...
        initFruits();
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        GridLayoutManager layoutManager = new GridLayoutManager(this, 2);
        recyclerView.setLayoutManager(layoutManager);
        adapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(adapter);
    }

     ...
}

效果如下:

screenshot.jpg

这样图片就展示出来了,每个水果都是在一个CardView中,并且还有圆角和阴影。但是,发现Toolbar被遮挡了,擦擦擦~~~这时就需要AppBarLayout来解决了。

8、AppBarLayout

首先来分析一下为什么RecycleView会把Toolbar给遮挡住!
原因:由于RecycleView和Toolbar都放置在CoordinatorLayout中,而CoordinatorLayout是一个加强版的FrameLayout,那么FrameLayout中的所有控件在不进行明确定位情况下,默认都会摆放在布局的左上角,所以产生了遮挡。

传统情况下,使用偏移是唯一解决的办法,既让RecycleView向下偏移一个Toolbar的高度,从而保证不会遮挡到Toolbar。

这里使用AppBarLayout,Design Support库提供。实际上AppBarLayout是一个垂直方向的LinearLayout,它在内部做了很多滚动事件的封装,并使用了一些Material Degisn的设计理念。

使用AppBarLayout只需两步就可以解决刚才的遮挡问题!

  • 第一步将Toolbar嵌套到AppBarLayout中
  • 第二步给RecycleView指定一个布局行为

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

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

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

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


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

可以看到,首先定义一个AppBarLayout,并将Toolbar放置在了AppBarLayout里面,然后在RecycleView中使用app:layout_behavior属性,其中appbar_scrolling_view_behavior这个字符串由Design Support库提供。

当AppBarLayout接收到滚动事件的时候,它内部的子控件其实是可以指定如何去影响这些事件的。通过app:layout_scrollFlags="scroll|enterAlways|snap" 属性就能实现。

  • scroll:表示当RecycleView向上滚动的时候,Toolbar会跟着一起向上滚动并实现隐藏。
  • enterAlways:表示当RecycleView向下滚动的时候,Toolbar会跟着一起向下滚动并重新显示。
  • snap:表示当Toolbar还没有完全隐藏或显示的时候,会根据当前滚动的距离,自动选择是隐藏还是显示。

效果如下:

9、SwipeRefreshLayout 下拉刷新

SwipeRefreshLayout 由support-v4库提供。修改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/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

        ...

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

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


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

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

在RecycleView的外面又嵌套了一成SwipeRefreshLayout,这样RecycleView就自动拥有下拉刷新的功能了。

另外注意:由于RecycleView现在变成了SwipeRefreshLayout的子控件,因此之前使用app:layout_behavior声明的布局行为现在也要移到SwipeRefreshLayout中才行。

修改MainActivity:

public class MainActivity extends AppCompatActivity {
  
    private SwipeRefreshLayout swipeRefreshLayout;
  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...
        swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
        swipeRefreshLayout.setColorSchemeResources(R.color.colorAccent);
        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                refreshFruits();
            }
        });
    }

    private void refreshFruits() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        initFruits();
                        adapter.notifyDataSetChanged();
                        //表示刷新事件结束,并隐藏刷新进度条
                        swipeRefreshLayout.setRefreshing(false);
                    }
                });
            }
        }).start();
    }
     ...
}

setColorSchemeResources()方法来设置下拉刷新进度条的颜色。
setOnRefreshListener()方法设置下拉刷新的监听器。
swipeRefreshLayout.setRefreshing(false) 表示刷新事件结束,并隐藏刷新进度条。
效果如下:

10、CollapingToolbarLayout 可折叠式标题栏

CollapingToolbarLayout是一个作用于Toolbar基础之上的布局,它也是由Design Support库提供的。CollapingToolbarLayout可以让Toolbar的效果变得更加丰富,不仅仅是展示一个标题栏,而是能够实现非常华丽的效果。

CollapingToolbarLayout 是不能独立存在的,它只能作为AppBarLayout的直接子布局。而AppBarLayout又必须是CoordinatorLayout的子布局。

这里通过水果的详情页面作为展示。新建FruitActivity,布局为activity_fruit.xml。

整个布局采用分段方式编写,分为两个部分,一个是水果的标题栏,另一个是水果内容详情。

  1. 首先实现标题栏部分,使用CoordinatorLayout 作为最外层布局。
<?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.support.design.widget.CoordinatorLayout>
  1. 接着在NestedScrollView中嵌套一个AppBarLayout
<?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.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>
  1. 接下来在AppBarLayout中再嵌套一个CollapsingToolbarLayout
<?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.support.design.widget.AppBarLayout
          android:id="@+id/appBar"
          android:layout_width="match_parent"
          android:layout_height="250dp">

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

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

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

app:contentScrim属性用于指定CollapsingToolbarLayout在趋于折叠状态以及折叠之后的背景色,其实CollapsingToolbarLayout在折叠之后就是一个普通的Toolbar,那么背景肯定应该是colorPrimary了。

app:layout_scrollFlags属性之前见过,只不过之前是给Toolbar指定的,现在移到外面了。其中scroll表示CollapsingToolbarLayout会随着水果内容详情的滚动一起滚动; exitUntilCollapsed表示当CollapsingToolbarLayout随着滚动完成折叠之后就保留在界面上,不再移出屏幕。

4 . 接下来,在CollapsingToolbarLayout中定义标题栏的具体内容

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

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

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

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin" />
         </android.support.design.widget.CollapsingToolbarLayout>

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

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

app:layout_collapseMode用于指定当前控件在CollapsingToolbarLayout折叠过程中的折叠模式。其中ImageView指定成 parallax,表示会在折叠的过程中产生一定的错位偏移,这种视觉效果比较好;Toolbar指定成pin,表示在折叠过程中位置始终保持不变。

5 . 这样水果的标题栏就编写完成了,下面开始编写水果的内容详情部分,继续修改activity_fruit.xml

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

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

           <android.support.v7.widget.CardView
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:layout_marginBottom="15dp"
               android:layout_marginLeft="15dp"
               android:layout_marginRight="15dp"
               android:layout_marginTop="35dp"
               app:cardCornerRadius="4dp">

               <TextView
                   android:id="@+id/fruit_content_text"
                   android:layout_width="match_parent"
                   android:layout_height="wrap_content"
                   android:layout_margin="10dp" />
           </android.support.v7.widget.CardView>
       </LinearLayout>
   </android.support.v4.widget.NestedScrollView>

   <android.support.design.widget.FloatingActionButton
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_margin="16dp"
       android:src="@drawable/ic_comment"
       app:layout_anchor="@id/appBar"
       app:layout_anchorGravity="bottom|end" />
</android.support.design.widget.CoordinatorLayout>

水果详情页的最外层使用了一个NestedScrollView,注意它和AppBarLayout是平级的。

NestedScrollView是在ScrollView基础上增加了响应滚动事件的功能。由于CoordinatorLayout本身可以响应滚动事件了,因此在它的内部就需要使用RecycleView或者NestedScrollView这样的布局。另外,这里通过app:layout_behavior属性指定了一个布局行为,这和之前在RecycleView中的用法一样。

最后还加入了一个FloatingActionButton,它和AppBarLayout、
NestedScrollView是平级的。app:layout_anchor="@id/appBar"属性指定一个锚点,这样悬浮按钮就会出现在水果标题栏的区域里。

现在加入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 collapsingToolbar = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
        ImageView fruitImageView = (ImageView) findViewById(R.id.fruit_image_view);
        TextView fruitContentText = (TextView) findViewById(R.id.fruit_content_text);
        setSupportActionBar(toolbar);
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.setDisplayHomeAsUpEnabled(true);
        }
        collapsingToolbar.setTitle(fruitName);
        Glide.with(this).load(fruitImageId).into(fruitImageView);
        String fruitContent = generateFruitContent(fruitName);
        fruitContentText.setText(fruitContent);
    }

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

最后修改FruitAdapter

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
    ...
    @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;
    }

   ...
}

当点击首页水果的图片时,就会跳转到详情页。详情页的效果如下:

11、充分利用系统状态栏空间

想要让背景图能够和系统状态栏融合,需要借助android:fitsSystemWindows属性来实现。

在CoordinatorLayout、AppBarLayout、CollapsingToolbarLayout这种嵌套结构布局中,将控件的android:fitsSystemWindows属性指定为true

<?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/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

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

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

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

只是设置了android:fitsSystemWindows属性还是不行的,还必须在程序的主题中将状态栏颜色指定成透明色才行。为了兼容5.0以下版本,需要这么做:

在res下新建values-v21目录,然后再创建styles.xml文件

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

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

</resources>

这里定义了一个FruitActivityTheme主题,然后在FruitActivityTheme中将状态栏颜色指定成透明色。由于values-v21目录是Android5.0 及以上系统才会读取的,因此这么声明是没问题的。

为了适配5.0以下版本,还需要在values/styles.xml文件进行修改。

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

    </style>

</resources>

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

最后,在AndroidManifest.xml文件中修改FruitActivity,让其使用这个主题才行。

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

 

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值