美文网首页
看完这篇文章,我保证你也会用 RoundedBitmapDraw

看完这篇文章,我保证你也会用 RoundedBitmapDraw

作者: 极速24号 | 来源:发表于2019-04-03 07:26 被阅读0次

    1. 什么是 RoundedBitmapDrawable,它存在的意义是什么?

    RoundedBitmapDrawable 是 Android 版本 22.1.0 的时候加入的,它的主要作用是创建圆角的 Drawable。

    A Drawable that wraps a bitmap and can be drawn with rounded corners.

    Google 添加此类的原因可能是弥补 Android API 中没有直接支持创建圆角 Drawable 的空缺吧。

    2. 怎么用?

    RoundedBitmapDrawable 不仅可以创建圆角的 Drawable,还可以创建圆角矩形的 Drawable,接下来,我们就针对这两种情况分别讨论。

    2.1 圆形 Drawable

    2.1.1 概述

    创建和应用 RoundedBitmapDrawable 一共分三步:

    1. 创建 RoundedBitmapDrawable 对象;
    2. 设置 RoundedBitmapDrawable 为圆形;
    3. 将 RoundedBitmapDrawable 设置到 ImageView 上。
    2.1.2 详述
    1. 创建 RoundedBitmapDrawable 对象
    RoundedBitmapDrawable circleDrawable = RoundedBitmapDrawableFactory.create(@NonNull Resources res, @Nullable Bitmap bitmap);
    
    1. 设置 RoundedBitmapDrawable 为圆形
    RoundedBitmapDrawable.setCircular(boolean circular);
    
    1. 将 RoundedBitmapDrawable 设置到 ImageView 上
    ImageView.setImageDrawable(@Nullable Drawable drawable);
    
    2.1.3 应用实例
    //1. 资源文件
    
    <?xml version="1.0" encoding="utf-8"?>
    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white"
        tools:context=".roundedbitmapdrawabletutorial.RoundedBitmapDrawableTutorialActivity">
    
        <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_circle_fit_center"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/padding_small"
                android:background="@color/white"
                android:scaleType="fitCenter"
                android:src="@drawable/tiger" />
    
        </LinearLayout>
    </ScrollView>
    
    //2. Activity
    
    public class RoundedBitmapDrawableTutorialActivity extends AppCompatActivity {
    
        private int mScreenWidth, mScreenHeight, mViewWidth, mViewHeight;
        private ImageView mCircleFitCenterView;
        private LinearLayout.LayoutParams mCircleLayoutParams;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_rounded_bitmap_drawable_tutorial);
            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(){
            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);
        }
    
        private void initData(){
            //第一步
            RoundedBitmapDrawable circleDrawable = RoundedBitmapDrawableFactory.create(getResources(), BitmapFactory.decodeResource(getResources(), R.drawable.tiger));
            //第二步
            circleDrawable.setCircular(true);
            //第三步
            mCircleFitCenterView.setImageDrawable(circleDrawable);
        }
    
    }
    

    最终效果如下:

    圆形 Drawable

    2.2 圆角矩形 Drawable

    2.2.1 概述

    创建和应用 RoundedBitmapDrawable 一共分三步:

    1. 创建 RoundedBitmapDrawable 对象;
    2. 为 RoundedBitmapDrawable 设置圆角的半径;
    3. 将 RoundedBitmapDrawable 设置到 ImageView 上。
    2.2.2 详述
    1. 创建 RoundedBitmapDrawable 对象
    RoundedBitmapDrawable circleDrawable = RoundedBitmapDrawableFactory.create(@NonNull Resources res, @Nullable Bitmap bitmap);
    
    1. 为 RoundedBitmapDrawable 设置圆角的半径
    RoundedBitmapDrawable.setCornerRadius(float cornerRadius);
    
    1. 将 RoundedBitmapDrawable 设置到 ImageView 上
    ImageView.setImageDrawable(@Nullable Drawable drawable);
    
    2.2.3 应用实例
    //1. 资源文件
    
    <?xml version="1.0" encoding="utf-8"?>
    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white"
        tools:context=".roundedbitmapdrawabletutorial.RoundedBitmapDrawableTutorialActivity">
    
        <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_round_rectangle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/padding_small"
                android:background="@color/white"
                android:scaleType="fitCenter"
                android:src="@drawable/tiger" />
    
        </LinearLayout>
    </ScrollView>
    
    //2. Activity
    public class RoundedBitmapDrawableTutorialActivity extends AppCompatActivity {
    
        private int mScreenWidth, mScreenHeight, mViewWidth, mViewHeight;
        private ImageView mRoundRectangleView;
        private LinearLayout.LayoutParams mRoundRectangleLayoutParams;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_rounded_bitmap_drawable_tutorial);
            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(){
            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(){
            //第一步
            RoundedBitmapDrawable roundRectangleDrawable = RoundedBitmapDrawableFactory.create(getResources(), BitmapFactory.decodeResource(getResources(), R.drawable.tiger));
            //第二步
            roundRectangleDrawable.setCornerRadius((mViewWidth < mViewHeight ? mViewWidth : mViewHeight)/32);
            //第三步
            mRoundRectangleView.setImageDrawable(roundRectangleDrawable);
        }
    
    }
    

    最终效果如下:

    圆角矩形 Drawable

    只需要对 RoundedBitmapDrawable 的圆角的半径稍作修改,就可以的得到如下效果:

    圆角矩形 Drawable 稍作修改版本

    修改方法如下:

    //第一步
    RoundedBitmapDrawable roundRectangleDrawable = RoundedBitmapDrawableFactory.create(getResources(), BitmapFactory.decodeResource(getResources(), R.drawable.tiger));
    //第二步
    roundRectangleDrawable.setCornerRadius((mViewWidth < mViewHeight ? mViewWidth : mViewHeight)/2));
    //第三步
    mRoundRectangleView.setImageDrawable(roundRectangleDrawable);
    

    是不是很好用?

    3. RoundedBitmapDrawable 实现原理是什么?

    知道了怎么用 RoundedBitmapDrawable 之后,让我们一块看下,RoundedBitmapDrawable 到底是如何“变圆”的。

    1. 进入 RoundedBitmapDrawableFactory 类的 create(@NonNull Resources res, @Nullable Bitmap bitmap) 方法:
    public static RoundedBitmapDrawable create(@NonNull Resources res, @Nullable Bitmap bitmap) {
        return (RoundedBitmapDrawable)(VERSION.SDK_INT >= 21 ? new RoundedBitmapDrawable21(res, bitmap) : new RoundedBitmapDrawableFactory.DefaultRoundedBitmapDrawable(res, bitmap));
    }
    

    在这里我们只研究 VERSION.SDK_INT 小于 21 的情况,另外一种情况,大家自己去分析吧。

    1. 进入 RoundedBitmapDrawableFactory 类的 DefaultRoundedBitmapDrawable 方法:
    private static class DefaultRoundedBitmapDrawable extends RoundedBitmapDrawable {
        DefaultRoundedBitmapDrawable(Resources res, Bitmap bitmap) {
            super(res, bitmap);
        }
    
        public void setMipMap(boolean mipMap) {
            if (this.mBitmap != null) {
                BitmapCompat.setHasMipMap(this.mBitmap, mipMap);
                this.invalidateSelf();
            }
    
        }
    
        public boolean hasMipMap() {
            return this.mBitmap != null && BitmapCompat.hasMipMap(this.mBitmap);
        }
    
        void gravityCompatApply(int gravity, int bitmapWidth, int bitmapHeight, Rect bounds, Rect outRect) {
            GravityCompat.apply(gravity, bitmapWidth, bitmapHeight, bounds, outRect, 0);
        }
    }
    

    不难发现,DefaultRoundedBitmapDrawable 继承至 RoundedBitmapDrawable,并且 DefaultRoundedBitmapDrawable 构造方法实际上调用的是 RoundedBitmapDrawable 的构造方法。

    1. 进入 RoundedBitmapDrawable 的构造方法:
    RoundedBitmapDrawable(Resources res, Bitmap bitmap) {
        if (res != null) {
            // 获取屏幕密度
            this.mTargetDensity = res.getDisplayMetrics().densityDpi;
        }
    
        this.mBitmap = bitmap;
        if (this.mBitmap != null) {
            // 计算 Bitmap 的 Width、Height
            this.computeBitmapSize();
            // 初始化 BitmapShader,今天主角终于出现了
            this.mBitmapShader = new BitmapShader(this.mBitmap, TileMode.CLAMP, TileMode.CLAMP);
        } else {
            this.mBitmapWidth = this.mBitmapHeight = -1;
            this.mBitmapShader = null;
        }
    
    }
    
    //computeBitmapSize
    private void computeBitmapSize() {
        this.mBitmapWidth = this.mBitmap.getScaledWidth(this.mTargetDensity);
        this.mBitmapHeight = this.mBitmap.getScaledHeight(this.mTargetDensity);
    }
    

    在 RoundedBitmapDrawable 的构造方法里,首先获取到屏幕的密度,其次获取传入的 Bitmap 的宽、高,最后初始化 BitmapShader。其实到这里就可以结束了,因为 RoundedBitmapDrawable 实现圆角的方式已经很明了了——通过给 Paint 设置 BitmapShader。当然,这是对于那些已经熟练掌握自定义控件的人说的,如果你对自定义控件不熟悉,那就接着往下看吧。

    1. 进入 RoundedBitmapDrawable 的 draw 方法:
    public void draw(@NonNull Canvas canvas) {
        Bitmap bitmap = this.mBitmap;
        if (bitmap != null) {
            this.updateDstRect();
            if (this.mPaint.getShader() == null) {
                canvas.drawBitmap(bitmap, (Rect)null, this.mDstRect, this.mPaint);
            } else {
                canvas.drawRoundRect(this.mDstRectF, this.mCornerRadius, this.mCornerRadius, this.mPaint);
            }
    
        }
    }
    

    在 RoundedBitmapDrawable 的 draw 方法中,首先调用了 updateDstRect() 方法,然后再根据 BitmapShader 是否为 null 决定到底是直接绘制 Bitmap(canvas.drawBitmap),还是绘制圆角矩形(canvas.drawRoundRect)。

    1. 进入 updateDstRect 方法:
    void updateDstRect() {
        //默认情况下,该属性为 true
        if (this.mApplyGravity) {
            //是否为圆形
            if (this.mIsCircular) {
                //1. 圆形
                
                //计算 Bitmap 短边
                int minDimen = Math.min(this.mBitmapWidth, this.mBitmapHeight);
                //为 mDstRect 设置 Width、Height 属性
                this.gravityCompatApply(this.mGravity, minDimen, minDimen, this.getBounds(), this.mDstRect);
                //计算 mDstRect 短边
                int minDrawDimen = Math.min(this.mDstRect.width(), this.mDstRect.height());
                //比较 mDstRect 的短边与 mDstRect Width、Height 的关系,进而缩放 mDstRect,以使 mDstRect 为正方形
                int insetX = Math.max(0, (this.mDstRect.width() - minDrawDimen) / 2);
                int insetY = Math.max(0, (this.mDstRect.height() - minDrawDimen) / 2);
                this.mDstRect.inset(insetX, insetY);
                //确定圆角的半径
                this.mCornerRadius = 0.5F * (float)minDrawDimen;
            } else {
                //2. 矩形
                
                //为 mDstRect 设置 Width、Height 属性
                this.gravityCompatApply(this.mGravity, this.mBitmapWidth, this.mBitmapHeight, this.getBounds(), this.mDstRect);
            }
    
            this.mDstRectF.set(this.mDstRect);
            if (this.mBitmapShader != null) {
                //通过 BitmapShader 对应的 Matrix 使 BitmapShader 中的 Bitmap 从 mDstRectF 左上放开始绘制
                this.mShaderMatrix.setTranslate(this.mDstRectF.left, this.mDstRectF.top);
                //将 BitmapShader 中的 Bitmap 缩放至与 mDstRectF 尺寸一样
                this.mShaderMatrix.preScale(this.mDstRectF.width() / (float)this.mBitmap.getWidth(), this.mDstRectF.height() / (float)this.mBitmap.getHeight());
                this.mBitmapShader.setLocalMatrix(this.mShaderMatrix);
                //为 Paint 设置 BitmapShader
                this.mPaint.setShader(this.mBitmapShader);
            }
    
            this.mApplyGravity = false;
        }
    
    }
    

    注释已经写的很清楚了,updateDstRect 方法其实主要就是干三件事:

    • 计算 RoundedBitmapDrawable 所在 DstRectF 的 Width、Height 属性;
      • RoundedBitmapDrawable 如果为圆形的时候,还要计算矩形(确切地说,应该是正方形)圆角的半径
    • 计算 BitmapShader 中 Bitmap 的 Width、Height 与 RoundedBitmapDrawable 所在 DstRectF 的 Width、Height 大小关系,进而缩放 Bitmap
    • 为 Paint 设置 BitmapShader

    下面是我测试的时候打的断点的截图:

    updateDstRect 断点

    其中用 1、2 标示的地方是计算 RoundedBitmapDrawable 所在 DstRectF 的 Width、Height 属性,用 3 标出的地方为当 RoundedBitmapDrawable 为圆形时,计算出来的矩形(确切地说,应该是正方形)圆角的半径。剩下的另外两个步骤已经在代码中注释的很明显了,我就不赘述了。

    1. 再回到 draw 方法:
    public void draw(@NonNull Canvas canvas) {
        Bitmap bitmap = this.mBitmap;
        if (bitmap != null) {
            this.updateDstRect();
            if (this.mPaint.getShader() == null) {
                canvas.drawBitmap(bitmap, (Rect)null, this.mDstRect, this.mPaint);
            } else {
                canvas.drawRoundRect(this.mDstRectF, this.mCornerRadius, this.mCornerRadius, this.mPaint);
            }
    
        }
    }
    

    因为 BitmapShader 不为 null,所以进入 drawRoundRect 方法。

    因为 mCornerRadius 为 mDstRectF 短边的一半,所以就有了下面两张图:

    • 正方形
    圆形 Drawable
    • 矩形
    圆角矩形 Drawable 稍作修改版本

    4. 容易出错的地方有哪些?

    在使用 RoundedBitmapDrawable 的时候,需要注意地方有:

    1. RoundedBitmapDrawable 引用的图片资源,须为正方形,否则会被强制压缩。
    2. ImageView 的 scaleType 须为 fitCenter,否则出现“不良反应”

    4.1 RoundedBitmapDrawable 引用的图片资源,须为正方形,否则会被强制压缩

    其实在分析 RoundedBitmapDrawable 实现原理的时候,就说过,如果 Bitmap 的 Width、Height 与 RoundedBitmapDrawable 所在 RectF 的 Width、Height 不一致的时候,会被强制缩放。

    如下图所示:

    强制压缩

    解决的方法其实也简单,只需要在向 RoundedBitmapDrawableFactory.create(@NonNull Resources res, @Nullable Bitmap bitmap) 方法传入 Bitmap 之前判断 Bitmap 的是否为正方形。如果不是正方形,则手动从 Bitmap 中截取一个正方形;如果是正方形,则直接将 Bitmap 传递给 RoundedBitmapDrawableFactory.create(@NonNull Resources res, @Nullable Bitmap bitmap) 方法。

    4.1.1 处理方法:根据传入的 Bitmap 的短边将 Bitmap 转换成正方形
    //根据传入的 Bitmap 的短边将 Bitmap 转换成正方形
    public static Bitmap transferToSquareBitmap(Bitmap bitmap) {
        if (bitmap == null) {
            return null;
        }
        int bitmapWidth = bitmap.getWidth();
        int bitmapHeight = bitmap.getHeight();
        int squareSideLength = Math.min(bitmapWidth, bitmapHeight);
        Bitmap squareBitmap = Bitmap.createBitmap(squareSideLength, squareSideLength, Bitmap.Config.ARGB_8888);
        int squareBitmapWidth = squareBitmap.getWidth();
        int squareBitmapHeight = squareBitmap.getHeight();
        int deltaX, deltaY;
        if(bitmapWidth > squareBitmapWidth){
            deltaX = - (bitmapWidth - squareBitmapWidth)/2;
        }else {
            deltaX = (bitmapWidth - squareBitmapWidth)/2;
        }
        if(bitmapHeight > squareBitmapHeight){
            deltaY = - (bitmapHeight/2 - squareBitmapHeight/2);
        }else {
            deltaY = (bitmapHeight/2 - squareBitmapHeight/2);
        }
        Canvas squareCanvas = new Canvas(squareBitmap);
        squareCanvas.drawBitmap(bitmap, deltaX, deltaY, null);
        return squareBitmap;
    }
    
    4.1.2 测试
    //第一步
    Bitmap circleBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.tiger);
    //将 Bitmap 转换成正方形
    circleBitmap = BitmapUtils.transferToSquareBitmap(circleBitmap);
    RoundedBitmapDrawable circleDrawable = RoundedBitmapDrawableFactory.create(getResources(), circleBitmap);
    //第二步
    circleDrawable.setCircular(true);
    //第三步
    mCircleFitCenterView.setImageDrawable(circleDrawable);
    

    最终效果如下:

    Bitmap 转换成正方形

    4.2 ImageView 的 scaleType 须为 fitCenter,否则出现“不良反应”

    接下来分别对下面四种情况,展开讨论:

    1. 圆形
      • fitCenter
      • centerCrop
    2. 矩形
      • fitCenter
      • centerCrop
    4.2.1 圆形
    <?xml version="1.0" encoding="utf-8"?>
    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center_horizontal"
            android:orientation="vertical">
            
            <!--android:scaleType="fitCenter"-->
            <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/white"
                android:scaleType="fitCenter"
                android:src="@drawable/tiger"
                />
            
            <!--android:scaleType="centerCrop"-->
            <ImageView
                android:id="@+id/rounded_bitmap_drawable_circle_center_crop"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/padding_small"
                android:background="@color/white"
                android:scaleType="centerCrop"
                android:src="@drawable/tiger"/>
    
        </LinearLayout>
    </ScrollView>
    
    public class RoundedBitmapDrawableTutorialActivity extends AppCompatActivity {
    
        private ImageView mCircleFitCenterView, mCircleCenterCropView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_rounded_bitmap_drawable_tutorial);
            initView();
            initData();
        }
    
        private void initView(){
            mCircleFitCenterView = findViewById(R.id.rounded_bitmap_drawable_circle_fit_center);
            mCircleCenterCropView = findViewById(R.id.rounded_bitmap_drawable_circle_center_crop);
        }
    
        private void initData(){
            //第一步
            RoundedBitmapDrawable circleDrawable = RoundedBitmapDrawableFactory.create(getResources(), BitmapFactory.decodeResource(getResources(), R.drawable.tiger));
            //第二步
            circleDrawable.setCircular(true);
            //第三步
            mCircleFitCenterView.setImageDrawable(circleDrawable);
    
            mCircleCenterCropView.setImageDrawable(circleDrawable);
        }
    
    }
    

    最终效果如下:

    Drawable 为圆形时未出现不良反应

    由于此时二者效果一样,所以,我就把图拉到了二者的中间,因此,大家看到的是两个老虎的半张脸。

    结论:当 RoundedBitmapDrawable 为圆形时,ImageView 的 scaleType 不为 fitCenter 时,没有“不良反应”。

    4.2.2 矩形
    <?xml version="1.0" encoding="utf-8"?>
    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center_horizontal"
            android:orientation="vertical">
    
            <!--android:scaleType="fitCenter"-->
            <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/white"
                android:scaleType="fitCenter"
                android:src="@drawable/tiger"/>
    
            <!--android:scaleType="centerCrop"-->
            <ImageView
                android:id="@+id/rounded_bitmap_drawable_round_rectangle_center_crop"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/padding_small"
                android:background="@color/white"
                android:scaleType="centerCrop"
                android:src="@drawable/tiger"/>
    
        </LinearLayout>
    </ScrollView>
    
    public class RoundedBitmapDrawableTutorialActivity extends AppCompatActivity {
    
        private ImageView mRoundRectangleView, mRoundRectangleCenterCropView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_rounded_bitmap_drawable_tutorial);
            initView();
            initData();
        }
    
        private void initView(){
            mRoundRectangleView = findViewById(R.id.rounded_bitmap_drawable_round_rectangle);
            mRoundRectangleCenterCropView = findViewById(R.id.rounded_bitmap_drawable_round_rectangle_center_crop);
        }
    
        private void initData(){
            Bitmap roundRectangleBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.tiger);
            //第一步
            RoundedBitmapDrawable roundRectangleDrawable = RoundedBitmapDrawableFactory.create(getResources(), roundRectangleBitmap);
            //第二步
            roundRectangleDrawable.setCornerRadius((mViewWidth < mViewHeight ? mViewWidth : mViewHeight)/32);
            //第三步
            mRoundRectangleView.setImageDrawable(roundRectangleDrawable);
    
    
            mRoundRectangleCenterCropView.setImageDrawable(roundRectangleDrawable);
        }
    
    }
    

    最终效果如下:

    矩形不良反应

    由上图可知,scaleType 为 fitCenter 的 ImageView 圆角正常显示,而 scaleType 为 centerCrop 的 ImageView 圆角未正常显示。

    原因其实很简单,因为当 ImageView scaleType 为 centerCrop 时,当 Drawable 尺寸比 ImageView 尺寸大时,Drawable 短边将缩小至与 ImageView 对应边相等,Drawable 长边根据相应的缩放系数进行缩放,之后将 Drawable 中间显示在 ImageView 中间;当 Drawable 尺寸比 ImageView 尺寸小时,Drawable 短边放大至与 ImageView 对应边相等,Drawable 长边根据相应的缩放系数进行缩放,之后将 Drawable 中间显示在 ImageView 中间。

    由上图中 scaleType 为 fitCenter 的 ImageView 显示效果可知,转换之后的 Drawable 尺寸比 ImageView 尺寸小,因此此时会将 Drawable 短边放大至与 ImageView 对应边相等、长边根据相应的缩放系数进行缩放。也就是说,不是 Drawable 未被转换成圆角,而是 Drawable 的圆角超出了 Drawable 所在 ImageView 的显示范围。

    验证这个结论到底正不正确其实也很简单,只用将 ImageView 的 scaleType 设置为能满足下面条件的:

    当 Drawable 尺寸比 ImageView 尺寸小时,Drawable 不进行任何处理,直接显示在 ImageView 中间。

    而 CENTER_INSIDE 刚好满足此条件。

    <?xml version="1.0" encoding="utf-8"?>
    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center_horizontal"
            android:orientation="vertical">
    
            <!--android:scaleType="fitCenter"-->
            <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/white"
                android:scaleType="fitCenter"
                android:src="@drawable/tiger" />
    
            <!--android:scaleType="centerInside"-->
            <ImageView
                android:id="@+id/rounded_bitmap_drawable_round_rectangle_center_crop"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/padding_small"
                android:background="@color/white"
                android:scaleType="centerInside"
                android:src="@drawable/tiger" />
    
        </LinearLayout>
    </ScrollView>
    

    最终效果如下:

    scaleType 改为 centerInside 之后

    将 ImageView 的 scaleType 设置为 centerInside 之后,第二个 ImageView 的圆角又回来了,成功验证上面的猜想。

    对 ImageView scaleType 属性不了解的人可以看我的另外一篇文章:

    这一次,彻底帮你搞明白 ImageView ScaleType

    5. 如何将 RoundedBitmapDrawable 封装成一个工具类?

    说了这么多了,想必大家都已经掌握如何自定义圆角头像了,接下来,我们将 RoundedBitmapDrawable 封装成一个工具类,这样在后面使用的时候,就可以直接拿来用了。

    首先,要能区分目标 Drawable 是圆形还是矩形,因此,有了用于区分 Drawable 形状的泛型类:

    public enum DrawableShape {
    
        CIRCLE,
        RECTANGLE
    
    }
    

    其次,要能自定义转换之后的 Drawable 的尺寸,另外,如果是矩形,还能定义矩形圆角的半径。于是有了下面这个类:

    public class RoundedBitmapDrawableUtils {
    
        public static 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;
        }
    
    }
    

    只能传 Bitmap?!局限是不是有点大?马上改一下:

    public class RoundedBitmapDrawableUtils {
    
        public static Drawable getRoundedDrawable(Context context, String pathName, DrawableShape drawableShape, float newWidth, float newHeight, float cornerRadius){
            return getRoundedDrawable(context, BitmapFactory.decodeFile(pathName), drawableShape, newWidth, newHeight, cornerRadius);
        }
    
        public static Drawable getRoundedDrawable(Context context, int id, DrawableShape drawableShape, float newWidth, float newHeight, float cornerRadius){
            return getRoundedDrawable(context, BitmapFactory.decodeResource(context.getResources(), id), drawableShape, newWidth, newHeight, cornerRadius);
        }
    
        public static Drawable getRoundedDrawable(Context context, InputStream is, DrawableShape drawableShape, float newWidth, float newHeight, float cornerRadius){
            return getRoundedDrawable(context, BitmapFactory.decodeStream(is), drawableShape, newWidth, newHeight, cornerRadius);
        }
    
        public static 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;
        }
    
    }
    

    赶紧试试?

    <?xml version="1.0" encoding="utf-8"?>
    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white">
    
        <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_circle_fit_center"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/padding_small"
                android:background="@color/white"
                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/white"
                android:scaleType="fitCenter"
                android:src="@drawable/tiger" />
    
        </LinearLayout>
    </ScrollView>
    
    public class RoundedBitmapDrawableTutorialActivity extends AppCompatActivity {
    
        private int mScreenWidth, mScreenHeight, mViewWidth, mViewHeight;
        private ImageView mCircleFitCenterView, mRoundRectangleView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_rounded_bitmap_drawable_tutorial);
            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(){
            mCircleFitCenterView = findViewById(R.id.rounded_bitmap_drawable_circle_fit_center);
            mRoundRectangleView = findViewById(R.id.rounded_bitmap_drawable_round_rectangle);
        }
    
        private void initData(){
            mCircleFitCenterView.setImageDrawable(RoundedBitmapDrawableUtils.getRoundedDrawable(this, BitmapFactory.decodeResource(getResources(), R.drawable.tiger), DrawableShape.CIRCLE, 0, 0, 0));
            mRoundRectangleView.setImageDrawable(RoundedBitmapDrawableUtils.getRoundedDrawable(this, BitmapFactory.decodeResource(getResources(), R.drawable.tiger), DrawableShape.RECTANGLE, mViewWidth, mViewHeight, (mViewWidth < mViewHeight ? mViewWidth : mViewHeight)/32));
        }
    
    }
    

    最终效果如下:

    封装后的工具类处理的效果图

    还在等什么,赶紧去试试吧。

    6.参考文献

    1. RoundedBitmapDrawable
    2. RoundedBitmapDrawableFactory
    3. 这一次,彻底帮你搞明白 ImageView ScaleType

    相关文章

      网友评论

          本文标题:看完这篇文章,我保证你也会用 RoundedBitmapDraw

          本文链接:https://www.haomeiwen.com/subject/uqiobqtx.html