1.Navigation View
对于应用程序,它代表着一个标准的导航菜单。菜单内容可以由菜单资源文件填充。
NavigationView通常放在一个DrawerLayout里面。
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<!-- your content layout -->
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="LinearLayoutManager" />
<android.support.design.widget.NavigationView
android:id="@+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/drawer_header"
app:menu="@menu/drawer" />
</android.support.v4.widget.DrawerLayout>
这里有两个需要注意的属性:
app:headerLayout="@layout/drawer_header"(可选的)控制着header的布局,app:menu="@menu/drawer"是菜单资源用于填充导航的条目(可以在运行的时候更新)。
/res/layout/drawer_header.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorPrimary"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<ImageView
android:id="@+id/drawer_header_portrait"
android:layout_width="@dimen/size_90dp"
android:layout_height="@dimen/size_90dp"
android:layout_gravity="left|top"
android:scaleType="centerInside"
android:src="@drawable/me" />
<TextView
android:layout_width="@dimen/size_90dp"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="王诚宇"
android:textColor="@android:color/white"
android:textSize="@dimen/textSize_16sp" />
</LinearLayout>
最简单的抽屉菜单是一组可勾选的菜单条目的集合,/res/menu/drawer.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/navigation_item_1"
android:checked="true"
android:icon="@drawable/ic_home_2x"
android:title="@string/navigation_item_1" />
<item
android:id="@+id/navigation_item_2"
android:icon="@drawable/ic_message_2x"
android:title="@string/navigation_item_2" />
</group>
</menu>
被选中的条目将在导航抽屉中突出显示,以确保用户知道哪一个导航条目是当前选中的。
也可以在菜单中通过使用subheaders来分离菜单条目组:
<item
android:id="@+id/navigation_subheader"
android:title="@string/navigation_subheader">
<menu>
<item
android:id="@+id/navigation_sub_item_1"
android:icon="@drawable/ic_android"
android:title="@string/navigation_sub_item_1"/>
<item
android:id="@+id/navigation_sub_item_2"
android:icon="@drawable/ic_android"
android:title="@string/navigation_sub_item_2"/>
</menu>
</item>
要获得选中条目时的回调方法,可以通过NavigationView的
setNavigationItemSelectedListener()来设置。
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(MenuItem item) {
if (item.getItemId() == R.id.navigation_item_1) {
//do something
return true;
}
return false;
}
});
onNavigationItemSelected()方法中提供了点击的MenuItem,可以处理选择事件,改变选择状态,加载新的内容,关闭抽屉,或者其它任何动作行为。
小技巧:
- 通过NavigationView.setItemTextColor()设置条目文本颜色。
- 通过NavigationView.addView(customeView)自定义NavigationView的布局。
监听打开和关闭事件
要监听drawer的打开和关闭事件,可以通过
DrawerLayout.setDrawerListener(new DrawerLayout.DrawerListene接口的实现类())来设置。DawerLayout.DrawerListener接口为drawer事件提供了回调方法,如 onDrawerOpened() 和 onDrawerClosed()方法。
然而,与其实现DawerLayout.DrawerListener接口,如果activity中含有action bar,可以继承ActionBarDrawerToggle类来代替。ActionBarDrawerToggle类实现了DawerLayout.DrawerListener接口,因此可以覆盖那些回调方法。
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, getToolbar(), R.string.open, R.string.close) {
/** Called when a drawer has settled in a completely closed state. */
public void onDrawerClosed(View view) {
super.onDrawerClosed(view);
getSupportActionBar().setTitle(R.string.menu);
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
/** Called when a drawer has settled in a completely open state. */
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
getSupportActionBar().setTitle(R.string.detail);
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
};
mDrawerLayout.addDrawerListener(mDrawerToggle);
与应用程序图标一起打开和关闭
用户可以以一个滑动手势或指向屏幕左边缘来打开和关闭导航抽屉,如果使用了action bar,应该允许用户通过触摸应用程序图标来打开和关闭它。而且应用程序图标也应该用一个特殊的图标来指示导航抽屉的存在。这些操作完全可以用ActionBarDrawerToggle类来实现。
为了让ActionBarDrawerToggle工作,通过它的构造器创建对象,需要的参数有:
- 持drawer的activity
- DrawerLayout
- 指示drawer的drawable资源
- 描述“打开drawer”的string资源
- 描述“关闭drawer”的string资源
另外还需要在activity生命周期方法中做一些操作:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.navigation_view, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
mDrawerToggle.syncState();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mDrawerToggle.onConfigurationChanged(newConfig);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Pass the event to ActionBarDrawerToggle, if it returns
// true, then it has handled the app icon touch event
if (mDrawerToggle.onOptionsItemSelected(item)) {
return true;
}
// Handle your other action bar items...
return super.onOptionsItemSelected(item);
}
DrawerLayout的Bug
如果直接使用android.support.v4.widget.DrawerLayout,会抛出
java.lang.IllegalArgumentException: DrawerLayout must be measured with MeasureSpec.EXACTLY异常。
EXACTLY表示:父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize(测量模式下的规格大小)所指定的值。它对应于LayoutParams中的match_parent和具体的数值这两种模式。(这里已经是给DrawerLayout指定了match_parent,然而没有用)
解决方案:
- 指定DrawerLayout的大小
ViewGroup.LayoutParams params = mDrawerLayout.getLayoutParams();
params.width = 屏幕宽度;
params.height = 内容宽度;//除去StatusBar高度、ActionBar高度
mDrawerLayout.setLayoutParams(params);
- 重写DrawerLayout的onMeasure()方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
widthMeasureSpec = MeasureSpec.makeMeasureSpec(
MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(
MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
2.Floating Action Button
floating action button 是一个在操作界面上表示主要动作的圆形按钮,设计库中的FloatingActionButton提供了一个单独的实现,它的默认颜色使用来自主题中的colorAccent。
floating action button 除了正常大小,还支持mini大小(fabSize=“mini”,为了配合与其它元素保持视觉连续性)。FloatingActionButton继承自ImageView,所以可以通过android:src或setImageDrawable()来设置显示的图标。
<android.support.design.widget.FloatingActionButton
android:id="@+id/floating_action_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:onClick="onClick"
android:src="@android:drawable/ic_menu_add"
app:fabSize="mini"
app:rippleColor="@android:color/holo_red_light" />
3.Snackbar
snackbar 提供了一个轻量级、快速反馈的操作。它显示在屏幕的底部,而且包含一个可选的单独的动作文本。在一定时间后会带着动画的方式自动从屏幕中消失。此外,也可以在时间到之前手动关闭它。
Snackbar snackbar = Snackbar.make(v, "这是Snackbar!", Snackbar.LENGTH_LONG)
.setAction("取消", new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
snackbar.setActionTextColor(Color.RED);//设置动作文本颜色
View sbView = snackbar.getView();//得到显示文本的View
TextView textView = (TextView) sbView.findViewById(android.support.design.R.id.snackbar_text);
textView.setTextColor(Color.YELLOW);
snackbar.show();
4.Tabs
通过tabs来切换不同的视图,对于material design 来说已经不是一个新概念了,它们和顶级层导航模式或组织不同内容类别(如不同曲风的音乐)是一样的。
设计库中的TabLayout实现了固定的(所有的tab均分视图的宽度)和滑动的(没有一个固定的大小,但可以水平滑动)tabs。在代码中添加tabs:
TabLayout tabLayout = ...;
tabLayout.addTab(tabLayout.newTab().setText("Tab 1"))
然而,如果用ViewPager在tabs之间进行水平分页,可以直接从PagerAdapter的getPageTitle()创建tabs,然后通过setupWithViewPager()将两者结合起来。这确保tab选择事件触发时可以更新ViewPager,页面改变时可以更新选中的tab。
TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
final ViewPager viewPager = (ViewPager) findViewById(R.id.view_pager);
viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager()));
tabLayout.setupWithViewPager(viewPager);
private class ViewPagerAdapter extends FragmentPagerAdapter {
private final String[] mTitles = {"音乐", "电影", "小说", "美术"};
private List<Fragment> mFragments = new ArrayList<>(mTitles.length);
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
mFragments.add(your fragment);
mFragments.add(your fragment);
mFragments.add(your fragment);
mFragments.add(your fragment);
}
@Override
public int getCount() {
return mTitles.length;
}
@Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
@Override
public CharSequence getPageTitle(int position) {
return mTitles[position];
}
}
CoordinatorLayout, motion, and scrolling
独特的视觉效果只是material design 的一部分:运动也是创建 material design 应用程序的重要的一部分。然而在material design 中有很多关于运动的部分,包括触摸波纹和意味深长的过渡,设计库中引进了CoordinatorLayout,它在子视图之间的触摸事件控制上提供了另外一种层级,这让设计库中的许多组件都可以利用它。
CoordinatorLayout and floating action buttons
一个很好的例子是当将FloatingActionButton作为CoordinatorLayout的子View,然后将CoordinatorLayout传递给调用Snackbar.make()的时候-snackbar 不会显示在floating action button上,而是FloatingActionButton会利用CoordinatorLayout提供的额外回调方法,在snackbar动画显示时向上移动,在snackbar动画消失时回到原来的位置,这仅支持Android 3.0或者版本更高的设备-不需要额外的代码。
CoordinatorLayout还提供了一个layout_anchor 属性,它和layout_anchorGravity属性一起,可以用来放置浮动的视图,如FloatingActiongButton,相对于其它的视图。
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/activity_horizontal_margin"
android:src="@android:drawable/ic_menu_add"
app:fabSize="normal"
app:layout_anchor="@id/app.bar.layout"
app:layout_anchorGravity="bottom|right|end" />
CoordinatorLayout and the app bar
CoordinatorLayout其它主要的用例是关于app bar(以前为action bar)和滚动技巧。在布局中使用Toolbar,更容易自定义应用程序标志性部分的外观。设计库把这个带到了另一个层级:使用AppBarLayout可以让Toolbar和其它视图(TabLayout提供的tabs)对标记有ScrollingViewBehavior的相邻视图的滚动事件作出反应。因此,可以创建一个布局,如:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:elevation="@dimen/size_2dp"
app:statusBarBackground="?attr/colorPrimary">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="@id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:clipToPadding="true"
android:fitsSystemWindows="true"
android:minHeight="?attr/actionBarSize"
app:elevation="@dimen/size_2dp"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:theme="@style/ThemeOverlay.AppCompat.ActionBar" />
<android.support.design.widget.TabLayout
android:id="@id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>
现在,当滚动RecyclerView(ViewPager中的RecyclerView)的时候,AppBarLayout可以对有滚动标志的子View的进入(滚进屏幕)和退出(滚出屏幕)事件做出响应。标志包括:
- scroll:这个标志应该为所有想在屏幕上滚动的视图设置-对于那些不使用这个标志的视图,将被固定在屏幕的顶部。
- enterAlways:这个标志确保任何向下的滚动都将使这个View变得可见,从而实现“快速返回”模式。
- enterAlwaysCollapsed:当View即声明了minHeight 又使用了这个标志,它将只显示它的最低高度(在“collapsed”情况下),当滚动的视图到达它的顶部时将扩大它的整个高度。
- exitUntilCollapsed:这个标志会造成View在存在之前一直滚动直到它收拢(显示它的最小高度)。
Collapsing Toolbars
直接将Toolbar添加到AppBarLayout中,可以访问enterAlwaysCollapsed和exitUntilCollapsed标志,但是不能访问不同元素对收拢做出反映的详细控制。为此,可以使用CollapsingToolbarLayout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:id="@+id/app.bar.layout"
android:layout_width="match_parent"
android:layout_height="@dimen/size_256dp"
android:fitsSystemWindows="true">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/collapsing.toolbar.layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:title="@string/coordinator_layout">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
android:src="@drawable/me"
app:layout_collapseMode="parallax" />
<android.support.v7.widget.Toolbar
android:id="@id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:clipToPadding="true"
android:fitsSystemWindows="true"
android:minHeight="?attr/actionBarSize"
app:elevation="@dimen/size_2dp"
app:layout_collapseMode="pin"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:theme="@style/ThemeOverlay.AppCompat.ActionBar" />
</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">
<TextView
android:id="@+id/coordinator_layout_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/activity_horizontal_margin"
android:textSize="@dimen/textSize_18sp" />
</android.support.v4.widget.NestedScrollView>
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/activity_horizontal_margin"
android:src="@android:drawable/ic_menu_add"
app:fabSize="normal"
app:layout_anchor="@id/app.bar.layout"
app:layout_anchorGravity="bottom|right|end" />
</android.support.design.widget.CoordinatorLayout>
当view收拢的时候,CollapsingToolbarLayout的
app:layout_collapseMode="pin"可以确保Toolbar依然固定在屏幕的顶部。更好的是,当CollapsingToolbarLayout和Toolbar一起使用的时候,在布局全部显示时标题自动变大,收拢时过渡到它的默认大小。但需要注意的是,标题是通过CollapsingToolbarLayout的setTitle()来设置,而不是Toolbar。
除了固定一个view以外,还可以使用app:layout_collapseMode=“parallax” (可以选择app:layout_collapseParallaxMultiplier=“0.7” 来设置视差乘数)来实现视差滑动(如CollapsingToolbarLayout中的ImageView)。对CollapsingToolbarLayout来说,这个使用例子与app:contentScrim="?attr/colorPrimary"属性配对会更好。