美文网首页一起来学习Material Design
12(下) 最佳的UI体验---Material Design实

12(下) 最佳的UI体验---Material Design实

作者: 努力生活的西鱼 | 来源:发表于2017-02-23 15:52 被阅读114次

    本章的主要的知识点:

    • 卡片式布局
      1. CardView
      2. AppBarLayout
    • 下拉刷新
    • 可折叠式标题栏
      1. CollapsingToolbarLayout
      2. 充分利用系统状态栏空间
    高冷女.jpg

    12.5 卡片式布局

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

    12.5.1 CardView

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

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

    这里定义了一个CardView布局,我们可以通过app:cardCornerRadius属性指定卡片圆角的弧度,数值越大,圆角的弧度也越大。另外还可以通过app:elevation属性指定卡片的高度,高度值越大,投影范围也越大,但是投影效果越淡,高度值越小,投影范围也越小,但是投影效果越浓。这一点和FloatingActionButton是一致的。

    添加依赖

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

    这里添加了一个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.Toolbar>
            
            <android.support.v7.widget.RecyclerView
                android:id="@+id/recycler_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
                
            </android.support.v7.widget.RecyclerView>
    
            <android.support.design.widget.FloatingActionButton
                android:id="@+id/fab"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom|end"
                android:layout_margin="16dp"
                android:src="@drawable/done"
                app:elevation="8dp"/>
        </android.support.design.widget.CoordinatorLayout>
    
        ``
    
    </android.support.v4.widget.DrawerLayout>
    
    

    这里我们在CoordinatorLayout中添加了一个RecyclerView,给它指定了一个id,然后将宽度和高度都设置为match_parent,这样RecyclerView也就占满了整个布局的空间。

    定义一个实体类Fruit

    public class Fruit
    {
        private String name;
        
        private int imageId;
    
        public Fruit(String name, int imageId)
        {
            this.name = name;
            this.imageId = imageId;
        }
    
        public String getName()
        {
            return name;
        }
    
        public int getImageId()
        {
            return imageId;
        }
    }
    
    

    Fruit类中只有两个字段,name表示水果的名字,imageId表示水果对应图片的资源id。
    新建fruit_item.xml

    <android.support.v7.widget.CardView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:cardCornerRadius="4dp"
        android:layout_margin="5dp">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
    
            <ImageView
                android:id="@+id/fruit_image"
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:scaleType="centerCrop"/>
    
            <TextView
                android:id="@+id/fruit_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_margin="5dp"
                android:textSize="16sp"/>
    
        </LinearLayout>
    
    </android.support.v7.widget.CardView>
    

    这里使用了CardView来作为子项的最外层布局,从而使得RecyclerView中的每个元素都是在卡片当中的。CardView由于是一个FrameLayout,因此它没有什么方便的定位方式,这里我们只好在CardView中再嵌套一个LinearLayout,然后在LinearLayout中放置具体的内容。

    注意在ImageView中我们使用了一个scaleType属性,这个属性可以指定图片的缩放模式。由于各张水果图片的长宽比例可能不一致,为了让所有的图片都能填充满整个ImageView,这里使用了centerCrop模式,它可以让图片保持原有比例填充满ImageView,并将超出屏幕的部分裁减掉。

    新建FruitAdapter类,让这个类继承自RecyclerView.Adapter,并将泛型指定为FruitAdapter.ViewHolder

    public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder>
    {
    
        private Context mContext;
        
        private List<Fruit> mFruitList;
    
        public FruitAdapter(Context context, List<Fruit> mFruitList)
        {
            this.mContext = context;
            this.mFruitList = mFruitList;
        }
    
        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
        {
            if (mContext != null)
            {
                mContext = parent.getContext();
            }
            View view = LayoutInflater.from(mContext).inflate(R.layout.fruit_item,parent,false);
            return new ViewHolder(view);
        }
    
        @Override
        public void onBindViewHolder(ViewHolder holder, int position)
        {
            Fruit fruit = mFruitList.get(position);
            holder.fruitName.setText(fruit.getName());
            Glide.with(mContext).load(fruit.getImageId()).into(holder.fruitImage);
        }
    
        @Override
        public int getItemCount()
        {
            return mFruitList.size();
        }
    
        static class ViewHolder extends RecyclerView.ViewHolder
        {
            CardView cardView;
            ImageView fruitImage;
            TextView fruitName;
            
            public ViewHolder(View itemView)
            {
                super(itemView);
                cardView = (CardView) itemView;
                fruitImage = (ImageView) itemView.findViewById(R.id.fruit_image);
                fruitName = (TextView) itemView.findViewById(R.id.fruit_name);
            }
        }
    }
    
    

    首先调用Glide。with()方法并传入一个Context,Activity或Fragment参数,然后调用load()方法去加载图片,可以是一个URL地址,也可以是一个本地路径,或则是一个资源id,最后调用into()方法将图片设置到具体一个ImageView中就可以了。

    这次我从网上找的这些水果图片像素都非常高,如果不进行压缩就直接展示的话,很容易就会引起内存溢出。而使用Glide就完全不需要担心这回事,因为Glide在内部做了许多非常复杂的逻辑操作,其中就包括了图片压缩,我们只需要安心按照Glide的标准用法去加载图片就可以了。

    public class MainActivity extends AppCompatActivity
    {
    private Fruit[] fruits = {
                new Fruit("Apple",R.drawable.apple),
                new Fruit("Banana",R.drawable.banana),
                new Fruit("Orange",R.drawable.orange),
                new Fruit("Watermelon",R.drawable.watermelon),
                new Fruit("Pear",R.drawable.pear),
                new Fruit("Grape",R.drawable.grape),
                new Fruit("Pineapple",R.drawable.pineapple),
                new Fruit("Strawberry",R.drawable.strawberry),
                new Fruit("Cherry",R.drawable.cherry),
                new Fruit("Mango",R.drawable.mango)};
    
        private List<Fruit> fruitList = new ArrayList<>();
    
        private FruitAdapter adapter;
    
        private RecyclerView recyclerView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState)
        {
            ``
            initFruits();
            recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
            GridLayoutManager layoutManager = new GridLayoutManager(this,2);
            recyclerView.setLayoutManager(layoutManager);
            adapter = new FruitAdapter(this,fruitList);
            recyclerView.setAdapter(adapter);
        }
    
        public void initFruits()
        {
            fruitList.clear();
            for (int i = 0; i < 50; i++)
            {
                Random random = new Random();
                int index = random.nextInt(fruits.length);
                fruitList.add(fruits[index]);
            }
        }
    }
    

    在MainActivity中我们首先定义了一个数组,数组里面存放了很多个Fruit的实例,每个实例都代表着一种水果。然后在initFruit()方法中,先是清空一下fruitList中的数据,接着使用一个随机函数,从刚才定义的Fruit数组中随机挑选一个水果放入fruitList当中,这样每次打开程序看到的水果数据都是不同的。

    卡片式布局效果

    仔细观察可以看到Toolbar被RecyclerView挡住了,这就需要借助AppBarLayout了

    12.5.2 AppBarLayout

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

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

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

    <android.support.v4.widget.DrawerLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <android.support.design.widget.CoordinatorLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            
            <android.support.design.widget.AppBarLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
    
                <android.support.v7.widget.Toolbar
                    android:id="@+id/toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    android:background="?attr/colorPrimary"
                    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                    app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
                </android.support.v7.widget.Toolbar>
                
            </android.support.design.widget.AppBarLayout>
            
            <android.support.v7.widget.RecyclerView
                android:id="@+id/recycler_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:layout_behavior="@string/appbar_scrolling_view_behavior">
    
            </android.support.v7.widget.RecyclerView>
    
            ``
        </android.support.design.widget.CoordinatorLayout>
    
    

    我们首先定义了一个AppBarLayout,并将Toolbar放置在了AppBarLayout里面,然后在RecyclerView中使用app:layout_behavior属性指定了一个布局行为,其中appbar_scrolling_view_behavior这个字符串也是由Design Support库提供的。

    事实上,当RecyclerView滚动的时候就已经将滚动事件都通知给AppStoreBarLayout了只是我们还没进行处理而已。

    当AppBarLayout接收到滚动事件的时候,它内部的子控件是可以指定如何去影响这些事件的,通过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">
                </android.support.v7.widget.Toolbar>
    

    这里在Toolbar中添加了一个app:layout_scrollFlags属性,并将这个属性的值指定成了scroll|enterAlways|snap。其中,scroll表示当RecyclerView向上滚动的时候,Toolbar会跟着一起向上滚动并实现隐藏;enterAlways表示当RecyclerView向下滚动的时候,Toolbar会跟着一起向下滚动并重新现实,snap表示Toolbar还没有完全隐藏或显示的时候,会根据当前滚动的距离,自动选择是隐藏还是显示。

    12.6 下拉刷新

    谷歌为了让Android的下拉刷新风格能有一个统一的标准,于是在Material Design中制定了一个官方的设计规范。

    SwipeRefreshLayout就是用于实现下拉刷新功能的核心类,它是由support-v4库提供的。我们把想要实现下拉刷新功能的控件放置到SwipeRefreshLayout中,就可以迅速让这个控件支持下拉刷新。

    <android.support.v4.widget.DrawerLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <android.support.design.widget.CoordinatorLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            ``
    
            <android.support.v4.widget.SwipeRefreshLayout
                android:id="@+id/swipe_refresh"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:layout_behavior="@string/appbar_scrolling_view_behavior">
    
                <android.support.v7.widget.RecyclerView
                    android:id="@+id/recycler_view"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent">
    
                </android.support.v7.widget.RecyclerView>
    
            </android.support.v4.widget.SwipeRefreshLayout>
    
            ``
    

    我们在RecyclerView的外面又嵌套了一层SwipeRefreshLayout,这样RecyclerView就自动拥有下拉刷新功能了。另外需要注意,由于RecyclerView现在变成了SwipeRefreshLayout的子控件,因此之前使用app:layout_behavior声明的布局行为现在也要移到SwipeRefreshLayout中才行。

    虽然RecyclerView已经支持下拉刷新功能了,但是我们还要在代码中处理具体的刷新逻辑才行。

    public class MainActivity extends AppCompatActivity
    {
        ······
        private SwipeRefreshLayout swipeRefreshLayout;
    
        @Override
        protected void onCreate(Bundle savedInstanceState)
        {
           super.onCreate(savedInstanceState);
           setContentView(R.layout.activity_main);
    
           swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
            swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary);
            swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener()
            {
                @Override
                public void onRefresh()
                {
                    refreshFruits();
                }
            });
        }
       
       ······
    
        private void refreshFruits()
        {
            new Thread(new Runnable()
            {
                @Override
                public void run()
                {
                    try
                    {
                        Thread.sleep(2000);
                    } catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                    runOnUiThread(new Runnable()
                    {
                        @Override
                        public void run()
                        {
                            initFruits();;
                            adapter.notifyDataSetChanged();
                            swipeRefreshLayout.setRefreshing(false);
                        }
                    });
                }
            }).start();
        }
    
    }
    

    首先通过findViewById()方法拿到SwipeRefreshLayout的实例,然后调用setColorSchemeResources()方法来设置下拉刷新进度条的颜色,这里我们使用主题中的colorPrimary作为进度条的颜色了。接着调用setOnRefreshListener()方法来设置一个下拉刷新的监听器,当触发了下拉刷新操作的时候就会回调这个监听器的onRefresh()方法,然后我们在这里去处理具体的刷新逻辑就行了。

    通常情况下,onRefresh()方法中应该是去网络上请求最新的数据,然后再将这些数据展示出来。refreshFruits()方法中先是开启了一个线程,然后将线程沉睡两秒钟。之所以这麽做,是因为本地刷新操作速度非常快,如果不将线程沉睡的话,刷新立刻就结束了,从而看不到刷新的过程。沉睡结束之后,这里使用了runOnUiThread()方法将线程切换回主线程,然后调用initFruits()方法重新生成数据,接着再调用FruitAdapternotifyDataSetChanged()方法通知数据发生了变化,最后调用SwipeRefreshLayoutsetRefershing()方法并传入false,用于表示刷新事件结束,并隐藏刷新进度条。

    12.7 可折叠式标题栏

    虽说我们现在的标题栏是使用Toolbar来编写的,不过它看上去和传统的ActionBar其实没什么两样,只不过可以响应RecyclerView的滚动事件来进行隐藏和显示。而Material Design中并没有限定标题栏必须是长这样子的,事实上,我们可以根据自己的喜好随意定制标题栏的样式。

    12.7.1 CollapsingToolbarLayout

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

    CollapsingToolbarLayout是不能独立存在的,它在设计的时候就被限定只能作为AppBarLayout的直接子布局来使用。而AppBarLayout又必须是CoordinatorLayout的子布局。

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

    1.我们使用了新的布局CollapsingToolbarLayout,android:theme属性指定了一个ThemeOverlay.AppCompat.Dark.ActionBar的主题,app:contentScrim属性用于指定CollapsingToolbarLayout在趋于折叠状态以及折叠之后的背景色,其实CollapsingToolbarLayout在折叠之后就是一个普通的Toolbar,那么背景色肯定应该是colorPrimary了。app:layout_scrollFlags属性我们也是见过的,只不过之前是给Toolbar指定的,现在也移到外面来了。其中scroll表示CollapsingToolbarLayout会随着水果内容详情的滚动一起滚动,exitUntilCollapsed表示当CollapsingToolbarLayout随着滚动完成折叠之后就保留在界面上,不再移出屏幕。

    2.我们在CollapsingToolbarLayout中定义了一个ImageView和Toolbar。app:layout_collapseMode,它用于指定当前控件在CollapsingToolbarLayout折叠过程中的折叠模式,其中Toolbar指定成pin,表示在折叠过程中位置始终保持不变,ImageView指定成parallax,表示会在折叠的过程中产生一定的错位便宜,这种模式的视觉效果会非常好。

    3.水果内容详情的最外层布局使用了一个NestedScrollView,注意它和AppBarLayout是平级的。我们之前学过ScrollView的用法,它允许使用滚动的方式来查看屏幕以外的数据,而NestedScrollView在此基础上还增加了嵌套响应滚动事件的功能。由于CoordinatorLayout本身已经可以响应滚动事件了,因此我们在它的内部就需要使用NestedScrollView或RecyclerView这样的布局。另外,这里还通过app:layout_behavior属性指定了一个布局行为,这和之前在RecyclerView中的用法是一模一样的。

    不管是ScrollView还是NestedScrollView,他们的内部都只允许存在一个直接子布局。因此,如果我们想要在里面放入很多东西的话,通常都会先嵌套一个LinearLayout,然后再在LinearLayout中放入具体的内容就可以了。

    3.需要注意的是,这里为了让界面更加美观,我在CardView和TextView上都加一些边距。其中CardView的marginTop加了35dp的边距,这是为下面要编写的东西留出空间。

    4.这里加入了一个FloatingActionButton,它和AppBarLayout以及NestedScrollView是平级的,FloatingActionButton中使用app:layout_anchor属性指定了一个锚点,我们将锚点设置为AppBarLayout,这样悬浮按钮就会出现在水果标题栏的区域内,接着又使用app:layout_anchorGravity属性将悬浮按钮定位在标题栏区域的右下角。

    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);
        }
    
        //500个水果名去
        private String generateFruitContent(String fruitName)
        {
            StringBuilder fruitContent = new StringBuilder();
            for (int i = 0; i < 500; i++)
            {
                fruitContent.append(fruitName);
            }
            return fruitContent.toString();
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item)
        {
            switch (item.getItemId())
            {
                case android.R.id.home:
                    finish();
                    break;
            }
            return super.onOptionsItemSelected(item);
        }
    }
    
    

    通过Intent获取到传入的水果名和水果图片的资源id,然后通过findViewById()方法拿到刚才在布局文件中定义的各个控件的实例。接着就是使用了Toolbar的标准用法,将它作为ActionBar显示,并启用HomeAsUp按钮。HomeAsUp按钮的默认图标就是一个返回箭头。

    接下来开始填充界面上的内容,调用CollapsingToolbarLayout的setTitle()方法将水果名设置成当前界面的标题,然后使用Glide加载传入的水果图片,并设置到标题栏的ImageView上面。使用了一个generateFruitContent()方法将水果名循环拼接500次,从而生成了一个比较长的字符串,将它设置到了TextView上面。

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

    @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
        {
            if (mContext != null)
            {
                mContext = parent.getContext();
            }
            View view = LayoutInflater.from(mContext).inflate(R.layout.fruit_item,parent,false);
    
            final ViewHolder holder = new ViewHolder(view);
            holder.cardView.setOnClickListener(new View.OnClickListener()
            {
                @Override
                public void onClick(View v)
                {
                    int position = holder.getAdapterPosition();
                    Fruit fruit = mFruitList.get(position);
                    Intent intent = new Intent(mContext,FruitActivity.class);
                    intent.putExtra(FruitActivity.FRUIT_NAME,fruit.getName());
                    intent.putExtra(FruitActivity.FRUIT_IMAGE_ID,fruit.getImageId());
                    mContext.startActivity(intent);
                }
            });
    
            return holder;
        }
    
    

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

    水果的详情展示界面

    这个界面上的内容分为3部分,水果标题栏,水果内容详情和悬浮按钮。Toolbar和水果背景图完美地融合到了一起,既保证了图片的展示空间,又不影响Toolbar的任何功能,那个向左的箭头就是用来返回上一活动的。

    12.7.2 充分利用系统状态栏空间

    你会发现水果的背景图片和系统的状态栏总有一些不搭的感觉。

    只不过很可惜的是,在Android5.0系统之前,我们是无法对状态栏的背景或颜色进行操作的,那个时候也还没有Material Design的概念。但是Android5.0及之后的系统都是支持这个功能的,在Android5.0及之后的系统中,使用背景图和状态栏融合的模式,在之前的系统中使用普通的模式。

    想要让背景图能够和系统状态栏融合,需要借助android:fitsSystemWindows这个属性来实现。在CoordinatorLayout,AppBarLayout,CollapsingToolbarLayout这种嵌套结构的布局中,将控件的android:fitsSystemWindows属性指定成true,就表示该控件会出现在系统状态栏里。对应到我们的程序,那就是水果标题栏中的ImageView应该设置这个属性了。不过只给ImageView设置这个属性是没有用的,我们必须将ImageView布局结构中的所有父布局都设置上这个属性才可以。

    <android.support.design.widget.CoordinatorLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        tools:context="com.example.materialtest.FruitActivity"
        android:fitsSystemWindows="true">
    
        <android.support.design.widget.AppBarLayout
            android:id="@+id/appBar"
            android:layout_width="match_parent"
            android:layout_height="250dp"
            android:fitsSystemWindows="true">
    
            <android.support.design.widget.CollapsingToolbarLayout
                android:id="@+id/collapsing_toolbar"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                app:contentScrim="?attr/colorPrimary"
                app:layout_scrollFlags="scroll|exitUntilCollapsed"
                android:fitsSystemWindows="true">
    
                <ImageView
                    android:id="@+id/fruit_image_view"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:scaleType="centerCrop"
                    app:layout_collapseMode="parallax"
                    android:fitsSystemWindows="true"/>
    
                ········
            </android.support.design.widget.CollapsingToolbarLayout>
    
        </android.support.design.widget.AppBarLayout>
    
    ·················
    
    </android.support.design.widget.CoordinatorLayout>
    
    

    但是即使我们将android:fitsSystemWindows属性都设置好了还是没有用的,因为还必须在程序的主题中将状态栏颜色指定成透明色才行。指定成透明色的方法很简单,在主题中将android:statusBarColor这个属性的值指定成@android:color/transparent就可以了。但问题在于,android:statusBarColor这个属性是从API21,也就是Android5.0系统开始才有的,之前的系统无法指定这个属性。那么,系统差异性的功能实现就要从这里开始了。

    右击res目录--->New--->Directory,创建一个values-v21目录,然后右击values-v21目录--->New--->Values resource file,创建一个styles.xml文件。

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

    这里我们定义了一个FruitActivityTheme主题,它是专门给FruitActivity使用的。FruitActivityTheme的parent主题是AppTheme,也就是说,它继承了AppTheme中的所有特性。然后我们在FruitActivityTheme中将状态栏中的颜色指定成透明色,由于values-v21目录是只有Android5.0及以上的系统才回去读取的,因此这么生命是没有问题的。

    但是Android5.0之前的系统却无法识别FruitActivityTheme这个主题,因此我们还需要对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之前的系统无法指定状态栏的颜色,因此这里什么都不用做就可以了。

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

    这里使用android:theme属性单独给FruitActivity指定了FruitActivityTheme这个主题,这样我们就大功告成了。

    效果图

    相关文章

      网友评论

        本文标题:12(下) 最佳的UI体验---Material Design实

        本文链接:https://www.haomeiwen.com/subject/kbcbwttx.html