自定義是否帶邊框圓形和圓角矩形的ImageView

自定義一個支持圓形预吆,圓角矩形的ImageView龙填,并且可以設置相應的邊框的ImageView,支持設置邊框寬度拐叉,設置圓角的半徑岩遗。效果圖如下:


image.png

通過繼承ImageView,然后重寫ImageView的onDraw()的對樣式進行擴展和改變凤瘦。
這里使用的是BitmapShader對圖像進行裁剪宿礁,其中著色器的TileMode有三種形式:

CLAMP:Bitmap以其內容的最后一行像素填充的高的空白部分或者一列像素填充寬空白部分。
MIRROR :Bitmap以其中內容精選的方式填充剩余空白蔬芥。
REPEAT: Bitmap以其內容以重復的方式填充剩余空白梆靖。

接下來直接貼上完整的代碼,代碼中有關鍵代碼的注釋笔诵,或者可以參考下方的參考鏈接:

package com.example.hx.cirimageviewlearn;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;

import androidx.annotation.Nullable;


@SuppressLint("AppCompatCustomView")
public class CircleImageView extends ImageView {
    private static final int RECT = 1;
    private static final int CIRCLE = 2;
    private Paint mPaint;
    private Bitmap mRawBitmap;
    private BitmapShader mShader;
    private Matrix matrix;
    private Paint mBorderPaint;

    private RectF mRectBorder;
    private RectF mRectBitmap;

    //邊框的寬度
    private int mBorderWidth;

    //邊框的顏色
    private int mBorderColor;

    //是否設置邊框返吻,默認不設置 false
    private boolean createBorder;

    /**
     * 0 ClAMP : Bitmap以其內容的最后一行像素填充剩余的高的空白或者最后一列像素填充剩余寬空白
     * 1 MIRROR :Bitmap以其內容以鏡像的方式填充剩余空白
     * 2 REPEAT :Bitmap以其內容以重復的方式填充剩余空白
     */
    private int mTileY = 0;

    private int mTileX = 0;

    //邊角得半徑
    private int mRoundRadius;


    /**
     * circle 圓形 默認
     * rect 帶圓角得形狀
     */
    private int mShapeType;


    public CircleImageView(Context context) {
        this(context, null);
    }

    public CircleImageView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircleImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public CircleImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
        //獲取自定以的屬性
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView);
        mTileX = array.getInt(R.styleable.CircleImageView_mTileX, 0);
        mTileY = array.getInt(R.styleable.CircleImageView_mTileY, 0);
        mBorderColor = array.getColor(R.styleable.CircleImageView_mBorderColor, 0xFF0080FF);
        mBorderWidth = array.getDimensionPixelOffset(R.styleable.CircleImageView_mBorderWidth, 4);
        createBorder = array.getBoolean(R.styleable.CircleImageView_createBorder, false);
        mShapeType = array.getInt(R.styleable.CircleImageView_mShapeType, CIRCLE);
        mRoundRadius = array.getDimensionPixelOffset(R.styleable.CircleImageView_mRoundRadius, 10);
        array.recycle();
    }
    /**
     * 在onDraw中不要有過多復雜的邏輯,和過于復雜多余的計算乎婿,否則會導致繪制不全的現象
     *在onDraw方法中如果一個值是多次使用的测僵,就通過變量先計算好,不要每次用的時候才計算谢翎,影響計算的效率
     *
     * @param canvas
     */

    @Override
    protected void onDraw(Canvas canvas) {
        //獲取設置圖片的Bitmap對象捍靠。
        Bitmap bitmap = getBitmap(getDrawable());
        if (bitmap != null) {
            //支持Padding的屬性
            final int paddingLeft = getPaddingLeft();
            final int paddingRight = getPaddingRight();
            final int paddingTop = getPaddingTop();
            final int paddingBottom = getPaddingBottom();
            //通過減去padding的屬性值,獲得圖片正真的高度
            float width = getWidth() - paddingLeft - paddingRight;
            float height = getHeight() - paddingTop - paddingBottom;
            float diameter = Math.min(width, height);
            //如果是矩形森逮,則直接是寬高剂公,是圓形為半徑
            float dstWidth = mShapeType == RECT ? width : diameter;
            float dstHeight = mShapeType == RECT ? height : diameter;
            float doubleBorderWidth = mBorderWidth * 2.0f;
            float halfBorderWidth = mBorderWidth / 2.0f;
            //判斷是否已經創(chuàng)建,或者復用
            if (mShader == null || !bitmap.equals(mRawBitmap)) {
                mRawBitmap = bitmap;
                mShader = createBitmapShader(mRawBitmap, mTileX, mTileY);
            }
            if (mShader != null) {
                //設置縮放比例吊宋,縮放的比例應該是正式的寬度與原來的寬度的比纲辽。而寬度和高度還需要減去邊框的寬度*2
                matrix.setScale((dstWidth - doubleBorderWidth) / mRawBitmap.getWidth(), (dstHeight - doubleBorderWidth) / mRawBitmap.getHeight());
                //為著色器色器設置矩陣
                mShader.setLocalMatrix(matrix);
            }
            //畫筆設置著色器
            mPaint.setShader(mShader);
            //如果設置了邊框的颜武,對邊框的畫筆進行設置
            if (createBorder) {
                //設置的樣式是邊框
                mBorderPaint.setStyle(Paint.Style.STROKE);
                //邊框的寬度
                mBorderPaint.setStrokeWidth(mBorderWidth);
                //如果是不設置邊框得 使邊框得畫筆變?yōu)橥该?                mBorderPaint.setColor(createBorder ? mBorderColor : Color.TRANSPARENT);
            }
            if (mShapeType == RECT) {
                //畫矩形
                createRoundRect(canvas, width, height, doubleBorderWidth, halfBorderWidth);
            } else {
                //畫圓
                createCircle(canvas, diameter / 2.0f, halfBorderWidth);
            }
        } else {
            super.onDraw(canvas);
        }
    }

    /**
     * 畫矩形
     * @param canvas 畫布
     * @param width  圖形的寬度
     * @param height 圖形的高度
     * @param doubleBorderWidth  邊框的寬度*2
     * @param halfBorderWidth 邊框的寬度/2
     */
    private void createRoundRect(Canvas canvas, float width, float height, float doubleBorderWidth, float halfBorderWidth) {
        //邊框的矩形,至于為什么要減去一半的原因是
        // 繪制帶邊框的矩形(其他形狀同理)拖吼,矩形的邊界是邊框的中心鳞上,而不是邊框的邊界,
        // 所以在繪制這些帶邊框的形狀時吊档,需要減去邊框寬度的一半
        mRectBorder.set(halfBorderWidth, halfBorderWidth, width - halfBorderWidth, height - halfBorderWidth);
        //圖形的矩形
        mRectBitmap.set(0.0f, 0.0f, width - doubleBorderWidth, height - doubleBorderWidth);
        float bitmapRadius = Math.max((mRoundRadius - mBorderWidth), 0.0f);
        if (createBorder) {
            float rectRadius = Math.max(mRoundRadius - halfBorderWidth, 0.0f);
            //畫邊邊框
            canvas.drawRoundRect(mRectBorder, rectRadius, rectRadius, mBorderPaint);
            //畫布平移
            canvas.translate(mBorderWidth, mBorderWidth);
        }
        //畫圖像得
        canvas.drawRoundRect(mRectBitmap, bitmapRadius, bitmapRadius, mPaint);
    }

    /**
     * 畫圓形
     * @param canvas
     * @param radius
     * @param halfBorderWidth
     */
    private void createCircle(Canvas canvas, float radius, float halfBorderWidth) {
        //圖形正真的半徑還要減去邊框的寬度
        float realRadius = radius - mBorderWidth;
        if (createBorder) {
            //畫邊框篙议,畫邊框的半徑一定要減去邊框的一邊
            canvas.drawCircle(radius, radius, radius - halfBorderWidth, mBorderPaint);
            //平移畫布
            canvas.translate(mBorderWidth, mBorderWidth);
        }
        canvas.drawCircle(realRadius, realRadius, realRadius, mPaint);
    }


    private void init() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setAntiAlias(true);//設置抗鋸齒
        //該方法千萬別放到onDraw()方法里面調用,否則會不停的重繪的怠硼,因為該方法調用了invalidate() 方法
        //View Layer 繪制所消耗的實際時間是比不使用 View Layer 時要高的鬼贱,所以要慎重使用。所以我們將View Layer關閉
        //否則會出現黑色背景的現象
        setLayerType(View.LAYER_TYPE_NONE, null);
        matrix = new Matrix();
        mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mBorderPaint.setAntiAlias(true);
        mRectBitmap = new RectF();
        mRectBorder = new RectF();
    }

    /**
     * 根據不同的類型獲取Bitmap
     *
     * @param drawable
     * @return
     */
    public Bitmap getBitmap(Drawable drawable) {
        //如果是圖片類型則直接返回
        if (drawable instanceof BitmapDrawable) {
            return ((BitmapDrawable) drawable).getBitmap();
        } else if (drawable instanceof ColorDrawable) {
            //顏色類型
            Rect rect = drawable.getBounds();
            int width = rect.right - rect.left;
            int height = rect.bottom - rect.top;
            int color = ((ColorDrawable) drawable).getColor();
            Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            canvas.drawARGB(Color.alpha(color), Color.red(color), Color.green(color), Color.blue(color));
            return bitmap;
        } else {
            return null;
        }
    }

    /**
     * 根據不同的mTileX香璃,mTileY創(chuàng)建BitmapShader
     *
     * @param mTileX
     * @param mTileY
     * @return
     */
    public BitmapShader createBitmapShader(Bitmap bitmap, int mTileX, int mTileY) {
        BitmapShader.TileMode tileModeX;
        BitmapShader.TileMode tileModeY;
        switch (mTileX) {
            case 1:
                tileModeX = BitmapShader.TileMode.MIRROR;
                break;
            case 2:
                tileModeX = BitmapShader.TileMode.REPEAT;
                break;
            default:
                tileModeX = BitmapShader.TileMode.CLAMP;
        }

        switch (mTileY) {
            case 1:
                tileModeY = BitmapShader.TileMode.MIRROR;
                break;
            case 2:
                tileModeY = BitmapShader.TileMode.REPEAT;
                break;
            default:
                tileModeY = BitmapShader.TileMode.CLAMP;
                break;
        }
        return new BitmapShader(bitmap, tileModeX, tileModeY);

    }

    public int getTileX() {
        return mTileX;
    }

    public void setTileX(int mTileX) {
        this.mTileX = mTileX;
    }

    public int getTileY() {
        return mTileY;
    }

    public void setTileY(int mTileY) {
        this.mTileY = mTileY;
    }

    public Paint getmPaint() {
        return mPaint;
    }

    public void setmPaint(Paint mPaint) {
        this.mPaint = mPaint;
    }

    public Bitmap getmRawBitmap() {
        return mRawBitmap;
    }

    public void setmRawBitmap(Bitmap mRawBitmap) {
        this.mRawBitmap = mRawBitmap;
    }

    public BitmapShader getmShader() {
        return mShader;
    }

    public void setmShader(BitmapShader mShader) {
        this.mShader = mShader;
    }

    @Override
    public Matrix getMatrix() {
        return matrix;
    }

    public void setMatrix(Matrix matrix) {
        this.matrix = matrix;
    }

    public Paint getmBorderPaint() {
        return mBorderPaint;
    }

    public void setmBorderPaint(Paint mBorderPaint) {
        this.mBorderPaint = mBorderPaint;
    }

    public RectF getmRectBorder() {
        return mRectBorder;
    }

    public void setmRectBorder(RectF mRectBorder) {
        this.mRectBorder = mRectBorder;
    }

    public RectF getmRectBitmap() {
        return mRectBitmap;
    }

    public void setmRectBitmap(RectF mRectBitmap) {
        this.mRectBitmap = mRectBitmap;
    }

    public int getmBorderWidth() {
        return mBorderWidth;
    }

    public void setmBorderWidth(int mBorderWidth) {
        this.mBorderWidth = mBorderWidth;
    }

    public int getmBorderColor() {
        return mBorderColor;
    }

    public void setmBorderColor(int mBorderColor) {
        this.mBorderColor = mBorderColor;
    }

    public boolean isCreateBorder() {
        return createBorder;
    }

    public void setCreateBorder(boolean createBorder) {
        this.createBorder = createBorder;
    }

    public int getmRoundRadius() {
        return mRoundRadius;
    }

    public void setmRoundRadius(int mRoundRadius) {
        this.mRoundRadius = mRoundRadius;
    }

    public int getmShapeType() {
        return mShapeType;
    }

    public void setmShapeType(int mShapeType) {
        this.mShapeType = mShapeType;
    }
}

自定以的屬性这难,在res/values的目錄下新建一個attrs.xml文件,設置相關的屬性:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!-- CircleImageView的屬性 -->
    <declare-styleable name="CircleImageView">
        <attr name="mTileX" />
        <attr name="mTileY" />
        <attr name="createBorder" format="boolean" />
        <attr name="mBorderColor" format="color" />
        <attr name="mBorderWidth" format="dimension" />
        <attr name="mShapeType" />
        <attr name="mRoundRadius" format="dimension" />
    </declare-styleable>
    <attr name="mShapeType">
        <enum name="rounded_rect" value="1" />
        <enum name="circle" value="2" />
    </attr>
    <attr name="mTileX">
        <enum name="clamp" value="0" />
        <enum name="mirror" value="1" />
        <enum name="repeat" value="2" />
    </attr>
    <attr name="mTileY">
        <enum name="clamp" value="0" />
        <enum name="mirror" value="1" />
        <enum name="repeat" value="2" />
    </attr>
</resources>

最后使用:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <com.example.hx.cirimageviewlearn.CircleImageView
        android:id="@+id/image1"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_marginStart="60dp"
        android:src="@drawable/image"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:mShapeType="circle"
        tools:ignore="MissingConstraints" />

    <com.example.hx.cirimageviewlearn.CircleImageView
        android:id="@+id/image2"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_marginEnd="52dp"
        android:src="@drawable/image"
        app:createBorder="true"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:mBorderColor="@color/colorAccent"
        app:mBorderWidth="2dp"
        app:mShapeType="circle"
        tools:ignore="MissingConstraints" />

    <com.example.hx.cirimageviewlearn.CircleImageView
        android:id="@+id/image3"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_marginStart="60dp"
        android:layout_marginTop="96dp"
        android:src="@drawable/image"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/image1"
        app:mRoundRadius="10dp"
        app:mShapeType="rounded_rect"
        tools:ignore="MissingConstraints" />

    <com.example.hx.cirimageviewlearn.CircleImageView
        android:id="@+id/image4"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_marginTop="96dp"
        android:layout_marginEnd="52dp"
        android:src="@drawable/image"
        app:createBorder="true"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/image2"
        app:mBorderColor="@color/colorAccent"
        app:mBorderWidth="2dp"
        app:mRoundRadius="10dp"
        app:mShapeType="rounded_rect"
        tools:ignore="MissingConstraints" />


</androidx.constraintlayo

參考鏈接:https://www.cnblogs.com/snser/p/5159126.html
https://blog.csdn.net/bobo_zai/article/details/104653340

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末葡秒,一起剝皮案震驚了整個濱河市姻乓,隨后出現的幾起案子,更是在濱河造成了極大的恐慌眯牧,老刑警劉巖蹋岩,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異学少,居然都是意外死亡剪个,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門版确,熙熙樓的掌柜王于貴愁眉苦臉地迎上來禁偎,“玉大人,你說我怎么就攤上這事阀坏∪缗” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵忌堂,是天一觀的道長盒至。 經常有香客問我,道長士修,這世上最難降的妖魔是什么枷遂? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮棋嘲,結果婚禮上酒唉,老公的妹妹穿的比我還像新娘。我一直安慰自己沸移,他們只是感情好痪伦,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布侄榴。 她就那樣靜靜地躺著,像睡著了一般网沾。 火紅的嫁衣襯著肌膚如雪癞蚕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天辉哥,我揣著相機與錄音桦山,去河邊找鬼。 笑死醋旦,一個胖子當著我的面吹牛恒水,可吹牛的內容都是我干的。 我是一名探鬼主播饲齐,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼钉凌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了箩张?” 一聲冷哼從身側響起甩骏,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤窗市,失蹤者是張志新(化名)和其女友劉穎先慷,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體咨察,經...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡论熙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了摄狱。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脓诡。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖媒役,靈堂內的尸體忽然破棺而出祝谚,到底是詐尸還是另有隱情,我是刑警寧澤酣衷,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布交惯,位于F島的核電站,受9級特大地震影響穿仪,放射性物質發(fā)生泄漏席爽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一啊片、第九天 我趴在偏房一處隱蔽的房頂上張望只锻。 院中可真熱鬧,春花似錦紫谷、人聲如沸齐饮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沈矿。三九已至上真,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間羹膳,已是汗流浹背睡互。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留陵像,地道東北人就珠。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像醒颖,于是被迫代替她去往敵國和親妻怎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359