阴影和剪裁

View的z属性

Material Design建议为了凸显布局的层次,建议使用阴影效果,并且Android L为了简化大家的工作,对View进行了扩展,能使大家非常方便的创建阴影效果:

在5.0之前,我们的视图都是二维的,只有x轴和y轴,现在,android新增了z轴。x轴和y轴描述了一个view的大小和位置,而z轴描述了view在父视图上抬起的视觉,体现效果就是阴影。下图的两个view的z属性分别为2dp和8dp的视觉效果:


Z属性可以通过elevation和translationZ进行修改。z = elevation+translationZZ属性,就能让其具备阴影的层次感。

Z属性会扩大view的显示区域,如果它的大小大于或等于父视图的大小,那么它的阴影效果就无法显示了,view并不会因为z属性而把自身缩小腾出空间显示阴影。

Z属性不仅影响着view的阴影效果,还影响着view的绘制顺序,在同一个父view内部,Z属性越小,绘制的时机就越早。也就是优先被绘制,而z属性越大,则绘制时间越晚,后绘制的将会遮盖住先绘制的,只有Z属性相同,才按照添加的顺序绘制。

View的轮廓

在Android的世界里,所有的View都是矩形的,虽然可以给View设置背景圆形的图片,即可在界面显示出圆形的内容,但是View的大小实际上仍然是矩形,并且设置的图片也是实际上也是矩形,只是圆形意外的区域为透明色。

如果系统根据View的大小来为我们生成对应的阴影,有时候就会出现很奇怪的效果。

为了解决该类问题,View增加了一个新的描述来指明内容显示的形状,这就是轮廓。通过shape设置的背景,View会自动根据shape的形状进行轮廓判定,通过color设置的背景,View默认其轮廓和View的大小一样。但是通过图片进行背景设置,View则无法获知轮廓的形状,这个时候就需要我们程序员显示的指定。

android:outlineProvider来指定轮廓的判定方式:nonebackgroundboundspaddedBoundssetOutlineProvider来指定一个View的轮廓:

ViewOutlineProvider viewOutlineProvider = new ViewOutlineProvider() {
    public void getOutline(View view, Outline outline) {
        // 可以指定圆形,矩形,圆角矩形,path
        outline.setOval(0, 0, view.getWidth(), view.getHeight());
    }
};
View.setOutlineProvider(viewOutlineProvider );

注意:如果采用图片作为背景,即使在xml布局中指定android:outlineProvider为background也不会显示阴影,只有通过代码中指定轮廓来显示。

View的剪裁

给View指定轮廓,可以决定阴影的显示形状,如果给View指定一个小于自身大小的轮廓,则阴影通常会被View遮住,这个时候View的显示内容并没有因为轮廓的缩小而缩小。

setClipToOutline方法,就可以根据轮廓来剪裁一个View。想要剪裁轮廓,必须要给View先指定轮廓,并且轮廓是可以被剪裁的,目前只有圆形,矩形,圆角矩形支持剪裁,可以通过outline.canClip()来判断一个轮廓是否支持剪裁。

Path剪裁不会改变View的大小,但是如果Path的范围比View要的bounds要小,则剪裁后会改变View的位置,位置偏移和Z属性有关,这可能是一个BUG,view的设计者可能在绘制阴影时根据轮廓偏移了画布,而在绘制完后忘记把画布还原了。

剪裁不会改变View的测量大小和布局大小,也不会改变View的触摸区域,剪裁只是在onDraw的时候对画布做了剪裁处理,剪裁也不同于scale,scale是调整画布matrix的缩放属性,调整后,View仍然能完整显示,而剪裁是缩小画布的剪裁区域,剪裁后我们只能看到View的不一部分。

试图给View一个比较大的轮廓进行剪裁也是不成功的,实验证明剪裁后的View只能比原有体积小。扩大轮廓只会扩大轮廓的绘制区域。

剪裁是一个非常消耗资源的操作,我们不应该用此来做动画效果,如果要实现这样的动画,可以使用Reveal Effect

Demo展示

接下来就用demo的形式来看看上边介绍的效果。


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.itydl.elevation.MainActivity">

    <!--红色背景-->
    <View
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_margin="30dp"
        android:background="#e80303"
        android:elevation="15dp"
        />
    <!--绿色背景-->
    <View
        android:layout_marginLeft="80dp"
        android:layout_marginTop="80dp"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="#03e822"
        android:elevation="15dp"
        />
    <!--自定义圆形背景-->
    <View
        android:layout_marginLeft="220dp"
        android:layout_marginTop="80dp"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@drawable/blue_shape"
        android:elevation="15dp"
        />
    <!--圆形图片背景-->
    <View
        android:layout_marginLeft="160dp"
        android:layout_marginTop="230dp"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@mipmap/head"
        android:outlineProvider="paddedBounds"
        android:elevation="15dp"
        android:id="@+id/head"/>
</RelativeLayout>


看一下渲染效果就可以了啦,或者直接运行。如下:


android Toolbar设置下阴影 android view阴影_android

可以阴影效果。但是值得注意的是,最后一张圆形图片,不是我们想要的效果。即使设置了outlineProvider,结果还是不能令人满意,因为要通过代码来设置。看一下活动中设置图片阴影效果的代码:


public class MainActivity extends AppCompatActivity {


    private View mHead;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHead = findViewById(R.id.head);
        mHead.setOutlineProvider(new ViewOutlineProvider() {
            @Override
            //view就是mHead;outline设置参数
            public void getOutline(View view, Outline outline) {
                //可以指定圆形,矩形,圆角矩形.设置轮廓。参数是相对该View,而不是相对屏幕
                outline.setOval(0,0,view.getWidth(),view.getHeight());
            }
        });

    }
}

再次运行程序:


这样就达到了我们预期的效果了。

裁剪

就像前面介绍的,给View指定轮廓,可以决定阴影的显示形状,如果给View指定一个小于自身大小的轮廓,则阴影通常会被View遮住,这个时候View的显示内容并没有因为轮廓的缩小而缩小。

例如:我把设置轮廓时候的api修改一下:

outline.setOval(0,0,view.getWidth()-20,view.getHeight()-20);

这个时候运行后,由于阴影是跟着轮廓大小来做阴影效果的,图片view大小挡住了轮廓阴影大小。所以就不会出现上边效果了。


通过设置如下代码:

mHead.setClipToOutline(true);

运行程序(我把未设置setClipToOutLIne和设置后的这个图片单独拿出来):


android Toolbar设置下阴影 android view阴影_阴影效果_02

可以看到经过裁剪后的图片,明显变小了。但是第一张没有阴影效果,第二张设置裁剪后又有了阴影效果。