Android 5.0新特性之Material Design

1. Toolbar

  • Toolbar的强大之处在于:不仅继承了ActionBar的所有功能,而且灵活性很高,可以配合其他控件来完成一些Material Design的效果。
  • 新建一个项目,默认显示ActionBar,是因为项目中指定的主题含有ActionBar。
android:theme="@style/AppTheme"

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
  • 准备使用Toolbar替代ActionBar,指定一个不带ActionBar的主题,通常以下两种:
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
Light表示淡色主题,主体颜色淡色,陪衬颜色深色。
<style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
默认是深色主题,主体颜色深色,陪衬颜色淡色。
  • 我们需要了解几个名词colorPrimary、colorPrimaryDark、colorAccent、textColorPrimary、windowBackground和navigationBarColor所指定的位置。

2. 编写一个Toolbar

  • 布局activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<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="wrap_content"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

</FrameLayout>

说明:
1.xmlns:android 表示可以使用android:id等。xmlns:app 表示可以使用app:attribute。
2.整体是淡色主题,Toolbar上的各种要素自动使用深色系,字体颜色是黑色。为了体验更好,借助android:theme单独设置Toolbar的主题是深色主题,这样字体颜色就会是白色。
3.Toolbar单独设置为深色主题,菜单按钮弹出的菜单项也会变成深色主题,太难看,所以我们使用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);
    }
}

说明:外观和功能和ActionBar一致。
  • 修改标题栏上显示的文字内容
android:label="Fruits"
  • 添加action按钮
    新建res-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>

说明:
1.showAsAction指定按钮的显示位置。always:永远显示在Toolbar中,如果屏幕空间不够则不显示;ifRoom:屏幕空间足够的情况下显示在Toolbar中,不够的话显示在菜单中;never表示永远显示在菜单当中。
2.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,"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,"Settings",Toast.LENGTH_SHORT).show();
                break;
        }
        return true;
    }
}

说明:之前章节有讲,这里就不再赘述。

3. 显示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: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:textColor="#FFF00000"
        android:textSize="30sp" />

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

说明:
1.最外层是DrawerLayout,包含两个子控件,第一个控件是FrameLayout,第二个控件是TextView,前者显示主屏幕,后者显示滑动菜单内容。
2.第二个控件借助layout_gravity指定滑动出现的方式,left是左边,right是右边,start根据系统语言进行判断。

4. 主屏幕Toolbar左边添加导航按钮,实现点击显示滑动菜单

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        mDrawerlayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.setDisplayHomeAsUpEnabled(true);
            actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
        }
    }
    
说明:
1. 获取Drawerlayout控件的实例,用于设置滑动菜单的显示方式。
2. 获取ActionBar的实例,setDisplayHomeAsUpEnabled显示导航按钮,按钮显示出来,利用setHomeAsUpIndicator设置按钮图标。
case android.R.id.home:
    mDrawerlayout.openDrawer(GravityCompat.START);
    break;
    
说明:设置滑动菜单显示方式。

5. NavigationView

我们可以任意定制我们的滑动菜单的布局,不过谷歌提供了一个一种更好的方法——使用NavigationView,让滑动菜单页面的实现变得非常简单。

  • 添加依赖:
compile 'com.android.support:design:25.3.1'
compile 'de.hdodenhof:circleimageview:2.1.0'
  • 创建menu菜单nav_menu:
<?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>
        <item
            android:id="@+id/nav_friends"
            android:icon="@drawable/nav_friends"
            android:title="Friends"></item>
        <item
            android:id="@+id/nav_location"
            android:icon="@drawable/nav_location"
            android:title="Location"></item>
        <item
            android:id="@+id/nav_mail"
            android:icon="@drawable/nav_mail"
            android:title="Mail"></item>
        <item
            android:id="@+id/nav_task"
            android:icon="@drawable/nav_task"
            android:title="Tasks"></item>
    </group>
</menu>

说明:checkableBehavior表示item只可以单选。
  • 定制headerLayout,它的布局命名为nav_header:
<?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">

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

    <TextView
        android:id="@+id/username"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="fkq339@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" />
</RelativeLayout>
  • 修改activity_main:
<?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: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.design.widget.NavigationView>

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

说明:之前的滑动菜单为TextView,现在用NavigationView代替TextView。利用:app:headerLayout和app:menu设置我们刚才准备好的menu和headerLayout.
  • 处理点击事件

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        mDrawerlayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        NavigationView navigationview = (NavigationView) findViewById(R.id.nav_view);
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.setDisplayHomeAsUpEnabled(true);
            actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
        }
        navigationview.setCheckedItem(R.id.nav_call);
        navigationview.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                mDrawerlayout.closeDrawer(Gravity.START);
                return true;
            }
        });
    }

说明:获取NavigationView的实例,并且设置Call菜单项为默认选中,点击后回调onNavigationItemSelected方法,这个时候我们关闭滑动菜单。

6. FloatingActionButton

  • 布局:
<android.support.design.widget.FloatingActionButton
    app:elevation="8dp"
    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" />
    
说明:
1.elevation指定的是悬浮的高度。
2.layout_gravity让控件位于底部,end根据系统语言决定左边或者右边。
3.layout_margin表示控件周围有空隙,更加美观。
4.src 可以用一张图片展示在悬浮按钮上。
  • 点击事件:
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(this);

说明:并没有特殊之处,它和普通的button一样。

7. Snackbar

  • Snackbar不是Toast的替代品。
  • Toast作用是告诉用户现在发生了什么事情,用户被动接收;而Snackbar则允许在提示的过程中加入一个可交互的按钮,当用户点击按钮的时候可以执行一些额外的逻辑操作。
  • 举例子:点击按钮,执行删除的操作,Snackbar的交互就可以在点击后起到反悔的功能。
    @Override
    public void onClick(View v) {
        Snackbar.make(v,"Data deleted",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();
    }
    
说明:
1. Snackbar.make方法接收三个参数:第一个参数接收一个View,只要是当前布局的任意一个View就可以,Snackbar会使用这个View来自动查找最外层的布局,来展示Snackbar;第二个参数是Snackbar显示的内容;第三个参数是显示的时间。
2. 接着调用setAction来执行反悔的操作。

8. CoordinatorLayout

  • CoordinatorLayout是一个加强版的FrameLayout,可以监听所有子控件的各种事件,然后做出最为合理的响应。
  • Snackbar提示将悬浮按钮遮挡住了,而如果能让CoordinatorLayout监听到Snackbar的弹出事件,那么它自动会将内部的FloatingActionButton向上偏移,从而确保不会被Snackbar遮挡到。
  • 使用非常简单:替换下FrameLayout即可。
<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_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="16dp"
        android:src="@drawable/ic_done"
        app:elevation="8dp" />
</android.support.design.widget.CoordinatorLayout>

说明:虽然Snackbar不在CoordinatorLayout里面,但是Snackbar的make方法接收的第一个参数View是FloatingActionButton,也就是说Snackbar基于FloatingActionButton触发的。FloatingActionButton是CoordinatorLayout的子控件,自然可以监听到Snackbar。

9. CardView

CardView也是一个FrameLayout,只是额外提供了圆角和阴影等效果。以下是结合RecyclerView的示例。其中的知识点我们已经在第3章详细讲解,这里不在赘述。

  • 添加依赖:
    compile 'com.android.support:recyclerview-v7:25.3.1'
    compile 'com.android.support:cardview-v7:25.3.1'
    compile 'com.github.bumptech.glide:glide:3.7.0'
  • 整体布局:
 <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_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:layout_margin="16dp"
            android:src="@drawable/ic_done"
            app:elevation="8dp" />
    </android.support.design.widget.CoordinatorLayout>
  • 子项布局:
<?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"
    android:orientation="vertical"
    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>

说明:
1.CardView没有好的定位方式,所以里面包裹一个LinearLayout。
2.app:cardCornerRadius设置的是圆角的大小。
3.scaleType指的是图片的缩放模式。
  • 实体类
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 void setName(String name) {
        this.name = name;
    }

    public int getImageId() {
        return imageId;
    }

    public void setImageId(int imageId) {
        this.imageId = imageId;
    }
}
  • FruitAdapter类
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
    private Context mContext;
    private List<Fruit> mFruitList;

    static class ViewHolder extends RecyclerView.ViewHolder {

        CardView cardview;
        ImageView fruitimage;
        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);
        }
    }

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

    @Override
    public FruitAdapter.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(FruitAdapter.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:
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;
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);
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]);
    }
}

10. AppBarLayout

  • CardView示例中RecyclerView遮挡住了Toolbar,因为它俩都在CoordinatorLayout中,而CoordinatorLayout是一个FrameLayout,所以造成了这个bug.
  • 解决方法:借助AppBarLayout:第一步将Toolbar嵌套到AppBarLayout中,第二步给RecyclerView指定一个布局行为。
  • 修改activity_main:
<android.support.design.widget.AppBarLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <android.support.v7.widget.Toolbar
        app:layout_scrollFlags="scroll|enterAlways|snap"
        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>

说明:
1.RecyclerView借助app:layout_behavior属性指定一个布局行为,目的是建立Toolbar和RecyclerView之间的关联。
2.借助app:layout_behavior,Toolbar在接收到RecyclerView滚动事件的时候,会进行相应的操作。
3.scroll表示Recy向上滚动的时候,Toolbar会跟着一起向上滚动并实现隐藏;enterAlwys表示Recy向下滚动的时候,Toolbar会跟着一起向下滚动并重新显示。snap表示当Toolbar还没有完全隐藏或显示的时候,会根据当前滚动的距离,自动选择是隐藏还是显示。

11. SwipeRefreshLayout

  • 把想要实现下拉刷新功能的控件放置到SwipeRefreshLayout中,就可以迅速让这个控件支持下拉刷新。app:layout_behavior要放在SwipeRefreshLayout中。
<android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/swipe_refresh"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <android.support.v7.widget.RecyclerView
                android:id="@+id/recycler_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"></android.support.v7.widget.RecyclerView>
        </android.support.v4.widget.SwipeRefreshLayout>
  • 修改MainActiivty,处理刷新逻辑
swiperefreshlayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
        swiperefreshlayout.setColorSchemeColors(getResources().getColor(R.color.colorPrimary));
        swiperefreshlayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                refreshFruits();
            }
        });
        
private void refreshFruits() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    Thread.sleep(2000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        initFruits();
                        adapter.notifyDataSetChanged();
                        swiperefreshlayout.setRefreshing(false);
                    }
                });

            }
        }).start();
    }
说明:
1. setColorSchemeColors 处理下拉刷新的颜色。
2. onRefresh处理具体的刷新逻辑。
3. adapter.notifyDataSetChanged()通知页面刷新。
4. setRefreshing隐藏下拉刷新。

12. CollapsingToolbarLayout

  • 借助CollapsingToolbarLayout可根据自己的喜好随意定制出标题栏的样式,让Toolbar更加丰富,不仅仅是展示一个标题栏,而是能够实现非常华丽的效果。
  • CollapsingToolbarLayout不能独立存在,它在设计的时候就被限定只能作为AppBarLayout的直接子布局来使用。而AppBarLayout又必须是CoordinatorLayout的子布局。
  • 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:orientation="vertical">

    <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:scaleType="centerCrop"
                app:layout_collapseMode="parallax" />

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

说明:
1. ThemeOverlay.AppCompat.Dark.ActionBar是一个深色的主题,保证字体颜色是浅色系。
2. contentScrim用于指定趋于折叠状态以及折叠以后的背景色。CollapsingToolbarLayout折叠之后就是一个普通的Toolbar,背景色是colorPrimary.
3. layout_scrollFlags:scroll表示CollapsingToolbarLayout会随着水果内容详情的滚动一起滚动,exitUntilCollapsed表示当CollapsingToolbarLayout随着滚动完成折叠之后就保留在界面上,不再移出屏幕。
4. 高级版的标题栏是由普通的标题栏加上图片组合而成的。
5. layout_collapseMode 用于指定CollapsingToolbarLayout折叠过程中的折叠模式,其中Toolbar指定成pin,表示在折叠的过程中位置始终不变,ImageView指定成parallax,表示在折叠过程中产生一定的错误偏移。
  • activity_main 水果内容详情部分:
 <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="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_margin="10dp" />
            </android.support.v7.widget.CardView>
        </LinearLayout>
    </android.support.v4.widget.NestedScrollView>

说明:
1. NestedScrollView除了具有ScrollView的功能之外,还具有嵌套响应滚动事件的功能,并且指定了布局行为,向CoodinatorLayout传递滚动事件。
2. NestedScrollView只能有一个子布局,我们添加一个LinearLayout,里面是一个卡片式布局,布局里面是一个TextView,显示水果的内容详情。
  • 添加FloatingActionButton:
<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" />
说明:
1. FloatingActionButton和AppBarLayout以及NestedScrollView是平级的。
2. layout_anchor指定一个锚点,将锚点设置为AppBarLayout,这样悬浮按钮就会出现在水果标题栏的区域内。
3. layout_anchorGravity将悬浮按钮定位在标题栏区域的右下角。
  • FruitActivity:
public class FruitActivity extends AppCompatActivity {
    private static final String FRUIT_NAME = "fruit_name";
    private static final String FRUIT_IMAGE_ID = "fruit_image_id";

    @Override
    protected void onCreate(@Nullable 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 fruitContext = generateFruitContext(fruitName);
    }

    private String generateFruitContext(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);
    }
}

说明:
1. 通过intent获取传入的水果名称和水果图片的资源id,然后通过fv拿到布局文件中各个控件的实例。显示Toolbar,启用HomeAsUp按钮。
2. collapsingToolbar的setTilte设置当前页面的标题;使用Glide加载传入的水果图片。 onOptionsItemSelected方法中处理了HOMEASUP按钮的点击事件。
  • 修改FruitAdapter,设置点击事件:
@Override
    public FruitAdapter.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();
                Log.e("FruitAdapter","position:"+position);
                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;
    }
    
说明:通过Intent传递数据启动FruitActivity。

13. 充分利用系统状态栏空间

  • 借助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:fitsSystemWindows="true"
    android:layout_height="match_parent"
    android:orientation="vertical">

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

        <android.support.design.widget.CollapsingToolbarLayout
            android:fitsSystemWindows="true"
            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:fitsSystemWindows="true"
                android:id="@+id/fruit_image_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                app:layout_collapseMode="parallax" />
说明:
1. ImageView以及父类标签都要指定属性。
  • 指定状态栏颜色为透明色并且区分版本:
  1. 设置状态栏颜色为透明色是从Android 5.0系统开始才有的,之前的系统无法指定这个属性,所以需要区别对待。
  2. 创建values-v21目录,新建style.xml的resource file:
<resources>
    <style name="FruitActivityTheme" parent="AppTheme">
        <item name="android:statusBarColor">@android:color/transparent</item>
    </style>
</resources>

说明:
1.values-v21只有5.0才可以读取。
2.FruitActivityTheme专门给FruitActivity使用,parent主题是AppTheme,也就是说它继承了AppTheme的所有特性。
3.指定FruitActivityTheme为透明色。

3.values-styles.xml指定FruitActivity的5.0之前的主题设置:

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

说明:5.0之前无法指定状态栏的颜色,这里什么都不做即可。

4.配置FruitActivity主题

<activity android:name=".FruitActivity" android:theme="@style/FruitActivityTheme"></activity>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容