谷歌在 2014 年 Google I/O 大会上推出一套全新的界面设计语言——Material Design
Toolbar
Toolbar 不仅继承了 ActionBar 的所有功能,而且灵活性更高,可以配合其他控件来完成一些 Material Design 的效果
新建一个项目,默认都是会显示 ActionBar 这是根据项目中指定的主题来显示,打开 AndroidManifest.xml 文件,application 标签中有一个 android:theme 属性,指定了一个 AppTheme 的主题,打开 res/values/styles.xml 文件,里面定义了一个 AppTheme 的主题,然后指定它的 parent 是 Theme.AppCompat.Light.DarkActionBar,所以项目中自带 ActionBar
这里需要使用 Toolbar 来替代 ActionBar,所以需要指定一个不带 ActionBar 的主题,通常有
- Theme.AppCompat.NoActionBar:表示深色主题,会将界面主题设置成深色,陪衬颜色设置成浅色
- Theme.AppCompat.Light.NoActionBar:表示淡色主题,会将界面主题设置成浅色,陪衬颜色设置成深色
将 parent设置成 Theme.AppCompat.Light.NoActionBar 既
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
AppTeme 中的属性重写了,colorPrimary、colorPrimaryDark、colorAccent
属性 | 描述 |
---|---|
colorPrimary | 应用的主要色调,actionBar默认使用该颜色,Toolbar导航栏的底色 |
colorPrimaryDark | 应用的主要暗色调,statusBarColor默认使用该颜色 |
statusBarColor | 状态栏颜色,默认使用colorPrimaryDark |
windowBackground | 窗口背景颜色 |
navigationBarColor | 底部栏颜色 |
colorForeground | 应用的前景色,ListView的分割线,switch滑动区默认使用该颜色 |
colorBackground | 应用的背景色,popMenu的背景默认使用该颜色 |
colorAccent | CheckBox,RadioButton,SwitchCompat等一般控件的选中效果默认采用该颜色 |
colorControlNormal | CheckBox,RadioButton,SwitchCompat等默认状态的颜色 |
colorControlHighlight | 控件按压时的色调 |
colorControlActivated | 控件选中时的颜色,默认使用colorAccent |
colorButtonNormal | 默认按钮的背景颜色 |
editTextColor | 默认EditView输入框字体的颜色 |
textColor | Button,textView的文字颜色 |
textColorPrimary | DisableOnly RadioButton checkbox等控件的文字 |
textColorPrimary | 应用的主要文字颜色,actionBar的标题文字默认使用该颜色 |
colorSwitchThumbNormal | switch thumbs 默认状态的颜色. (switch off) |
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_height="match_parent"
android:layout_width="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:logo="@mipmap/ic_launcher"
app:title="标题"
app:titleTextColor="#FFFFFF"
app:subtitle="副标题"
app:subtitleTextColor="#FFFFFF"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" >
</androidx.appcompat.widget.Toolbar>
</LinearLayout>
首先指定了一个新的命名空间 xmlns:app 由于 Material Design 是在 Android5.0 后出现的,例如 app:popupTheme 原来的老系统的 android:[attribute] 不存在 popupTheme 属性,为了兼容之前的老系统,就创建使用了一个新的命名空间,这样老系统至需要引用这个命名空间就可以使用这个属性了
之后定义了一个 Toolbar 控件,由于刚才在 styles.xml 文件中奖程序的主题指定为浅色主题,因此 Toolbar 现在也是浅色主题,而 Toolbar 上面的各种元素会自动使用深色主题,为了和主体颜色区别开,我们想将 Toolbar 设置成深色主题,为了让 Toolbar 单独使用深色主题,需要使用 android:theme 属性指定成深色的主题,但这时 Toolbar 上的元素还是深色主题,所以使用 app:popupTheme 属性将其设置成浅色主题,其他的都很好理解,Toolbar 的高度设置成了和 ActionBar 的高度一样
<?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/toolbar_backup"
android:icon="@drawable/ic_exit"
android:title="Backup"
app:showAsAction="always"/>
<item
android:id="@+id/toolbar_delete"
android:icon="@drawable/delete"
android:title="Delete"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/toolbar_settings"
android:icon="@drawable/settings"
android:title="Settings"
app:showAsAction="never"/>
</menu>
新建一个菜单文件,app:showAsAction 有三种值可选
- always:表示永远显示在 Toolbar 中,如果屏幕空间不够则不显示
- ifRoom:如果屏幕空间足够显示在 Toolbar 中,不够则显示在菜单中
- never:永远显示在菜单中
注意 Toolbar中只会显示图标,菜单中只会显示文字
图标可以去阿里的网站下载https://www.iconfont.cn/collections/index
导入 svg 图像
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tool_bar);
Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar_toolbar);
//把Toolbar 设置到 ActionBar里面 这样就可以当做ActionBar来使用 ,这一步很重要 不这样设置 就是空白,并且下面两行代码位置不能替换
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null){
//设置Toolbar home键可点击
actionBar.setDisplayHomeAsUpEnabled(true);
//设置Toolbar home键图标 如果不设置,默认是一个箭头
//actionBar.setHomeAsUpIndicator(R.drawable.ic_drawer_am);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()){
case R.id.toolbar_backup:
Toast.makeText(ToolBarActivity.this, "你点击了backup", Toast.LENGTH_SHORT).show();
break;
case R.id.toolbar_delete:
Toast.makeText(ToolBarActivity.this, "你点击了delete", Toast.LENGTH_SHORT).show();
break;
case R.id.toolbar_settings:
Toast.makeText(ToolBarActivity.this, "你点击了settings", Toast.LENGTH_SHORT).show();
break;
default:
break;
}
return true;
}
首先通过 findViewById() 方法获得 Toolbar 的实例,然后调用 setSupportActionBar() 方法将 Toolbar 的实例传入,这样就可以使用 Toolbar,又与 ActionBar 外观一致,其他的都是常规的设置菜单操作
滑动菜单
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar_drawerlayout"
android:layout_height="match_parent"
android:layout_width="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:logo="@mipmap/ic_launcher"
app:title="标题"
app:titleTextColor="#FFFFFF"
app:subtitle="副标题"
app:subtitleTextColor="#FFFFFF"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" >
</androidx.appcompat.widget.Toolbar>
</FrameLayout>
<LinearLayout
android:layout_gravity="start"
android:id="@+id/toolbar_linearlayout"
android:background="#FFFFFF"
android:orientation="vertical"
android:layout_width="300dp"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="侧滑菜单"
android:textSize="30sp"
android:background="#FFFFFF"/>
</LinearLayout>
</androidx.drawerlayout.widget.DrawerLayout>
DrawerLayout 控件中放置了两个直接子控件,第一个子控件是 FrameLayout 用于作为主屏幕中显示的内容,第二个子控件是 LinearLayout 用于作为滑动菜单中显示的内容,第二个子控件的 android:layout_gravity 属性是必须的,作用是告诉 DrawerLayout 滑动菜单是从屏幕左边还是右边滑出,指定了 start 表示根据系统语言进行判断,如果系统语言是从左到右,比如英语、汉语滑动菜单就在左边,如果系统语言是从右到左,比如阿拉伯语,滑动菜单就在右边
private DrawerLayout drawerLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tool_bar);
Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar_toolbar);
setSupportActionBar(toolbar);
drawerLayout = (DrawerLayout)findViewById(R.id.toolbar_drawerlayout);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
//设置Toolbar home键可点击
actionBar.setDisplayHomeAsUpEnabled(true);
//设置Toolbar home键图标 如果不设置,默认是一个箭头
actionBar.setHomeAsUpIndicator(getResources().getDrawable(R.drawable.ic_liebiao));
}
}
获得 DrawerLayout 的实例
switch (item.getItemId()){
......
case android.R.id.home:
drawerLayout.openDrawer(GravityCompat.START);
default:
break;
}
在 onOptionsItemSelected() 方法中添加 HomeAsUp 按钮的点击事件,注意 HomeAsUp 按钮的 id 永远是 android.R.id.home 然后调用 DrawerLayout 的 openDrawer() 方法将滑动菜单显示出来
NavigationView
添加依赖
implementation 'com.google.android.material:material:1.3.0-alpha02'
implementation 'de.hdodenhof:circleimageview:3.1.0'
第一行是 Design Support 库,NavigationView 就是 Design Support 库中的一个控件,第二个是可以实现图片圆形化
开始使用 NavigationView 需要准备两个文件:navigationviewmenu 和 headerLayout,menu 是用于在 NavigationView 中显示具体菜单项,headerLayout 用来显示头部布局
<?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_menu"
android:icon="@drawable/caidan"
android:title="菜单"/>
<item
android:id="@+id/nav_mifan"
android:icon="@drawable/mifan"
android:title="米饭"/>
<item
android:id="@+id/nav_freewifi"
android:icon="@drawable/freewifi"
android:title="wifi"/>
<item
android:id="@+id/nav_putaojiu"
android:icon="@drawable/putaojiu"
android:title="葡萄酒"/>
<item
android:id="@+id/nav_miantiao"
android:icon="@drawable/miantiao"
android:title="面条"/>
</group>
</menu>
在 <menu> 中嵌套一个 <group> 标签,然后将 group 的 android:checkableBehavior 属性设置成 single,group 表示是一个组,checkableBehavior 为 single 表示组中所有的菜单项为单选,其他的都是基本的菜单操作
<?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="200dp"
android:padding="10dp"
android:background="?attr/colorPrimary">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/header_image"
android:layout_width="70dp"
android:layout_height="70dp"
android:src="@drawable/img_4"
android:layout_centerInParent="true"/>
<TextView
android:id="@+id/header_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="moleinuoyi"
android:textColor="#ffffff"
android:textSize="14sp"/>
<TextView
android:id="@+id/header_mail"
android:layout_above="@id/header_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1231312312@qq.com"
android:textColor="#ffffff"
android:textSize="14sp"/>
</RelativeLayout>
最外层是一个 RelativeLayout 指定它的背景色为 colorPrimary,RelativeLayout 中放置了 3 个控件,CircleImageView 是一个将图片圆形化的控件,使用 android:src 属性指定一张图片,设置 android:layout_alignParentBottom 属性为 true 让图片放于整个控件的中间位置,另外两个 TextView 用于显示用户名和邮箱地址
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar_drawerlayout"
android:layout_height="match_parent"
android:layout_width="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:logo="@mipmap/ic_launcher"
app:title="标题"
app:titleTextColor="#FFFFFF"
app:subtitle="副标题"
app:subtitleTextColor="#FFFFFF"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" >
</androidx.appcompat.widget.Toolbar>
</FrameLayout>
<com.google.android.material.navigation.NavigationView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
app:menu="@menu/navigationviewmenu"
app:headerLayout="@layout/headerlayout"/>
</androidx.drawerlayout.widget.DrawerLayout>
然后就可以将之前的 LinearLayout 换成 NavigationView,通过 app:menu 属性和 app:headerLayout 属性 将之前准备的文件设置进去,就定义完成了
注意 android:layout_gravity=“start” 属性是必要的
@Override
protected void onCreate(Bundle savedInstanceState) {
......
......
NavigationView navigationView = (NavigationView)findViewById(R.id.toolbar_navigationview);
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()){
case R.id.nav_menu:
drawerLayout.closeDrawers();
break;
case R.id.nav_mifan:
Toast.makeText(ToolBarActivity.this, "你点击了米饭", Toast.LENGTH_SHORT).show();
break;
case R.id.nav_freewifi:
Toast.makeText(ToolBarActivity.this, "你点击了wifi", Toast.LENGTH_SHORT).show();
break;
case R.id.nav_putaojiu:
Toast.makeText(ToolBarActivity.this, "你点击了葡萄酒", Toast.LENGTH_SHORT).show();
break;
case R.id.nav_miantiao:
Toast.makeText(ToolBarActivity.this, "你点击了面条", Toast.LENGTH_SHORT).show();
break;
default:
break;
}
return true;
}
});
}
最后为菜单添加点击事件,首先获取 NavigationView 的实例,然后调用 setNavigationItemSelectedListener() 为菜单项设置一个监听器,当用户点击任意菜单时,就会回掉 onNavigationItemSelected() 方法中
悬浮按钮
FloatingActionButton
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar_drawerlayout"
android:layout_height="match_parent"
android:layout_width="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar_toolbar"
......
/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/toolbar_FloatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="@drawable/settings"/>
</FrameLayout>
......
</androidx.drawerlayout.widget.DrawerLayout>
android:layout_gravity=“bottom|end” end 和之前的 start 是一样的,根据系统语言选择,不过与之相反, android:layout_margin 与边缘空出一些边距,android:src 设置了一个图标
app:elevation 可以设置悬浮图标的高度值,高度值越大,投影范围越大,阴影越浓,反而反之
@Override
protected void onCreate(Bundle savedInstanceState) {
......
FloatingActionButton floatingActionButton = (FloatingActionButton)findViewById(R.id.toolbar_FloatingActionButton);
floatingActionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(ToolBarActivity.this, "你点击了FloatingActionButton", Toast.LENGTH_SHORT).show();
}
});
}
添加点击监听事件,其实就只是个普通的按钮
Snackbar
以前都只是使用 Toast 来作为提示工具,Snackbar 在提示当中加入了一个可以交互的按钮,当用户点击按钮时,可以执行一些额外的操作
floatingActionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "点击了FloatingActionButton", Snackbar.LENGTH_SHORT).setAction("other", new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(ToolBarActivity.this, "你点击了other", Toast.LENGTH_SHORT).show();
}
}).show();
}
});
调用了 Snackbar 的 make() 方法来创建一个 Snackbar 对象,第一个参数是一个 View,只要是当前布局的任何一个 View 都可以,第二个是提示内容,第三个是显示的时长
接着调用一个 setAction() 方法来设置一个动作,最后调用 show() 方法让 Snackbar 显示出来
CoordinatorLayout
是一个加强版的 FrameLayout,这个布局也是 Design Support 库提供,CoordinatorLayout 可以监听所有子控件的各种事件,例如刚刚弹出的 Snackbar 提示将悬浮按钮遮住了,就可以让 CoordinatorLayout 监听到 Snackbar 的弹出事件,让悬浮按钮向上偏移,从而不会被遮挡
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar_drawerlayout"
android:layout_height="match_parent"
android:layout_width="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
......
......
/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
......
......
/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/toolbar_navigationview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
app:menu="@menu/navigationviewmenu"
app:headerLayout="@layout/headerlayout"/>
</androidx.drawerlayout.widget.DrawerLayout>
可以看到只是对 FrameLayout 进行了一个替换
卡片式布局
CardView
CardView 是用于实现卡片式布局效果的重要控件,CardView 也是一个 FrameLayout ,只是额外提供了圆角和阴影的效果
implementation 'com.github.bumptech.glide:glide:4.11.0'
添加了一个 Glide 库的依赖,Glide 是一个强大的图片加载库,不仅可以加载本地图片,还可以加载网络图片,Gif图片,甚至是本地视频
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar_drawerlayout"
android:layout_height="match_parent"
android:layout_width="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
......
......
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/toolbar_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
......
......
/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
......
......
/>
</androidx.drawerlayout.widget.DrawerLayout>
在 CoordinatorLayout 中添加了一个 RecyclerView,指定 id 设置长款,都为 match_parent,这样 Recycler 就沾满了整个布局空间
定义一个图片的实体类
public class MyImage {
private String imageName;
private int imageId;
public MyImage(String imageName, int imageId) {
this.imageName = imageName;
this.imageId = imageId;
}
public String getImageName() {
return imageName;
}
public void setImageName(String imageName) {
this.imageName = imageName;
}
public int getImageId() {
return imageId;
}
public void setImageId(int imageId) {
this.imageId = imageId;
}
}
只有两个字段,一个是图片资源的id,一个是图片的名称
然后需要为 RecyclerView 子项指定一个自定义的布局,在 layout 目录下新建 image_item.xml 文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_margin="5dp"
app:cardCornerRadius="4dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/image_image"
android:layout_width="match_parent"
android:layout_height="100dp"
android:scaleType="centerCrop"/>
<TextView
android:id="@+id/image_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="5dp"
android:textSize="16sp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
定义一个 CardView 布局,通过 app:cardCornerRadius 属性指定卡片圆角的弧度,数值越大,圆角弧度越大,app:elevation 属性指定卡片的高度,高度越大,阴影范围越大,阴影月淡,与 FloatingActionButton 一致,因为 CardView 是一个 FrameLayout ,因此没有什么好的定位方式,所以又嵌套了一个 LinearLayout ,里面放置了一个ImageView,一个TextView,ImageView 中 android:scaleType 属性表示图片的缩放模式 centerCrop 表示让图片保持原来比例填充,超出屏幕部分裁剪掉。
public class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ViewHolder> {
private Context context;
private List<MyImage> imageList;
public ImageAdapter(List<MyImage> imageList){
this.imageList = imageList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (context == null){
context = parent.getContext();
}
View view = LayoutInflater.from(context).inflate(R.layout.image_item, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
MyImage image = imageList.get(position);
holder.imageName.setText(image.getImageName());
Glide.with(context).load(image.getImageId()).into(holder.imageView);
}
@Override
public int getItemCount() {
return imageList.size();
}
static class ViewHolder extends RecyclerView.ViewHolder{
CardView cardView;
ImageView imageView;
TextView imageName;
public ViewHolder(View view){
super(view);
cardView = (CardView)view;
imageView = (ImageView) view.findViewById(R.id.image_image);
imageName = (TextView) view.findViewById(R.id.image_name);
}
}
}
为 RecyclerView 准备一个适配器,与前面的适配器写法相同,唯一不同的是再 onBindViewHolder() 方法中使用了 Glide 来加载图片
Glide.with(context) 方法并传入一个 Context 、Activaty 或 Fragment 参数,然后调用 load() 方法去加载图片,可以是一个 URL 地址,也可以是一个本地路径,或者是一个 id 资源,最后调用 into() 方法将图片设置到某一个 ImageView 中
其次,从网络上找来的图片可能像素非常高,如果不经过压缩就直接展示的话,很容易造成内存溢出,而是用了 Glide 加载图片的过程中包括的对图片的压缩
private MyImage[] images = {new MyImage("111", R.drawable.img_3),
new MyImage("222", R.drawable.img_4),
new MyImage("333", R.drawable.img_5),
new MyImage("444", R.drawable.img_6),
new MyImage("555", R.drawable.img_7),
new MyImage("666", R.drawable.img_8),
new MyImage("777", R.drawable.img_9)};
private List<MyImage> imageList = new ArrayList<>();
private ImageAdapter imageAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
......
......
initImage();
RecyclerView recyclerView = (RecyclerView)findViewById(R.id.toolbar_recycler_view);
GridLayoutManager layoutManager = new GridLayoutManager(this, 2);
recyclerView.setLayoutManager(layoutManager);
imageAdapter = new ImageAdapter(imageList);
recyclerView.setAdapter(imageAdapter);
}
private void initImage(){
imageList.clear();
for (int i=0;i<20;i++){
Random random = new Random();
int index = random.nextInt(images.length);
imageList.add(images[index]);
}
}
先定义一个数组,数组里存放很多个 MyImage 的实例,每个实例都表示一张图片,然后在 initImage() 方法中,先是清空图片列表 imageList,再随机从 MyImage 数组中随机选出 20 个放到 imageList 中,之后就是标准的 RecyclerView 的用法,不过这里使用了 GirdLayoutManager 布局,它的构造函数接收两个参数,第一个是 Context,第二个是列数
但是 Toolbar 被 RecyclerView 遮挡住了
AppBarLayout
由于 RecyclerView 和 ToolBar 都是放在 CoordinatorLayout 中,CoordinatorLayout 是一个加强的 FrameLayout,FrameLayout 对所有的控件不进行明确的定位,所有的控件都默认摆放到左上角,从而产生了遮挡的现象,可以简单的使用偏移进行解决,让 RecyclerView 向下偏移一个 Toolbar 的高度,从而不会遮挡住 Toolbar
AppBarLayout 是一个垂直方向的 LinearLayout,它在内部做了很多否滚动事件的封装
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar_drawerlayout"
android:layout_height="match_parent"
android:layout_width="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout ... >
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar ...
app:layout_scrollFlags="scroll|enterAlways|snap"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/toolbar_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton ... />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView ... />
</androidx.drawerlayout.widget.DrawerLayout>
定义了一个 AppBarLayout,并将 Toolabr 放置在了 AppBarLayout 里面,然后在 RecyclerView 中使用了 app:layout_behavior 属性,指定了一个布局行为,appbar_scrolling_view_behavior 是由 Design Support 库提供的
当 RecyclerView 滑动的时候已经将滚动事件通知给 AppBarLayout,在 Toolbar 标签中添加一个 app:layout_scrollFlags 属性,并将这个值指定为 scroll|enterAlways|snap ,scroll 表示当 RecyclerView 向上滚动的时候,Toolbar 会跟着一起向上滚动并实现隐藏,enterAlways 表示当 RecyclerView 向下滚动的时候,Toolbar 会跟着一起向上滚动并重新显示,snap 表示当 Toolbar 还没有完全隐藏或显示的时候,会根据当前滚动的距离自动选择是隐藏还是现实
- scroll:所有想滚动出屏幕的view都需要设置这个flag, 没有设置这个flag的view将被固定在屏幕顶部
- enterAlways:这个flag让任意向下的滚动都会导致该view变为可见,启用快速“返回模式”
- enterAlwaysCollapsed:当你的视图已经设置 minHeight 属性又使用此标志时,你的视图只能已最小高度进入,只有当滚动视图到达顶部时才扩大到完整高度
- exitUntilCollapsed:上滚动事件时,Toolbar 向上滚动退出直至最小高度,但不会完全退出屏幕
- snap:滚动比例的一个吸附效果。也就是说,不会存在局部显示的情况,滚动 Toolbar 的部分高度,当我们松开手指时,Toolbar 要么向上全部滚出屏幕,要么向下全部滚进屏幕,类似微信小程序界面
https://www.jianshu.com/p/7caa5f4f49bd
下拉刷新
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar_drawerlayout"
android:layout_height="match_parent"
android:layout_width="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
....../>
</com.google.android.material.appbar.AppBarLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/toolbar_swipeRefresh"
android:layout_height="match_parent"
android:layout_width="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/toolbar_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
....../>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
....../>
</androidx.drawerlayout.widget.DrawerLayout>
swipeRefreshLayout 就是用于实现下拉刷新的核心类,将想要实现下拉刷新的控件放到 swipeRefreshLayout 中,就可以实现控件支持下拉刷新
注意,由于 RecyclerView 变成了 SwipeRefreshLayout 的子控件,所以之前使用的 app:layout_behavior 声明的布局行为也要移动到 SwipeRefreshLayout 中
@Override
protected void onCreate(Bundle savedInstanceState) {
......
......
//下拉刷新
swipeRefreshLayout = (SwipeRefreshLayout)findViewById(R.id.toolbar_swipeRefresh);
swipeRefreshLayout.setColorSchemeColors(getResources().getColor(R.color.colorPrimary));
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
refreshImage();
}
});
}
private void refreshImage(){
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
runOnUiThread(new Runnable() {
@Override
public void run() {
initImage();
imageAdapter.notifyDataSetChanged();
swipeRefreshLayout.setRefreshing(false);
}
});
}
}).start();
先获得 SwipeRefreshLayout 的实例,然后调用 setColorSchemeColors() 方法设置刷新进度条的颜色,接着调用 setOnRefreshListener() 方法里设置一个刷新的监听器,定触发了下拉刷新的时候就会回掉这个监听器的 onRefresh() 方法,然后在里面实现刷新的逻辑
refreshImage() 方法先是创建了一个线程,然后将线程沉睡2秒模拟网络请求数据,之后使用 runOnUiThread() 方法将线程切换回主线成,调用 initImage() 方法重新生成数据,再调用 适配器的 notifyDataSetChanged() 方法通知数据发生了变化,最后调用 SwipeRefreshLayout 的 setRefreshing() 方法并传入 false 表示刷新事件完成,隐藏进度条
可折叠式标题栏
CollapsingToolbarLayout 是一个作用于 Toolbar 基础之上的布局,但是 CollapsingToolbarLayout 不能独立存在,只能作为 AppBarLayout 的直接子布局来使用,而 AppBarLayout 又必须是 CoordinatorLayout 是子布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/image_appBar"
android:layout_width="match_parent"
android:layout_height="250dp"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/image_collapsingToolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:id="@+id/image_imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:fitsSystemWindows="true"
app:layout_collapseMode="parallax"/>
<androidx.appcompat.widget.Toolbar
android:id="@+id/image_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"/>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.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">
<androidx.cardview.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/image_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"/>
</androidx.cardview.widget.CardView>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:elevation="8dp"
android:src="@drawable/settings"
app:layout_anchor="@id/image_appBar"
app:layout_anchorGravity="bottom|end"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
新建一个 ImageActivity 用于作为图片展示页面,分为图片标题栏,和图片详细内容,图片标题栏用 AppBarLayout 实现,图片详细内容用 NestedScrollView 实现
1.首先使用 CoordinatorLayout 作为最外层了布局,android:fitsSystemWindows=“true” 属性左后讲
图片标题栏
2.接着在 CoordinatorLayout 中嵌套了一个 AppBarLayout 设置 id,指定 长宽
3.再接着在 AppBarLayout 中嵌套一个 CollapsingToolbarLayout 使用 android:theme 属性指定了一个 @style/ThemeOverlay.AppCompat.Dark.ActionBar 的主题,app:contentScrim 属性用于指定 ToolbarLayout 在折叠之后的背景颜色,CollapsingToolbarLayout 折叠之后就只是一个普通的 Toolbar,app:layout_scrollFlags 属性指定为 scroll 表示 CollapsingToolbarLayout 会随着内容详情一起滚动,exitUntilCollapsed 表示当 CollapsingToolbarLayout 随着内容详情滚动完成就会停留在页面之上,不会移出屏幕
4.在 CollapsingToolbarLayout 中定义一个 ImageView 和 一个 Toolbar,意味着这个标题栏由图片和普通的标题栏组成, ImageView 标签中android:scaleType 属性表示图片的缩放模式 centerCrop 表示让图片保持原来比例填充,超出屏幕部分裁剪掉, app:layout_collapseMode 属性表示当前控件在 CollapsingToolbarLayout 折叠过程中的折叠模式,parallax 表示会在折叠过程中产生一定的错位偏移,而在 Toolbar 中 app:layout_collapseMode=“pin” 属性表示,在页面滚动的过程中 Toolbar 会一直停留在顶部
图片详细内容
5.图片详细内容最外层使用 NestedScrollView,NestedScrollView 除了拥有 ScrollView 的用法还增加了嵌套想用滚动事件的功能,由于 CoordinatorLayout 已经可以响应滚动事件了,因此在它的内部需要使用 NestedScrollView 或 RecyclerView 布局,app:layout_behavior="@string/appbar_scrolling_view_behavior" 属性指定了一个布局的行为
6.不管是 NestedScrollView 或 RecyclerView 布局,它们的内部只允许存在一个直接布局,所以先嵌套了一个 LinearLayout,然后再在 LinearLayout 中放具体内容
7.在 LinearLayout 中放置一个 CardView 卡片式布局,又在这个卡片式布局中放入了一个 TextView 用于显示内容的详情,app:cardCornerRadius 属性设置卡片的角弧度, CardView 和 TextView 都增加了一些边距
8.最后添加了一个悬浮按钮,FloatingActionButton,使用 app:layout_anchor="@id/image_appBar" 属性指定了一个锚点,表示悬浮按钮会出现在 AppBarLayout 区域内, app:layout_anchorGravity 属性将悬浮按钮设置到右下角
public class ImageActivity extends AppCompatActivity {
public static final String IMAGE_NAME = "image_name";
public static final String IMAGE_ID = "image_id";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image);
Intent intent = getIntent();
String imageName = intent.getStringExtra(IMAGE_NAME);
int imageId = intent.getIntExtra(IMAGE_ID, 0);
Toolbar toolbar = (Toolbar)findViewById(R.id.image_toolbar);
CollapsingToolbarLayout collapsingToolbarLayout = (CollapsingToolbarLayout)findViewById(R.id.image_collapsingToolbar);
ImageView imageView = (ImageView)findViewById(R.id.image_imageView);
TextView textView = (TextView)findViewById(R.id.image_textView);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null)
actionBar.setDisplayHomeAsUpEnabled(true);
collapsingToolbarLayout.setTitle(imageName);
Glide.with(this).load(imageId).into(imageView);
String imageContent = generateImageView(imageName);
textView.setText(imageContent);
}
private String generateImageView(String imageName){
StringBuilder imageContext = new StringBuilder();
for (int i=0;i<50;i++){
imageContext.append("["+imageName+"]");
}
return imageContext.toString();
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()){
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}
onCreate() 方法中通过 Intent 获取到传入的图片名和图片资源 id,然后通过 findViewById() 方法获取到布局文件中定义的各个控件的实例,接着就是 Toolbar 标准用法
之后开始填充界面上的内容,调用 CollapsingToolbarLayout 的 setTitle() 方法将图片的名显示到标题上,然后使用 Glide 加载传入图片并设置到 ImageView 上,再设置图片详细内容显示到 TextView 上
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (context == null){
context = parent.getContext();
}
View view = LayoutInflater.from(context).inflate(R.layout.image_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();
MyImage myImage = imageList.get(position);
Intent intent = new Intent(context, ImageActivity.class);
intent.putExtra(ImageActivity.IMAGE_NAME, myImage.getImageName());
intent.putExtra(ImageActivity.IMAGE_ID, myImage.getImageId());
context.startActivity(intent);
}
});
return holder;
}
需要给 CardView 注册一个监听事件,在点击图片的时候跳转到 ImageActivity 界面上去
这里图片背景和状态栏有不搭的感觉,在 Android5.0 之前的系统,是无法对状态栏的背景颜色进行操作的,需要借助 android:fitsSystemWindows 属性来实现,将 CoordinatorLayout、AppBarLayout、CollapsingToolbarLayout 这种嵌套结构布局的 android:fitsSystemWindows 属性指定成 true,就表示该空间会出现在系统的状态栏中,对应程序就是将 ImagView 布局中所有的父布局都设置上这个属性
除此之外还必须将状态栏中的颜色指定成透明,在主题中将 android:statusBarColor 属性指定成 @android:color/transparent 就行,但是这属性属于 API 21 ,如果将这个主题写在 src/main/res/values/styles.xml 文件中会报错
values-v21 目录只有 Android 5.0 以上的系统才会去读取
在原来的 src/main/res/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="ImageActivityTheme" parent="AppTheme"/>
</resources>
最后需要修改 AndroidManifest.xml 文件中代码,添加 Android:theme 属性指定 ImageActivityTheme 这个主图
<application
android:name="org.litepal.LitePalApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
......
<activity android:name="com.example.material.ImageActivity"
android:theme="@style/ImageActivityTheme"/>
......
</application>