在咱们平时生活中,大多数的 App 不光是圆角头像,有不少 App 在圆角头像上还加了一个边框,如:android
今天咱们就在 《看完这篇文章,我保证你也会用 RoundedBitmapDrawable 建立圆角头像》 的基础上再向前走一步——为 RoundedBitmapDrawable 加边框。canvas
在 GoF 的 23 种设计模式中,有一种设计模式叫装饰器模式。它的主要做用就是在不改变一个对象自己的基础上给对象增长额外的新行为。举个简单的例子:小的时候,每逢开学新书发下来的以后,大多数人都会给新书包个书皮,以使得书具备防止磨损的功能。映射到咱们今天要讲的内容里,咱们要作的就是给 RoundedBitmapDrawable 也加一个装饰器,使得它“有”一个边框。其实,从某种意义上来讲,咱们天天穿的衣服也是一种装饰器。冬天的时候,正是由于穿了衣服,咱们才有了抵御严寒的能力;下雨、下雪的时候,正是由于穿了雨衣,咱们才有了抵御雨、雪的能力。设计模式
动态地给一个对象增长一些额外的指责,就增长对象功能来讲,装饰器模式比生成子类实现更为灵活。bash
Attach additional responsibilities to an object dynamically. Decorator provide a flxcible alternative to subclassing for extending functionality.微信
抽象构件是具体构件和抽象装饰类共同的父类,它主要用于定义在具体构件中实现的业务方法,它的引入使用客户端能够以一致的方式处理装饰器类和被装饰的类。ide
具体构件主要用于实如今抽象构件中声明的方法,而且能够在须要的时候被装饰器装饰以增长其功能。post
抽象装饰类主要用于装饰具体构件,给其添加新功能,但这些功能并不直接在抽象装饰类中实现,而是在其子类中。在实际的应用中,抽象装饰类将维护一个指向抽象构件的引用,经过该引用调用装饰以前的构件的方法,并在抽象装饰类的子类中扩展该方法,以达到增长构件功能的目的。测试
具体装饰类主要用于为构件添加新功能。ui
接下来,咱们经过代码,看下装饰器模式是如何运做的:this
//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。
为 RoundedBitmapDrawable 加边框的步骤跟上面的《装饰器模式解析》步骤基本一致:
public interface RoundBitmapDrawableInterface {
Drawable getRoundedDrawable(Context context, Bitmap bitmap, DrawableShape drawableShape, float newWidth, float newHeight, float cornerRadius);
}
复制代码
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;
}
}
复制代码
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;
}
}
复制代码
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;
}
}
复制代码
接下来,让咱们一块儿看下如何使用上面的定义的类。
//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 建立圆角头像》)里讲过的,因此在这里不赘述了,不懂的小伙伴能够回看以前的文章,或者在下面留言,我会尽可能帮忙解答的。
最终效果以下:
下面是个人微信赞扬码和支付宝的收款码的经上述处理以后的结果:
最后,若是这篇文章真的帮到你了,顺手点个赞吧~