通过装饰器模式为 RoundedBitmapDrawable 加边框

1. 为什么要给 RoundedBitmapDrawable 加边框?

在我们平时生活中,大多数的 App 不光是圆角头像,有很多 App 在圆角头像上还加了一个边框,如:





今天我们就在 《看完这篇文章,我保证你也会用 RoundedBitmapDrawable 创建圆角头像》 的基础上再向前走一步——为 RoundedBitmapDrawable 加边框。

2. 为 RoundedBitmapDrawable 加边框的原理是什么?

在 GoF 的 23 种设计模式中,有一种设计模式叫装饰器模式。它的主要作用就是在不改变一个对象本身的基础上给对象增加额外的新行为。举个简单的例子:小的时候,每逢开学新书发下来的之后,大多数人都会给新书包个书皮,以使得书具有防止磨损的功能。映射到我们今天要讲的内容里,我们要做的就是给 RoundedBitmapDrawable 也加一个装饰器,使得它“有”一个边框。其实,从某种意义上来说,我们每天穿的衣服也是一种装饰器。冬天的时候,正是因为穿了衣服,我们才有了抵御严寒的能力;下雨、下雪的时候,正是因为穿了雨衣,我们才有了抵御雨、雪的能力。

2.1 什么是装饰器模式?

动态地给一个对象增加一些额外的指责,就增加对象功能来说,装饰器模式比生成子类实现更为灵活。

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

2.2 装饰器模式的结构
  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 创建圆角头像》)里讲过的,所以在这里不赘述了,不懂的小伙伴可以回看之前的文章,或者在下面留言,我会尽量帮忙解答的。

最终效果如下:



下面是我的微信赞赏码和支付宝的收款码的经上述处理之后的结果:



最后,如果这篇文章真的帮到你了,顺手点个赞吧~

4. 参考文献

  1. 《看完这篇文章,我保证你也会用 RoundedBitmapDrawable 创建圆角头像》
  2. Decorator Pattern
  3. Decorator
  4. 《设计模式》刘伟
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值