1. 程式人生 > >陰影效果 ShadowLayout 佈局實現(讓控制元件實現立體效果)

陰影效果 ShadowLayout 佈局實現(讓控制元件實現立體效果)

效果

這裡寫圖片描述
第二張和第三張圖是加入了陰影效果的,是不是覺得立體感很強,感覺圖片是浮在螢幕上。這個效果也可以用Google 提供擴充套件包下的CardView控制元件來實現,而這篇文章是帶大家自己來實現這樣一個效果。

原理

我們仔細觀察上圖,可以發現,有帶陰影效果的圖和沒帶陰影效果的圖,其實就一個地方不同,就是在圖片的底下繪製了陰影效果,而圖片的大小都沒變。所以我們要做的就是給子 View 繪製陰影。那麼陰影部分怎麼繪製呢?這裡是整個效果實現的一個難點;陰影部分其實就是一張 bitmap 圖片,而接下來的工作就是如何生成一張這樣效果的 bitmap 圖,還有就是 bitmap 圖片繪製位置的確定。

實現

1、陰影效果的 bitmap 圖片的生成
最簡單的辦法叫美工做一張,我們把他轉化成.9圖片,這是一種方法;還有一種方法就是用程式碼生成這樣一張 bitmap,要實現這樣的效果,我們需要用到 Paint 畫筆中的一個屬性

public MaskFilter setMaskFilter(MaskFilter maskfilter) {
//...
}

這個 MaskFilter有一個子類BlurMaskFilter就能實現這樣的效果,一般把它叫為毛玻璃效果。這個類的實現需要傳兩個引數

public BlurMaskFilter(float radius, Blur style) {
        //...
}

radius:漸變效果的距離。

style:模式,這裡有四中模式

    public enum Blur {
        /**
         * Blur inside and outside the original border.
         */
        NORMAL(0),

        /**
         * Draw solid inside the border, blur outside.
         */
        SOLID(1),

        /**
         * Draw nothing inside the border, blur outside.
         */
OUTER(2), /** * Blur inside the border, draw nothing outside. */ INNER(3); Blur(int value) { native_int = value; } final int native_int; }

這幾種模式到底是怎麼的呢?來看看下面那張圖
這裡寫圖片描述
以上圖形是通過

    public void drawRect(RectF rect, Paint paint) {
        ;
    }

看到這張圖加上上面幾種模式的註解,應該很清楚了。
這裡有一個注意點是:我們繪製矩形的時候,如果沒有設定這種模糊效果,這繪製的圖形的大小就是矩形的大小,如果繪製了模糊效果,則圖形的大小需要加上例項化BlurMaskFilter時候的radius,就是漸變的距離。
建立 bitmap 的程式碼如下:

        //設定畫筆的 style
        mPaint.setStyle(Paint.Style.FILL);
        //設定畫筆的模糊效果
        mPaint.setMaskFilter(new BlurMaskFilter(BLUR_WIDTH, BlurMaskFilter.Blur.NORMAL));
        //設定畫筆的顏色
        mPaint.setColor(Color.BLACK);
        //建立 bitmap 圖片
        mShadowBitmap = Bitmap.createBitmap(mOriginRect.width(), mOriginRect.height(), Bitmap.Config.ARGB_8888);
        //繫結到畫布上
        Canvas canvas = new Canvas(mShadowBitmap);
        //讓畫布平移,這裡為什麼要平移,看了前面圖片就知道
        canvas.translate(BLUR_WIDTH,BLUR_WIDTH);
        //繪製陰影效果
        canvas.drawRoundRect(mDesRecF, mRadius, mRadius, mPaint);

2、bitmap 圖片繪製位置的確定
bitmap 的繪製,是放在

protected void dispatchDraw(Canvas canvas) {
//...
}

這裡有各地方需要注意,需要先繪製 bitmap,在呼叫

super.dispatchDraw(canvas);

為什麼?很好理解了,因為super.dispatchDraw(canvas);是分發繪製機制,Layout 的所有子類的繪製都需要通過它來分發,如果先繪製子類,那麼 bitmap 陰影部分就會顯示在子類的上面,會把子類覆蓋。
程式碼如下:

    @Override
    protected void dispatchDraw(Canvas canvas) {

        int N = getChildCount() ;
        for (int i = 0; i < N ; i ++){
            View view = getChildAt(i) ;
            if (view.getVisibility() == GONE ||view.getVisibility() == INVISIBLE||
                    view.getAlpha() == 0){
                continue;
            }
            int left = view.getLeft() ;
            int top = view.getTop() ;
            /*儲存畫布的位置*/
            canvas.save() ;
            /*平移畫布*/
            canvas.translate(left + (1-mDepth)*80,top + (1-mDepth)*80);
            /*設定繪製陰影畫筆的透明度*/
            mAlphaPaint.setAlpha((int) (125 + 100 * (mDepth)));
            /*獲取陰影的繪製寬度*/
            mDesRecF.right = view.getWidth() ;
            /*獲取陰影的繪製高度*/
            mDesRecF.bottom = view.getHeight() ;
            /*繪製陰影*/
            canvas.drawBitmap(mShadowBitmap, mOriginRect, mDesRecF, mAlphaPaint);
            /*還原畫筆*/
            canvas.restore();
        }
        super.dispatchDraw(canvas);

    }

到這裡整個效果的 Layout 佈局就寫完了,程式碼非常簡潔,總共100行程式碼不到,索性就全部貼出來吧

/**
 * Created by moon.zhong on 2015/3/25.
 */
public class ShadowLayout extends RelativeLayout {

    private float mDepth = 0.5f;
    private Bitmap mShadowBitmap;
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private final int BLUR_WIDTH = 5 ;
    private final Rect mOriginRect = new Rect(0,0,150+ 2*BLUR_WIDTH,150+2*BLUR_WIDTH) ;
    private RectF mDesRecF = new RectF(0,0,150,150) ;
    private int mRadius = 6 ;
    private Paint mAlphaPaint ;
    public ShadowLayout(Context context) {
        super(context);
        initView(context);
    }

    public ShadowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

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

    private void initView(Context context) {
        setWillNotDraw(false);
        //設定畫筆的 style
        mPaint.setStyle(Paint.Style.FILL);
        //設定畫筆的模糊效果
        mPaint.setMaskFilter(new BlurMaskFilter(BLUR_WIDTH, BlurMaskFilter.Blur.NORMAL));
        //設定畫筆的顏色
        mPaint.setColor(Color.BLACK);
        //建立 bitmap 圖片
        mShadowBitmap = Bitmap.createBitmap(mOriginRect.width(), mOriginRect.height(), Bitmap.Config.ARGB_8888);
        //繫結到畫布上
        Canvas canvas = new Canvas(mShadowBitmap);
        //讓畫布平移,這裡為什麼要平移,看了前面圖片就知道
        canvas.translate(BLUR_WIDTH,BLUR_WIDTH);
        //繪製陰影效果
        canvas.drawRoundRect(mDesRecF, mRadius, mRadius, mPaint);
        mAlphaPaint = new Paint(Paint.ANTI_ALIAS_FLAG) ;

    }

    public void setDepth(float depth){
        mDepth = depth ;

        invalidate();
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {

        int N = getChildCount() ;
        for (int i = 0; i < N ; i ++){
            View view = getChildAt(i) ;
            if (view.getVisibility() == GONE ||view.getVisibility() == INVISIBLE||
                    view.getAlpha() == 0){
                continue;
            }
            int left = view.getLeft() ;
            int top = view.getTop() ;
            /*儲存畫布的位置*/
            canvas.save() ;
            /*平移畫布*/
            canvas.translate(left + (1-mDepth)*80,top + (1-mDepth)*80);
            /*設定繪製陰影畫筆的透明度*/
            mAlphaPaint.setAlpha((int) (125 + 100 * (mDepth)));
            /*獲取陰影的繪製寬度*/
            mDesRecF.right = view.getWidth() ;
            /*獲取陰影的繪製高度*/
            mDesRecF.bottom = view.getHeight() ;
            /*繪製陰影*/
            canvas.drawBitmap(mShadowBitmap, mOriginRect, mDesRecF, mAlphaPaint);
            /*還原畫筆*/
            canvas.restore();
        }
        super.dispatchDraw(canvas);

    }

}

再來看一下動態的效果圖
這裡寫圖片描述

總結

整體實現不是特別難,最主要的是 Paint類中 的

 mPaint.setMaskFilter(new BlurMaskFilter(BLUR_WIDTH, BlurMaskFilter.Blur.NORMAL));

在建立 bitmap 的時候,bitmap 的大小一定要加上2倍的BLUR_WIDTH也就是

private final Rect mOriginRect = new Rect(0,0,150+ 2*BLUR_WIDTH,150+2*BLUR_WIDTH) ;

至於為什麼前面已經講了。