MaterialDesign设计0:定义阴影与裁剪视图

本文解析了MaterialDesign中视图阴影效果的实现原理及设置方法,并介绍了如何使用揭露动画和裁剪视图来增强用户体验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在读谷歌的官方文档,整理一下,以备不时之需。部分内容如有错误,欢迎留言指正。

前言

本篇博客解读MaterialDesign中的视图高度剪裁视图。视图高度说的通俗点,就是我们在MaterialDesign中看到的View的阴影效果。官方给定的示意图:
视图高度
该效果图分为上下两个View,上图阴影较大,下图阴影较小,从阴影效果带来的直观感受来看,上面的View明显比下面的View要“高”一些。这种感觉和生活中的常识是一致的:假设阳光下有一张纸,放在桌子上时的阴影几乎只有边缘部分,而当我们把它拿起时,阴影会随着我们拿起的高度升高而随之变大。

谷歌便把这种感官添加到了View中:默认情况下的View(放在桌子上的纸),应该如下图所示,只有一条淡淡的阴影(2dp),当我们对View进行类似单击等操作时(拿起桌子上的纸),View的阴影发生改变(升高了6dp),从而使用户有一种View被“拿起来”等待操作的感觉。

下面我们来看看各类View在MD中的阴影效果,先放效果图:
效果图

  • Button按钮:自带阴影交互效果,所以这里这里就不拿Button举例子了。
  • 背景可绘制View:如TextView、CardView、ImageView等可以设置背景的View。

    以下内容都基于背景可绘制的View进行讲解,为什么要求背景可绘制呢?因为有了背景才有阴影,你想想空气这类透明的事物会产生阴影效果吗?所以这里要求View的背景是可绘制的,并且背景不能为透明


动手操作:设置阴影交互

组成:阴影交互效果由两部分组成:默认高度(elevation)和动态转换高度(translationZ),即官方文档中说的“Z轴高度 = elevation + translationZ“
  • 设置默认高度elevation,以TextView为例子

     <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:background="#03A9F4"
        android:clickable="true"
        android:elevation="2dp"
        android:gravity="center"
        android:text="TextView有阴影"
        android:textSize="20sp"/>

    background——>设定背景,一定要注意背景不能为透明
    clickable——>让其能响应用户的点击
    elevation——>2dp的默认阴影效果。
    *注意:CardView中的默认阴影效果需要调用app:cardElevation=”2dp”
    对比效果如下

    这里写图片描述
    仔细看,是能看出来下图的2dp阴影的。但是此时单击TextView并没有使阴影变大。

  • 设置动态转换高度translationZ

    阴影变大的操作,是通过translationZ属性来操作的。translationZ指的是View在Z轴上的显示高度(也就是把View”拿起来”的高度)。默认的translationZ应该为0(因为默认的View没有被”拿起来”),只有当我们和View产生交互时,才应该让translationZ的值增加,产生更大的阴影效果。

    因此,translationZ这个属性不是让我们在Veiw的布局中直接使用固定值的,而是应该根据View的不同交互状态而做出大小的调整。Google文档方案为”selector+属性动画”来动态改变translationZ的数值。

    1. 下面我们建立一个selector命名为selector_view_raise.xml,里面根据View的不同状态设置不同的属性动画

      <selector xmlns:android="http://schemas.android.com/apk/res/android">
          <!--按下状态,translationZ变为6dp-->
          <item android:state_pressed="true">
              <objectAnimator
                  android:duration="@android:integer/config_shortAnimTime"
                  android:propertyName="translationZ"
                  android:valueTo="6dp"
                  android:valueType="floatType"></objectAnimator>
          </item>
          <!--默认状态,translationZ变回0dp-->
          <item>
              <objectAnimator
                  android:duration="@android:integer/config_shortAnimTime"
                  android:propertyName="translationZ"
                  android:valueTo="0dp"
                  android:valueType="floatType"></objectAnimator>
          </item>
      </selector>
    2. 把定义好的selector_view_raise.xml设置到TextView中,即可产生阴影交互

      android:stateListAnimator="@animator/selector_view_raise"     

      如果不想要View的阴影交互行为,请将 android:stateListAnimator 属性设置为 @null。

    3. 运行后点击TextView,即可出现文章开头展示的动态效果
  • 只要是背景可绘制View,都可产生阴影交互。阴影的效果,会根据绘制的背景边缘进行自动填充。下面我们来自定义一个背景,看看效果


自定义shape的阴影交互

上面例子中只使用的颜色作为背景。除了颜色,我们还可以自定义shape,用shape作为View的背景,阴影会根据我们定义的shape边缘,自动产生阴影效果。

  • 定义一个圆角shape命名为shape_background_blue.xml

    <shape xmlns:android="http://schemas.android.com/apk/res/android"
           android:shape="rectangle">
        <solid android:color="@color/colorPrimary"></solid>
        <corners android:radius="5dp"></corners>
    </shape>
  • 设置View的background属性

    android:background="@drawable/shape_background_blue"      
  • 该View将自动绘制一个带有圆角的阴影


剪裁视图和使用揭露动画

在5.0后,我们可以对边界为矩形、圆形和圆角矩形的View进行剪裁操作。可使用View.setClipToOutline() 方法或 android:clipToOutline 属性将视图裁剪至其轮廓区域,也可调用View.setClipBounds(Rect)来裁剪制定的区域。

谷歌指出,裁剪View是一个成本高昂的操作,因此不可以把剪裁操作作为属性动画(比如想把一个正方形一点点的剪成一个圆)。 如果要实现类似效果,请使用揭露效果动画

下面我们先来看一下揭露效果 + 裁剪操作
揭露效果和裁剪操作
通过效果图我们可以发现:
1.首次点击搜索按钮时,蓝色区域收起,再次点击,蓝色展开
2.点击裁剪按钮,蓝色区域被裁剪为左下角的1/4
3.再次点击搜索按钮,被裁剪后的蓝色区域收起,再次点击,蓝色展开

  • 编写布局,这一步比较简单,我用了3个ImageView来分别显示蓝色区域、搜索按钮、裁剪按钮。大家copy过去就可以了,图标自行替换

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <ImageView
            android:id="@+id/iv_blue_scope"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:background="@color/colorPrimary"
            />
        <ImageView
            android:id="@+id/iv_search"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:background="@android:drawable/ic_menu_search"
            />
        <ImageView
            android:id="@+id/iv_cut"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
           android:background="@drawable/ic_content_cut_black_24dp"/>
    </LinearLayout>
  • 找到控件,对控件设置单击事件

    //控件
    ImageView iv_blue_scope;
    ImageView iv_search;
    ImageView iv_cut;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_0);
        iv_blue_scope = (ImageView) findViewById(R.id.iv_blue_scope);
        iv_search = (ImageView) findViewById(R.id.iv_search);
        iv_cut = (ImageView) findViewById(R.id.iv_cut);
        iv_search.setOnClickListener(this);
        iv_cut.setOnClickListener(this);
    
    }
    //单击事件
    public void onClick(View view) {
        int id = view.getId();
        switch (id) {
            case R.id.iv_search:
                //揭露ImageView
                exposeImageView();
                break;
            case R.id.iv_cut:
                //裁剪操作
                clipImageView();
                break;
        }
    }
  • 在单击事件中,我们先来看揭露操作exposeImageView(),对于揭露操作,官方给了很简明的示例,我这里就是根据官方的示例,进行了部分的修改。直接上代码了,注意看注释

       private void exposeImageView() {
            //获取剪裁中心点,即扩散点,此处以View的右上角为中心点
            int cx = iv_blue_scope.getRight();
            int cy = iv_blue_scope.getTop();
            //获取最终剪裁的半径,以View的最长边为准
            int finalRadius = Math.max(iv_blue_scope.getWidth(), iv_blue_scope.getHeight());
            //创建动画
            Animator anim = null;
            //根据View的当前显示状态进行展开和收起的操作
            if (iv_blue_scope.getVisibility() == View.INVISIBLE) {
                //创建圆形的揭露动画,从指定View(iv_blue_scope)的中心点( cx, cy,)开始,揭露半径从0到View的最长边(finalRadius)
                anim = ViewAnimationUtils.createCircularReveal(iv_blue_scope, cx, cy, 0, finalRadius);
                //显示View,开启动画
                iv_blue_scope.setVisibility(View.VISIBLE);
                anim.start();
            } else {
                //创建圆形的反向揭露动画,揭露半径从最长边到0即可
                anim = ViewAnimationUtils.createCircularReveal(iv_blue_scope, cx, cy, finalRadius, 0);
                //注意,这里需要先展示动画,等动画结束后才可以把View隐藏掉。所以要对动画进行侦听
                anim.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        iv_blue_scope.setVisibility(View.INVISIBLE);
                    }
                });
                anim.start();
            }
        }
  • 裁剪View,我这里是裁剪了部分区域,未执行剪裁到边界的操作,因为大部分的View边界和View大小一致,剪裁到边界相当于不剪裁。

    private void clipImageView() {
        //获取iv_clipped的中心坐标
        int cx = (iv_blue_scope.getLeft() + iv_blue_scope.getRight()) / 2;
        int cy = (iv_blue_scope.getTop() + iv_blue_scope.getBottom()) / 2;
        //实例化裁剪区域,此处我们裁剪该View的左下角的1/4区域
        Rect rect = new Rect(iv_blue_scope.getLeft(), cy, cx, iv_blue_scope.getBottom());
        //根据以上区域裁剪iv_clipped
        iv_blue_scope.setClipBounds(rect);
    }

源码:MaterialDesignDemo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YoungHong1992

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值