Android進階:12、最簡單的方式實現自定義陰影效果

網話說UI設計有三寶 :透明,陰影,加圓角。不少UI在作設計的時候都喜歡作卡片形式,而後添加陰影。卡片UI確實挺好看,可是對Android開發者來講,顯示陰影卻並不那麼手到擒來,由於Android對陰影沒有作出很好的支持。java

CardView

谷歌也許早就注意到了UI的三寶之一陰影,因而開發了一個繼承FrameLayout的CardView公開發這使用,這個控件雖然在v7包裏,可是須要單獨添加依賴才能夠使用,就好像不是親生的似的!android

CardView本質上繼承FrameLayout,須要添加依賴才能夠使用:面試

compile 'com.android.support:cardview-v7:25.3.1'
複製代碼

當你知道它繼承FrameLayout的時候你就知道怎麼使用了,可是這個CardView有不少侷限性,好比不能修改陰影的顏色,不能修改陰影的深淺。這就很詭異了,根本沒法知足UI設計潮流的心裏。
那爲了產品蒸蒸日上,早日走上人生巔峯,實現財富自由,應該如何讓你的APP支持修改陰影的顏色呢?canvas

有個很暴力的辦法,就是吧CardView的代碼本身摳出來,而後本身定製.
可是如今我本身探索了一個新的較爲簡單的添加陰影的實現方案,僅供參考性能優化

ShadowCardView

思路:首先要明確陰影的實現思路是什麼,其實就是顏色致使的視覺錯覺。說白了就是在你的Card周圍畫一個漸變的體現立體感的顏色。
基於上述思路,咱們在一個在一個view上畫一個矩形的圖形,讓他周圍有漸變色的陰影便可。因而咱們想起幾個API:bash

  • 類:Paint 用於在Android上畫圖的類,至關於畫筆
  • 類:Canvas 至關於畫布,Android上的view的繪製都與他相關
  • 方法:paint.setShadowLayer能夠給繪製的圖形增長陰影,還能夠設置陰影的顏色架構



    如上圖咱們,紅色部分使咱們繪製的圖形,邊框之內,紅色以外的是陰影的顯示部分。
    有了以上的知識點已經知道能夠本身畫陰影了,可是什麼時機開始畫呢?ide

咱們知道谷歌開發的CardView的控件繼承了FrameLayout,方便咱們自由擴展。那麼咱們也須要繼承FrameLayout。性能

ShadowCardView繼承FrameLayout以後,能夠重寫其一個方法:優化

@Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
    }
複製代碼

這個方法是ViewGroup在繪製子View的時候調用的,那麼咱們能夠在這個時候進行陰影的繪製。

首先,這個方法已經爲咱們提供了這個View的畫布:Canvas,咱們能夠直接在上面進行陰影的繪製,代碼以下:

Paint shadowPaint = new Paint();
    shadowPaint.setColor(Color.RED);
    shadowPaint.setStyle(Paint.Style.FILL);
    shadowPaint.setAntiAlias(true);
    float left = 45;
    float top = 45;
    float right = getWidth() - 45;
    float bottom = getHeight() - 45;

    shadowPaint.setShadowLayer(45, 0, 0, getContext().getResources().getColor(R.color.color_000000));
    RectF rectF = new RectF(left, top, right, bottom);
    canvas.drawRoundRect(rectF, 0, 0, shadowPaint);
    canvas.save();
複製代碼
  • 建立畫筆,設置畫筆的顏色,風格
  • 獲取繪畫的範圍:ShadowCard的範圍減去須要的陰影的範圍,假如陰影的寬度爲45px,則在ShadowCard內部的45px內進行繪製
  • 給畫筆設置陰影的顏色,陰影的模糊度,模糊度值越大越模糊,且不能爲0
  • 建立RectF,最後開始繪畫。

這樣陰影就能夠成功繪製了,這個方法代碼量不多,很簡單,也很實用。



效果如上圖

爲了更好的封裝,咱們能夠爲上面須要的參數進行定製,好比陰影的顏色,陰影的寬度,陰影的上下偏移,陰影的模糊度。
所有代碼:

public class ShadowViewCard extends FrameLayout {
    private static final int DEFAULT_VALUE_SHADOW_COLOR = R.color.shadow_default_color;
    private static final int DEFAULT_VALUE_SHADOW_CARD_COLOR = R.color.shadow_card_default_color;
    private static final int DEFAULT_VALUE_SHADOW_ROUND = 0;

    private static final int DEFAULT_VALUE_SHADOW_RADIUS = 10;
    private static final int DEFAULT_VALUE_SHADOW_TOP_HEIGHT = 5;
    private static final int DEFAULT_VALUE_SHADOW_LEFT_HEIGHT = 5;
    private static final int DEFAULT_VALUE_SHADOW_RIGHT_HEIGHT = 5;
    private static final int DEFAULT_VALUE_SHADOW_BOTTOM_HEIGHT = 5;
    private static final int DEFAULT_VALUE_SHADOW_OFFSET_Y = 0;
    private static final int DEFAULT_VALUE_SHADOW_OFFSET_X = DEFAULT_VALUE_SHADOW_TOP_HEIGHT / 3;

    private int shadowRound;
    private int shadowColor;
    private int shadowCardColor;
    private int shadowRadius;
    private int shadowOffsetY;
    private int shadowOffsetX;
    private int shadowTopHeight;
    private int shadowLeftHeight;
    private int shadowRightHeight;
    private int shadowBottomHeight;

    public ShadowViewCard(Context context) {
        this(context, null);
    }

    public ShadowViewCard(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ShadowViewCard(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context, attrs);
    }

    private void initView(Context context, AttributeSet attrs) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ShadowViewCard);
        shadowRound = a.getDimensionPixelSize(R.styleable.ShadowViewCard_shadowRound, DEFAULT_VALUE_SHADOW_ROUND);
        shadowColor = a.getColor(R.styleable.ShadowViewCard_shadowColor, getResources().getColor(DEFAULT_VALUE_SHADOW_COLOR));
        shadowCardColor = a.getColor(R.styleable.ShadowViewCard_shadowCardColor, getResources().getColor(DEFAULT_VALUE_SHADOW_CARD_COLOR));
        shadowTopHeight = a.getDimensionPixelSize(R.styleable.ShadowViewCard_shadowTopHeight, dp2px(getContext(), DEFAULT_VALUE_SHADOW_TOP_HEIGHT));
        shadowRightHeight = a.getDimensionPixelSize(R.styleable.ShadowViewCard_shadowRightHeight, dp2px(getContext(), DEFAULT_VALUE_SHADOW_RIGHT_HEIGHT));
        shadowLeftHeight = a.getDimensionPixelSize(R.styleable.ShadowViewCard_shadowLeftHeight, dp2px(getContext(), DEFAULT_VALUE_SHADOW_LEFT_HEIGHT));
        shadowBottomHeight = a.getDimensionPixelSize(R.styleable.ShadowViewCard_shadowBottomHeight, dp2px(getContext(), DEFAULT_VALUE_SHADOW_BOTTOM_HEIGHT));
        shadowOffsetY = a.getDimensionPixelSize(R.styleable.ShadowViewCard_shadowOffsetY, dp2px(getContext(), DEFAULT_VALUE_SHADOW_OFFSET_Y));
        shadowOffsetX = a.getDimensionPixelSize(R.styleable.ShadowViewCard_shadowOffsetX, dp2px(getContext(), DEFAULT_VALUE_SHADOW_OFFSET_X));
        shadowRadius = a.getInteger(R.styleable.ShadowViewCard_shadowRadius, DEFAULT_VALUE_SHADOW_RADIUS);
        a.recycle();
        setPadding(shadowLeftHeight, shadowTopHeight, shadowRightHeight, shadowBottomHeight);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
    }

    public static int dp2px(Context context, float dipValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        Paint shadowPaint = new Paint();
        shadowPaint.setColor(Color.WHITE);
        shadowPaint.setStyle(Paint.Style.FILL);
        shadowPaint.setAntiAlias(true);
        float left = shadowLeftHeight;
        float top = shadowTopHeight;
        float right = getWidth() - shadowRightHeight;
        float bottom = getHeight() - shadowBottomHeight;
        shadowPaint.setShadowLayer(shadowRadius, shadowOffsetX, shadowOffsetX, shadowColor);
        RectF rectF = new RectF(left, top, right, bottom);
        canvas.drawRoundRect(rectF, shadowRound, shadowRound, shadowPaint);
        canvas.save();
        super.dispatchDraw(canvas);
    }
}
複製代碼

attr.xml

<declare-styleable name="ShadowViewCard">
        <!--圓角度-->
        <attr name="shadowRound" format="dimension" />
        <!--陰影的高度-->
        <attr name="shadowLeftHeight" format="dimension" />
        <attr name="shadowTopHeight" format="dimension" />
        <attr name="shadowRightHeight" format="dimension" />
        <attr name="shadowBottomHeight" format="dimension" />
        <!--上方陰影的偏離度-->
        <attr name="shadowOffsetY" format="dimension" />
        <attr name="shadowOffsetX" format="dimension" />
        <!--陰影顏色-->
        <attr name="shadowCardColor" format="color" />
        <attr name="shadowColor" format="color" />
        <!--陰影顏色模糊度,越大越模糊-->
        <attr name="shadowRadius" format="integer" />
    </declare-styleable>
複製代碼

color.xml

<color name="shadow_default_color">#1a000000</color>
 <color name="shadow_card_default_color">#ffffff</color>複製代碼

喜歡個人系列性文章的話,動動你的小手指點個讚的

最後送福利了,如今關注我而且加入羣聊能夠獲取包含源碼解析,自定義View,動畫實現,架構分享等。
內容難度適中,篇幅精煉,天天只需花上十幾分鍾閱讀便可。
你們能夠跟我一塊兒探討,歡迎加羣探討,有flutter—性能優化—移動架構—資深UI工程師 —NDK相關專業人員和視頻教學資料,還有更多面試題等你來拿~

羣號:925019412

相關文章
相關標籤/搜索