Android原生实现控件阴影方案(API28及以上)

Android控件的阴影效果的实现方式有很多种,这里介绍一下另一种Android原生的阴影实现方案(API28及以上)。

我们利用elevation、outlineAmbientShowColor、outlineSpotShadowColor来实现一个带阴影的Button。

实现效果如下图,阴影宽度分别为2dp、10dp、30dp:
在这里插入图片描述

阴影原理

阴影效果的实现采用的是Android原生的View的属性,拔高Z轴。Z轴会让View产生阴影的效果。

阴影的效果由Z轴(elevation)、光源(shadowColor)和环境阴影透明度(ambientShadowAlpha)三者综合决定的。所以我们需要对这三者进行合理的设置。环境阴影透明度是通过主题theme设置的,一般为1,因此起决定性作用的是Z轴(elevation)和光源(shadowColor)。

光源又分为主光源(outlineSpotShadowColor)和环境光源(outlineAmbientShowColor)。

阴影原理
在这里插入图片描述

实现

属性

在attrs.xml文件中,创建Button的自定义属性:

    <declare-styleable name="shape_button">
        <attr name="android:background" />
        <attr name="android:textColor" />
        
        <!-- shadow 相关-->
        <attr name="carbon_elevation" />
        <attr name="carbon_elevationShadowColor" />
        <attr name="carbon_elevationAmbientShadowColor" />
        <attr name="carbon_elevationSpotShadowColor" />

        <attr name="android:textStyle" />
        <attr name="android:textAppearance" />
        <attr name="android:singleLine" />
        <attr name="android:maxLines" />
        <attr name="android:textAllCaps" />
    </declare-styleable>

另外,创建一个styles_button.xml

<resources xmlns:tools="http://schemas.android.com/tools">
    <style name="carbon_Button" parent="@android:style/Widget.Button">
        <item name="android:gravity">center_vertical|center_horizontal</item>
        <item name="android:paddingTop">0dp</item>
        <item name="android:paddingBottom">0dp</item>
        <item name="android:paddingLeft">@dimen/carbon_paddingHalf</item>
        <item name="android:paddingRight">@dimen/carbon_paddingHalf</item>
        <item name="android:minHeight">36dp</item>
        <item name="android:minWidth">88dp</item>
        <item name="carbon_elevation">@dimen/carbon_elevationButton</item>
        <item name="android:textColor">@null</item>
        <item name="android:textAppearance">?attr/carbon_textAppearanceButton</item>
        <item name="android:maxLines">1</item>
        <item name="android:background">@color/carbon_defaultColorControl</item>
    </style>
</resources>

自定义ShapeButton

创建一个阴影接口,这样所有需要阴影的控件实现这个接口就好了:

/**
 * 阴影相关
 */
public interface ShadowView {
    /**
     * The elevation value. There are useful values of elevation defined in xml as
     * carbon_elevationFlat, carbon_elevationLow, carbon_elevationMedium, carbon_elevationHigh,
     * carbon_elevationMax.
     */
    float getElevation();

    void setElevation(float elevation);

    float getTranslationZ();

    void setTranslationZ(float translationZ);

    ColorStateList getElevationShadowColor();

    void setElevationShadowColor(ColorStateList color);

    void setElevationShadowColor(int color);

    int getOutlineAmbientShadowColor();

    void setOutlineAmbientShadowColor(int color);

    void setOutlineAmbientShadowColor(ColorStateList color);

    int getOutlineSpotShadowColor();

    void setOutlineSpotShadowColor(int color);

    void setOutlineSpotShadowColor(ColorStateList color);

}

创建ShapeButton.java 实现ShadowView:

public class ShapeButton extends AppCompatButton implements ShadowView{
    public ShapeButton(@NonNull Context context) {
        super(context);
        initButton(null, android.R.attr.buttonStyle, R.style.carbon_Button);
    }

    public ShapeButton(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initButton(attrs, android.R.attr.buttonStyle, R.style.carbon_Button);
    }

    public ShapeButton(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initButton(attrs, defStyleAttr, R.style.carbon_Button);
    }

    public ShapeButton(Context context, String text, OnClickListener listener) {
        super(context);
        initButton(null, android.R.attr.buttonStyle, R.style.carbon_Button);
        setText(text);
        setOnClickListener(listener);
    }

    private static int[] elevationIds = new int[]{
            R.styleable.shape_button_carbon_elevation,
            R.styleable.shape_button_carbon_elevationShadowColor,
            R.styleable.shape_button_carbon_elevationAmbientShadowColor,
            R.styleable.shape_button_carbon_elevationSpotShadowColor
    };
	// 重点就是利用下面的属性:
	// 拔高Z轴可以通过控制elevation和translationZ。区别:
	// elevation:一般是写在 xml 文件中做静态配置,单纯的控制Z轴;
	// translateZ:除了控制Z轴,还可以用来控制动画效果,比如我们点击按钮时希望它有一个弹起的效果。
	// 由于我们只需要实现阴影效果,所以我们只关注elevation即可。
    private float elevation = 0;
    private float translationZ = 0;
    // 表示光源的颜色
    private ColorStateList ambientShadowColor, spotShadowColor;

    private void initButton(AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.shape_button, defStyleAttr, defStyleRes);
        // todo-获取自定义属性的初始化
        //  Carbon.initElevation(this, a, elevationIds);
        a.recycle();
    }

    @Override
    public float getElevation() {
        return elevation;
    }

    @Override
    public void setElevation(float elevation) {
      // todo-设置elevation的值
    }

    @Override
    public float getTranslationZ() {
        return translationZ;
    }

    public void setTranslationZ(float translationZ) {
      // todo-设置translationZ的值
    }

    @Override
    public ColorStateList getElevationShadowColor() {
        return ambientShadowColor;
    }

    @Override
    public void setElevationShadowColor(ColorStateList shadowColor) {
        // todo-设置ElevationShadowColor的值,这个光源值表示统一主光源和环境光源的颜色
    }

    @Override
    public void setElevationShadowColor(int color) {
       // todo-设置ElevationShadowColor的值,这个光源值表示统一主光源和环境光源的颜色
    }

    @Override
    public void setOutlineAmbientShadowColor(ColorStateList color) {
        // todo-设置OutlineAmbientShadowColor的值
    }

    @Override
    public void setOutlineAmbientShadowColor(int color) {
        // todo-设置OutlineAmbientShadowColor的值
    }

    @Override
    public int getOutlineAmbientShadowColor() {
        return ambientShadowColor.getDefaultColor();
    }

    @Override
    public void setOutlineSpotShadowColor(int color) {
         // todo-设置OutlineSpotShadowColor的值
    }

    @Override
    public void setOutlineSpotShadowColor(ColorStateList color) {
       // todo-设置OutlineSpotShadowColor的值
    }

    @Override
    public int getOutlineSpotShadowColor() {
        return ambientShadowColor.getDefaultColor();
    }
}

属性初始化

public class Carbon {

    public static final boolean IS_LOLLIPOP_OR_HIGHER = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;

    public static final boolean IS_PIE_OR_HIGHER = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
    private Carbon() {
    }

    public static void initElevation(ShadowView view, TypedArray a, int[] ids) {
        int carbon_elevation = ids[0];
        int carbon_shadowColor = ids[1];
        int carbon_ambientShadowColor = ids[2];
        int carbon_spotShadowColor = ids[3];
		// 获取xml设置的elevation属性的值,并设置给View
        float elevation = a.getDimension(carbon_elevation, 0);
        view.setElevation(elevation);
        // 获取xml设置的shadowColor属性的值,并设置给View
        ColorStateList shadowColor = a.getColorStateList(carbon_shadowColor);
        view.setElevationShadowColor(shadowColor != null ? shadowColor.withAlpha(255) : null);
        //如果有设置环境光源 ambientShadowColor,则设置给View
        if (a.hasValue(carbon_ambientShadowColor)) {
            ColorStateList ambientShadowColor = a.getColorStateList(carbon_ambientShadowColor);
            view.setOutlineAmbientShadowColor(ambientShadowColor != null ? ambientShadowColor.withAlpha(255) : null);
        }
        //如果有设置主光源spotShadowColor,则设置给View
        if (a.hasValue(carbon_spotShadowColor)) {
            ColorStateList spotShadowColor = a.getColorStateList(carbon_spotShadowColor);
            view.setOutlineSpotShadowColor(spotShadowColor != null ? spotShadowColor.withAlpha(255) : null);
        }
    }
}

设置阴影相关属性

设置Elevation

@Override
    public void setElevation(float elevation) {
    // 针对API=28以上
        if (Carbon.IS_PIE_OR_HIGHER) {
            super.setElevation(elevation);
            super.setTranslationZ(translationZ);
        } else if (Carbon.IS_LOLLIPOP_OR_HIGHER) {
            if (ambientShadowColor == null || spotShadowColor == null) {
                super.setElevation(elevation);
                super.setTranslationZ(translationZ);
            } else {
                super.setElevation(0);
                super.setTranslationZ(0);
            }
        } else if (elevation != this.elevation && getParent() != null) {
            ((View) getParent()).postInvalidate();
        }
        this.elevation = elevation;
    }

设置translationZ

public void setTranslationZ(float translationZ) {
        if (translationZ == this.translationZ)
            return;
        if (Carbon.IS_PIE_OR_HIGHER) {
            super.setTranslationZ(translationZ);
        } else if (Carbon.IS_LOLLIPOP_OR_HIGHER) {
            if (ambientShadowColor == null || spotShadowColor == null) {
                super.setTranslationZ(translationZ);
            } else {
                super.setTranslationZ(0);
            }
        } else if (translationZ != this.translationZ && getParent() != null) {
            ((View) getParent()).postInvalidate();
        }
        this.translationZ = translationZ;
    }

设置主光源和环境光源

@Override
    public void setElevationShadowColor(ColorStateList shadowColor) {
        ambientShadowColor = spotShadowColor = shadowColor;
        setElevation(elevation);
        setTranslationZ(translationZ);
    }
    
    @Override
    public void setElevationShadowColor(int color) {
        ambientShadowColor = spotShadowColor = ColorStateList.valueOf(color);
        setElevation(elevation);
        setTranslationZ(translationZ);
    }

// 设置环境光源
@Override
    public void setOutlineAmbientShadowColor(ColorStateList color) {
        ambientShadowColor = color;
        if (Carbon.IS_PIE_OR_HIGHER) {
            super.setOutlineAmbientShadowColor(color.getColorForState(getDrawableState(), color.getDefaultColor()));
        } else {
            setElevation(elevation);
            setTranslationZ(translationZ);
        }
    }

# 设置主光源
@Override
    public void setOutlineSpotShadowColor(ColorStateList color) {
        spotShadowColor = color;
        if (Carbon.IS_PIE_OR_HIGHER) {
            super.setOutlineSpotShadowColor(color.getColorForState(getDrawableState(), color.getDefaultColor()));
        } else {
            setElevation(elevation);
            setTranslationZ(translationZ);
        }

    }

@Override
    public void draw(Canvas canvas) {
        if (Carbon.IS_PIE_OR_HIGHER) {
            if (spotShadowColor != null)
                super.setOutlineSpotShadowColor(spotShadowColor.getColorForState(getDrawableState(), spotShadowColor.getDefaultColor()));
            if (ambientShadowColor != null)
                super.setOutlineAmbientShadowColor(ambientShadowColor.getColorForState(getDrawableState(), ambientShadowColor.getDefaultColor()));
        }
        super.draw(canvas);
    }

怎样使用

<com.chinatsp.demo1.shadow.ShapeButton
                android:layout_width="56dp"
                android:layout_height="56dp"
                android:layout_margin="@dimen/carbon_padding"
                android:background="#ffffff"
                android:stateListAnimator="@null"
                android:text="TOM"
                android:elevation="20dp"
                app:carbon_elevationShadowColor="@color/carbon_amber_700" />

完整代码:

public class ShapeButton extends AppCompatButton implements ShadowView{
    public ShapeButton(@NonNull Context context) {
        super(context);
        initButton(null, android.R.attr.buttonStyle, R.style.carbon_Button);
    }

    public ShapeButton(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initButton(attrs, android.R.attr.buttonStyle, R.style.carbon_Button);
    }

    public ShapeButton(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initButton(attrs, defStyleAttr, R.style.carbon_Button);
    }

    public ShapeButton(Context context, String text, OnClickListener listener) {
        super(context);
        initButton(null, android.R.attr.buttonStyle, R.style.carbon_Button);
        setText(text);
        setOnClickListener(listener);
    }

    private static int[] elevationIds = new int[]{
            R.styleable.shape_button_carbon_elevation,
            R.styleable.shape_button_carbon_elevationShadowColor,
            R.styleable.shape_button_carbon_elevationAmbientShadowColor,
            R.styleable.shape_button_carbon_elevationSpotShadowColor
    };

    private float elevation = 0;
    private float translationZ = 0;
    private ColorStateList ambientShadowColor, spotShadowColor;

    private void initButton(AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.shape_button, defStyleAttr, defStyleRes);
        Carbon.initElevation(this, a, elevationIds);
        a.recycle();
    }

    @Override
    public float getElevation() {
        return elevation;
    }

    @Override
    public void setElevation(float elevation) {
        if (Carbon.IS_PIE_OR_HIGHER) {
            super.setElevation(elevation);
            super.setTranslationZ(translationZ);
        } else if (Carbon.IS_LOLLIPOP_OR_HIGHER) {
            if (ambientShadowColor == null || spotShadowColor == null) {
                super.setElevation(elevation);
                super.setTranslationZ(translationZ);
            } else {
                super.setElevation(0);
                super.setTranslationZ(0);
            }
        } else if (elevation != this.elevation && getParent() != null) {
            ((View) getParent()).postInvalidate();
        }
        this.elevation = elevation;
    }

    @Override
    public float getTranslationZ() {
        return translationZ;
    }

    public void setTranslationZ(float translationZ) {
        if (translationZ == this.translationZ)
            return;
        if (Carbon.IS_PIE_OR_HIGHER) {
            super.setTranslationZ(translationZ);
        } else if (Carbon.IS_LOLLIPOP_OR_HIGHER) {
            if (ambientShadowColor == null || spotShadowColor == null) {
                super.setTranslationZ(translationZ);
            } else {
                super.setTranslationZ(0);
            }
        } else if (translationZ != this.translationZ && getParent() != null) {
            ((View) getParent()).postInvalidate();
        }
        this.translationZ = translationZ;
    }

    @Override
    public ColorStateList getElevationShadowColor() {
        return ambientShadowColor;
    }

    @Override
    public void setElevationShadowColor(ColorStateList shadowColor) {
        ambientShadowColor = spotShadowColor = shadowColor;
        setElevation(elevation);
        setTranslationZ(translationZ);
    }

    @Override
    public void setElevationShadowColor(int color) {
        ambientShadowColor = spotShadowColor = ColorStateList.valueOf(color);
        setElevation(elevation);
        setTranslationZ(translationZ);
    }

    @Override
    public void setOutlineAmbientShadowColor(ColorStateList color) {
        ambientShadowColor = color;
        if (Carbon.IS_PIE_OR_HIGHER) {
            super.setOutlineAmbientShadowColor(color.getColorForState(getDrawableState(), color.getDefaultColor()));
        } else {
            setElevation(elevation);
            setTranslationZ(translationZ);
        }
    }

    @Override
    public void setOutlineAmbientShadowColor(int color) {
        setOutlineAmbientShadowColor(ColorStateList.valueOf(color));
    }

    @Override
    public int getOutlineAmbientShadowColor() {
        return ambientShadowColor.getDefaultColor();
    }

    @Override
    public void setOutlineSpotShadowColor(int color) {
        setOutlineSpotShadowColor(ColorStateList.valueOf(color));
    }

    @Override
    public void setOutlineSpotShadowColor(ColorStateList color) {
        spotShadowColor = color;
        if (Carbon.IS_PIE_OR_HIGHER) {
            super.setOutlineSpotShadowColor(color.getColorForState(getDrawableState(), color.getDefaultColor()));
        } else {
            setElevation(elevation);
            setTranslationZ(translationZ);
        }

    }

    @Override
    public int getOutlineSpotShadowColor() {
        return ambientShadowColor.getDefaultColor();
    }

    @Override
    public void draw(Canvas canvas) {
        if (Carbon.IS_PIE_OR_HIGHER) {
            if (spotShadowColor != null)
                super.setOutlineSpotShadowColor(spotShadowColor.getColorForState(getDrawableState(), spotShadowColor.getDefaultColor()));
            if (ambientShadowColor != null)
                super.setOutlineAmbientShadowColor(ambientShadowColor.getColorForState(getDrawableState(), ambientShadowColor.getDefaultColor()));
        }
        super.draw(canvas);
    }
}

猜你喜欢

转载自blog.csdn.net/jxq1994/article/details/133681659