Fresco 圖片圓角實現原理及 Android 中圖片圓角實現方法
上篇文章 介紹了 Fresco 基礎使用和實現圖片圓角的方法,可以通過兩種方式來實現圓角:BITMAP_ONLY 模式和 OVERLAY_COLOR 模式。本文通過分析 Fresco 原始碼來介紹這兩種方式實現圓角的原理,並總結 Android 中常用的實現圖片圓角的方法。
本文重點分析 Fresco 中實現圖片圓角的原始碼,其他部分的原始碼,將在後續文章中介紹。
Fresco 中圓角實現原理
在 com.facebook.drawee.drawable 包中有如下檔案
- Rounded.java
- RoundedBitmapDrawable.java
- RoundedColorDrawable.java
- RoundedCornersDrawable.java
其中 Rounded 是圓角實現類的介面,定義了圓角類實現的方法:
public interface Rounded {
void setCircle(boolean isCircle);
void setRadius(float radius);
void setRadii(float[] radii);
void setBorder(int color, float width);
}
其他三個類實現了 Rounded 介面,來實現兩種不同模式的圓角,RoundedCornersDrawable.java 用於實現 OVERLAY_COLOR 模式的圓角,而 RoundedBitmapDrawable.java 和 RoundedCorlorDrawable.java 都是用於實現 BITMAP_ONLY 模式的圓角,兩者的區別在於傳入的資源型別不同,前者是對 BitmapDrawable 進行圓角處理,而後者是對 ColorDrawable 進行處理。
瞭解了原始碼中實現圖片圓角的結構,下面開始進入到具體的程式碼中瞭解具體的實現過程
BITMAP_ONLY 模式
作為預設的實現模式,首先來了解下這種模式的實現過程
進入到 RoundedBitmapDrawable.java 中,首先看它的繪製過程,找到 draw() 方法:
@Override
public void draw(Canvas canvas) {
updateTransform();//更新圖片變換矩陣
updateNonzero();//更新 0 值,判斷有沒有 設定圓形,圓角,邊框等屬性
if (!mIsNonzero) {//如果沒有設定以上屬性,則 mIsNonzero 返回 false,直接呼叫父類的繪製
super.draw(canvas);
return;
}
updatePath();//更新 Path
updatePaint();//更新畫筆
int saveCount = canvas.save();//儲存畫布狀態
canvas.concat(mInverseTransform);//設定變換矩陣
canvas.drawPath(mPath, mPaint);//繪製 Path
if (mBorderWidth != 0) {//繪製邊框
mBorderPaint.setStrokeWidth(mBorderWidth);
mBorderPaint.setColor(DrawableUtils.multiplyColorAlpha(mBorderColor, mPaint.getAlpha()));
canvas.drawPath(mPath, mBorderPaint);
}
canvas.restoreToCount(saveCount);//合併影象
}
從 draw() 方法可以瞭解到圓角圖片的繪製過程:
- 更新變換矩陣,用於圖片大小縮放適配
- 判斷有沒有設定屬性,如果沒有則直接繪製,如果有則進行下一步
- 更新 Path,根據屬性確定繪製的形狀
- 更新 Paint,將圖片資源填充到畫筆
- 繪製圖片,繪製邊框
本文的重點是瞭解圓角的實現過程,所以接下來就進入到 updatePath() 和 updatePaint() 中看看 Path 和 Paint 是怎樣實現圓角的
private void updatePath() {
if (mIsPathDirty) {//大概是說如果有對圖片進行設定
mPath.reset();//重置 Path
mRootBounds.inset(mBorderWidth/2, mBorderWidth/2);//矩形向內縮排半個邊框寬度,避免邊框遮擋圖片
if (mIsCircle) {//如果設定為圓形圖片,則 Path 設定為圓形,否則就設定為矩形
mPath.addCircle(
mRootBounds.centerX(),
mRootBounds.centerY(),
Math.min(mRootBounds.width(), mRootBounds.height())/2,
Path.Direction.CW);
} else {
mPath.addRoundRect(mRootBounds, mCornerRadii, Path.Direction.CW);
}
mRootBounds.inset(-(mBorderWidth/2), -(mBorderWidth/2));//Path 設定完成,恢復矩形
mPath.setFillType(Path.FillType.WINDING);
mIsPathDirty = false;
}
}
從上面的程式碼中可以大致瞭解到其主要是根據屬性值 (mIsCircle) 來配置 Path,主要使用到 Path 的兩個方法:addCircle() 和 addRoundRect(),這兩個方法分別實現繪製圓形和繪製矩形,其引數描述如下:
- addCircle(float x,float y,float radius,Direction dir)
- 圓心 x 座標
- 圓心 y 座標
- 圓的半徑
- 繪製圓的方向,值 Direction.CCW 為逆時針方向,值 Directin.CW 為順時針方向
- addRoundRect(RectF rect,float[] radii,Direction dir)
- 外接矩形
- 圓角半徑陣列,共 8 個值,每兩個為一對,順序為:左上 -> 右上 -> 右下 -> 左下
- 繪製圓的方向,同上
以上,便完成了 Path 的更新,接下來是對 Paint 的更新
private void updatePaint() {
Bitmap bitmap = getBitmap();//獲取需要繪製的 Bitmap
if (mLastBitmap == null || mLastBitmap.get() != bitmap) {//防止重複引用
mLastBitmap = new WeakReference<Bitmap>(bitmap);//新建一個弱引用 Bitmap 物件
mPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));//設定 Shader
mIsShaderTransformDirty = true;
}
if (mIsShaderTransformDirty) {//設定變換矩陣
mPaint.getShader().setLocalMatrix(mTransform);
mIsShaderTransformDirty = false;
}
}
從這裡大概清楚了這種方式是通過 BitmapShader 方式來實現圖片圓角的。於是這裡產生了一個疑問:既然可以直接使用 BitmapShader 來實現圓角,那如果接下來再直接使用 canvas 的 drawCircle() 和 drawRoundRect()也能實現圓形圖片和圓角圖片,為什麼還要多此一舉的使用 Path 來繪製圓形和圓角矩形呢? 我對此的理解是:canvas 的drawRoundRect() 沒有辦法實現四個不同大小的圓角,而通過 Path 的 addRoundRect() 方法是能夠實現不同圓角的圖片,使用Path是為了滿足這個需求。至於為什麼不能在 xml 佈局檔案中設定不同大小的圓角而只能在程式碼中設定這個問題,依然不明白,期待在後續的分析中能夠解決這個問題。
以上,是實現圖片圓角過程中主要的步驟,至於後面的繪製邊框,沒太大的難度,這裡就不再敘述。
OVERLAY_COLOR 模式
這種模式的實現在 RoundedCornersDrawable.java 檔案中,可以看到,其實這個類中還存在著另外一種模式:CLIPPING 模式,從官方文件中可以瞭解到不使用這個模式的原因,這裡就不再敘述。直接看使用 OVERLAY_COLOR 模式的程式碼實現,同樣的,首先找到繪製過程 draw() 方法:
@Override
public void draw(Canvas canvas) {
Rect bounds = getBounds();
switch (mType) {
case CLIPPING://暫不支援這種方式,跳過
// clip, note: doesn't support anti-aliasing
int saveCount = canvas.save();
mPath.setFillType(Path.FillType.EVEN_ODD);
canvas.clipPath(mPath);
super.draw(canvas);
canvas.restoreToCount(saveCount);
break;
case OVERLAY_COLOR:
super.draw(canvas);//首先讓父類繪製圖像
mPaint.setColor(mOverlayColor);//設定畫筆顏色
mPaint.setStyle(Paint.Style.FILL);//設定畫筆樣式為填充
mPath.setFillType(Path.FillType.INVERSE_EVEN_ODD);//設定 Path 的填充模式
canvas.drawPath(mPath, mPaint);//畫遮蓋圖層
if (mIsCircle) {//如果是圓形,則用 Canvas 畫一個圓形
// INVERSE_EVEN_ODD will only draw inverse circle within its bounding box, so we need to
// fill the rest manually if the bounds are not square.
float paddingH = (bounds.width() - bounds.height() + mBorderWidth) / 2f;
float paddingV = (bounds.height() - bounds.width() + mBorderWidth) / 2f;
if (paddingH > 0) {
canvas.drawRect(bounds.left, bounds.top, bounds.left + paddingH, bounds.bottom, mPaint);
canvas.drawRect(
bounds.right - paddingH,
bounds.top,
bounds.right,
bounds.bottom,
mPaint);
}
if (paddingV > 0) {
canvas.drawRect(bounds.left, bounds.top, bounds.right, bounds.top + paddingV, mPaint);
canvas.drawRect(
bounds.left,
bounds.bottom - paddingV,
bounds.right,
bounds.bottom,
mPaint);
}
}
break;
}
if (mBorderColor != Color.TRANSPARENT) {//畫邊框
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(mBorderColor);
mPaint.setStrokeWidth(mBorderWidth);
mPath.setFillType(Path.FillType.EVEN_ODD);
canvas.drawPath(mPath, mPaint);
}
}
從上面的程式碼中可以瞭解到這種方式的實現原理大致如下:
- 每次設定屬性的時候都更新一下 Path ,也就是根據屬性決定 Path 是畫圓形還是圓角矩形
- 將圖片按照正常的方式先畫出來(呼叫super.draw())
- 對Path設定填充模式為 INVERSE_EVEN_ODD,取 Path 未佔用的區域(佔用的是圓形或者圓角矩形區域)
- 畫出 Path,這樣未佔用的區域就是指定的背景色了,佔用區域就是圓角圖片
- 判斷如果是圓形,則再畫一個圓形,按照上述填充規則
- 最後如果有邊框就畫邊框,同樣的規則
下面通過一幅示意圖簡單描述一下這種方式的實現原理:
繪製過程大致是這樣,我認為只要理解了 Path 的填充模式,這個原理就很好理解的,關於 Path 的填充模式,這篇文章中有詳細介紹,可以參考一下。
以上,通過原始碼分析的方式理解了 Fresco 中圓角矩形的實現原理,在分析這些程式碼的時候我查閱了一些資料,包括 Drawable 類,Path 類,Paint 類等一些類的使用方法,然後結合原始碼,跟著思路慢慢得出結果,並實現了一個簡單的自定義 View 來驗證結果。效果如下,程式碼在 GitHub 上
Android 中實現圓角的方案
在 Android 中有很多方法能夠實現圓角矩形。根據現實生活中的經驗,要對一張圖片實現圓角,無非兩種方式,一種是剪出圓角,另一種是遮住圓角。因此,可以簡單的將實現圓角的方案分成兩類:
- 剪裁:從原始圖片中剪出一個圓角圖片
- 覆蓋:在原始圖片上遮蓋住圓角多餘的部分,剩下的可見部分就是圓角矩形了
基於這兩類方法,我大概總結了一些可以實現圓角矩形的方法,如下
方案一:將原始圖片中擷取的圓角矩形圖片放在一個新建的Bitmap中
這種方式大致是 剪裁 類的方式,主要程式碼如下:
public static Bitmap toRoundCorner(Bitmap bitmap, int pixels) {
Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
bitmap.getHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final int color = 0xff424242;
final Paint paint = new Paint();
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
final RectF rectF = new RectF(rect);
final float roundPx = pixels;
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);
return output;
}
可以看到,這個方法中新建了一個指定寬高的 Bitmap 物件,然後建立了一個相同大小的矩形,利用畫布繪製時指定圓角大小,這樣在畫布上就有了一個圓角矩形,然後設定畫筆的剪裁方式為 Mode.SRC_IN,將原圖新增到畫布上,就形成了一個圓角矩形的圖片。不推薦使用這種方式來實現圖片圓角,因為這種方式會對每一個要實現圓角的圖片生成一個新的 Bitmap 物件,將會增加記憶體消耗,在需要載入大量圖片的時候就會很可能引發記憶體洩漏。
方案二:通過 Xfermode 實現
這種方式是一種 覆蓋 類的方式,關於 Xfremode 有一張比較經典的示意圖可以很好的解釋他是做什麼的
使用 Xfremode 的具體實現就不多敘述,感興趣的可以參考這篇文章
方案三:通過對 ViewGroup 進行設定,使包裹在內部的圖片呈現圓角矩形
這種方式依然是一種 覆蓋 的方式,只不過不是對當前要顯示的圖片進行覆蓋,而是上升到父容器,對父容器進行設定,內部的圖片不做任何改變。依然不推薦這種方式,因為通過這種方式實現圓角圖片會增加布局的層級,在Android效能優化中有提到過儘量減少佈局的層次巢狀,因此這種方式僅作參考,下面是實現程式碼:
public class RoundRelativeLayout extends RelativeLayout {
private float radius;
private boolean isPathValid;
private Path mPath = new Path();
public RoundRelativeLayout(Context context) {
super(context);
}
public RoundRelativeLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RoundRelativeLayout);
radius = ta.getDimension(R.styleable.RoundRelativeLayout_radius, 0);
ta.recycle();
}
@Override
protected void dispatchDraw(Canvas canvas) {
canvas.clipPath(getRoundRectPath());
super.dispatchDraw(canvas);
}
@Override
public void draw(Canvas canvas) {
canvas.clipPath(getRoundRectPath());
super.draw(canvas);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int oldWidth = getMeasuredWidth();
int oldHeight = getMeasuredHeight();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int newWidth = getMeasuredWidth();
int newHeight = getMeasuredHeight();
if (newWidth != oldWidth || newHeight != oldHeight) {
isPathValid = false;
}
}
private Path getRoundRectPath() {
if (isPathValid) {
return mPath;
}
mPath.reset();
int width = getWidth();
int height = getHeight();
RectF bounds = new RectF(0, 0, width, height);
mPath.addRoundRect(bounds, radius, radius, Path.Direction.CW);
isPathValid = true;
return mPath;
}
}
在使用時,只需要將要實現圓角的圖片放在這個自定義Layout內部就行了:
<com.tc.view.RoundRelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:radius="20dp">
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@mipmap/img_test"/>
</com.tc.view.RoundRelativeLayout>
方案四:使用形狀 Shape 覆蓋
上層是一個圓角矩形的圓環形狀,覆蓋在下層要顯示的 ImageView 上,這是一種 覆蓋 的方式。作為了解,這種方式也不推薦,因為要使用兩個ImageView來顯示一張圖片,上層圓角矩形圓環形狀需要先定製,包括圓角大小,顏色等屬性,導致可定製性不強,使用不方便,僅作參考。實現程式碼如下:
首先在 /res/drawable 目錄下新建形狀 frame.xml :
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="#00ffffff" />
<corners android:radius="12dp" />
<stroke android:width="6dp" android:color="#ffffffff" />
</shape>
接著在使用時將兩個 ImageView 放在同一個 ViewGroup 中即可:
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="6dp"
android:src="@mipmap/img_test"/>
<ImageView
android:src="@drawable/frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
方案五:使用 Android 自帶的剪下方法
這種方式僅支援 API 21 及以上版本,具體使用方法如下:
首先在 res/drawable/ 目錄下建立 形狀檔案 round_corners.xml:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="10dp" />
</shape>
接著在使用時,只需要對 View 設定 background 屬性為這個 形狀檔案即可:
<ImageView
android:id="@+id/clip_img"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@mipmap/img_test"
android:background="@drawable/round_corners"/>
接著在 java 程式碼中 findView 並設定 ImageView.setClipToOutline(true);
ImageView clipImg= (ImageView) findViewById(R.id.clip_img);
clipImg.setClipToOutline(true);
方案六: .9.png 實現
通過新增一個.9.png 的背景來實現圖片的圓角,關於.9.png 圖的製作,可以參考網上的一些資料教程,難度不大,這裡只做介紹,感興趣的朋友可以嘗試一下。
以上,介紹了 Fresco 中實現圓角圖片的兩種方式,並總結了一些 Android 中實現圓角圖片的方案,文章中用到的程式碼放在 GitHub 上。水平有限,如有錯誤或疏漏,還望不吝賜教。
相關推薦
Fresco 圖片圓角實現原理及 Android 中圖片圓角實現方法
上篇文章 介紹了 Fresco 基礎使用和實現圖片圓角的方法,可以通過兩種方式來實現圓角:BITMAP_ONLY 模式和 OVERLAY_COLOR 模式。本文通過分析 Fresco 原始碼來介紹這兩種方式實現圓角的原理,並總結 Android 中常用的實現圖
jdk7中hashmap實現原理和jdk8中hashmap的改進方法總結
原文連結:http://blog.csdn.net/vking_wang/article/details/141665931. HashMap的資料結構資料結構中有陣列和連結串列來實現對資料的儲存,但這兩者基本上是兩個極端。 陣列陣列儲存區間是連續的,佔用記憶體嚴重
前端跨域之Jsonp實現原理及.Net下Jsonp的實現
ali action query localhost info col 分享圖片 ice head jsonp的本質是通過script標簽的src屬性請求到服務端,拿到到服務端返回的數據 ,因為src是可以跨域的。前端通過src發送跨域請求時在請求的url帶上回調函數,服務
Android中繪製圓角矩形圖片及任意形狀圖片
package com.example.phototest; import android.os.Bundle; import android.app.Activity; import android.graphics.Bitmap; import android.graphics.BitmapFactor
Android中三級快取實現原理及LruCache 原始碼分析
介紹 oom異常:大圖片導致 圖片的三級快取:記憶體、磁碟、網路 下面通過一張圖來了解下三級快取原理: 程式碼: public class Davince { //使用固定執行緒池優化 private static Exec
Android ListView動畫特效實現原理及源代碼
stat 每一個 應該 所有 ner haar .get tde pri Android 動畫分三種,當中屬性動畫為我們最經常使用動畫,且能滿足項目中開發差點兒所有需求,google官方包支持3.0+。我們能夠引用三方包nineoldandr
Android--四大元件之BroadCastReceiver(生命週期、實現原理及使用等)
####1. BroadCastReceiver是什麼? ####2. 廣播型別 ######1). 有序廣播 ######2). 無序廣播 ####3. 生命週期 ####4. 實現原理 ####5. 使用方法 ####6. 許可權問題(安全性) ####7. LocalBroad
LSI(LSA)潛在語義索引原理及sklearn中的實現
想要了解潛在語義索引的原理推薦以下三個連結,仔細看下就能基本掌握LSI的原理: 1.文字主題模型之潛在語義索引(LSI) 2.奇異值分解(SVD)原理與在降維中的應用 3.latent semantic analysis via the singular value decompos
JDK8中的HashMap實現原理及原始碼分析
本篇所述原始碼基於JDK1.8.0_121 在寫上一篇線性表的文章的時候,筆者看的是Android原始碼中support24中的Java程式碼,當時發現這個ArrayList和LinkedList的原始碼和Java官方的沒有什麼區別,然而在閱讀HashMap原
在Android中繪製圓角矩形圖片
轉自:http://blog.chinaunix.net/uid-20771867-id-3260250.html 圓角矩形圖片在蘋果的產品中很流行,相比於普通的矩形,很多人都喜歡圓角矩形的圖片,下面在Android中實現將普通的矩形圖片繪製成圓角矩形。
Android中利用Picasso實現圖片壓縮指定任意尺寸
之前做專案時,有個需求是指定照片壓縮到任意寬高尺寸上傳給伺服器。當時我自己寫了個圖片壓縮方法,但是不夠完美,小問題不斷(比如OOM之類的)。後來看到了神器Picasso不光能載入網路圖片,還能以任意尺寸載入本地圖片。於是我想,既然Picasso能任意尺寸載入本地圖片,那它肯
Android三種動畫實現原理及使用
Android動畫目前分為三種:Frame Animation 幀動畫,通過順序播放一系列影象從而產生動畫效果,。圖片過多時容易造成OOM(Out Of Memory記憶體用完)異常。Tween Animation 補間動畫(又叫view動畫),是通過對場景裡的物件不斷做影象
Java 中 synchronized 的實現原理及偏向鎖、輕量級鎖、自旋鎖、公平鎖簡介
在多執行緒程式設計中,synchronized 一直都是元老級別的存在,很多人都稱之為重量級鎖。本文來簡單介紹synchronized的實現原理,以及為減少獲得鎖和釋放鎖所帶來的效能損耗而引進的偏向鎖與輕量級鎖。 Java中使用synchronized來實現
在Android中如何使用clipPath()方法實現簡單的裁剪圓形圖片
裁剪圓形圖片的方式有很多,這篇文章主要為大家介紹如何使用clipPath()方法裁剪圓形圖片。 首先,我們先看效果圖: 裁剪前: 裁剪後: 接下來,我們來一步一步的實現。 1.新建一個
Android Plugin插樁式實現外掛化開發(一)-實現原理及Activity外掛化實現
1. 前言在現在一些大型的Android應用中都採用了外掛化的開發方式,比如美團,支付寶及我們常用的微信等採用了插修的化的開發方式來進行開發的,既然國內一流的網際網路公司都採用這樣的方式來開發那它一定能帶給開發部署大型應用帶來很大的便捷,那麼外掛化的優勢在哪裡呢?1.1 外掛
Android中實現類似探探中圖片左右滑動切換效果
偶然之間發現探探的左右滑動的圖片挺好玩,試著去做了下,再到後來,看到許多大神也推出了同樣仿探探效果的部落格,從頭到尾閱讀下來,寫得通俗易懂,基本上沒什麼問題。於是,實現仿探探效果的想法再次出現在腦海中。那麼,還猶豫什麼,趁熱來一發吧!就這麼愉快地決定了。
spring web.xml中 攔截器(Interceptor)的實現原理及程式碼示例
前言:前面2篇部落格,我們分析了Java中過濾器和監聽器的實現原理,今天我們來看看攔截器。1,攔截器的概念 java裡的攔截器是動態攔截Action呼叫的物件,它提供了一種機制可以使開發者在一個Action執行的前後執行一段程式碼,也可以在一個Action執行前阻止其執
Android最便捷banner輪播圖實現原理及程式碼
原理圖: 程式碼實現 public class CyclerViewPager extends ViewPager { public CyclerViewPager(Context context) { super(conte
struts2中,一個Form表單配置多個action實現原理及案例
一、原理說明 以登入註冊為例 login.jsp:Form表單中包含登入、註冊兩個按鈕。登入按鈕配置LoginAction.java,註冊按鈕配置RegistAction.java <%@ page language="java" content
Android中使用CardView實現圓角對話方塊
前言:隨著使用者體驗的不斷的加深,良好的UI視覺效果也必不可少,以前方方正正的對話方塊樣式在APP已不復存在,取而代之的是帶有圓角效果的Dialog,之前設定對畫框的圓角效果都是通過drawable/shape屬性來完成,隨著Google API的不斷更新,API 21(An