看完這篇文章含友,我保證你也會用 RoundedBitmapDrawable 創(chuàng)建圓角頭像

1. 什么是 RoundedBitmapDrawable改执,它存在的意義是什么絮短?

RoundedBitmapDrawable 是 Android 版本 22.1.0 的時候加入的,它的主要作用是創(chuàng)建圓角的 Drawable喧锦。

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

Google 添加此類的原因可能是彌補 Android API 中沒有直接支持創(chuàng)建圓角 Drawable 的空缺吧读规。

2. 怎么用?

RoundedBitmapDrawable 不僅可以創(chuàng)建圓角的 Drawable燃少,還可以創(chuàng)建圓角矩形的 Drawable束亏,接下來,我們就針對這兩種情況分別討論阵具。

2.1 圓形 Drawable

2.1.1 概述

創(chuàng)建和應用 RoundedBitmapDrawable 一共分三步:

  1. 創(chuàng)建 RoundedBitmapDrawable 對象碍遍;
  2. 設置 RoundedBitmapDrawable 為圓形;
  3. 將 RoundedBitmapDrawable 設置到 ImageView 上阳液。
2.1.2 詳述
  1. 創(chuàng)建 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 概述

創(chuàng)建和應用 RoundedBitmapDrawable 一共分三步:

  1. 創(chuàng)建 RoundedBitmapDrawable 對象怕敬;
  2. 為 RoundedBitmapDrawable 設置圓角的半徑;
  3. 將 RoundedBitmapDrawable 設置到 ImageView 上帘皿。
2.2.2 詳述
  1. 創(chuàng)建 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 實現(xiàn)原理是什么?

知道了怎么用 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);
    }
}

不難發(fā)現(xiàn)桑驱,DefaultRoundedBitmapDrawable 繼承至 RoundedBitmapDrawable,并且 DefaultRoundedBitmapDrawable 構造方法實際上調(diào)用的是 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,今天主角終于出現(xiàn)了
        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橡伞。其實到這里就可以結(jié)束了,因為 RoundedBitmapDrawable 實現(xiàn)圓角的方式已經(jīng)很明了了——通過給 Paint 設置 BitmapShader晋被。當然兑徘,這是對于那些已經(jīng)熟練掌握自定義控件的人說的,如果你對自定義控件不熟悉羡洛,那就接著往下看吧挂脑。

  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 方法中,首先調(diào)用了 updateDstRect() 方法欲侮,然后再根據(jù) 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;
    }

}

注釋已經(jīng)寫的很清楚了氓奈,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 為圓形時桑包,計算出來的矩形(確切地說,應該是正方形)圓角的半徑哑了。剩下的另外兩個步驟已經(jīng)在代碼中注釋的很明顯了,我就不贅述了拆火。

  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蚯窥,否則出現(xiàn)“不良反應”

4.1 RoundedBitmapDrawable 引用的圖片資源,須為正方形矛紫,否則會被強制壓縮

其實在分析 RoundedBitmapDrawable 實現(xiàn)原理的時候,就說過,如果 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 處理方法:根據(jù)傳入的 Bitmap 的短邊將 Bitmap 轉(zhuǎn)換成正方形
//根據(jù)傳入的 Bitmap 的短邊將 Bitmap 轉(zhuǎn)換成正方形
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 轉(zhuǎn)換成正方形
circleBitmap = BitmapUtils.transferToSquareBitmap(circleBitmap)轧膘;
RoundedBitmapDrawable circleDrawable = RoundedBitmapDrawableFactory.create(getResources(), circleBitmap);
//第二步
circleDrawable.setCircular(true);
//第三步
mCircleFitCenterView.setImageDrawable(circleDrawable);

最終效果如下:

Bitmap 轉(zhuǎn)換成正方形

4.2 ImageView 的 scaleType 須為 fitCenter,否則出現(xiàn)“不良反應”

接下來分別對下面四種情況拯啦,展開討論:

  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 為圓形時未出現(xiàn)不良反應

由于此時二者效果一樣,所以,我就把圖拉到了二者的中間趁餐,因此吠各,大家看到的是兩個老虎的半張臉勉抓。

結(jié)論:當 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 長邊根據(jù)相應的縮放系數(shù)進行縮放,之后將 Drawable 中間顯示在 ImageView 中間减途;當 Drawable 尺寸比 ImageView 尺寸小時酣藻,Drawable 短邊放大至與 ImageView 對應邊相等,Drawable 長邊根據(jù)相應的縮放系數(shù)進行縮放,之后將 Drawable 中間顯示在 ImageView 中間诀紊。

由上圖中 scaleType 為 fitCenter 的 ImageView 顯示效果可知莉测,轉(zhuǎn)換之后的 Drawable 尺寸比 ImageView 尺寸小子姜,因此此時會將 Drawable 短邊放大至與 ImageView 對應邊相等凫佛、長邊根據(jù)相應的縮放系數(shù)進行縮放碧磅。也就是說换况,不是 Drawable 未被轉(zhuǎn)換成圓角,而是 Drawable 的圓角超出了 Drawable 所在 ImageView 的顯示范圍徙硅。

驗證這個結(jié)論到底正不正確其實也很簡單阳懂,只用將 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 封裝成一個工具類?

說了這么多了雌澄,想必大家都已經(jīng)掌握如何自定義圓角頭像了斋泄,接下來,我們將 RoundedBitmapDrawable 封裝成一個工具類镐牺,這樣在后面使用的時候炫掐,就可以直接拿來用了。

首先任柜,要能區(qū)分目標 Drawable 是圓形還是矩形卒废,因此,有了用于區(qū)分 Drawable 形狀的泛型類:

public enum DrawableShape {

    CIRCLE,
    RECTANGLE

}

其次宙地,要能自定義轉(zhuǎn)換之后的 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
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末钞诡,一起剝皮案震驚了整個濱河市郑现,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌荧降,老刑警劉巖接箫,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異朵诫,居然都是意外死亡辛友,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門剪返,熙熙樓的掌柜王于貴愁眉苦臉地迎上來废累,“玉大人,你說我怎么就攤上這事脱盲∫乇酰” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵宾毒,是天一觀的道長驼修。 經(jīng)常有香客問我,道長诈铛,這世上最難降的妖魔是什么乙各? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮幢竹,結(jié)果婚禮上耳峦,老公的妹妹穿的比我還像新娘。我一直安慰自己焕毫,他們只是感情好蹲坷,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著邑飒,像睡著了一般循签。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上疙咸,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天县匠,我揣著相機與錄音,去河邊找鬼。 笑死乞旦,一個胖子當著我的面吹牛贼穆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播兰粉,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼故痊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了玖姑?” 一聲冷哼從身側(cè)響起愕秫,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎客峭,沒想到半個月后豫领,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡舔琅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年等恐,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片备蚓。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡课蔬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出郊尝,到底是詐尸還是另有隱情二跋,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布流昏,位于F島的核電站扎即,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏况凉。R本人自食惡果不足惜谚鄙,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望刁绒。 院中可真熱鬧闷营,春花似錦、人聲如沸知市。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嫂丙。三九已至娘赴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間跟啤,已是汗流浹背筝闹。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工媳叨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留腥光,地道東北人关顷。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像武福,于是被迫代替她去往敵國和親议双。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容