Android学习笔记之Material Design实战

来源:互联网 发布:淘宝放错类目降权 编辑:程序博客网 时间:2024/06/05 00:58

Material Design是在2014年Google I/O大会上重磅推出的一套全新的界面设计语言,是由谷歌的设计工程师们基于传统优秀的设计原则,结合丰富的创意和科学技术所发明的一套全新的界面设计语言,包含了视觉、运动、互动效果等特性。

为支持Material Design UI设计风格,谷歌在2015年的Google I/O大会上推出了一个Design Support库,这个库将Material Design中最具代表性的一些控件和效果进行了封装,使得开发者在即使不了解Material Design的情况下也能非常轻松地将自己地应用Material化。


一、Toolbar

Toolbar将会是我们接触地第一个Material控件,虽说对于Toolbar你暂时应该还是比较陌生地,但是对于它地另一个相关控件ActionBar,你应该有点熟悉。每个活动最顶部地那个标题栏其实就i是ActionBar,不过ActionBar由于其设计地原因,被限定只能位于活动地顶部,从而不能实现一些Material Design的效果,因此官方现在已经不再建议使用ActionBar了。

Toolbar的强大之处在于,它不仅继承了ActionBar的所有功能,而且灵活性很高,可以配合其他控件来完成一些Material Design的效果。

首先你要知道,任何一个新建的项目,默认都是会显示ActionBar的,那么这个ActionBar到底是哪里来的呢?其实这是根据项目中指定的主题来显示的,AndroidManifest.xml中如下所示:

<application

android:allowBackup="true"

android:icon="@mipmap/ic_launcher"

android:label="@string/app_name"

android:supportRtl="true"

android:theme=‘@style/AppTheme"

>

...

</application>

可以看到,这里使用android:theme属性指定了一个AppTheme的主题,那么这个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主题,我们之前所有的项目中自带的ActionBar就是因为指定了这个主题才出现的。

而现在我们准备使用Toolbar来替代ActionBar,因此需要指定一个不带ActionBar的主题,通常Theme.AppCompat.NoActionBar和ThemeAppCompat.Light.NoActionBar这两种主题可选,其中Theme.AppCompat.NoActionBar表示深色主题,它会将界面的主体颜色设成深色,陪衬颜色设成淡色。而Theme.AppCompat.Light.NoActionBar表示淡色主题,他会将界面的主体颜色设成淡色,陪衬颜色设成深色。具体效果你可以试一试,这里由于我们之前的程序一直都是以淡色为主的,那么我就选用淡色主题了了,如下所示:

<resources>

<!--Base application theme -->

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">

<!-- Customize your theme here. -->

<item name="colorPrimary">@color/colorPrimary</item>

<item name="colorPrimaryDark">@color/colorPrimaryDark</item>

<item name="colorAccent">@color/colorAccent</item>

</style>

</resources>

然后观察一下AppTheme中的属性重写,这里重写了colorPrimary、colorPrimaryDark和colorAccent这3个属性的颜色。除上述3个属性之外,我们还可以通过textColorPrimary、windowBackground和navigationBarColor等属性来控制更多位置的颜色。不过唯独colorAccent这个属性比较难理解,它不只是用来指定这样一个按钮的颜色,而是更多表达了一个强调的意思,比如一些控件的选中状态也会使用这个颜色。

现在我们已经将ActionBar隐藏起来了,那么接下来看一看如何使用Toolbar来替代ActionBar。修改activity_material_design_test.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:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

</FrameLayout>


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="@android:drawable/ic_menu_close_clear_cancel"        android:title="Backup"        app:showAsAction="always"        />    <item        android:id="@+id/delete"        android:icon="@android:drawable/ic_menu_delete"        android:title="Delete"        app:showAsAction="ifRoom"        />    <item        android:id="@+id/settings"        android:icon="@android:drawable/ic_menu_more"        android:title="Settings"        app:showAsAction="never"        /></menu>

使用app:showAsAction来指定按钮的显示位置,之所以这里再次使用了app命名空间,同样是为了能够兼容低版本的系统。showAsAction主要有以下几种值可选:always表示永远显示在Toolbar中,如果屏幕控件不够则不显示;ifRoom表示屏幕控件足够的情况下显示在Toolbar中,不够的化就显示在菜单当中;never则表示永远显示在菜单当中。注意,Toolbar中的action按钮只会显示图标,菜单中的action按钮只会显示文字。


主代码如下:

public class MaterialDesignTestActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_material_design_test);        initUI();    }    public void initUI(){        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);        setSupportActionBar(toolbar);                ActionBar actionBar = getSupportActionBar();        if(actionBar!=null){            actionBar.setDisplayHomeAsUpEnabled(true);            actionBar.setHomeAsUpIndicator(R.mipmap.ic_launcher);        }    }    public boolean onCreateOptionsMenu(Menu menu){        getMenuInflater().inflate(R.menu.toolbar,menu);        return true;    }    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;            default:        }        return true;    }}



二、滑动菜单

滑动菜单可以说是Material Design中最常见的效果之一了。所谓的滑动菜单就是将一些菜单选项隐藏起来,而不是放置在主屏幕上,然后通过滑动的方式将菜单显示出来。这种方式既节省了屏幕空间,又实现了非常好的动画效果,是Material Design中推荐的做法。

1.DrawerLayout


activity_material_design_test.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:popupTheme="@style/ThemeOverlay.AppCompat.Light"

/>

</FrameLayout>

<TextView

android:layout_width=‘match_parent"

android:layout_height="match_parent"

android:layout_gravity="start"

android:text="This is menu"

android:textSize="30sp"

android:background="#FFF"

/>

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

可以看到,这里最外层的控件使用了DrawerLayout,这个控件是由support-v4库提供的。DrawerLayout中放置了两个直接子控件,第一个子空间是FrameLayout,用于作为主屏幕显示的内容;第二个子控件这里使用了一个TextView,用于作为滑动菜单中显示的内容,起始使用什么都可以,DrawerLayout并没有限制只能使用固定的控件。

但是关于第二个子控件有一点需要注意,layout_gravity这个属性是必须指定的,因为我们需要告诉DrawerLayout滑动菜单是在屏幕的左边还是右边,指定left表示滑动菜单在左边,指定right滑动菜单在右边,这里指定start,表示会根据系统语言进行判断。

public class MaterialDesignTestActivity extends AppCompatActivity {    private DrawerLayout mDrawerLayout;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_material_design_test);        initUI();    }    public void initUI(){        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);        setSupportActionBar(toolbar);        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawaer_layout);                ActionBar actionBar = getSupportActionBar();        if(actionBar!=null){            actionBar.setDisplayHomeAsUpEnabled(true);            actionBar.setHomeAsUpIndicator(R.mipmap.ic_launcher);        }        /*findViewById(R.id.tv_menu).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                mDrawerLayout.closeDrawers();            }        });*/    }    public boolean onCreateOptionsMenu(Menu menu){        getMenuInflater().inflate(R.menu.toolbar,menu);        return true;    }    public boolean onOptionsItemSelected(MenuItem item){        switch(item.getItemId()){            case android.R.id.home:                mDrawerLayout.openDrawer(GravityCompat.START);                break;            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;            default:        }        return true;    }}


2.NavigationView

NavigationView是Design Support库中提供的一个控件,它不仅是严格按照MaterialDesign的要求来进行设计的,而且还可以将滑动菜单页面的实现变得非常简单。


首先,这个控件是Design Support库中提供的,俺么我们就需要将这个库引入到项目中才行。打开app/build.gradle文件,再dependencies闭包中添加如下内容:

dependencies{

compile fileTree(dir:'libs',include:['*.jar'])

compile 'com.android.support.appcompat-v7:24.2.1'

testCompile 'junit:junit:4.12'

compile 'com.android.support;design;24.2.1'

compile 'de.hdodenhof:circleimageview:2.1.0'

}

这里添加了两行依赖关系,第一行就是Design Support库,第二行是一个开源项目CircleImageView,它可以用来轻松实现图片图形化的功能,我们待会就会用到它。CircleImageView的项目主页地址是:https://github.com/hdodenhof/CircleImageView。

在开始使用之前,我们还需要提前准备好两个东西:menu和headerLayout。menu是用来在NavigationView中显示具体的菜单项的,headerLayout则是用来在NavigationView中显示头部布局的。

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="@android:drawable/ic_menu_call"            android:title="Call"            />        <item            android:id="@+id/nav_friends"            android:icon="@android:drawable/ic_menu_agenda"            android:title="Friends"            />        <item            android:id="@+id/nav_location"            android:icon="@android:drawable/ic_menu_mylocation"            android:title="Location"            />        <item            android:id="@+id/nav_mail"            android:icon="@android:drawable/ic_menu_report_image"            android:title="Mail"            />        <item            android:id="@+id/nav_task"            android:icon="@android:drawable/ic_menu_recent_history"            android:title="Tasks"            />    </group></menu>

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:padding="10dp"    android:background="?attr/colorPrimary"    >    <de.hdodenhof.circleimageview.CircleImageView        android:id="@+id/icon_iamge"        android:layout_width="70dp"        android:layout_height="70dp"        android:src="@mipmap/ic_launcher"        android:layout_centerInParent="true"        />    <TextView        android:id="@+id/mail"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_alignParentBottom="true"        android:text="tonygreen@gmail.com"        android:textColor="#FFF"        android:textSize="14sp"        />    <TextView        android:id="@+id/username"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_above="@id/mail"        android:text="Tony Green"        android:textColor="#FFF"        android:textSize="14sp"        /></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"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:id="@+id/drawaer_layout"    >    <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:id="@+id/tv_menu"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_gravity="start"        android:text="This is menu"        android:textSize="30sp"        android:background="#FFF"        />-->    <android.support.design.widget.NavigationView        android:id="@+id/nav_view"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_gravity="start"        app:menu="@menu/nav_menu"        app:headerLayout="@layout/nav_header"        /></android.support.v4.widget.DrawerLayout>

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

public class MaterialDesignTestActivity extends AppCompatActivity {    private DrawerLayout mDrawerLayout;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_material_design_test);        initUI();    }    public void initUI(){        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);        setSupportActionBar(toolbar);        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawaer_layout);        NavigationView navView = (NavigationView) findViewById(R.id.nav_view);        ActionBar actionBar = getSupportActionBar();        if(actionBar!=null){            actionBar.setDisplayHomeAsUpEnabled(true);            actionBar.setHomeAsUpIndicator(R.mipmap.ic_launcher);        }        navView.setCheckedItem(R.id.nav_call);        navView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener(){            @Override            public boolean onNavigationItemSelected(@NonNull MenuItem item) {                mDrawerLayout.closeDrawers();                return true;            }        });        /*findViewById(R.id.tv_menu).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                mDrawerLayout.closeDrawers();            }        });*/    }    public boolean onCreateOptionsMenu(Menu menu){        getMenuInflater().inflate(R.menu.toolbar,menu);        return true;    }    public boolean onOptionsItemSelected(MenuItem item){        switch(item.getItemId()){            case android.R.id.home:                mDrawerLayout.openDrawer(GravityCompat.START);                break;            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;            default:        }        return true;    }}


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


1.FloatingActionButton

FloatingActionButton是Design Support库中提供的一个控件,这个控件可以帮助我们比较轻松地实现悬浮按钮地效果。

布局代码如下所示:

<?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"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:id="@+id/drawaer_layout"    >    <FrameLayout    android:layout_width="match_parent"    android:layout_height="match_parent"    >        <android.support.v7.widget.Toolbar        android:id="@+id/toolbar"        android:layout_width="match_parent"        android:layout_height="?attr/actionBarSize"        android:background="?attr/colorPrimary"        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"        />        <android.support.design.widget.FloatingActionButton            android:id="@+id/fab"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_gravity="bottom|end"            android:layout_margin="16dp"            android:src="@android:drawable/ic_menu_share"            app:elevation="8dp"            />    </FrameLayout>    </android.support.v4.widget.DrawerLayout>

这里使用app:elevation属性来给FloatingActionButton指定一个高度值,高度值越大,投影范围也越大,但是投影效果越淡,高度值越小,投影范围也越小,但是投影效果越浓。

代码中FloatingActionButton点击事件处理如下:

FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);fab.setOnClickListener(new View.OnClickListener(){    @Override    public void onClick(View view) {        Toast.makeText(MaterialDesignTestActivity.this,"FAB clicked",Toast.LENGTH_SHORT).show();    }});


2.Snackbar

提示工具Snackbar并不是Toast的替代品,它们两者之间有着不同的应用场景。Toast的作用是告诉用户现在发生了什么事情,但同时用户只能被动接收这个事情,因为没有什么办法让用户进行选择。而Snackbar则在这方面进行了扩展,它允许在提示当中加入一个可交互按钮,当用户点击按钮的时候可以执行一些额外的逻辑操作。打个比方,如果我们在执行删除操作的时候只弹出一个Toast提示,那么用户要是误删了后果很严重,但是如果我们增加一个Undo按钮,就相当于给用户提供了一种弥补措施,从而大大降低了事故发生的概率,提升了用户体验。

Snackbar的用法也非常简单,它和Toast是基本相似的,只不过可以额外增加一个按钮的点击事件。

代码中实现如下所示:

FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);fab.setOnClickListener(new View.OnClickListener(){    @Override    public void onClick(View view) {        Snackbar.make(view,"Data delete", Snackbar.LENGTH_SHORT).                setAction("Undo",new View.OnClickListener(){                    @Override                    public void onClick(View view) {                        Toast.makeText(MaterialDesignTestActivity.this,"Data restored",Toast.LENGTH_SHORT).show();                    }                }).show();    }});

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

不管是出现还是消失,Snackbar都是带有动画小伙的,因此视觉体验也会比较好。


不过你有没有发现一个bug,这个Snackbar竟然将我们的悬浮按钮给遮挡住了。虽然说也不是什么重大问题,因为Snackbar过一会儿就会自动消失,但这种用户体验总归是不友好的。有没有什么办法能解决一下呢?当然有,只需要借助CoordinatorLayout就可以轻松解决。


3.CoordinatorLayout

CoordinatorLayout可以说是一个加强版的FrameLayout,这个库也是由Design Support库提供的。它在普通情况下的作用和FrameLayout基本一致。事实上,CoordinatorLayout可以监听其所有子控件的各种事件,然后自动帮助我们做出最为合理的响应。

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

将原布局中的FrameLayout替换成CoordinatorLayout,代码如下:

<?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"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:id="@+id/drawaer_layout"    >    <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="@android:drawable/ic_menu_share"            app:elevation="8dp"            />    </android.support.design.widget.CoordinatorLayout>    <!--<TextView        android:id="@+id/tv_menu"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_gravity="start"        android:text="This is menu"        android:textSize="30sp"        android:background="#FFF"        />-->    <android.support.design.widget.NavigationView        android:id="@+id/nav_view"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_gravity="start"        app:menu="@menu/nav_menu"        app:headerLayout="@layout/nav_header"        /></android.support.v4.widget.DrawerLayout>

由于CoordinatorLayout本身就是一个加强版的FrameLayout,因此这种替换不会有任何的副作用。

可以看到,悬浮按钮自动向上偏移了Snackbar的同等高度,从而确保不会被遮挡住,当Snackbar消失的时候,悬浮按钮会自动向下偏移回到原来位置。

另外悬浮按钮的向上和向下偏移也是伴随着动画效果的,且和Snackbar完全同步,整体效果看上去特别赏心悦目。

不过我们回过头来再思考一下,刚才说的是CoordinatorLayout可以监听其所有子控件的各种事件,但是Snackbar好像并不是CoordiantorLayout的子控件吧,为什么它却可以被监听到呢?

其实道理很简单,还记得我们再Snackbar的make()方法中传入的第一个参数吗?这个参数就是用来指定Snackbar是基于哪个View来出发的,刚才我们传入的是FloatingActionButton本身,而其是CoordinatorLayout中的子控件,因此这个事件就理所应当能被监听到了。你可以做个试验,如果给Snackbar的make()方法出入一个DrawerLayout,那么Snackbar就会再次遮挡住悬浮按钮,因为DrawerLayout不是CoordinatorLayout的子控件,CoordinatorLayout也就无法监听到Snackbar的弹出和隐藏事件了。


四、卡片式布局

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

1.CardView

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

eg.

<android.support.v7.widget.CardView

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属性指定卡片的高度,高度值越大,投影范围也越大,但是投影效果越淡,高度值越小,投影范围也越小,但是投影效果越浓。

添加波纹点击效果

默认情况,CardView是不可点击的,并且没有任何的触摸反馈效果。触摸反馈动画在用户点击CardView时可以给用户以视觉上的反馈。为了实现这种行为,你必须提供一下属性:

<android.support.v7.widget.CardView  ...  android:clickable="true"  android:foreground="?android:attr/selectableItemBackground">  ...</android.support.v7.widget.CardView>

使用android:foreground=”?android:attr/selectableItemBackground”可以使CardView点击产生波纹的效果,有触摸点向外扩散。

常见属性

cardBackgroundColor -----》 设置cardview背景颜色

cardCornerRadius -----》设置cardview 圆角的半径

cardElevation----》设置cardview的阴影

contentPadding-----》设置cardview的内间距

5.x上面可以设置点击水波纹效果

android:foreground="?attr/selectableItemBackground"

cardPreventCornerOverlap ------》防止api20之前圆角与内容重叠 ,在21以后不会重叠

cardUseCompatPadding ----》设置为true 可以为不同L版提供内间距兼容 ,默认为false

eg.实现图片列表

依赖库:

dependencies {    compile fileTree(dir: 'libs', include: ['*.jar'])    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {        exclude group: 'com.android.support', module: 'support-annotations'    })    compile 'com.android.support:appcompat-v7:25.3.1'    testCompile 'junit:junit:4.12'    compile 'com.android.support:design:25.3.1'    compile 'com.github.bumptech.glide:glide:3.7.0'    compile 'com.android.support:recyclerview-v7:25.3.1'    compile 'de.hdodenhof:circleimageview:2.1.0'    compile 'com.android.support:cardview-v7:25.3.1'}

这里添加了一个Glide库的依赖。Glide是一个超级强大的图片加载库,它不仅可以用于加载本地图片,还可以加载网络图片、GIF图片、甚至是本地视频。最重要的是,Glide的用法非常简单,只需一行代码就能轻松实现复杂的图片加载功能。Glide的项目主页地址是:https://github.com/bumptech.glide。

主布局代码:
<?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"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:id="@+id/drawaer_layout"    >    <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.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="@android:drawable/ic_menu_share"            app:elevation="8dp"            />    </android.support.design.widget.CoordinatorLayout>    <!--<TextView        android:id="@+id/tv_menu"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_gravity="start"        android:text="This is menu"        android:textSize="30sp"        android:background="#FFF"        />-->    <android.support.design.widget.NavigationView        android:id="@+id/nav_view"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_gravity="start"        app:menu="@menu/nav_menu"        app:headerLayout="@layout/nav_header"        /></android.support.v4.widget.DrawerLayout>
定义一个实体类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 void setName(String name) {        this.name = name;    }    public int getImageId() {        return imageId;    }    public void setImageId(int imageId) {        this.imageId = imageId;    }}
RecyclerView子项布局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_iamge"            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来作为子项的最外层布局,CardView由于是一个FrameLayout,因此没有什么方便的定位方式,这里我们只好再其中再嵌入一个LinearLayout,然后在里面再放置具体内容。


为RecyclerView准备一个适配器,新建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_iamge);            fruitName = (TextView) view.findViewById(R.id.fruit_name);        }    }    public FruitAdapter(List<Fruit> fruitList){        mFruitList=fruitList;    }    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);    }    public int getItemCount(){        return mFruitList.size();    }}

主代码如下所示:

public class MaterialDesignTestActivity extends AppCompatActivity {    private DrawerLayout mDrawerLayout;    private Fruit[] fruits = {new Fruit("Apple",R.mipmap.ic_launcher),new Fruit("Banana",R.mipmap.ic_launcher),            new Fruit("Orange",R.mipmap.ic_launcher),new Fruit("Watermelon",R.mipmap.ic_launcher),            new Fruit("Pear",R.mipmap.ic_launcher),new Fruit("Grape",R.mipmap.ic_launcher),            new Fruit("Pineapple",R.mipmap.ic_launcher),new Fruit("Strawberry",R.mipmap.ic_launcher),            new Fruit("Cherry",R.mipmap.ic_launcher),new Fruit("Mango",R.mipmap.ic_launcher)    };    private List<Fruit> fruitList = new ArrayList<>();    private FruitAdapter adapter;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_material_design_test);        initUI();        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);    }    public void initUI(){        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);        setSupportActionBar(toolbar);        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawaer_layout);        NavigationView navView = (NavigationView) findViewById(R.id.nav_view);        ActionBar actionBar = getSupportActionBar();        if(actionBar!=null){            actionBar.setDisplayHomeAsUpEnabled(true);            actionBar.setHomeAsUpIndicator(R.mipmap.ic_launcher);        }        navView.setCheckedItem(R.id.nav_call);        navView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener(){            @Override            public boolean onNavigationItemSelected(@NonNull MenuItem item) {                mDrawerLayout.closeDrawers();                return true;            }        });        /*findViewById(R.id.tv_menu).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                mDrawerLayout.closeDrawers();            }        });*/        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);        fab.setOnClickListener(new View.OnClickListener(){            @Override            public void onClick(View view) {                Snackbar.make(view,"Data delete", Snackbar.LENGTH_SHORT).                        setAction("Undo",new View.OnClickListener(){                            @Override                            public void onClick(View view) {                                Toast.makeText(MaterialDesignTestActivity.this,"Data restored",Toast.LENGTH_SHORT).show();                            }                        }).show();            }        });    }    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]);        }    }    public boolean onCreateOptionsMenu(Menu menu){        getMenuInflater().inflate(R.menu.toolbar,menu);        return true;    }    public boolean onOptionsItemSelected(MenuItem item){        switch(item.getItemId()){            case android.R.id.home:                mDrawerLayout.openDrawer(GravityCompat.START);                break;            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;            default:        }        return true;    }}


运行后,列表实现了,但同时我们也会发现之前的Toolbar被RecyclerView给挡住了,这个问题又该怎么解决呢?这就需要借助到另外一个工具了——AppBarLayout.


2.AppBarLayout

CoordinatorLayout就是要给加强版的FrameLayout,解决上述遮挡问题传统情况下,使用偏移是唯一的解决办法,即让RecyclerView向下偏移一个Toolbar的高度,从而保证不会遮挡到Toolbar。

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


解决步骤:

第一步将Toolbar嵌套到AppBarLayout中,第二步给RecyclerView指定一个布局行为。

主布局代码修改后如下所示:

<?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"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:id="@+id/drawaer_layout"    >    <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"                app:layout_scrollFlags="scroll|enterAlways|snap"                />        </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.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="@android:drawable/ic_menu_share"            app:elevation="8dp"            />    </android.support.design.widget.CoordinatorLayout>    <!--<TextView        android:id="@+id/tv_menu"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_gravity="start"        android:text="This is menu"        android:textSize="30sp"        android:background="#FFF"        />-->    <android.support.design.widget.NavigationView        android:id="@+id/nav_view"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_gravity="start"        app:menu="@menu/nav_menu"        app:headerLayout="@layout/nav_header"        /></android.support.v4.widget.DrawerLayout>

可以看到,布局文件并没有太大的变化,我们首先定义了一个AppBarLayout,并将Toolbar放置在了AppBarLayout里面,然后再RecyclerView中使用app:layout_behavior属性指定了一个布局行为。其中appbar_scrolling_view_behavior这个字符串也是由Design Support库提供的。这样就解决了RecyclerView遮挡住Toolbar的问题。

看看滚动过程中如何体现Material Design风格的,事实上,当RecyclerView滚动的时候就已经将滚动事件都通知给AppBarLayout了。当AppBarLayout接收到滚动事件的时候,它内部的子控件其实是可以指定如何去影响这些事件的,通过app:layout_scrollFlags属性就能实现。

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


五、下拉刷新

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

<?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"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:id="@+id/drawaer_layout"    >    <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"                app:layout_scrollFlags="scroll|enterAlways|snap"                />        </android.support.design.widget.AppBarLayout>        <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.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="@android:drawable/ic_menu_share"            app:elevation="8dp"            />    </android.support.design.widget.CoordinatorLayout>    <!--<TextView        android:id="@+id/tv_menu"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_gravity="start"        android:text="This is menu"        android:textSize="30sp"        android:background="#FFF"        />-->    <android.support.design.widget.NavigationView        android:id="@+id/nav_view"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_gravity="start"        app:menu="@menu/nav_menu"        app:headerLayout="@layout/nav_header"        /></android.support.v4.widget.DrawerLayout>

这里我们在RecyclerView外面又嵌套了一层SwipeRefreshLayout,这样RecyclerView现在变成了SwipeRefreshLayout的子控件,因此前面使用的app:layout_behavior声明的布局行为现在也要移到SwipeRefreshLayout中才行。

public class MaterialDesignTestActivity extends AppCompatActivity {    private DrawerLayout mDrawerLayout;    private Fruit[] fruits = {new Fruit("Apple",R.mipmap.ic_launcher),new Fruit("Banana",R.mipmap.ic_launcher),            new Fruit("Orange",R.mipmap.ic_launcher),new Fruit("Watermelon",R.mipmap.ic_launcher),            new Fruit("Pear",R.mipmap.ic_launcher),new Fruit("Grape",R.mipmap.ic_launcher),            new Fruit("Pineapple",R.mipmap.ic_launcher),new Fruit("Strawberry",R.mipmap.ic_launcher),            new Fruit("Cherry",R.mipmap.ic_launcher),new Fruit("Mango",R.mipmap.ic_launcher)    };    private List<Fruit> fruitList = new ArrayList<>();    private FruitAdapter adapter;    private SwipeRefreshLayout swipeRefresh;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_material_design_test);        initUI();        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);        swipeRefresh = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);        swipeRefresh.setColorSchemeResources(R.color.colorPrimary);        swipeRefresh.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();;                        swipeRefresh.setRefreshing(false);                    }                });            }        }).start();    }    public void initUI(){        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);        setSupportActionBar(toolbar);        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawaer_layout);        NavigationView navView = (NavigationView) findViewById(R.id.nav_view);        ActionBar actionBar = getSupportActionBar();        if(actionBar!=null){            actionBar.setDisplayHomeAsUpEnabled(true);            actionBar.setHomeAsUpIndicator(R.mipmap.ic_launcher);        }        navView.setCheckedItem(R.id.nav_call);        navView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener(){            @Override            public boolean onNavigationItemSelected(@NonNull MenuItem item) {                mDrawerLayout.closeDrawers();                return true;            }        });        /*findViewById(R.id.tv_menu).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                mDrawerLayout.closeDrawers();            }        });*/        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);        fab.setOnClickListener(new View.OnClickListener(){            @Override            public void onClick(View view) {                Snackbar.make(view,"Data delete", Snackbar.LENGTH_SHORT).                        setAction("Undo",new View.OnClickListener(){                            @Override                            public void onClick(View view) {                                Toast.makeText(MaterialDesignTestActivity.this,"Data restored",Toast.LENGTH_SHORT).show();                            }                        }).show();            }        });    }    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]);        }    }    public boolean onCreateOptionsMenu(Menu menu){        getMenuInflater().inflate(R.menu.toolbar,menu);        return true;    }    public boolean onOptionsItemSelected(MenuItem item){        switch(item.getItemId()){            case android.R.id.home:                mDrawerLayout.openDrawer(GravityCompat.START);                break;            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;            default:        }        return true;    }}


六、可折叠式标题栏

1.CollapsingToolbarLayout

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

不过,CollapsingToolbarLayout是不能独立存在的,它在设计的时候就被限定只能作为AppBarLayout的直接子布局来使用,而AppBarLayout又必须是CoordinatorLayout的子布局,因此实现该功能要综合运用前面所学的各种知识。

eg.实现列表详情展示页

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"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="com.eastelsoft.nuonuo.activity.FruitActivity">    <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:id="@+id/fruit_image_view"                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: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:layout_width="wrap_content"                    android:layout_height="wrap_content"                    android:id="@+id/fruit_content_text"                    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="@android:drawable/ic_menu_share"        app:layout_anchor="@id/appBar"        app:layout_anchorGravity="bottom|end"        /></android.support.design.widget.CoordinatorLayout>

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

app:layout_scrollFlags属性之前用过,scroll表示CollapsingToolbarLayout会随着水果内容详情的滚动一起滚动,exitUntilCollapsed表示当CollapsingToolbarLayout随着滚动完成折叠之后就保留在界面上,不再移出屏幕。

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

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

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

FruitActivity代码如下:

public class FruitActivity extends AppCompatActivity {    public static final String FRUIT_NAME="fruit_name";    public static final String FRUIT_IMAGE_ID="fruit_image_id";    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_fruit);        Intent intent = getIntent();        String fruitName = intent.getStringExtra(FRUIT_NAME);        int fruitImageId = intent.getIntExtra(FRUIT_IMAGE_ID,0);        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);        CollapsingToolbarLayout 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();    }    public boolean onOptionsItemSelected(MenuItem item){        switch(item.getItemId()){            case android.R.id.home:                finish();                return true;        }        return super.onOptionsItemSelected(item);    }}

FruitAdapter中添加点击事件:

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


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

虽然说现在水果详情展示界面的效果已经非常华丽了,但着并不代表我们不能再进一步地提升。观察一下,你会发现水果地背景图片和系统地状态栏总有一些不搭地感觉,如果我们能将背景图和状态栏融合到一起,那这个视觉体验绝对能提升好几个档次。

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

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

<?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"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.eastelsoft.nuonuo.activity.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:layout_width="match_parent"            android:layout_height="match_parent"            android:id="@+id/fruit_image_view"            android:scaleType="centerCrop"            app:layout_collapseMode="parallax"            android:fitsSystemWindows="true"            />        <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: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:layout_width="wrap_content"                android:layout_height="wrap_content"                android:id="@+id/fruit_content_text"                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="@android:drawable/ic_menu_share"    app:layout_anchor="@id/appBar"    app:layout_anchorGravity="bottom|end"    /></android.support.design.widget.CoordinatorLayout>
但是,即使我们将android:fitsSystemWindows属性都设置好了还是没有用的,因为还必须在程序的主题中将状态栏颜色指定成透明色才行。指定成透明色的方法很简单,在主题中将android:statusBarColor这个属性的值指定成@android:color/transparent就可以了。但问题在于,android:statusBarColor这个属性是从API21,也就是Android5.0系统开始才有的,之前的系统无法指定这个属性,。那么,系统差异型的功能实现就要从这里开始了。
在res中创建values-21目录,创建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>
但是,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之前的系统无法指定状态栏的颜色,因此这里什么都不做就可以了。
最后,我们还需要让FruitActivity使用这个主题才可以,修改AndroidManifest.xml中的代码,如下所示:
<activity android:name=".activity.FruitActivity"    android:theme="@style/FruitActivityTheme"    ></activity

总结

这里我们充分利用了Design Support库、support-v4库、appcompat-v7库,以及一些开源项目来实现一个高度Material化的应用程序。不过说到底,我让然还是在以一个开发者的思维给你讲解Material Design,侧重于如何去实现这些效果。而实际上,Material Design的设计思维和设计理念才是更加重要的东西,当然这部分内容应该是UI设计人员去学习的,如果你也感兴趣的化,可以参考一下Material Design的官方文章:https://material.google.com。



原创粉丝点击