本章的主要的知识点:
- 卡片式布局
- CardView
- AppBarLayout
- 下拉刷新
- 可折叠式标题栏
- CollapsingToolbarLayout
- 充分利用系统状态栏空间
12.5 卡片式布局
卡片式布局也是Material Design中提出的一个新的概念,它可以让页面中的元素看起来就像在卡片中一样,并且还能拥有圆角和投影。
12.5.1 CardView
CardView
是用于实现卡片式布局效果的重要控件,有appcompat-v7
库提供,实际上,CardView
也是一个FrameLayout
,只是额外提供了圆角和阴影等效果,看上去会有立体的感觉。
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="4dp"
app:elevation="5dp">
<TextView
android:id="@+id/info_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.v7.widget.CardView>
这里定义了一个CardView
布局,我们可以通过app:cardCornerRadius
属性指定卡片圆角的弧度,数值越大,圆角的弧度也越大。另外还可以通过app:elevation
属性指定卡片的高度,高度值越大,投影范围也越大,但是投影效果越淡,高度值越小,投影范围也越小,但是投影效果越浓。这一点和FloatingActionButton
是一致的。
添加依赖
compile 'com.android.support:cardview-v7:25.1.1'
compile 'com.android.support:recyclerview-v7:25.1.1'
compile 'com.github.bumptech.glide:glide:3.7.0'
这里添加了一个Glide
库的依赖。Glide
是一个超级强大的图片加载库,它不仅可以用于加载本地图片,还可以加载网络图片,GIF
图片,甚至是本地视频。最重要的是,Glide
的用法非常简单,只需一行代码就能轻松实现复杂的图片加载功能。
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
</android.support.v7.widget.Toolbar>
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="@drawable/done"
app:elevation="8dp"/>
</android.support.design.widget.CoordinatorLayout>
``
</android.support.v4.widget.DrawerLayout>
这里我们在CoordinatorLayout
中添加了一个RecyclerView
,给它指定了一个id
,然后将宽度和高度都设置为match_parent
,这样RecyclerView
也就占满了整个布局的空间。
定义一个实体类Fruit
public class Fruit
{
private String name;
private int imageId;
public Fruit(String name, int imageId)
{
this.name = name;
this.imageId = imageId;
}
public String getName()
{
return name;
}
public int getImageId()
{
return imageId;
}
}
Fruit
类中只有两个字段,name
表示水果的名字,imageId
表示水果对应图片的资源id
。
新建fruit_item.xml
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="4dp"
android:layout_margin="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/fruit_image"
android:layout_width="match_parent"
android:layout_height="100dp"
android:scaleType="centerCrop"/>
<TextView
android:id="@+id/fruit_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="5dp"
android:textSize="16sp"/>
</LinearLayout>
</android.support.v7.widget.CardView>
这里使用了CardView
来作为子项的最外层布局,从而使得RecyclerView
中的每个元素都是在卡片当中的。CardView
由于是一个FrameLayout
,因此它没有什么方便的定位方式,这里我们只好在CardView
中再嵌套一个LinearLayout
,然后在LinearLayout
中放置具体的内容。
注意在ImageView
中我们使用了一个scaleType
属性,这个属性可以指定图片的缩放模式。由于各张水果图片的长宽比例可能不一致,为了让所有的图片都能填充满整个ImageView
,这里使用了centerCrop
模式,它可以让图片保持原有比例填充满ImageView
,并将超出屏幕的部分裁减掉。
新建FruitAdapter
类,让这个类继承自RecyclerView.Adapter
,并将泛型指定为FruitAdapter.ViewHolder
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder>
{
private Context mContext;
private List<Fruit> mFruitList;
public FruitAdapter(Context context, List<Fruit> mFruitList)
{
this.mContext = context;
this.mFruitList = mFruitList;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
if (mContext != null)
{
mContext = parent.getContext();
}
View view = LayoutInflater.from(mContext).inflate(R.layout.fruit_item,parent,false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position)
{
Fruit fruit = mFruitList.get(position);
holder.fruitName.setText(fruit.getName());
Glide.with(mContext).load(fruit.getImageId()).into(holder.fruitImage);
}
@Override
public int getItemCount()
{
return mFruitList.size();
}
static class ViewHolder extends RecyclerView.ViewHolder
{
CardView cardView;
ImageView fruitImage;
TextView fruitName;
public ViewHolder(View itemView)
{
super(itemView);
cardView = (CardView) itemView;
fruitImage = (ImageView) itemView.findViewById(R.id.fruit_image);
fruitName = (TextView) itemView.findViewById(R.id.fruit_name);
}
}
}
首先调用Glide.with()
方法并传入一个Context
,Activity
或Fragment
参数,然后调用load()
方法去加载图片,可以是一个URL
地址,也可以是一个本地路径,或则是一个资源id
,最后调用into()
方法将图片设置到具体一个ImageView
中就可以了。
这次我从网上找的这些水果图片像素都非常高,如果不进行压缩就直接展示的话,很容易就会引起内存溢出。而使用Glide
就完全不需要担心这回事,因为Glide
在内部做了许多非常复杂的逻辑操作,其中就包括了图片压缩,我们只需要安心按照Glide
的标准用法去加载图片就可以了。
public class MainActivity extends AppCompatActivity {
private Fruit[] fruits = {
new Fruit("Apple",R.drawable.apple),
new Fruit("Banana",R.drawable.banana),
new Fruit("Orange",R.drawable.orange),
new Fruit("Watermelon",R.drawable.watermelon),
new Fruit("Pear",R.drawable.pear),
new Fruit("Grape",R.drawable.grape),
new Fruit("Pineapple",R.drawable.pineapple),
new Fruit("Strawberry",R.drawable.strawberry),
new Fruit("Cherry",R.drawable.cherry),
new Fruit("Mango",R.drawable.mango)};
private List<Fruit> fruitList = new ArrayList<>();
private FruitAdapter adapter;
private RecyclerView recyclerView;
@Override
protected void onCreate(Bundle savedInstanceState)
{
``
initFruits();
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
GridLayoutManager layoutManager = new GridLayoutManager(this,2);
recyclerView.setLayoutManager(layoutManager);
adapter = new FruitAdapter(this,fruitList);
recyclerView.setAdapter(adapter);
}
public void initFruits()
{
fruitList.clear();
for (int i = 0; i < 50; i++)
{
Random random = new Random();
int index = random.nextInt(fruits.length);
fruitList.add(fruits[index]);
}
}
}
在MainActivity
中我们首先定义了一个数组,数组里面存放了很多个Fruit
的实例,每个实例都代表着一种水果。然后在initFruit()
方法中,先是清空一下fruitList
中的数据,接着使用一个随机函数,从刚才定义的Fruit
数组中随机挑选一个水果放入fruitList
当中,这样每次打开程序看到的水果数据都是不同的。
仔细观察可以看到Toolbar
被RecyclerView
挡住了,这就需要借助AppBarLayout
了
12.5.2 AppBarLayout
由于RecyclerView
和Toolbar
都是放置在CoordinatorLayout
中的,CoordinatorLayout
就是一个加强版的FrameLayout
。那么FrameLayout
中的所有控件在不进行明确定位的情况下,默认都会排放在左上角,从而也就产生了遮挡的现象。
传统情况下使用偏移是唯一的解决办法,则让RecyclerView
向下偏移一个Toolbar
的高度,从而保证到不会遮挡到Toolbar
。
这里我准备使用Design Support
库中提供的另外一个工具---AppBarLayout
。AppBarLayout实际上是一个垂直方向上的LinearLayout,它在内部做了很多滚动事件的封装,并应用了一些Material Design
的设计理念。
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
</android.support.v7.widget.RecyclerView>
``
</android.support.design.widget.CoordinatorLayout>
我们首先定义了一个AppBarLayout
,并将Toolbar
放置在了AppBarLayout
里面,然后在RecyclerView
中使用app:layout_behavior
属性指定了一个布局行为,其中appbar_scrolling_view_behavior
这个字符串也是由Design Support
库提供的。
事实上,当RecyclerView
滚动的时候就已经将滚动事件都通知给AppStoreBarLayout
了只是我们还没进行处理而已。
当AppBarLayout
接收到滚动事件的时候,它内部的子控件是可以指定如何去影响这些事件的,通过app:layout_scrollFlags
属性就能实现。
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:layout_scrollFlags="scroll|enterAlways|snap">
</android.support.v7.widget.Toolbar>
这里在Toolbar
中添加了一个app:layout_scrollFlags
属性,并将这个属性的值指定成了scroll|enterAlways|snap
。其中,scroll
表示当RecyclerView
向上滚动的时候,Toolbar
会跟着一起向上滚动并实现隐藏;enterAlways
表示当RecyclerView
向下滚动的时候,Toolbar
会跟着一起向下滚动并重新现实,snap
表示Toolbar
还没有完全隐藏或显示的时候,会根据当前滚动的距离,自动选择是隐藏还是显示。
12.6 下拉刷新
谷歌为了让Android
的下拉刷新风格能有一个统一的标准,于是在Material Design
中制定了一个官方的设计规范。
SwipeRefreshLayout
就是用于实现下拉刷新功能的核心类,它是由support-v4
库提供的。我们把想要实现下拉刷新功能的控件放置到SwipeRefreshLayout
中,就可以迅速让这个控件支持下拉刷新。
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
``
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</android.support.v4.widget.SwipeRefreshLayout>
``
我们在RecyclerView
的外面又嵌套了一层SwipeRefreshLayout
,这样RecyclerView
就自动拥有下拉刷新功能了。另外需要注意,由于RecyclerView
现在变成了SwipeRefreshLayout
的子控件,因此之前使用app:layout_behavior
声明的布局行为现在也要移到SwipeRefreshLayout
中才行。
虽然RecyclerView
已经支持下拉刷新功能了,但是我们还要在代码中处理具体的刷新逻辑才行。
public class MainActivity extends AppCompatActivity
{
······
private SwipeRefreshLayout swipeRefreshLayout;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener()
{
@Override
public void onRefresh()
{
refreshFruits();
}
});
}
······
private void refreshFruits()
{
new Thread(new Runnable()
{
@Override
public void run()
{
try
{
Thread.sleep(2000);
} catch (InterruptedException e)
{
e.printStackTrace();
}
runOnUiThread(new Runnable()
{
@Override
public void run()
{
initFruits();;
adapter.notifyDataSetChanged();
swipeRefreshLayout.setRefreshing(false);
}
});
}
}).start();
}
}
首先通过findViewById()
方法拿到SwipeRefreshLayout
的实例,然后调用setColorSchemeResources()
方法来设置下拉刷新进度条的颜色,这里我们使用主题中的colorPrimary
作为进度条的颜色了。接着调用setOnRefreshListener()
方法来设置一个下拉刷新的监听器,当触发了下拉刷新操作的时候就会回调这个监听器的onRefresh()
方法,然后我们在这里去处理具体的刷新逻辑就行了。
通常情况下,onRefresh()
方法中应该是去网络上请求最新的数据,然后再将这些数据展示出来。refreshFruits()
方法中先是开启了一个线程,然后将线程沉睡两秒钟。之所以这麽做,是因为本地刷新操作速度非常快,如果不将线程沉睡的话,刷新立刻就结束了,从而看不到刷新的过程。沉睡结束之后,这里使用了runOnUiThread()
方法将线程切换回主线程,然后调用initFruits()
方法重新生成数据,接着再调用FruitAdapter
的notifyDataSetChanged()
方法通知数据发生了变化,最后调用SwipeRefreshLayout
的setRefershing()
方法并传入false
,用于表示刷新事件结束,并隐藏刷新进度条。
12.7 可折叠式标题栏
虽说我们现在的标题栏是使用Toolbar来编写的,不过它看上去和传统的ActionBar其实没什么两样,只不过可以响应RecyclerView的滚动事件来进行隐藏和显示。而Material Design中并没有限定标题栏必须是长这样子的,事实上,我们可以根据自己的喜好随意定制标题栏的样式。
12.7.1 CollapsingToolbarLayout
CollapsingToolbarLayout是一个作用于Toolbar基础之上的布局,它也是由Design Support库提供的。CollapsingToolbarLayout可以让Toolbar的效果变得更加丰富,不仅仅是展示一个标题栏,而是能够实现非常华丽的效果。
CollapsingToolbarLayout是不能独立存在的,它在设计的时候就被限定只能作为AppBarLayout的直接子布局来使用。而AppBarLayout又必须是CoordinatorLayout的子布局。
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="250dp">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:layout_collapseMode="parallax"/>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin">
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="15dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginTop="35dp"
app:cardCornerRadius="4dp">
<TextView
android:id="@+id/fruit_content_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"/>
</android.support.v7.widget.CardView>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:src="@drawable/done"
app:layout_anchor="@id/appBar"
app:layout_anchorGravity="bottom|end"/>
</android.support.design.widget.CoordinatorLayout>
1.我们使用了新的布局CollapsingToolbarLayout,android:theme属性指定了一个ThemeOverlay.AppCompat.Dark.ActionBar的主题,app:contentScrim属性用于指定CollapsingToolbarLayout在趋于折叠状态以及折叠之后的背景色,其实CollapsingToolbarLayout在折叠之后就是一个普通的Toolbar,那么背景色肯定应该是colorPrimary了。app:layout_scrollFlags属性我们也是见过的,只不过之前是给Toolbar指定的,现在也移到外面来了。其中scroll表示CollapsingToolbarLayout会随着水果内容详情的滚动一起滚动,exitUntilCollapsed表示当CollapsingToolbarLayout随着滚动完成折叠之后就保留在界面上,不再移出屏幕。
2.我们在CollapsingToolbarLayout中定义了一个ImageView和Toolbar。app:layout_collapseMode,它用于指定当前控件在CollapsingToolbarLayout折叠过程中的折叠模式,其中Toolbar指定成pin,表示在折叠过程中位置始终保持不变,ImageView指定成parallax,表示会在折叠的过程中产生一定的错位便宜,这种模式的视觉效果会非常好。
3.水果内容详情的最外层布局使用了一个NestedScrollView,注意它和AppBarLayout是平级的。我们之前学过ScrollView的用法,它允许使用滚动的方式来查看屏幕以外的数据,而NestedScrollView在此基础上还增加了嵌套响应滚动事件的功能。由于CoordinatorLayout本身已经可以响应滚动事件了,因此我们在它的内部就需要使用NestedScrollView或RecyclerView这样的布局。另外,这里还通过app:layout_behavior属性指定了一个布局行为,这和之前在RecyclerView中的用法是一模一样的。
不管是ScrollView还是NestedScrollView,他们的内部都只允许存在一个直接子布局。因此,如果我们想要在里面放入很多东西的话,通常都会先嵌套一个LinearLayout,然后再在LinearLayout中放入具体的内容就可以了。
3.需要注意的是,这里为了让界面更加美观,我在CardView和TextView上都加一些边距。其中CardView的marginTop加了35dp的边距,这是为下面要编写的东西留出空间。
4.这里加入了一个FloatingActionButton,它和AppBarLayout以及NestedScrollView是平级的,FloatingActionButton中使用app:layout_anchor属性指定了一个锚点,我们将锚点设置为AppBarLayout,这样悬浮按钮就会出现在水果标题栏的区域内,接着又使用app:layout_anchorGravity属性将悬浮按钮定位在标题栏区域的右下角。
public class FruitActivity extends AppCompatActivity
{
public static final String FRUIT_NAME = "fruit_name";
public static final String FRUIT_IMAGE_ID = "fruit_image_id";
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fruit);
//获取跳转过来的数据
Intent intent = getIntent();
String fruitName = intent.getStringExtra(FRUIT_NAME);
int fruitImageId = intent.getIntExtra(FRUIT_IMAGE_ID,0);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
CollapsingToolbarLayout collapsingToolbar = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
ImageView fruitImageView = (ImageView) findViewById(R.id.fruit_image_view);
TextView fruitContentText = (TextView) findViewById(R.id.fruit_content_text);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null)
{
actionBar.setDisplayHomeAsUpEnabled(true);
}
collapsingToolbar.setTitle(fruitName);
Glide.with(this).load(fruitImageId).into(fruitImageView);
String fruitContent = generateFruitContent(fruitName);
fruitContentText.setText(fruitContent);
}
//500个水果名去
private String generateFruitContent(String fruitName)
{
StringBuilder fruitContent = new StringBuilder();
for (int i = 0; i < 500; i++)
{
fruitContent.append(fruitName);
}
return fruitContent.toString();
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case android.R.id.home:
finish();
break;
}
return super.onOptionsItemSelected(item);
}
}
通过Intent获取到传入的水果名和水果图片的资源id,然后通过findViewById()方法拿到刚才在布局文件中定义的各个控件的实例。接着就是使用了Toolbar的标准用法,将它作为ActionBar显示,并启用HomeAsUp按钮。HomeAsUp按钮的默认图标就是一个返回箭头。
接下来开始填充界面上的内容,调用CollapsingToolbarLayout的setTitle()方法将水果名设置成当前界面的标题,然后使用Glide加载传入的水果图片,并设置到标题栏的ImageView上面。使用了一个generateFruitContent()方法将水果名循环拼接500次,从而生成了一个比较长的字符串,将它设置到了TextView上面。
我们在onOptionsItemSelected()方法中处理了HomeAsUp按钮的点击事件,当点击了这个按钮时,就调用finish()方法关闭当前的活动,从而返回上一个活动。
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
if (mContext != null)
{
mContext = parent.getContext();
}
View view = LayoutInflater.from(mContext).inflate(R.layout.fruit_item,parent,false);
final ViewHolder holder = new ViewHolder(view);
holder.cardView.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
int position = holder.getAdapterPosition();
Fruit fruit = mFruitList.get(position);
Intent intent = new Intent(mContext,FruitActivity.class);
intent.putExtra(FruitActivity.FRUIT_NAME,fruit.getName());
intent.putExtra(FruitActivity.FRUIT_IMAGE_ID,fruit.getImageId());
mContext.startActivity(intent);
}
});
return holder;
}
这里我们给CardView注册了一个点击事件监听器,然后在点击事件中获取当前点击项的水果名和水果图片资源id,把他们传入到Intent中,最后调用startActivity()方法启动FruitActivity。
这个界面上的内容分为3部分,水果标题栏,水果内容详情和悬浮按钮。Toolbar和水果背景图完美地融合到了一起,既保证了图片的展示空间,又不影响Toolbar的任何功能,那个向左的箭头就是用来返回上一活动的。
12.7.2 充分利用系统状态栏空间
你会发现水果的背景图片和系统的状态栏总有一些不搭的感觉。
只不过很可惜的是,在Android5.0系统之前,我们是无法对状态栏的背景或颜色进行操作的,那个时候也还没有Material Design的概念。但是Android5.0及之后的系统都是支持这个功能的,在Android5.0及之后的系统中,使用背景图和状态栏融合的模式,在之前的系统中使用普通的模式。
想要让背景图能够和系统状态栏融合,需要借助android:fitsSystemWindows这个属性来实现。在CoordinatorLayout,AppBarLayout,CollapsingToolbarLayout这种嵌套结构的布局中,将控件的android:fitsSystemWindows属性指定成true,就表示该控件会出现在系统状态栏里。对应到我们的程序,那就是水果标题栏中的ImageView应该设置这个属性了。不过只给ImageView设置这个属性是没有用的,我们必须将ImageView布局结构中的所有父布局都设置上这个属性才可以。
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="com.example.materialtest.FruitActivity"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="250dp"
android:fitsSystemWindows="true">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
android:fitsSystemWindows="true">
<ImageView
android:id="@+id/fruit_image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:layout_collapseMode="parallax"
android:fitsSystemWindows="true"/>
········
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
·················
</android.support.design.widget.CoordinatorLayout>
但是即使我们将android:fitsSystemWindows属性都设置好了还是没有用的,因为还必须在程序的主题中将状态栏颜色指定成透明色才行。指定成透明色的方法很简单,在主题中将android:statusBarColor这个属性的值指定成@android:color/transparent就可以了。但问题在于,android:statusBarColor这个属性是从API21,也就是Android5.0系统开始才有的,之前的系统无法指定这个属性。那么,系统差异性的功能实现就要从这里开始了。
右击res目录--->New--->Directory,创建一个values-v21目录,然后右击values-v21目录--->New--->Values resource file,创建一个styles.xml文件。
<resources>
<style name="FruitActivityTheme" parent="AppTheme">
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
</resources>
这里我们定义了一个FruitActivityTheme主题,它是专门给FruitActivity使用的。FruitActivityTheme的parent主题是AppTheme,也就是说,它继承了AppTheme中的所有特性。然后我们在FruitActivityTheme中将状态栏中的颜色指定成透明色,由于values-v21目录是只有Android5.0及以上的系统才回去读取的,因此这么生命是没有问题的。
但是Android5.0之前的系统却无法识别FruitActivityTheme这个主题,因此我们还需要对values/styles.xml文件进行修改。
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="FruitActivityTheme" parent="AppTheme">
</style>
</resources>
可以看到,这里也定义了一个FruitActivityTheme主题,并且parent主题也是AppTheme,但是它的内部是空的。因为Android5.0之前的系统无法指定状态栏的颜色,因此这里什么都不用做就可以了。
<activity
android:name=".FruitActivity"
android:theme="@style/FruitActivityTheme">
</activity>
这里使用android:theme属性单独给FruitActivity指定了FruitActivityTheme这个主题,这样我们就大功告成了。