• 《第一行代码》阅读笔记(三十四)——Material Design


    ToolBar

    首先:注意使用androidx.appcompat.widget.Toolbar而不是android.widget.Toolbar

    在这一章节中,作者主要介绍了几个知识点。笔者在此梳理一些

    1. xmlns:app

    ——第一行代码
    这里使用xmlns:app指定了一个新的命名空间。思考一下,正是由于每个布局文件都会使用xmlns :android来指定一个命名空间,因此我们才能一直使用android:id、android:layout_ width 等写法,那么这里指定了xmlns:app, 也就是说现在可以使用app :attribute这样的写法了。但是为什么这里要指定一个xmlns:app的命名空间呢?这是由于Material Design是在Android 5.0系统中才出现的,而很多的Material属性在5.0 之前的系统中并不存在,那么为了能够兼容之前的老系统,我们就不能使用android:attribute这样的写法了,而是应该使用app:attribute。

    了解一下就行了,这里吗还涉及到一些xml中schemas技术。现在向下兼容做的并不多了,而这种命名空间的方式仍然经常使用于自定义控件的自定义属性声明,所以还是很有必要知道是怎么回事的,但是不用深究。

    1. 主题

    这里讲了一大堆,笔者没整明白怎么回事,实话说。不过不重要

    1. 自定义actionbar

    就很简单,第一步隐藏系统的actionbar,第二步传入toolbar,方法如下:

    Toolbar toolbar = findViewById(R.id.toolbar);
            setSupportActionBar(toolbar);
    
    1. 修改toolbar文字

    android:label="Fruits"
    其实label标签就是AppName,而toolbar上面的文字就是AppName

    1. Menu属性

    ——第一行代码
    可以看到,我们通过标签来定义action 按钮,android:id用于指定按钮的id,android: icon用于指定按钮的图标,android:title用于指定按钮的文字。接着使用app: showAsAction来指定按钮的显示位置,之所以这里再次使用了app命名空间,同样是为了能够兼容低版本的系统。showAsAction 主要有以下几种值可选:always表示永远显示在Toolbar中,如果屏幕空间不够则不显示;ifRoom表示屏幕空间足够的情况下显示在Toolbar中,不够的话就显示在菜单当中;never 则表示永远显示在菜单当中。注意,Toolbar中的action按钮只会显示图标,菜单中的action 按钮只会显示文字。

    DrawerLayout

    ——第一行代码
    但是关于第二个子控件有一点需要注意,layout_ gravity 这个属性是必须指定的,因为我们需要告诉DrawerLayout滑动菜单是在屏幕的左边还是右边,指定left 表示滑动菜单在左边,指定right表示滑动菜单在右边。这里我指定了start, 表示会根据系统语言进行判断,如果系统语言是从左往右的,比如英语、汉语,滑动菜单就在左边,如果系统语言是从右往左的,比如阿拉伯语,滑动菜单就在右边。

    还有一个要注意的就是现在很多全面屏手机,侧边左右滑动是返回的操作,需要避免误触才能出发滑动菜单。解决方法,一般手机顶部1/5部分左右滑动不是返回,或者关闭侧边操作。

    作者介绍了一个小demo还是很有意思的,展示一下
    第一步:编写xml文件

    <?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/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?android:attr/actionBarSize"
                android:background="?android:attr/colorPrimary"
                android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                app:popupTheme="@style/Theme.AppCompat.Light" />
    
        </FrameLayout>
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#22FFAF"
            android:layout_gravity="start"
            android:text="this is menu" />
    </androidx.drawerlayout.widget.DrawerLayout>
    

    第二步:编写Activity

    public class MainActivity extends AppCompatActivity {
    
        private DrawerLayout mDrawerLayout;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Toolbar toolbar = findViewById(R.id.toolbar);
            setSupportActionBar(toolbar);
            mDrawerLayout = findViewById(R.id.drawer_layout);
            ActionBar actionBar = getSupportActionBar();
            if (actionBar != null) {
                actionBar.setDisplayHomeAsUpEnabled(true);
            }
        }
    
        @Override
        public boolean onOptionsItemSelected(@NonNull MenuItem item) {
            switch (item.getItemId()) {
                case android.R.id.home:
                    mDrawerLayout.openDrawer(GravityCompat.START);
            }
            return true;
        }
    }
    

    主要就是两点,第一点actionBar.setDisplayHomeAsUpEnabled(true);激活默认HomeAsUp按钮,其实就是一个小箭头。setHomeAsUpIndicator()设置图标。

    第二点就是通过onOptionsItemSelected编辑点击事件,android.R.id.home是控件的名字。mDrawerLayout.openDrawer(GravityCompat.START);就是启动滑动菜单的方法。注意openDrawer()方法要求传入一个Gravity参数,为了保证这里的行为和XML中定义的一致,我们传入了GravityCompat.START。

    NavigationView

    implementation 'com.google.android.material:material:1.1.0'
    implementation 'de.hdodenhof:circleimageview:3.1.0'
    书中引用的两个依赖需要更换成这两个新的

    在布局方面都是非常简单的

    <de.hdodenhof.circleimageview.CircleImageView
            android:id="@+id/icon_image"
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:layout_centerInParent="true"
            android:src="@drawable/nav_icon" />
    

    CircleImageView就和imageView一模一样的使用

    <com.google.android.material.navigation.NavigationView
            android:id="@+id/nav_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="start"
            app:menu="@menu/nav_menu"
            app:headerLayout="@layout/nav_header"/>
    

    和普通控件没啥区别,主要是多了很多app的属性,书中使用到的就是menu和headerLayout,分别传入一个menu和一个布局。

     NavigationView navigationView = findViewById(R.id.nav_view);
            //设置默认选项
            navigationView.setCheckedItem(R.id.nav_call);
            //设置选项的点击事件
            navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
                @Override
                public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                    mDrawerLayout.closeDrawers();
                    return true;
                }
            });
    

    FloatActionButton

    <com.google.android.material.floatingactionbutton.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/nav_icon"
                app:elevation="100dp"/>
    

    特殊的地方在于app:elevation="100dp"表示悬浮的高度,但是笔者没有发现明显的变化。除此以外与button没有差别。

    ——第一行代码
    这里使用app:elevation属性来给FloatingActionButton 指定一个高度值,高度值越大,投影范围也越大,但是投影效果越淡,高度值越小,投影范围也越小,但是投影效果越浓。当然这些效果的差异其实都不怎么明显,我个人感觉使用默认的FloatingActionButton效果就已经足够了。

    Snackbar

    ——第一行代码
    首先要明确,Snackbar并不是Toast的替代品,它们两者之间有着不同的应用场景。Toast 的作用是告诉用户现在发生了什么事情,但同时用户只能被动接收这个事情,因为没有什么办法能让用户进行选择。而Snackbar则在这方面进行了扩展,它允许在提示当中加入一个可交互按钮,当用户点击按钮的时候可以执行一些额外的逻辑操作。打个比方,如果我们在执行删除操作的时候只弹出一个Toast提示,那么用户要是误删了某个重要数据的话肯定会十分抓狂吧,但是如果我们增加一个Undo按钮,就相当于给用户提供了一种弥补措施,从而大大降低了事故发生的概率,提升了用户体验。

    FloatingActionButton fab = findViewById(R.id.fab);
            fab.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Snackbar.make(v, "Data deleted", Snackbar.LENGTH_SHORT)
                            .setAction("Undo", new View.OnClickListener() {
                                @Override
                                public void onClick(View v) {
                                    Toast.makeText(MainActivity.this, "Data restored",
                                            Toast.LENGTH_SHORT).show();
                                }
                            }).show();
                }
            });
    

    ——第一行代码
    这里调用了Snackbar的make( )方法来创建一个Snackbar对象,make( )方法的第一个参数需要传入一个View,只要是当前界面布局的任意一个View都可以,Snackbar会使用这个View来自动查找最外层的布局,用于展示Snackbar。第二个参数就是Snackbar中显示的内容,第三个参数是Snackbar显示的时长。这些和Toast都是类似的。接着这里又调用了一个setAction()方法来设置一个动作,从而让Snackbar不仅仅是一个提示,而是可以和用户进行交互的。简单起见,我们在动作按钮的点击事件里面弹出一个Toast提示。最后调用show()方法让Snackbar显示出来。

    CoordinatorLayout

    在使用Snackbar的过程中,我们发现弹出的提示会遮挡FloatActionButton。解决这个问题的办法就是使用CoordinatorLayout。

    ——第一行代码
    事实上,CoordinatorLayout可以监听其所有子控件的各种事件,然后自动帮助我们做出最为合理的响应。举个简单的例子,刚才弹出的Snackbar 提示将悬浮按钮遮挡住了,而如果我们能让CoordinatorLayout监听到Snackbar的弹出事件,那么它会自动将内部的FloatingActionButton向上偏移,从而确保不会被Snackbar遮挡到。

    使用的方法就是用androidx.coordinatorlayout.widget.CoordinatorLayout替换FrameLayout

    CardView

    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    implementation 'com.google.android.material:material:1.1.0'
    implementation 'com.github.bumptech.glide:glide:4.11.0'

    书中引用的依赖需要更换成这新的

    ——第一行代码
    注意上述声明的最后一行, 这里添加了一个Glide库的依赖。Glide是一个超级强大的图片加载库,它不仅可以用于加载本地图片,还可以加载网络图片、GIF 图片、甚至是本地视频。最重要的是,Glide的用法非常简单,只需一行代码就能轻松实现复杂的图片加载功能,因此这里我们准备用它来加载水果图片。Glide的项目主页地址是: htps:/github.com/bumptech/glide。

    CardView只是一个非常简单的控件,可以去掉圆角,之前笔者介绍过的shape也是可以完成的。

    recycleView的部分相信大家都能得心应手了,所以这里主要介绍一下Glide

    Glide.with(mContext)
                    .load(xxx)
                    .placeholder(mContext.getResources().getDrawable(R.drawable.ic_launcher_background))
                    .into(view);
    

    首先调用Glide.with()方法并传人一个Context、Activity 或Fragment参数。然后调用load()方法去加载图片,可以是一个 URL地址,也可以是一个本地路径,或者是一个资源id。再然后就是placeholder()方法是用来显示加载过程中的过渡图片的,从网络上面获取图片往往较慢,需要使用本地的图片过渡一下,以免给用户带来不好的体验。最后调用into()方法将图片设置到具体某一个ImageView中就可以了。

    AppBarLayout

    AppBarLayout就是为了解决之前在CoordinatorLayout中recyclerview和toolbar冲突的问题。
    首先使用AppBarLayout包裹toolbar。
    然后在recyclerview中添加属性app:layout_behavior = "@string/appbar_scrolling_view_behavior"
    即可解决冲突

    优化:
    在toolbar中中添加属性app:layout_scrollFlags="scroll|enterAlways|snap"即可实现再向下滑动过程中隐藏toolbar,当再次返回顶端时,重现。

    ——第一行代码
    这里在Toolbar中添加了一个app:layout_ scrollFlags属性,并将这个属性的值指定成了scroll|enterAlways |snap。其中,scroll表示当RecyclerView向上滚动的时候,Toolbar会跟着一起向上滚动并实现隐藏;enterAlways表示当RecyclerView向下滚动的时候,Toolbar会跟着一起向下滚动并重新显示。snap表示当Toolbar还没有完全隐藏或显示的时候,会根据当前滚动的距离,自动选择是隐藏还是显示。

    下拉刷新

    SwipeRefreshLayout就是用于实现下拉刷新功能的核心类。使用SwipeRefreshLayout需要导入下面的依赖
    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'

     <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">
    
            <androidx.recyclerview.widget.RecyclerView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
    
        </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
    

    Activity

    public class MainActivity extends AppCompatActivity {
    
        private SwipeRefreshLayout swipeRefreshLayout;
        
        ...
    
            swipeRefreshLayout = findViewById(R.id.refresh);
            swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary);
            swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
                @Override
                public void onRefresh() {
                    refresh();
                }
            });
        }
    
        private void refresh() {
            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()方法,然后我们在这里去处理具体的刷新逻辑就可以了。

    CollapsingToolbarLayout

    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_width="match_parent"
        android:layout_height="match_parent">
    
        <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/appBar"
            android:layout_width="match_parent"
            android:layout_height="250dp">
    
            <com.google.android.material.appbar.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="@color/colorPrimary"
                app:layout_scrollFlags="scroll|exitUntilCollapsed">
    
                <ImageView
                    android:id="@+id/fruit_image_view"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:scaleType="centerCrop"
                    app:layout_collapseMode="parallax" />
    
                <androidx.appcompat.widget.Toolbar
                    android:id="@+id/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:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">
    
                <androidx.cardview.widget.CardView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_margin="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" />
                </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"
            android:src="@drawable/nav_icon"
            app:layout_anchor="@id/appBar"
            app:layout_anchorGravity="bottom|end" />
    
    </androidx.coordinatorlayout.widget.CoordinatorLayout>
    

    这些布局都没啥好说的。
    NestedScrollView和ScrollView类似。ScrollView它允许使用滚动的方式来查看屏幕以外的数据,而NestedScrollView在此基础之上还增加了嵌套响应滚动事件的功能。由于CoordinatorLayout本身已经可以响应滚动事件了,因此我们在它的内部就需要使用NestedScrollView或RecyclerView这样的布局。NestedScrollView和ScrollView一样,内部只能存在一个直接子布局,所以我们一般添加一个LinearLayout的嵌套。
    FloatingActionButton中的app:layout_anchor和app:layout_anchorGravity相当于一个锚点,来确定FloatingActionButton的位置。

    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 = findViewById(R.id.toolbar);
            CollapsingToolbarLayout collapsingToolbar = findViewById(R.id.collapsing_toolbar);
            ImageView fruitImageView = findViewById(R.id.fruit_image_view);
            TextView fruitContentText = 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);
        }
    
        private String generateFruitContent(String fruitName) {
            StringBuilder fruitContent = new StringBuilder();
            for (int i = 0; i < 500; i++) {
                fruitContent.append(fruitName);
            }
            return fruitContent.toString();
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            switch (item.getItemId()) {
                case android.R.id.home:
                    finish();
                    return true;
            }
            return super.onOptionsItemSelected(item);
        }
    }
    

    然后就是FruitActivity,没什么好说的,之后通过MainActivity中的recyclerview中的item跳转过来就完事了。

    充分利用系统状态栏控件

    第一步:

    ——第一行代码
    想要让背景图能够和系统状态栏融合,需要借助android:fitsSystemWindows这个属性来实现。在oordinatorLayout、AppBarLayout、CollapsingToolbarLayout这种嵌套结构的布局中,将控件的ndroid:fitsSystemWindows属性指定成true,就表示该控件会出现在系统状态栏里。对应到我们的程序,就是水果标题栏中的ImageView应该设置这个属性了。不过只给ImageView设置这个属性是没有用的,我们必须将ImageView布局结构中的所有父布局都设置上这个属性才可以。

    第二步:兼容安卓5.0,设置透明背景主题

    ——第一行代码
    但是,即使我们将android: fitsSystemWindows属性都设置好了还是没有用的,因为还必须在程序的主题中将状态栏颜色指定成透明色才行。指定成透明色的方法很简单,在主题中将android:statusBarColor属性的值指定成@android: color/transparent就可以了。但问题在于,android:statusBarColor 这个属性是从API 21,也就是Android 5.0 系统开始才有的,之前的系统无法指定这个属性。那么,系统差异型的功能实现就要从这里开始了。

    右击res目录→New→Directory,创建一个values-v21目录,然后右击values-v21目录→New→Values resource file,创建一个styles.xml文件。
    添加以下内容:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    
        <style name="FruitActivityTheme" parent="AppTheme" >
            <item name="android:statusBarColor">@android:color/transparent</item>
        </style>
    </resources>
    

    第三步:修改value/styles.xml

    <style name="FruitActivityTheme" parent="AppTheme">
        </style>
    

    添加以上代码就可以了

    第四步:将FruitActivity加载主题
    修改AndroidManifest中的相关部分,如下:

      <activity android:name=".FruitActivity"
                android:theme="@style/FruitActivityTheme">
            </activity>
    

    总结

    这一章节的内容非常有趣,而且可以让你的App颜值和交互大大提升,但是有些东西应该做到能用,会用。而不是去死记硬背,这也是编程的通理,因为它永远只是一个控件,和它一样的控件还有很多很多。笔者一开始就犯了一个很大的错误,认为这些都不能信手拈来,以后怎么做一名高级开发。诚然,真正高级的开发人员往往会把精力花费在钻研底层,而不是使用。当拥有雄厚的内力时,想要用好控件,只需要上网浏览一下相关的开发文档,就可以快速上手。
    所以会阅读文档也是十分关键,这里是谷歌Material的官方文档,有兴趣的朋友可以看看。
    https://material.google.com

  • 相关阅读:
    bzoj3237[Ahoi2013] 连通图
    bzoj3075[Usaco2013]Necklace
    bzoj1876[SDOI2009] SuperGCD
    bzoj3295[Cqoi2011] 动态逆序对
    BestCoder#86 E / hdu5808 Price List Strike Back
    bzoj2223[Coci 2009] PATULJCI
    bzoj2738 矩阵乘法
    poj 1321 -- 棋盘问题
    poj 3083 -- Children of the Candy Corn
    poj 2488 -- A Knight's Journey
  • 原文地址:https://www.cnblogs.com/zllk/p/13424566.html
Copyright © 2020-2023  润新知