标签, 将checkableBehavior 属性指定为single。 group表示一个组, checkableBehavior 表示所有菜单项只能单选。
menu就这样准备好了。。。接下来应该喜欢被headerLayout!!!我们就在headerLayout 中放置头像, 用户名, 邮箱地址这3项内容吧。
现在menu 和 headerLayout 都准备好了, NavigationView 随时准备着, 修改主活动布局:
//
通过app:menu 和 app:headerLayout 属性将我们刚才准备好的menu 和 headeLayout 设置进去, 这样NavigationView 就这样定义完成了。
NavigationView 定义好了之后, 我们还需要去处理菜单项的点击事件才行。 修改主活动:
public class MainActivity extends AppCompatActivity {
private DrawerLayout drawer_layout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
android.support.v7.widget.Toolbar toolbar = (android.support.v7.widget.Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
NavigationView nav_view = (NavigationView) findViewById(R.id.nav_view); //
drawer_layout = (DrawerLayout) findViewById(R.id.drawer_layout);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null){
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
}
nav_view.setCheckedItem(R.id.nav_call); //
nav_view.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
drawer_layout.closeDrawers();
return true;
}
}); //
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case android.R.id.home:
drawer_layout.openDrawer(GravityCompat.START);
break;
case R.id.backup:
Toast.makeText(this, "Backup",Toast.LENGTH_SHORT).show();
break;
case R.id.delete:
Toast.makeText(this,"Delete", Toast.LENGTH_SHORT).show();
break;
case R.id.settings:
Toast.makeText(this, "Setting", Toast.LENGTH_SHORT).show();
break;
default:
}
return true;
}
}
获得NavigationView 实例后调用它的setCheckedItem()方法 将Call菜单设置为默认选中。 接着调用 setNavigationItemSelectedListener()方法 来设置一个菜单项选中事件的监听器, 当用户点击任意菜单项时, 就会回调到onNavigationItemSelected() 方法中。 可以在这个方法中写出相应的逻辑处理, 此处只是将滑动菜单关闭。
![Material Design实战_第3张图片](http://img.e-com-net.com/image/info8/524592d4bf06411e93ddd675c3a942bc.jpg)
3、悬浮按钮和可交互提示
立体设计是Material Design 中一条非常重要的设计思想, 这种按钮不属于主界面平面的一部分, 而是位于另外一个维度的, 因此给人以悬浮感。 关于提示工具, 我们经常使用Toset, 但是Toast不能与用户交互, 只是起到有个通知的作用。
(1)、FloatingActionButton
修改主活动布局:
layout_gravity属性指定将这个控件放置于屏幕的右下角, end的原理即 如果系统语言是从左到右, 那么end就表示右边。
public class MainActivity extends AppCompatActivity {
private DrawerLayout drawer_layout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
android.support.v7.widget.Toolbar toolbar = (android.support.v7.widget.Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
NavigationView nav_view = (NavigationView) findViewById(R.id.nav_view);
drawer_layout = (DrawerLayout) findViewById(R.id.drawer_layout);
ActionBar actionBar = getSupportActionBar();
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); //
fab.setOnClickListener(new View.OnClickListener() { //
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "FAB clicked", Toast.LENGTH_SHORT).show();
}
});
......
}
FloatingActionBar 其实和普通Button 差不多, 都是调用setOnClickListener()方法来注册一个监听器。
![Material Design实战_第4张图片](http://img.e-com-net.com/image/info8/3863f120781d41218680f6c38ced86ba.jpg)
(2)、Snackbar
Design Support 库提供的更加先进的提示工具----Snackbar。 Snackbar并不是Toast的替代品, Snackbar允许在提示当中鸡加入一个可交互按钮, 当用户点击按钮时可以执行一些额外的逻辑操作。 比如当我们删除一些数据时, 添加一个Undo 按钮, 就相当于给用户提供了一种弥补措施, 从而提示了用户的使用体验。
Snackbar用法与Toast 相似, 只不过可以额外增加一个按钮的点击事件。 修改主活动:
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Snackbar.make(v, "Data delted", Snackbar.LENGTH_SHORT)
.setAction("Undo", new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "Data restored", Toast.LENGTH_SHORT).show();
}
}).show();
}
});
这里调用了Snackbar 的 make()方法来创建一个Snackbar对象, make() 方法的第一个参数需要传入一个View, 只要是当前界面布局的任意一个View都可以, Snackbar 会使用这个View 来自动来查找最外层的布局, 用于展示Snackbar。 第二个参数就是Snackbar 中显示的内容, 第三个参数是Snackbar 显示的时长。 接着调用setAction() 从而让Snackbar 不仅仅是一个提示, 而是可以和用户进行交互的。 最后调用show() 让Snackbar显示出来。
![Material Design实战_第5张图片](http://img.e-com-net.com/image/info8/16781ccd7f1342c290c2e8ad89efec14.jpg)
![Material Design实战_第6张图片](http://img.e-com-net.com/image/info8/ac98d6644c9440d5bf205397c86bf55c.jpg)
注意此处有一个bug, Snackbar竟然将悬浮按钮挡住了, 此时我们可以借助 CoordinatorLayout 就可以轻松解决。
(3)、CoordinatorLayout
CoordinatorLayout 可以说是一个加强版的FrameLayout, 也是由Design Support库提供的, 其必然就一些Material Design的魔力。 事实上 CoordinatorLayout 可以监听其所有子控件的各种事件, 然后帮我们做出合理的响应。 接下来我们使用以下CoordinatorLayout 。 只需要将原来的 FrameLayout 替换一下即可, 修改主活动布局:
![Material Design实战_第7张图片](http://img.e-com-net.com/image/info8/e29cfe26f664436daba8e2cf38f86609.jpg)
可以看到悬浮按钮随着Snackbar上下浮动了, 解决了上面的bug。
CoordinatorLAyout可以监听其所有子控件的各种事件, 但是Snackbar 并不是其控件, 但它为啥会被监听的呢? 其道
理是我们在Snackbar 的 make()方法中传入的第一个参数, 这个参数是用来指定Snackbar是基于那个View来触发的, 刚才传入的是FloatingActionButton 本身, 而FloatingActionButton 是 CoordinatorLayout 中的子控件, 因此这个事件 就能被监听到了。
4、卡片式布局
为了让主体内容也Material化, 准备在这放一些水果图片, 本节中我们将会学习如实现卡片式布局的效果。 卡片式布局也是
Materials Design中提到的一个新的概念, 它可以让页面中的元素看起来就像在卡片中一样, 并且还能拥有圆角和投影。
(1)、CardView
CardView是实现卡片式布局的重要控件, 由v7库提供。 实际上, CardView也是一个FrameLayout, 只是额外提供了圆角和阴影等效果, 看上去有立体的效果。
接下来我们使用RecyclerView 来填充项目的主界面, 由于我们需要用到RecyclerView, CardView 这几个控件, 因此必须在gradle中声明这些库的依赖:
implementation 'com.android.support:recyclerview-v7:26.1.0'
implementation 'com.android.support:cardview-v7:26.1.0'
implementation 'com.github.bumptech.glide:glide:3.7.0'
最后一行添加了 Glide库。 Glide是一个超级强大的图片加载库, 他不仅可以用于加载本地图片, 还可以加载网络图片、 GIF图片、 甚至是本地视频。 最重要的是, Glide的用法非常简单, 只需要一行代码就能实现复杂图片的加载。
接下来开始具体的代码实现, 修改主活动布局:
这里我们在CoordinatorLayout 中添加了一个RecyclerView, 给它指定一个id, 然后将宽高占满布局的空间。
接着定义一个实体类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;
}
}
name代表水果名字, imageId 代表图片资源id。 然后需要为RecyclerView 的子项指定一个我们自定义的布局, 在layout目录下新建 fruit_item.xml:
CardView来作为子项的最外层布局, 从而使得RecyclerView 中的每个元素都是在卡片中的。 CardView 由于是一个FrameLayout, 因此他没有什么方便地定位方式, 在这我们在其嵌套一个LinearLayout。 注意ImageView, 这里使用了centerCrop 模式, 它可以让图片保持原有比例充满 ImageView , 并将超出部分裁减掉。
接下来为 RecyclerView 准备一个适配器, 新建一个FruitAdapter 类, 让这个适配器去继承RecyclerView.Adapter, 并将泛型指定为 FruitAdapter.ViewHolder, 其中,ViewHolder是我们在FruitAdapter 中定义的一个内部类:
public class FruitAdapter extends RecyclerView.Adapter {
private Context mContext;
private List 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.fruit_name.setText(fruit.getName());
Glide.with(mContext).load(fruit.getImageId()).into(holder.fruit_image);
}
@Override
public int getItemCount() {
return mFruitList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
CardView cardView;
ImageView fruit_image;
TextView fruit_name;
public ViewHolder(View itemView) {
super(itemView);
cardView = (CardView) itemView;
fruit_name = (TextView) itemView.findViewById(R.id.fruit_name);
fruit_image = (ImageView) itemView.findViewById(R.id.fruit_image);
}
}
public FruitAdapter(List fruitList) {
mFruitList = fruitList;
}
}
代码虽长, 但并不难理解, 这里首先自定义了一个内部类 ViewHolder, ViewHolder 要继承自RecyclerView.ViewHolder。 然后ViewHolder 的构造函数中要传入一个View参数, 这个参数就是RecyclerView 子项的最外层布局, 那么我们就可以提供findViewById()方法获取布局中的ImageView 和 TextView 的实例。
接着看FruitAdapter 中也有一个构造函数, 这个方法用于把要展示的数据源传进来, 并赋值给一个全局变量mFruitList, 后续的操作都将在这个数据源的基础上进行。
继续看, 由于FruitAdapter 是继承自RecyclerView.Adapter 的, 那么就必须重写onCreateViewHolder()、 onBindViewHolder()、 和 getItemCount() 这3个方法。 onCreateViewHolder() 方法是用来创建ViewHolder实例的, 在这个方法中将 fruit_item 布局加载进来, 然后创建一个ViewHolder实例, 并把加载的布局传入到 构造函数中, 最后返回ViewHolder实例, onBindViewHolder() 方法是用于对RecyclerView子项数据进行赋值的, 在每个子项被滚到屏幕是被执行, 通过position 参数得到当前Fruit, 然后将数据设置到 ViewHolder 的 ImageView 和 TextView当中即可。 getItemCount() 直接返回数据源的长度。
在onBindViewHolder()方法中我们使用了Glide 来加载图片。 其用法为 Glide.with()方法传入一个 Context, Activity或 Fragment 参数, 然后调用 load()方法去加载图片, 可以是本地地址, 也可以是一个URL 地址, 或者是一个资源id, 最后调用into() 将图片设置到一个具体的ImageView 中即可。
Glide可以将高清图片进行压缩, 不会造成内存溢出比传统方式更好。
如此, 适配器就创建完成, 最后修改主活动:
public class MainActivity extends AppCompatActivity {
private DrawerLayout drawer_layout;
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 fruitList = new ArrayList<>();
private FruitAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
android.support.v7.widget.Toolbar toolbar = (android.support.v7.widget.Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
initFruits();
RecyclerView recycler_view = (RecyclerView) findViewById(R.id.recycler_view);
GridLayoutManager layoutManager = new GridLayoutManager(this, 2);
recycler_view.setLayoutManager(layoutManager);
adapter = new FruitAdapter(fruitList);
recycler_view.setAdapter(adapter);
NavigationView nav_view = (NavigationView) findViewById(R.id.nav_view);
drawer_layout = (DrawerLayout) findViewById(R.id.drawer_layout);
ActionBar actionBar = getSupportActionBar();
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Snackbar.make(v, "Data delted", Snackbar.LENGTH_SHORT)
.setAction("Undo", new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "Data restored", Toast.LENGTH_SHORT).show();
}
}).show();
}
});
if (actionBar != null){
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
}
nav_view.setCheckedItem(R.id.nav_call);
nav_view.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
drawer_layout.closeDrawers();
return true;
}
});
}
private void initFruits() {
fruitList.clear();
for(int i = 0; i < 50; i++){
Random random = new Random();
int index = random.nextInt(fruits.length);
fruitList.add(fruits[index]);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case android.R.id.home:
drawer_layout.openDrawer(GravityCompat.START);
break;
case R.id.backup:
Toast.makeText(this, "Backup",Toast.LENGTH_SHORT).show();
break;
case R.id.delete:
Toast.makeText(this,"Delete", Toast.LENGTH_SHORT).show();
break;
case R.id.settings:
Toast.makeText(this, "Setting", Toast.LENGTH_SHORT).show();
break;
default:
}
return true;
}
}
在数组中存放了多个Fruit 实例, 然后在initFruits() 方法中, 先是清空了一下 fruitList 中的数据, 接着使用一个随机函数 从数组中挑一个水果放入fruitList中, 为了让数据更丰富 使用了一个循环, 随机挑选50个水果。
GridLayoutManager布局方式的用法也没什么特别之处,它的构造函数接收两个参数, 第一个是Context, 第二个是列数, 这里我们希望每行中有两列数据。
![Material Design实战_第8张图片](http://img.e-com-net.com/image/info8/6ffed2bc4b0445718856838afaf162ad.jpg)
这里可以发现, ToolBar 被RecyclerView挡住了, 需要使用 AppBarLayout 解决这个问题。
(3)、AppBarLayout
RecyclerView 和 Toolbar 都是放置在CoordinatorLayout 中的, 由于 CoordinatorLayout是一个加强版的 FrameLayout , 那么FrameLayout 中所有的控件在不进行明确定位的情况下, 默认都摆放在左上角, 从而产生了遮挡。 这就是Toolbar 被 RecyclerView 遮挡的原因。
传统的情况下我们会使 RecyclerView 向下偏移一个ToolBar 的高度, 然而我们 的是CoordinatorLayout , 我们可以抛弃传统方式, 而使用 Design Support 库中提供的另外一个工具---AppBarLayout。 AppBarLayout实际上是一个垂直方向的 LinearLayout , 它在内部做了很多滚动事件的封装, 并应用的 Material Design的设计理念。
解决覆盖问题, 只需要两步, 第一 将Toolbar 嵌套到 AppBarLayout中, 第二 给 RecyclerView 指定一个布局行为。 修改主活动布局:
...
...
接下来我们来看看 AppBarLayout 到底能实现什么样的 Material Design效果。 接收到滚动事件的时候, 它内部的子控件其实是可以指定如何去影响这些事件的, 通过 app:layout_scrollFlags 属性就能实现。 修改主活动布局:
...
...
此处给ToolB中添加了app:layout_scrollFlags" 属性, 并赋值scroll|enterAlways|snap, 其中 scroll 表示当 RecyclerView 向上滚动的时候 Toolbar 会一起向上滚动并实现隐藏, enterAlways 表示当RecyclerView 向下滚动时, Toolbar 会一起向下滚动 并重新显示, snap 表示当Toolbar 还没有完全隐藏或显示 的时候, 会根据当前滚动的距离, 自动选择显示还是隐藏。
![Material Design实战_第9张图片](http://img.e-com-net.com/image/info8/12a030f1122f4a9cafa4ec4423c418a4.jpg)
像这种功能, 是ActionBar无法实现的, ToolBar为我们提供了更多可能。
5、下拉刷新
谷歌为了让Android的下拉刷新风格能有一个统一的标准, 于是在Material Design 中定制了一个官方的设计规范, 谷歌早就提供了现成的控件, 我们只需在项目中直接引用即可。
SwipRefreshLayout 就是用于实现下拉刷新的核心类, 由 support-v4 库提供的。 我们把控件放置到SwipRefreshLayout 中, 就可以迅速让这个控件实现下拉刷新。 在这个Test中, 支持下拉刷新的控件自然就是 RecyclerView 了, 修改主活动的布局:
...
...
如此RecyclerView 就具有下拉刷新功能了, 另外注意, 由于RecyclerView 现在变成了SwipRefreshLayout 的子控件, 因此之前使用app:layout_behavior 声明的布局行为现在也要移到 SwipRefreshLayout 中才行。
接下来需要在代码中实现具体的刷新逻辑。 修改主活动代码:
public class MainActivity extends AppCompatActivity {
private DrawerLayout drawer_layout;
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 fruitList = new ArrayList<>();
private FruitAdapter adapter;
private SwipeRefreshLayout swipeRefreshLayout;//
private SwipeRefreshLayout swipe_refresh;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
android.support.v7.widget.Toolbar toolbar = (android.support.v7.widget.Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
initFruits();
RecyclerView recycler_view = (RecyclerView) findViewById(R.id.recycler_view);
GridLayoutManager layoutManager = new GridLayoutManager(this, 2);
recycler_view.setLayoutManager(layoutManager);
adapter = new FruitAdapter(fruitList);
recycler_view.setAdapter(adapter);
NavigationView nav_view = (NavigationView) findViewById(R.id.nav_view);
drawer_layout = (DrawerLayout) findViewById(R.id.drawer_layout);
swipe_refresh = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
swipe_refresh.setColorSchemeResources(R.color.colorPrimary);
swipe_refresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
refreshFruits();
}
});
ActionBar actionBar = getSupportActionBar();
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Snackbar.make(v, "Data delted", Snackbar.LENGTH_SHORT)
.setAction("Undo", new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "Data restored", Toast.LENGTH_SHORT).show();
}
}).show();
}
});
if (actionBar != null){
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
}
nav_view.setCheckedItem(R.id.nav_call);
nav_view.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
drawer_layout.closeDrawers();
return true;
}
});
}
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();
swipe_refresh.setRefreshing(false);
}
});
}
}).start();
}
...
}
此处通过 findViewById() 方法获得实例, 然后调用 setColorSchemaResources() 方法来设置进度条颜色, setOnRefreshListener() 方法设置一个下拉刷新的监听器, 触发刷新时就会回调这个监听器 的onRefresh() 方法, 在这里可以去处理刷新逻辑。通常在是去网络请求最新数据,再将数据展示出来。 我们这就展示一个刷新本地图片操作, refreshFruits() 方法中 开启一个线程, 然后将线程沉睡两秒, 沉睡结束后 使用runOnUiThread() 方法将线程切换到主线程, 然后调用 initFruits() 重新生成数据, 接着调用FruitAdapter 的 notifyDataSetChanged() 方法通知数据发送了变化, 最后调用 SwipRefreshLayout 的 setRefreshing() 方法并传入false , 用于表示刷新结束, 并隐藏进度条。
![Material Design实战_第10张图片](http://img.e-com-net.com/image/info8/4d9582bd3b794bf6bbf271731bbc81b9.jpg)
6、可折叠式标题栏
在Material Design中可以根据我们的喜好指定标题栏的样式, 比如折叠式标题栏。 需要借助 CollapsingToolbarLayout 这个工具。
(1)、CollapsingToolbarLayout
CollapsingToolbarLayout 是一个可以作用于 ToolBar 基础之上的布局, 由Design Support 库 CollapsingToolbarLayout
可以让Toolbar 的效果更佳丰富。 不过它是不可以独立存在的, 在设计时就被限定只能作为 AppBarLayout 的直接子布局来使用。 而 AppBarLayout 又必须是 CoordinatorLayout 的子布局, 因此让我们这里的项目是个布局综合性蛮强的操作。
创建一个FruiteActivity, 开始编写水果内容详情, 我们来一步步实现。
注意始终定义一个 xmlns:app的命名空间, 在Material Design 的开发会经常用到它。
接着在CoordinatorLayout 中嵌套一个AppBarLayout, 如下:
接着在AppBarLayout 中再嵌套一个CollapsingToolbarLayout, 如下所示:
...
...
这里我们使用了新布局CollapsingToolbarLayout, android:theme 属性用于指定 CollapsingToolbarLayout 在趋于折叠状态以及折叠之后的背景色, 其实CollapsingToolbarLayout 折叠后就是一个普通的 Toolbar , 背景肯定是 colorPrimary 了。
app:layout_scrollFlags 属性我们也是见过的, 之前 Toolbar 见过, scroll 表示CollapsingToolbarLayout 会随着水果内容详情的滚动一起滚动, exitUnitilCollapsed 表示当CollapsingToolbarLayout 随着滚动完成折叠之后就保留在界面,不在移除屏幕。
接下来, 我们在 CollapsingToolbarLayout 中定义标题栏内容, 如下:
...
...
此处, 我们在 CollapsingToolbarLayout 中定义了一个 ImageView 和一个 Toolbar, 也就意味着, 这个高级版的标题将是
由普通标题栏 加上图片组成而成的。 这里有个app:layout_collapseMode 比较陌生。 它用于指定当前控件在 CollapsingToolbarLayout 折叠过程中的折叠模式, 其中Toolbar 指定为pin ,表示在折叠过程中位置始终保持不变, ImageViwe 指定成 parallax, 表示会在折叠的过程产生一定的错位漂移, 这种模式视觉效果好。
这样水果标题栏就完成了, 下面编写水果内容详情部分:
......
水果内容详情的最外层布局使用了一个 NestedScrollView, 注意它和AppBarLayout 是平级的。 ScrollView 的用法, 它允许
使用滚动的方式来查看屏幕外的数据, 而NestedView 在此基础之上还增加了嵌套响应滚动时间的功能。 由于CoordinatorLayout 本身已近可以响应滚动事件了, 因此我们在它的内部就需要使用 NestedScrollView 或 RecyclerView 这样的布局。 另外, 这里还通过 app: layout_behavior 属性指定了一个布局行为, 这和之前在 RecyclerView 中的用法是一模一样的。
不管是ScrollerView 还是 NestedScrollerView, 它们的内部都只允许一个直接子布局。 因此我们想要在里面放很多东西的话, 通常都会嵌套一个 LinearLayout , 然后在 LinearLayout 中放入具体的内容即可。在这里我们将准备使用一个 TextView来显示水果的内容详情, 并将 TextView 放在一个卡片式布局当中:
...
...
在CardView 和 TextView 上都加了一些边距。 CardView的 marginTop 加了35dp的边距, 为了下面要编写的东西留出空间。
FloatingActionButton, 和 AppBarLayout、 NestedScrollView 是平级的。 其中, 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 collapsing_toolbar = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
ImageView fruit_image_view = (ImageView) findViewById(R.id.fruit_image_view);
TextView fruit_content_text = (TextView) findViewById(R.id.fruit_content_text);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
collapsing_toolbar.setTitle(fruitName);
Glide.with(this).load(fruitImageId).into(fruit_image_view);
String fruitContent = generateFruitContent(fruitName);
fruit_content_text.setText(fruitContent);
}
private String generateFruitContent(String fruitName) {
StringBuilder fruitContent = new StringBuilder();
for (int i = 0; i < 500; i++){
fruitContent.append(fruitName);
}
return fruitContent.toString();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}
在onCreate() 方法中, 我们通过Intent 获取到传入水果的名 和水果图片的资源ID, 然后获得布局文件中的各个空间实例。 接着就用了 Toolbar的标准用法, 将它作为ActionBar 显示, 并启用HomeAsUp 按钮。 由于HomeAsUp 按钮的默认图标是一个返回按钮, 这正是我们希望的, 因此无需设置别的图标了。
接下来开始填充界面上的内容, 调用CollapsingToolbarLayout 的 setTitle() 将水果名设置为标题, 然后使用Glide 加载传入的水果图片, 并设置到标题栏的 ImageView 上面。 此处填充了500此水果名。
接着, 我们在onOptionsItemSelected() 方法中处理了 HomeAsUp 按钮的点击事件, 当点击按钮 就调用finish() 方法关闭此活动, 从而返回上一个活动。
最后一步, 处理RecyclerView 的点击事件, 不然就无法打开FruitActivity, 修改FruitAdapter:
public class FruitAdapter extends RecyclerView.Adapter {
private Context mContext;
private List 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);
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;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Fruit fruit = mFruitList.get(position);
holder.fruit_name.setText(fruit.getName());
Glide.with(mContext).load(fruit.getImageId()).into(holder.fruit_image);
}
@Override
public int getItemCount() {
return mFruitList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
CardView cardView;
ImageView fruit_image;
TextView fruit_name;
public ViewHolder(View itemView) {
super(itemView);
cardView = (CardView) itemView;
fruit_name = (TextView) itemView.findViewById(R.id.fruit_name);
fruit_image = (ImageView) itemView.findViewById(R.id.fruit_image);
}
}
public FruitAdapter(List fruitList) {
mFruitList = fruitList;
}
}
在这里, 我们给CardView 注册了一个点击事件监听器, 然后在点击事件当前点击项的水果名和图片资源id,把他们传入到intent 中, 最后调用startActivity() 启动FruitActivity。
(2)、充分利用系统状态栏空间
虽说水果详情页展示界面的效果已经非常华丽的, 但这并不代表我们不能再进一步地提高升。 其实这里我们能将背景图和状态栏融合到一起, 那这个视觉体验绝对提升几个档次。 但是再Android5.0之前, 是无法对状态栏的背景或颜色进行操作的, 那个时候也没有 Material Design 的概念。 因此在Android 5.0 系统之后我们完全可以 使用背景图和状态栏融合的模式。
此时需要借助 android:fitsSystemWindows 这个属性来将其属性设置为true , 这就表示控件会出现在系统状态栏里。在这里 就是水果标题栏中的 ImageView 应该设置这个属性了。 但是之给 ImageView设置此属性是不够的, 必须将ImageView 布局结构中的所有父布局都设置上这个属性才行, 修改acticity_fruit.xml:
......
但是, 只设置android:fitsSystemWindows 属性都设置好了是没有的, 因为还必须在程序的主题中将状态指定为透明色才行。 只要将主题将 android: statusBarColor 属性的值指定成 @android:color/transparent 就可以了。 但问题是, android:statusBar 这个属性是从API21, 也就是Android5.0 系统开始才有的, 之前的系统无法指定这个属性。
那么。。。
系统差异型功能实现就要从这里开始了。。。
右击res目录, 新建一个value-v21目录, 创建一个sytle.xml 文件:
- @android:color/transparent
在这里我们定义了一个FruitActivityTheme 主题, 它是专门给 FruitActivity使用的。 FruitActivityTheme 的 parent 主题是AppTheme, 也就是说它继承了 AppTheme的所有特性。 然后我们在 FruitActitityTheme主题 将状态栏颜色设置为透明, 且只有 Android5.0 及以上系统才会去读取。
但是再Android5.0 之前的系统时无法对其进行识别的, 因此我们还需要对 values/styles.xml文件进行修改:
//
可以看到这里定义了一个 FruitActivityTheme 主题,并且parent 主题也是 AppTheme, 但是它的内部是空的。 因为 Android5.0 之前的系统无法指定系统状态栏的颜色, 因此这里什么都不用做就可以了。
最后,我们还需要让 FruitActivity 使用这个主题才可以, 修改清单:
...
//
...
这里使用android:theme 属性单独给FruitActivity 指定了FruitActivityTheme 这个主题, 如此即大功告成。
现在只要是在Android5.0 及以上系统运行 MaterialTest 程序, 水果详情展示界面的效果就会如图:
![Material Design实战_第11张图片](http://img.e-com-net.com/image/info8/10502332587041de8ddb181644370ace.jpg)
结束!!!