天天看點

span從入門到精通2 自定義drawable

分析昨天的部落格 span從入門到精通 第三方工具類GifDrawable 發現有個知識點有必要先梳理下要不大家可能看着部落格都是懵逼的,這個知識點就是自定義drawable。

看看效果吧

span從入門到精通2 自定義drawable
span從入門到精通2 自定義drawable

首先我們先分析下源碼裡面drawable 是怎麼被調用的我們先看看view類 的setBackgroundDrawable 這個方法

public void setBackgroundDrawable(Drawable background) {
        computeOpaqueFlags();

        if (background == mBackground) {
            return;
        }

        boolean requestLayout = false;

        mBackgroundResource = ;

        /*
         * Regardless of whether we're setting a new background or not, we want
         * to clear the previous drawable. setVisible first while we still have the callback set.
         */
        if (mBackground != null) {
            if (isAttachedToWindow()) {
                mBackground.setVisible(false, false);
            }
            mBackground.setCallback(null);
            unscheduleDrawable(mBackground);
        }

        if (background != null) {
            Rect padding = sThreadLocal.get();
            if (padding == null) {
                padding = new Rect();
                sThreadLocal.set(padding);
            }
            resetResolvedDrawablesInternal();
            background.setLayoutDirection(getLayoutDirection());
            if (background.getPadding(padding)) {
                resetResolvedPaddingInternal();
                switch (background.getLayoutDirection()) {
                    case LAYOUT_DIRECTION_RTL:
                        mUserPaddingLeftInitial = padding.right;
                        mUserPaddingRightInitial = padding.left;
                        internalSetPadding(padding.right, padding.top, padding.left, padding.bottom);
                        break;
                    case LAYOUT_DIRECTION_LTR:
                    default:
                        mUserPaddingLeftInitial = padding.left;
                        mUserPaddingRightInitial = padding.right;
                        internalSetPadding(padding.left, padding.top, padding.right, padding.bottom);
                }
                mLeftPaddingDefined = false;
                mRightPaddingDefined = false;
            }

            // Compare the minimum sizes of the old Drawable and the new.  If there isn't an old or
            // if it has a different minimum size, we should layout again
            if (mBackground == null
                    || mBackground.getMinimumHeight() != background.getMinimumHeight()
                    || mBackground.getMinimumWidth() != background.getMinimumWidth()) {
                requestLayout = true;
            }

            // Set mBackground before we set this as the callback and start making other
            // background drawable state change calls. In particular, the setVisible call below
            // can result in drawables attempting to start animations or otherwise invalidate,
            // which requires the view set as the callback (us) to recognize the drawable as
            // belonging to it as per verifyDrawable.
            mBackground = background;
            if (background.isStateful()) {
                background.setState(getDrawableState());
            }
            if (isAttachedToWindow()) {
                background.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
            }

            applyBackgroundTint();

            // Set callback last, since the view may still be initializing.
            background.setCallback(this);

            if ((mPrivateFlags & PFLAG_SKIP_DRAW) != ) {
                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
                requestLayout = true;
            }
        } else {
            /* Remove the background */
            mBackground = null;
            if ((mViewFlags & WILL_NOT_DRAW) != 
                    && (mDefaultFocusHighlight == null)
                    && (mForegroundInfo == null || mForegroundInfo.mDrawable == null)) {
                mPrivateFlags |= PFLAG_SKIP_DRAW;
            }

            /*
             * When the background is set, we try to apply its padding to this
             * View. When the background is removed, we don't touch this View's
             * padding. This is noted in the Javadocs. Hence, we don't need to
             * requestLayout(), the invalidate() below is sufficient.
             */

            // The old background's minimum size could have affected this
            // View's layout, so let's requestLayout
            requestLayout = true;
        }

        computeOpaqueFlags();

        if (requestLayout) {
            requestLayout();
        }

        mBackgroundSizeChanged = true;
        invalidate(true);
        invalidateOutline();
    }
           

這裡面的意思大緻分析一下就是首先判斷設定的drawable是否跟以前的一樣如果一樣則不需要重新設定,如果舊的background不為空則清理下舊的background的資料,之後設定background的state,visible,callback等屬性,最終調用requestLayout這個方法重新繪制布局也就是會走layout跟draw等方法,系統會在draw方法裡面調用drawBackground這個處理我們來看drawBackground這個方法裡面做了些什麼處理

private void drawBackground(Canvas canvas) {
        final Drawable background = mBackground;
        if (background == null) {
            return;
        }

        setBackgroundBounds();

        // Attempt to use a display list if requested.
        if (canvas.isHardwareAccelerated() && mAttachInfo != null
                && mAttachInfo.mThreadedRenderer != null) {
            mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);

            final RenderNode renderNode = mBackgroundRenderNode;
            if (renderNode != null && renderNode.isValid()) {
                setBackgroundRenderNodeProperties(renderNode);
                ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
                return;
            }
        }

        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        if ((scrollX | scrollY) == ) {
            background.draw(canvas);
        } else {
            canvas.translate(scrollX, scrollY);
            background.draw(canvas);
            canvas.translate(-scrollX, -scrollY);
        }
    }
           

這裡我們隻關注一句話background.draw(canvas);即當我們在繪制内容之前會調用drawable的draw方法繪制背景,是以drawable裡面的draw方法是一個十分關鍵的代碼它會将我們需要的圖檔文字等内容繪制到界面中。好了源碼先分析到這裡我們還是先看看基本使用吧。

好了廢話先不多說了先看實作代碼吧我們首先定義一個drawable類。(一個極其簡單的類)

public class TestDrawable extends Drawable {
    private Paint paint;//畫筆
    private int mWidth = ;//圖檔寬與高的最小值

    public TestDrawable() {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(Color.YELLOW);
        paint.setStyle(Paint.Style.FILL);
    }

    public void setmWidth(int width){
        this.mWidth=width;
        invalidateSelf();//更新設定
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        canvas.drawCircle(mWidth / , mWidth / , mWidth / , paint);
    }

    @Override
    public void setAlpha(int i) {
        paint.setAlpha(i);
        invalidateSelf();//更新設定
    }

    @Override
    public int getIntrinsicHeight() {
        return mWidth;
    }

    @Override
    public int getIntrinsicWidth() {
        return mWidth;
    }

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {
        paint.setColorFilter(colorFilter);
        invalidateSelf();//更行設定
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

}
           

看到了吧是不是有一種恍然大明白的感覺

我們的設定極其簡單許多都是固定模式化的東西

1.初始化paint這個不用我多說了吧不知道的自覺面壁去

2.setAlpha中設定paint的alpha

3.getIntrinsicHeight getIntrinsicWidth當布局為wrap_content的時候我們要畫的内容的寬高

4.setColorFilter調用paint的setColorFilter處理

5.getOpacity一班傳回PixelFormat.TRANSLUCENT即可

6.最最核心的處理draw方法這裡我們畫了一個圓大家也可以畫其他圖形,圖檔等處理

7.額外添加一個方法setmWidth重新定義控件的寬度這裡要注意了invalidateSelf這個方法調用會重新繪制該布局

這下明白了吧下面就是外部的調用

TestDrawable drawable = new TestDrawable();
ImageView iv_main = findViewById(R.id.iv_main);
iv_main.setImageDrawable(drawable);
drawable.setmWidth();
           

一句iv_main.setImageDrawable将我們畫的drawable中的圓畫到了界面上是不是很神奇

當然大家如果需要做動畫的話可以調用invalidateSelf不斷改變我們畫的内容

如果大家還是不太明白可以看看畫圖檔是怎麼處理的代碼如下

public class TestDrawable extends Drawable {
    private Paint paint;//畫筆
    private int mWidth;//圖檔寬與高的最小值
    private Bitmap bitmap;//位圖

    public TestDrawable(Context context, int resID) {
        this(BitmapFactory.decodeResource(context.getResources(), resID));
    }

    public TestDrawable(Bitmap bitmap) {
        this.bitmap = bitmap;
        paint = new Paint();
        paint.setAntiAlias(true);//抗鋸齒
        paint.setDither(true);//抖動,不同螢幕尺的使用保證圖檔品質
        ///位圖渲染器
        BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        paint.setShader(bitmapShader);
        mWidth = Math.min(bitmap.getWidth(), bitmap.getHeight());
    }



    @Override
    public void draw(@NonNull Canvas canvas) {
        canvas.drawCircle(mWidth / , mWidth / , mWidth / , paint);
    }

    @Override
    public void setAlpha(int i) {
        paint.setAlpha(i);
        invalidateSelf();//更新設定
    }

    @Override
    public int getIntrinsicHeight() {
        return bitmap.getHeight();
    }

    @Override
    public int getIntrinsicWidth() {
        return bitmap.getWidth();
    }

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {
        paint.setColorFilter(colorFilter);
        invalidateSelf();//更行設定
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

}
           

代碼也不多希望大家看完部落格後能對drawable有個更深入的認識

github連結點選這裡

繼續閱讀