通過裝飾器模式為 RoundedBitmapDrawable 加邊框

極速24號發表於2019-04-05

1. 為什麼要給 RoundedBitmapDrawable 加邊框?

在我們平時生活中,大多數的 App 不光是圓角頭像,有很多 App 在圓角頭像上還加了一個邊框,如:

通過裝飾器模式為 RoundedBitmapDrawable 加邊框

通過裝飾器模式為 RoundedBitmapDrawable 加邊框

今天我們就在 《看完這篇文章,我保證你也會用 RoundedBitmapDrawable 建立圓角頭像》 的基礎上再向前走一步——為 RoundedBitmapDrawable 加邊框。

2. 為 RoundedBitmapDrawable 加邊框的原理是什麼?

在 GoF 的 23 種設計模式中,有一種設計模式叫裝飾器模式。它的主要作用就是在不改變一個物件本身的基礎上給物件增加額外的新行為。舉個簡單的例子:小的時候,每逢開學新書發下來的之後,大多數人都會給新書包個書皮,以使得書具有防止磨損的功能。對映到我們今天要講的內容裡,我們要做的就是給 RoundedBitmapDrawable 也加一個裝飾器,使得它“有”一個邊框。其實,從某種意義上來說,我們每天穿的衣服也是一種裝飾器。冬天的時候,正是因為穿了衣服,我們才有了抵禦嚴寒的能力;下雨、下雪的時候,正是因為穿了雨衣,我們才有了抵禦雨、雪的能力。

通過裝飾器模式為 RoundedBitmapDrawable 加邊框

2.1 什麼是裝飾器模式?

動態地給一個物件增加一些額外的指責,就增加物件功能來說,裝飾器模式比生成子類實現更為靈活。

Attach additional responsibilities to an object dynamically. Decorator provide a flxcible alternative to subclassing for extending functionality.

2.2 裝飾器模式的結構

通過裝飾器模式為 RoundedBitmapDrawable 加邊框
  1. Component(抽象構件)

抽象構件是具體構件和抽象裝飾類共同的父類,它主要用於定義在具體構件中實現的業務方法,它的引入使用客戶端可以以一致的方式處理裝飾器類和被裝飾的類。

  1. ConcreteComponent(具體構件)

具體構件主要用於實現在抽象構件中宣告的方法,並且可以在需要的時候被裝飾器裝飾以增加其功能。

  1. Decorator(抽象裝飾類)

抽象裝飾類主要用於裝飾具體構件,給其新增新功能,但這些功能並不直接在抽象裝飾類中實現,而是在其子類中。在實際的應用中,抽象裝飾類將維護一個指向抽象構件的引用,通過該引用呼叫裝飾之前的構件的方法,並在抽象裝飾類的子類中擴充套件該方法,以達到增加構件功能的目的。

  1. ConcreteDecorator(具體裝飾類)

具體裝飾類主要用於為構件新增新功能。

2.3 裝飾器模式解析

接下來,我們通過程式碼,看下裝飾器模式是如何運作的:

//1. 抽象構件

public interface Component {
	
	public void operation();
	
}
複製程式碼
//2. 具體構件

public class ConcreteComponent implements Component {

	@Override
	public void operation() {
		System.out.println("ConcreteComponent operation");
	}

}
複製程式碼
//3. 抽象裝飾類

public abstract class Decorator implements Component {

	private Component mComponent;
	
	public void setComponent(Component component) {
		this.mComponent = component;
	}
	
	public Component getComponent() {
		return mComponent;
	}
	
	@Override
	public void operation() {
		mComponent.operation();
	}

}
複製程式碼
//4. 具體裝飾類
public class ConcreteDecorator extends Decorator {

	@Override
	public void operation() {
		super.operation();
		addedBehavior();
	}
	
	private void addedBehavior(){
		System.out.println("ConcreteDecorator addedBehavior");
	}
	
}
複製程式碼
//5. Main 
public class Main {

	public static void main(String[] args) {
		Component component = new ConcreteComponent();
		Decorator decorator = new ConcreteDecorator();
		decorator.setComponent(component);
		decorator.operation();
	}

}
複製程式碼
//6. 測試結果
ConcreteComponent operation
ConcreteDecorator addedBehavior
複製程式碼

由上面的執行結果可知,ConcreteDecorator 已經為 ConcreteComponent 新增了新的功能 addedBehavior。

3. 如何通過裝飾器模式為 RoundedBitmapDrawable 加邊框?

3.1 具體步驟

為 RoundedBitmapDrawable 加邊框的步驟跟上面的《裝飾器模式解析》步驟基本一致:

  1. 定義抽象構件;
  2. 定義具體構件;
  3. 定義抽象裝飾類;
  4. 定義具體裝飾類。
3.1.1 定義抽象構件
public interface RoundBitmapDrawableInterface {

    Drawable getRoundedDrawable(Context context, Bitmap bitmap, DrawableShape drawableShape, float newWidth, float newHeight, float cornerRadius);

}
複製程式碼
3.1.2 定義具體構件
public final class RoundBitmapDrawable implements RoundBitmapDrawableInterface {

    @Override
    public Drawable getRoundedDrawable(Context context, Bitmap bitmap, DrawableShape drawableShape, float newWidth, float newHeight, float cornerRadius) {
        if (bitmap == null) {
            return null;
        }
        if(drawableShape == null){
            drawableShape = DrawableShape.CIRCLE;
        }
        int bitmapWidth = bitmap.getWidth();
        int bitmapHeight = bitmap.getHeight();
        if(newWidth != 0 && newHeight != 0){
            float scaleRatio = 0;
            if(Math.min(bitmapWidth, bitmapHeight) > Math.min(newWidth, newHeight)){
                scaleRatio = Math.min(newWidth, newHeight) / Math.min(bitmapWidth, bitmapHeight);
            }else if(Math.min(bitmapWidth, bitmapHeight) <= Math.min(newWidth, newHeight)){
                scaleRatio = Math.min(newWidth, newHeight) / Math.min(bitmapWidth, bitmapHeight);
            }
            Matrix matrix = new Matrix();
            matrix.postScale(scaleRatio, scaleRatio);
            bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmapWidth, bitmapHeight, matrix, false);
            bitmapWidth = bitmap.getWidth();
            bitmapHeight = bitmap.getHeight();
        }

        Bitmap dstBitmap;
        int dstBitmapWidth, dstBitmapHeight;
        int deltaX, deltaY;
        Canvas dstCanvas;
        RoundedBitmapDrawable dstDrawable = null;

        switch (drawableShape){
            case CIRCLE:
                dstBitmap = Bitmap.createBitmap(Math.min(bitmapWidth, bitmapHeight), Math.min(bitmapWidth, bitmapHeight), Bitmap.Config.ARGB_8888);
                dstBitmapWidth = dstBitmap.getWidth();
                dstBitmapHeight = dstBitmap.getHeight();
                if(bitmapWidth > dstBitmapWidth){
                    deltaX = - (bitmapWidth/2 - dstBitmapWidth/2);
                }else {
                    deltaX = (bitmapWidth/2 - dstBitmapWidth/2);
                }
                if(bitmapHeight > dstBitmapHeight){
                    deltaY = - (bitmapHeight/2 - dstBitmapHeight/2);
                }else {
                    deltaY = (bitmapHeight/2 - dstBitmapHeight/2);
                }
                dstCanvas = new Canvas(dstBitmap);
                dstCanvas.drawBitmap(bitmap, deltaX, deltaY, null);
                dstDrawable = RoundedBitmapDrawableFactory.create(context.getResources(), dstBitmap);
                dstDrawable.setCircular(true);
                break;
            case RECTANGLE:
                dstBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
                dstBitmapWidth = dstBitmap.getWidth();
                dstBitmapHeight = dstBitmap.getHeight();
                if(bitmapWidth > dstBitmapWidth){
                    deltaX = - (bitmapWidth/2 - dstBitmapWidth/2);
                }else {
                    deltaX = (bitmapWidth/2 - dstBitmapWidth/2);
                }
                if(bitmapHeight > dstBitmapHeight){
                    deltaY = - (bitmapHeight/2 - dstBitmapHeight/2);
                }else {
                    deltaY = (bitmapHeight/2 - dstBitmapHeight/2);
                }
                dstCanvas = new Canvas(dstBitmap);
                dstCanvas.drawBitmap(bitmap, deltaX, deltaY, null);
                dstDrawable = RoundedBitmapDrawableFactory.create(context.getResources(), dstBitmap);
                dstDrawable.setCornerRadius(cornerRadius);
                break;
        }

        return dstDrawable;
    }

}
複製程式碼
3.1.3 定義抽象裝飾類
public abstract class RoundBitmapDrawableDecorator implements RoundBitmapDrawableInterface {

    private RoundBitmapDrawableInterface mRoundBitmapDrawableInterface;

    public void setRoundBitmapDrawable(RoundBitmapDrawableInterface roundBitmapDrawableInterface){
        this.mRoundBitmapDrawableInterface = roundBitmapDrawableInterface;
    }

    @Override
    public Drawable getRoundedDrawable(Context context, Bitmap bitmap, DrawableShape drawableShape, float newWidth, float newHeight, float cornerRadius) {
        if(mRoundBitmapDrawableInterface != null){
            return mRoundBitmapDrawableInterface.getRoundedDrawable(context, bitmap, drawableShape, newWidth, newHeight, cornerRadius);
        }
        return null;
    }

}
複製程式碼
3.1.4 定義具體裝飾類
public class WhiteBorderRoundBitmapDrawableDecorator extends RoundBitmapDrawableDecorator {

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public Drawable getRoundedDrawable(Context context, Bitmap bitmap, DrawableShape drawableShape, float newWidth, float newHeight, float cornerRadius) {
        Drawable drawable = super.getRoundedDrawable(context, bitmap, drawableShape, newWidth, newHeight, cornerRadius);
        int drawableWidth = drawable.getIntrinsicWidth();
        int drawableHeight = drawable.getIntrinsicHeight();
        Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
        Bitmap backgroundBitmap = Bitmap.createBitmap(drawableWidth, drawableHeight, config);
        int backgroundBitmapWidth = backgroundBitmap.getWidth();
        int backgroundBitmapHeight = backgroundBitmap.getHeight();
        Canvas canvas = new Canvas(backgroundBitmap);
        drawable.setBounds(0, 0, drawableWidth, drawableHeight);
        drawable.draw(canvas);
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.WHITE);
        float strokeWidth;
        switch (drawableShape){
            case CIRCLE:
                strokeWidth = context.getResources().getDimension(R.dimen.padding_micro_x);
                paint.setStrokeWidth(strokeWidth);
                int backgroundBitmapCenterX = backgroundBitmap.getWidth()/2;
                int backgroundBitmapCenterY = backgroundBitmap.getHeight()/2;
                int backgroundBitmapRadius = (int)(Math.min(backgroundBitmapWidth, backgroundBitmapHeight)/2 - strokeWidth/2);
                canvas.drawCircle(backgroundBitmapCenterX, backgroundBitmapCenterY, backgroundBitmapRadius, paint);
                break;
            case RECTANGLE:
                strokeWidth = context.getResources().getDimension(R.dimen.padding_micro_xx);
                paint.setStrokeWidth(strokeWidth);
                float left = strokeWidth/2;
                float top = strokeWidth/2;
                float right = backgroundBitmapWidth - strokeWidth/2;
                float bottom = backgroundBitmapHeight - strokeWidth/2;
                cornerRadius = cornerRadius - strokeWidth/2;
                canvas.drawRoundRect(left, top, right, bottom, cornerRadius, cornerRadius, paint);
                break;
        }
        drawable = new BitmapDrawable(backgroundBitmap);
        return drawable;
    }

}
複製程式碼

3.2 例項演示

接下來,讓我們一起看下如何使用上面的定義的類。

//1. 資原始檔
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/grey_700">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_horizontal"
        android:orientation="vertical">


        <ImageView
            android:id="@+id/rounded_bitmap_drawable_base"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/padding_small"
            android:background="@color/grey_700"
            android:src="@drawable/tiger" />

        <ImageView
            android:id="@+id/rounded_bitmap_drawable_circle_fit_center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/padding_small"
            android:background="@color/grey_700"
            android:scaleType="fitCenter"
            android:src="@drawable/tiger" />

        <ImageView
            android:id="@+id/rounded_bitmap_drawable_round_rectangle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/padding_small"
            android:background="@color/grey_700"
            android:scaleType="fitCenter"
            android:src="@drawable/tiger" />

    </LinearLayout>
</ScrollView>
複製程式碼
public class RoundedBitmapDrawableActivity extends AppCompatActivity {

    private int mScreenWidth, mScreenHeight, mViewWidth, mViewHeight;
    private ImageView mBaseView, mCircleFitCenterView, mRoundRectangleView;
    private LinearLayout.LayoutParams mBaseLayoutParams, mCircleLayoutParams, mRoundRectangleLayoutParams;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_rounded_bitmap_drawable_tutorial_optimize);
        getScreenProperty();
        initView();
        initData();
    }

    private void getScreenProperty(){
        mScreenWidth = DisplayMetricsUtil.getScreenWidth(this);
        mScreenHeight = DisplayMetricsUtil.getScreenHeight(this);
        mViewWidth = mScreenWidth * 2/3;
        mViewHeight = mScreenHeight * 2/3;
    }

    private void initView(){
        mBaseView = findViewById(R.id.rounded_bitmap_drawable_base);
        mBaseLayoutParams = new LinearLayout.LayoutParams(mViewWidth, mViewHeight);
        mBaseLayoutParams.topMargin = (int)getResources().getDimension(R.dimen.padding_small);
        mBaseView.setLayoutParams(mBaseLayoutParams);


        mCircleFitCenterView = findViewById(R.id.rounded_bitmap_drawable_circle_fit_center);
        mCircleLayoutParams = new LinearLayout.LayoutParams(mViewWidth, mViewWidth);
        mCircleLayoutParams.topMargin = (int)getResources().getDimension(R.dimen.padding_small);
        mCircleFitCenterView.setLayoutParams(mCircleLayoutParams);


        mRoundRectangleView = findViewById(R.id.rounded_bitmap_drawable_round_rectangle);
        mRoundRectangleLayoutParams = new LinearLayout.LayoutParams(mViewWidth, mViewHeight);
        mRoundRectangleLayoutParams.topMargin = (int)getResources().getDimension(R.dimen.padding_small);
        mRoundRectangleView.setLayoutParams(mRoundRectangleLayoutParams);
    }

    private void initData(){
        //1. 建立具體構件
        RoundBitmapDrawableInterface roundBitmapDrawableInterface = new RoundBitmapDrawable();
        //2. 建立裝飾器物件
        RoundBitmapDrawableDecorator roundBitmapDrawableDecorator = new WhiteBorderRoundBitmapDrawableDecorator();
        //3. 將具體構件注入到裝飾器物件中,以使得具體構件有“顯示邊框”的能力
        roundBitmapDrawableDecorator.setRoundBitmapDrawable(roundBitmapDrawableInterface);

        //4. 測試未被裝飾的具體構件
        mBaseView.setImageDrawable(roundBitmapDrawableInterface.getRoundedDrawable(this,BitmapFactory.decodeResource(getResources(), R.drawable.tiger), DrawableShape.RECTANGLE, mViewWidth, mViewHeight, getResources().getDimension(R.dimen.padding_small)));

        //5. 測試裝飾過的具體構件(圓形)
        Drawable circleDrawable = roundBitmapDrawableDecorator.getRoundedDrawable(this, BitmapFactory.decodeResource(getResources(), R.drawable.tiger), DrawableShape.CIRCLE, 0, 0, 0);
        mCircleFitCenterView.setImageDrawable(circleDrawable);

        //6. 測試裝飾過的具體構件(矩形)
        Drawable rectangleDrawable = roundBitmapDrawableDecorator.getRoundedDrawable(this,BitmapFactory.decodeResource(getResources(), R.drawable.tiger), DrawableShape.RECTANGLE, mViewWidth, mViewHeight, getResources().getDimension(R.dimen.padding_small));
        mRoundRectangleView.setImageDrawable(rectangleDrawable);
    }
}
複製程式碼

程式碼沒什麼難的,都是之前的文章(《看完這篇文章,我保證你也會用 RoundedBitmapDrawable 建立圓角頭像》)裡講過的,所以在這裡不贅述了,不懂的小夥伴可以回看之前的文章,或者在下面留言,我會盡量幫忙解答的。

最終效果如下:

通過裝飾器模式為 RoundedBitmapDrawable 加邊框

下面是我的微信讚賞碼和支付寶的收款碼的經上述處理之後的結果:

通過裝飾器模式為 RoundedBitmapDrawable 加邊框

通過裝飾器模式為 RoundedBitmapDrawable 加邊框

最後,如果這篇文章真的幫到你了,順手點個贊吧~

4. 參考文獻

  1. 《看完這篇文章,我保證你也會用 RoundedBitmapDrawable 建立圓角頭像》
  2. Decorator Pattern
  3. Decorator
  4. 《設計模式》劉偉

相關文章