自定義一個支持圓形预吆,圓角矩形的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