這篇博客主要講解了Android實現圓形圖片的4種方式。android
Android中並無一個原生的控件,能夠顯示圓形或圓角圖片,所以須要咱們本身去定義這樣一個控件。canvas
實現圓形/圓角圖片的核心思想,就是按照必定形狀切割/繪製
咱們的原始控件,大概有如下4種方法:segmentfault
canvas.clipPath
方法,按照自定義的Path圖形去切割
控件canvas.setBitmapShader
,按照自定義的BitmapShader去從新繪製
控件view.setOutlineProvider/setClipToOutline
,按照自定義的Outline去切割
控件Transformation
變換,顯示圓形圖片關於ImageView的幾個知識點:app
AppCompatImageView
,而不是ImageView,尊重原創,轉載請註明出處 https://segmentfault.com/a/11...
本文出自 強哥大天才的博客框架
思路ide
咱們能夠定義一個圓形Path路徑,而後調用canvas.clipPath,將圖片
切割成圓形ui
缺陷this
可是這種方法有2個限制:url
setLayerType(View.LAYER_TYPE_SOFTWARE, null)
1.定義Radius屬性,用來設置圓角半徑code
注意事項:
dimension
,這是一個帶單位的值(float不帶單位)<declare-styleable name="RoundImageView"> <attr name="radius" format="dimension" /> </declare-styleable>
2.定義RoundImageView自定義圓形控件
注意事項
不支持硬件加速
,因此在使用前須要禁止硬件加速setLayerType(View.LAYER_TYPE_SOFTWARE, null)
super.onDraw方法前
,調用,不然無效(canvas已經被設置給View了)public class RoundImageView extends AppCompatImageView { private RectF mRect; private Path mPath; private float mRadius; public RoundImageView(Context context) { this(context, null); } public RoundImageView(Context context, AttributeSet attrs) { this(context, attrs, -1); } public RoundImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); getAttributes(context, attrs); initView(context); } /** * 獲取屬性 */ private void getAttributes(Context context, AttributeSet attrs) { TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RoundImageView); mRadius = ta.getDimension(R.styleable.RoundImageView_radius, -1); ta.recycle(); } /** * 初始化 */ private void initView(Context context) { mRect = new RectF(); mPath = new Path(); setLayerType(LAYER_TYPE_SOFTWARE, null); // 禁用硬件加速 } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (mRadius < 0) { clipCircle(w, h); } else { clipRoundRect(w, h); } } /** * 圓角 */ private void clipRoundRect(int width, int height) { mRect.left = 0; mRect.top = 0; mRect.right = width; mRect.bottom = height; mPath.addRoundRect(mRect, mRadius, mRadius, Path.Direction.CW); } /** * 圓形 */ private void clipCircle(int width, int height) { int radius = Math.min(width, height)/2; mPath.addCircle(width/2, height/2, radius, Path.Direction.CW); } @Override protected void onDraw(Canvas canvas) { canvas.clipPath(mPath); super.onDraw(canvas); } }
思路
經過Canvas.drawCircle本身去繪製一個圓形圖片,並設置給ImageView;
缺陷
這種方式有個限制,就是若是要定義一個圓角圖片,必須調用canvas.drawRoundRect進行繪製,可是這個方法要求API>=21
這裏,咱們能夠看到,ImageView底層顯示圖片的原理,就是利用Canvas將咱們的圖片資源給繪製到View控件上
1. 從圖片資源中,獲取Bitmap
Drawable轉Bitmap的2種方式
BitmapDrawable
中獲取drawable.draw(canvas)
,本身去繪製Bitmap注意事項:
src
和background
private void initBitmap() { Drawable drawable1 = getDrawable(); Drawable drawable2 = getBackground(); Drawable drawable = drawable1==null ? drawable2 : drawable1; // 不能在構造方法中獲取drawable,爲null if (drawable instanceof BitmapDrawable) { BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; mBitmap = bitmapDrawable.getBitmap(); } else { int width = drawable.getIntrinsicWidth(); // 圖片的原始寬度 int height = drawable.getIntrinsicHeight(); // 圖片的原始高度 mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(mBitmap); // drawable.setBounds(0,0,width,height); drawable.draw(canvas); } }
2. 根據Bitmap,建立着色器BitmapShader
BitmapShader
着色器
TileMode瓷磚類型:當Canvas的寬高大於Bitmap的尺寸時,採起的重複策略
TileMode.MIRROR
:圖片鏡像鋪開TileMode.REPEAT
:圖片重複鋪開TileMode.CLAMP
:複用最後一個像素點setLocalMatrix
:對着色器中的Bitmap進行矩陣變化private void initShader(Bitmap bitmap) { mShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); int bitmapWidth = bitmap.getWidth(); int bitmapHeight = bitmap.getHeight(); float sx = mWidth * 1.0f / bitmapWidth; float sy = mHeight * 1.0f / bitmapHeight; float scale = Math.max(sx, sy); Matrix matrix = new Matrix(); matrix.setScale(scale, scale); mShader.setLocalMatrix(matrix); }
3. 將着色器BitmapShader,設置給Paint
mPaint.setShader(mShader);
4. 利用Canvas,本身繪製圓形/圓角圖
注意點:
@Override protected void onDraw(Canvas canvas) { initPaint(); if (mRadius < 0) { float radius = Math.min(mWidth, mHeight) / 2; canvas.drawCircle(mWidth/2, mHeight/2, radius, mPaint); } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // 21及其以上 canvas.drawRoundRect(0, 0, mWidth, mHeight, mRadius, mRadius, mPaint); } else { super.onDraw(canvas); } } // super.onDraw(canvas); }
完整代碼
public class RoundImageView2 extends AppCompatImageView { private int mWidth; private int mHeight; private float mRadius; private Paint mPaint; private Bitmap mBitmap; private BitmapShader mShader; public RoundImageView2(Context context) { this(context, null); } public RoundImageView2(Context context, AttributeSet attrs) { this(context, attrs, -1); } public RoundImageView2(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); getAttributes(context, attrs); initView(context); } private void getAttributes(Context context, AttributeSet attrs) { TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RoundImageView); mRadius = ta.getDimension(R.styleable.RoundImageView_radius, -1); ta.recycle(); } private void initView(Context context) { mPaint = new Paint(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; } @Override protected void onDraw(Canvas canvas) { initPaint(); if (mRadius < 0) { float radius = Math.min(mWidth, mHeight) / 2; canvas.drawCircle(mWidth/2, mHeight/2, radius, mPaint); } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // 21及其以上 canvas.drawRoundRect(0, 0, mWidth, mHeight, mRadius, mRadius, mPaint); } else { super.onDraw(canvas); } } // super.onDraw(canvas); } /** * 設置畫筆 */ private void initPaint() { initBitmap(); initShader(mBitmap); mPaint.setShader(mShader); } /** * 獲取Bitmap */ private void initBitmap() { Drawable drawable1 = getDrawable(); Drawable drawable2 = getBackground(); Drawable drawable = drawable1==null ? drawable2 : drawable1; // 不能在構造方法中獲取drawable,爲null if (drawable instanceof BitmapDrawable) { BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; mBitmap = bitmapDrawable.getBitmap(); } else { int width = drawable.getIntrinsicWidth(); int height = drawable.getIntrinsicHeight(); mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(mBitmap); // drawable.setBounds(0,0,width,height); drawable.draw(canvas); } } /** * 獲取BitmapShader */ private void initShader(Bitmap bitmap) { mShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); int bitmapWidth = bitmap.getWidth(); int bitmapHeight = bitmap.getHeight(); float sx = mWidth * 1.0f / bitmapWidth; float sy = mHeight * 1.0f / bitmapHeight; float scale = Math.max(sx, sy); Matrix matrix = new Matrix(); matrix.setScale(scale, scale); mShader.setLocalMatrix(matrix); } }
思路
經過view.setOutlineProvider,給咱們的View控件設置一個圓形輪廓,而後讓View根據輪廓提供者進行切割
這個方法不一樣於前2種,前面2種方法,都是針對Canvas作文章,所以只能適用於圖片的圓形處理;而這個方法是實實在在的對View進行了切割,不只僅侷限於圖片,還能夠針對任何其餘View控件進行剪裁,
適用範圍更廣
(好比咱們能夠將整個頁面變成一個圓形顯示)
缺陷
可是這個方法有個限制,就是OutlineProvider只能適用於API>=21的版本,沒法兼容低版本
OutlineProvider輪廓提供者
OutlineProvider
輪廓提供者,能夠給View提供一個外輪廓,而且讓其根據輪廓進行剪切
view.setOutlineProvider
:設置輪廓提供者view.setClipToOutline
:根據輪廓進行剪切outline.setOval
:畫一個圓形輪廓outline.setRect
:畫一個矩形輪廓注意事項:
getOutline
方法,其中參數Outline
,就是提供給View的輪廓,咱們能夠根據須要自定義形狀完整代碼
public class RoundImageView3 extends AppCompatImageView { private float mRadius; public RoundImageView3(Context context) { this(context, null); } public RoundImageView3(Context context, AttributeSet attrs) { this(context, attrs, -1); } public RoundImageView3(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); getAttributes(context, attrs); initView(); } private void getAttributes(Context context, AttributeSet attrs) { TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RoundImageView); mRadius = ta.getDimension(R.styleable.RoundImageView_radius, -1); ta.recycle(); } private void initView() { if (android.os.Build.VERSION.SDK_INT >= 21) { ViewOutlineProvider outlineProvider = new ViewOutlineProvider(){ @Override public void getOutline(View view, Outline outline) { int width = view.getWidth(); int height = view.getHeight(); if (mRadius < 0) { int radius = Math.min(width, height) / 2; Rect rect = new Rect(width/2-radius, height/2-radius, width/2+radius, height/2+radius); outline.setOval(rect); // API>=21 } else { Rect rect = new Rect(0, 0, width, height); outline.setRoundRect(rect, mRadius); } } }; setClipToOutline(true); setOutlineProvider(outlineProvider); } } }
思路
經過Glide圖片加載框架實現,咱們只須要給RequestOptions
添加一個CircleCrop
變換,便可實現圓形圖片效果;若是要實現圓角圖片,則須要本身去定義一個BitmapTransformation
缺陷
沒有缺陷
1. Glide實現圓形圖片
Glide內置了不少針對圖形的Transformation變換,咱們能夠藉助其中的CircleCrop選項很是方便的實現圓形圖片的效果。
RequestOptions
選項CircleCrop
變換apply
,將RequestOptions設置給Glide的RequestBuilder
下面2種方式,均可以實現圓形圖片的效果,只是寫法不同:
public static void loadCircleImage1(Context context, String url, ImageView imageView) { Glide.with(context) .load(url) .apply(RequestOptions.circleCropTransform()) .into(imageView); } public static void loadCircleImage2(Context context, String url, ImageView imageView) { RequestOptions options = new RequestOptions() .circleCrop(); Glide.with(context) .load(url) .apply(options) .into(imageView); }
2. Glide顯示圓角圖片
Glide並無像提供CircleCrop那樣,提供一個圓角圖片的Transformation,所以若是須要顯示圓角圖片,那麼就須要本身去定義一個Transformation。
那麼,要怎麼去定義一個Transformation呢?咱們能夠參考Circrop的作法:
BitmapTransformation
transform
、updateDiskCacheKey
、equals
、hashCode
方法transform:實現變化的具體細節
BitmapPool
:能夠用來快速的獲取一個Bitmap的資源池,而且一般要在方法中返回這個獲取到的BitmaptoTransform
:須要變化的Bitmap原始資源;須要注意的是,這個原始資源並非最初的Bitmap,在調用這個方法以前Glide已經將原始Bitmap進行了合適的縮放outWidth
、outHeight
:Bitmap的理想尺寸;須要注意的是,這個尺寸並非Bitmap的尺寸,也不是ImageView的尺寸,Glide給咱們返回的這個尺寸是ImageView的最小寬高值(若是ImageView的寬高都是match_parent,那麼返回的是ImageView的最大寬高值)CircleCrop的源碼
public class CircleCrop extends BitmapTransformation { // The version of this transformation, incremented to correct an error in a previous version. // See #455. private static final int VERSION = 1; private static final String ID = "com.bumptech.glide.load.resource.bitmap.CircleCrop." + VERSION; private static final byte[] ID_BYTES = ID.getBytes(CHARSET); public CircleCrop() { // Intentionally empty. } /** * @deprecated Use {@link #CircleCrop()}. */ @Deprecated public CircleCrop(@SuppressWarnings("unused") Context context) { this(); } /** * @deprecated Use {@link #CircleCrop()} */ @Deprecated public CircleCrop(@SuppressWarnings("unused") BitmapPool bitmapPool) { this(); } // Bitmap doesn't implement equals, so == and .equals are equivalent here. @SuppressWarnings("PMD.CompareObjectsWithEquals") @Override protected Bitmap transform( @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) { return TransformationUtils.circleCrop(pool, toTransform, outWidth, outHeight); } @Override public boolean equals(Object o) { return o instanceof CircleCrop; } @Override public int hashCode() { return ID.hashCode(); } @Override public void updateDiskCacheKey(MessageDigest messageDigest) { messageDigest.update(ID_BYTES); } }
自定義的一個圓角BitmapTransformation
這個實現細節,與前面的「利用BitmapShader繪製一個圓角圖片」基本是同樣的。
public class GlideRoundRect extends BitmapTransformation { private float mRadius; private static final int VERSION = 1; private static final String ID = BuildConfig.APPLICATION_ID + ".GlideRoundRect." + VERSION; private static final byte[] ID_BYTES = ID.getBytes(CHARSET); @Override public void updateDiskCacheKey(MessageDigest messageDigest) { messageDigest.update(ID_BYTES); } @Override public boolean equals(Object o) { return o instanceof GlideRoundRect; } @Override public int hashCode() { return ID.hashCode(); } public GlideRoundRect(float radius) { super(); mRadius = radius; } @Override protected Bitmap transform(@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) { return roundRectCrop(pool, toTransform); } private Bitmap roundRectCrop(BitmapPool pool, Bitmap source) { if (source == null) return null; // 1. 根據source,建立一個BitmapShader BitmapShader bitmapShader = new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); Paint paint = new Paint(); paint.setShader(bitmapShader); // 2. 獲取一個新的Bitmap int sourceWidth = source.getWidth(); int sourceHeight = source.getHeight(); Bitmap bitmap = pool.get(sourceWidth, sourceHeight, Bitmap.Config.ARGB_8888); // 3. 給新的Bitmap附上圖形 Canvas canvas = new Canvas(bitmap); RectF rect = new RectF(0, 0, sourceWidth, sourceHeight); canvas.drawRoundRect(rect, mRadius, mRadius, paint); // 4. 返回Bitmap return bitmap; } }