Material Design 系列之CardView、FAB和Snackbar

前言

更多Material Design 文章请看:
Material Design 之 Toolbar 开发实践总结
Material Design之 AppbarLayout 开发实践总结
Material Design 之 Behavior的使用和自定义Behavior
Material Design 之 TabLayout 使用
Material Design 之 TextInputLayout和TextInputEditText
这是Material Design 系列的最后一篇文章,前面几篇文章讲了Material Design中一些比较重要并且常用的控件,最后这一篇文章算是一个补充,讲一下CardView、FloatActionButton 和 Snackbar。由于用法比较简单,所以就不每一个都拎出来单讲。以下分别是这三个控件的用法。

CardView(卡片)

卡片是一张带有材料属性的纸片,用作展示更多详细信息的入口点。卡片包含了一组特定的数据集,数据集含有各种相关信息,如主题照片、文本,链接等等。卡片有固定的宽度和可变的高度。最大高度限制于可适应平台上单一视图的内容,但如果需要它可以临时扩展(例如,显示评论栏)。卡片不会翻转以展示其背后的信息。

卡片集是共面的,或者统一平面的多张卡片布局。如下:

Material Design 系列之CardView、FAB和Snackbar_第1张图片
card_collection_1.png

一张卡片包含了一组特定的数据集,考虑在以下这些情况使用卡片:

  • 作为一个集合,比较多种数据类型,比如:图片、视频和文本。

  • 不需要直接比较(如:用户不会直接比较图片和文本)

  • 支持内容高度可变,比如评论。

  • 包含响应按钮,比如+1 按钮或者评论

  • 要使用网格列表,但需要显示更多文本来补充图片

以上就是使用卡片的一些场景,看一个使用不当的例子(图片来自官网):

Material Design 系列之CardView、FAB和Snackbar_第2张图片
card_wrong.png

** 错误示例:** 这种卡片的使用分散了用户的注意力,不能快速浏览,也不能忽略掉,所以将这些内容放在不同的卡片上是难以理解的。其实国内有些知名APP也没有按照规范来做,给我们做了错误的示范,如知乎日报首页:

Material Design 系列之CardView、FAB和Snackbar_第3张图片
zhihuribao.png

正确的用法如下:


Material Design 系列之CardView、FAB和Snackbar_第4张图片
card_right.png

正确示例:可快速浏览的列表,用来代替卡片,是表现没有许多操作的同类内容的合适方法。

以上就是Material Design 设计规范里给的使用卡片的一些场景和正确使用方法,更多的设计规范请看:Material Design 官网。

我们要怎么实现卡片设计呢?Google 给我们提供了CardView,并且是像下兼容的(L 以下仍然可以用)。CardView 的用法比较简单,重要的属性也就几个。其实用CardView主要实现的圆角和阴影效果。看一下CardView的属性:

  • app:cardBackgroundColor 设置卡片的背景色

  • app:cardCornerRadius 设置卡片的圆角

  • app:cardElevation 设置卡片的阴影

  • app:cardUseCompatPadding 是否添加padding

  • app:cardPreventCornerOverlap 在v20和v20以前的版本添加padding,防止CardView的内容和圆角相交

上面几个属性是CardView的几个常用的属性,当然也可以在代码中设置,调用CardView.setXXX就行

       mCardView = (CardView) findViewById(R.id.card_view);
        //设置背景
        mCardView.setCardBackgroundColor(getColor(R.color.colorPrimary));
        //设置圆角
        mCardView.setRadius(5);
        //设置阴影
        mCardView.setCardElevation(3);
        //设置 兼容padding 
        mCardView.setUseCompatPadding(true);
        //
        mCardView.setPreventCornerOverlap(true);

比较简单,就上面几个属性,都一一介绍了,看一些示例:



  
     
          
          
          
          

              
              
          
     
  
  
      
  

效果如下:

CardView.png

CardView 点击效果
Material Design 的设计就是为了更贴近现实生活中的场景,当点击之后,是会有反馈的,可以给CardView 添加点击效果。

 android:clickable="true"
  android:foreground="?attr/selectableItemBackground"

这样点击时就会有波纹扩散效果了,增加体验。

cardView_click.gif

FloatingActionButton (浮动操作按钮)

FloatingActionButton(浮动操作按钮,以下简称FAB)适用于特定的进阶操作。它是漂浮在 UI 上的一个圆形图标,具有一些动态的效果,比如变形、弹出、位移等等。FAB有3种尺寸,默认尺寸、mini 尺寸和 auto 尺寸 。

默认尺寸:适用于大多数情况
mini 尺寸:仅用于创建与其他屏幕元素视觉的连续性。
auto: 基于Window(窗口)大小变化的,当窗口大小小于470dp,会选择一个较小尺寸的button,更大一点的窗口就选择更大的button

可以通过 fabSize 来控制FAB的size。因为FAB这个类继承自ImageView,所以我们可以通过setImageDrawable() 方法来控制FAB icon 的显示。FAB 默认的背景色是colorAccent,如果你想在运行时改变它的颜色,你可以调用方法setBackgroundTintList(ColorStateList) 来改变。

介绍一下FAB的几个属性:

  • app:elevation 设置阴影

  • app:rippleColor 扩散效果的颜色

  • app:fabSize 设置 FAB 的 size

  • app:layout_anchor 设置锚点

  • app:useCompatPadding 兼容padding 可用

属性比较简单,前面讲Behavior 的时候提到过,FAB 和 AppbarLayout 的联动和FAB和Snackbar的Behavior 确保Snackbar 从底部弹出时,不会遮挡FAB,而会相应的上移。这2个也是FAB 常用的场景,效果如下:

FAB.gif

布局如下:



  

     
          
          
     
  
    

        

    
  
  

代码中改变FAB 颜色,icon等:


        fab1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Snackbar.make(fab1,"点击fab1",Snackbar.LENGTH_LONG).show();
            }
        });

        fab1.setBackgroundTintList(ColorStateList.valueOf(getResources().getColor(R.color.colorPrimary)));


        fab2.setImageResource(R.drawable.ic_book_list);

        fab2.setCompatElevation(6);
    
        fab2.setSize(FloatingActionButton.SIZE_NORMAL);

还可以监听FAB的隐藏或者显示,在项目中可能会有这样的需求,当FAB隐藏或者显示之后,接下来做什么操作,监听代码如下:

 fab2.hide(new FloatingActionButton.OnVisibilityChangedListener() {
            @Override
            public void onHidden(FloatingActionButton fab) {
                Log.i(TAG,"fab hidden...");
            }
        });
        fab2.show(new FloatingActionButton.OnVisibilityChangedListener() {
            @Override
            public void onShown(FloatingActionButton fab) {
                Log.i(TAG,"fab show...");
            }
        });

可以在对应的回调方法里做接下来的操作。

Snackbar

Snackbar 是一种针对操作的轻量级反馈机制,常以一个小的弹出框的形式,出现在手机屏幕下方或者桌面左下方。它们出现在屏幕所有层的最上方,包括浮动操作按钮。

它们会在超时或者用户在屏幕其他地方触摸之后自动消失。Snackbar 可以在屏幕上滑动关闭。当它们出现时,不会阻碍用户在屏幕上的输入,并且也不支持输入。屏幕上同时最多只能现实一个 Snackbar。

Android 也提供了一种主要用于提示系统消息的胶囊状的提示框 Toast。Toast 同 Snackbar 非常相似,但是 Toast 并不包含操作也不能从屏幕上滑动关闭。

用法:
Snackbar的高度应该能容纳下所提示的 文本,并且提示与操作相关,所以不应该提示长文本,Snackbar的用法与Toast非常相似。

弹出一个Toast 的代码:

Toast.makeText(FABSimpleActivity.this,"哈哈,我是Toast",Toast.LENGTH_SHORT).show();

弹出一个snackbar的代码:

 fab2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Snackbar.make(fab2,"哈哈,我是Snackbar",Snackbar.LENGTH_SHORT).show();
            }
        });

效果如下:

Material Design 系列之CardView、FAB和Snackbar_第5张图片
snackbar.png

从上面的代码可以看出,Toast与Snackbar的调用方法非常相似,第一个参数有点区别,Toast的第一个参数是一个Context,Snackbar的第一个参数是View,但其实都是殊途同归的,Snackbar也是根据传入的View找到一个Parent View ,然后再获取Context。或许你们跟我一样很奇怪为什么要绕着么大一圈来获取这个Context,像Toast 一样直接传一个Context不行吗?答案是不行的,因为需要告诉Snackbar,让它显示在哪个容器内。Snackbar 和Toast的方式不一样,看一下源码一目了然,走读一下源码:
1,在make方法里构造了Snackbar,传入的参数是根据传的View找到的Parent View :

 public static Snackbar make(@NonNull View view, @NonNull CharSequence text,
            @Duration int duration) {
        Snackbar snackbar = new Snackbar(findSuitableParent(view));
        snackbar.setText(text);
        snackbar.setDuration(duration);
        return snackbar;
    }

Snackbar 构造方法:


    private Snackbar(ViewGroup parent) {
        mTargetParent = parent;
        mContext = parent.getContext();

        ThemeUtils.checkAppCompatTheme(mContext);

        LayoutInflater inflater = LayoutInflater.from(mContext);
        mView = (SnackbarLayout) inflater.inflate(
                R.layout.design_layout_snackbar, mTargetParent, false);

        mAccessibilityManager = (AccessibilityManager)
                mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
    }

上面inflate 的时候用到了mTargetParent,告诉Snackbar要显示在哪个容器内。
再看一下show 的方式:

 public void show() {
        SnackbarManager.getInstance().show(mDuration, mManagerCallback);
    }

然后调用scheduleTimeoutLocked 方法:

 private void scheduleTimeoutLocked(SnackbarRecord r) {
        if (r.duration == Snackbar.LENGTH_INDEFINITE) {
            // If we're set to indefinite, we don't want to set a timeout
            return;
        }

        int durationMs = LONG_DURATION_MS;
        if (r.duration > 0) {
            durationMs = r.duration;
        } else if (r.duration == Snackbar.LENGTH_SHORT) {
            durationMs = SHORT_DURATION_MS;
        }
        mHandler.removeCallbacksAndMessages(r);
        mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_TIMEOUT, r), durationMs);
    }

最后是用Handler 发了一条消息,通知显示:


    static {
        sHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
            @Override
            public boolean handleMessage(Message message) {
                switch (message.what) {
                    case MSG_SHOW:
                        ((Snackbar) message.obj).showView();
                        return true;
                    case MSG_DISMISS:
                        ((Snackbar) message.obj).hideView(message.arg1);
                        return true;
                }
                return false;
            }
        });
    }

看到这儿大概就明白了,最终调用的是showView()这个方法显示:

final void showView() {
...
// 上面省略的部分主要是判断是不是CoordinatorLayout的子View,如果添加Behavior 

if (ViewCompat.isLaidOut(mView)) {
            if (shouldAnimate()) {
                // If animations are enabled, animate it in
                animateViewIn();
            } else {
                // Else if anims are disabled just call back now
                onViewShown();
            }
        } else {
            // Otherwise, add one of our layout change listeners and show it in when laid out
            mView.setOnLayoutChangeListener(new SnackbarLayout.OnLayoutChangeListener() {
                @Override
                public void onLayoutChange(View view, int left, int top, int right, int bottom) {
                    mView.setOnLayoutChangeListener(null);

                    if (shouldAnimate()) {
                        // If animations are enabled, animate it in
                        animateViewIn();
                    } else {
                        // Else if anims are disabled just call back now
                        onViewShown();
                    }
                }
            });
        }
}

以上分析了Snackbar 的创建显示过程。其实使用是很简单的,跟以前使用Toast提示差不多。

最后还有一点就是,Toast只能给个提示,而Snackbar我们还可以给他设置一个Action,当显示Snackbar的时候,我们点击Action按钮,执行相应的操作

代码:

private void showSnackbar(){
        Snackbar snackbar = Snackbar.make(fab2,"哈哈,我是Snackbar",Snackbar.LENGTH_SHORT);
        snackbar.setAction("UNDO", new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(FABSimpleActivity.this,"执行Undo操作",Toast.LENGTH_LONG).show();
            }
        });
        
        snackbar.setActionTextColor(getResources().getColor(R.color.DarkCyan));
        snackbar.setText("已经删除1张照片");
        snackbar.show();
    }

效果如下:

snackbar_action.gif

如上图所示,添加了一个UNDO 按钮,点击按钮之行相应操作。

最后

这个三个控件的用法比较简单,本文从它们的使用场景和原理讲了它们的基本用法,了解这些之后,可以加深印象。本文是Material Design 相关的最后一篇文章,可能还有一些零碎的东西没有讲到,还有一些像RecyclerView 这些的用法网上的博客已经很多了,有的也写得很好很详细,不打算再写。另外,Material Design 系列的Demo在这儿:MaterialDesignSamples

参考
Material Design 设计规范
Material Design 中文版

你可能感兴趣的:(Material Design 系列之CardView、FAB和Snackbar)