笔记:Android Material Design

谷歌在 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的背景默认使用该颜色
colorAccentCheckBox,RadioButton,SwitchCompat等一般控件的选中效果默认采用该颜色
colorControlNormalCheckBox,RadioButton,SwitchCompat等默认状态的颜色
colorControlHighlight控件按压时的色调
colorControlActivated控件选中时的颜色,默认使用colorAccent
colorButtonNormal默认按钮的背景颜色
editTextColor默认EditView输入框字体的颜色
textColorButton,textView的文字颜色
textColorPrimaryDisableOnly RadioButton checkbox等控件的文字
textColorPrimary应用的主要文字颜色,actionBar的标题文字默认使用该颜色
colorSwitchThumbNormalswitch 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 有三种值可选

  1. always:表示永远显示在 Toolbar 中,如果屏幕空间不够则不显示
  2. ifRoom:如果屏幕空间足够显示在 Toolbar 中,不够则显示在菜单中
  3. 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 还没有完全隐藏或显示的时候,会根据当前滚动的距离自动选择是隐藏还是现实

  1. scroll:所有想滚动出屏幕的view都需要设置这个flag, 没有设置这个flag的view将被固定在屏幕顶部
  2. enterAlways:这个flag让任意向下的滚动都会导致该view变为可见,启用快速“返回模式”
  3. enterAlwaysCollapsed:当你的视图已经设置 minHeight 属性又使用此标志时,你的视图只能已最小高度进入,只有当滚动视图到达顶部时才扩大到完整高度
  4. exitUntilCollapsed:上滚动事件时,Toolbar 向上滚动退出直至最小高度,但不会完全退出屏幕
  5. 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>         

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值