萬惡的國內(nèi)設(shè)計,都喜歡偏向于ios的扁平化風格,這里涉及到很多原因,國內(nèi)的網(wǎng)路環(huán)境導致大家無法使用國外的優(yōu)秀app;長期的ios一家獨大以及前喬布斯時代的老本至今仍未吃完給ios用戶和蹩腳的應(yīng)用設(shè)計者一發(fā)強心劑,讓他們以為ios的風格就是最好的風格;硬件的審美被同樣帶到了軟件中。
在此不排除扁平化風格存在一定的情景優(yōu)勢,但作為Material Design的擁護者,希望大家可以去如下地址下載個把國外的應(yīng)用把玩一番,打開國門,看看外面的世界,外面的風格。
2017 Google Play Awards:https://play.google.com/store/info/topic?id=merch_topic_30028d2_playwards2017_nomineesTP
以上純屬牢騷,扁平化狂熱者請繞道。。。
今天看到一個ios跟主管表明自己對于擬物風格的向往和對扁平風格的厭惡。。
言歸正傳,由于ios帶來的圓角風格,導致產(chǎn)品對著我指手畫腳,想讓我把前人未完成的圓角圖片坑給添上,做出如下的效果:
如果下面的兩個圓角你不會處理,請先移步Android背景圓角的實現(xiàn)
很明顯這是一個拼接的控件,你可以使用dialog,可以使用activity去實現(xiàn),這不是重點,重點是上方的圓角圖片怎樣去實現(xiàn)。
實現(xiàn)方式無非兩種,一種是自定義view實現(xiàn),一種是直接通過動態(tài)實現(xiàn)。但是兩者的宗旨是一樣的
自定義view實現(xiàn)
既然是圖片,那么很顯然,最先想到的肯定是ImageView,這里我們就去自定義一個ImageView
- 創(chuàng)建自定義類
public class RoundedImageView extends ImageView {
public RoundedImageView(Context context) {
super(context);
}
public RoundedImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RoundedImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
我記得我在面試寶典的文章中指明了自定義view的一些注意點,如上是必須實現(xiàn)的三種構(gòu)造方法,當然在新api中會出現(xiàn)第四種,此處寫三種即可。
- 先去搞個模具
想想一下,你要做一個臉盆,可能你先想到的不是去直接打造,而是想著先去制造一個模具,然后我們只需要往里面澆灌就可以得到我們的臉盆。
而這個圓角恰巧也可能通過這種方式來實現(xiàn),我們就先來看下如何去搞一個圓角的模具。
在此原諒我認為你是一個完全不具備java環(huán)境下繪畫基礎(chǔ)的或者說只具備一丟丟的開發(fā)者。
在Android的graphics包下有許多幾何繪圖相關(guān)的類,我們平時見得比較多的諸如Canvas
,Paint
都在這個類下面,平時我們會使用canvas參數(shù)傳遞給畫筆Paint,做一些字體的繪制等等,我們通常設(shè)置它的粗細,大小,鋸齒等等之后交給canvas對象來進行繪畫。
在paint的源碼搜索了一番發(fā)現(xiàn)并沒有出現(xiàn)round關(guān)鍵字的方法,這個類基本宣告無法實現(xiàn)圓角的東西。抱著幾何包中肯定存在實現(xiàn)圓角的類和函數(shù)的執(zhí)著,決定繼續(xù)在graphic
包中搜尋。
好了注意了,接下去要進入到詭異的自圓其說了。。。
很慶幸的在graphic
包下發(fā)現(xiàn)了Path
類,顧名思義就是路線類,感覺很有可能能跟圓角的東西掛上鉤,讓我們?nèi)ピ创a里面看一下。。。
然后你會發(fā)現(xiàn)它有包含round的方法名addRoundRect
。。(不知各位看官覺得這一段接的硬么)
我們看一下,總共有四個方法
public void addRoundRect(float left, float top, float right, float bottom, float rx, float ry,
Direction dir) {
...
}
public void addRoundRect(RectF rect, float[] radii, Direction dir) {
...
}
public void addRoundRect(float left, float top, float right, float bottom, float[] radii,
Direction dir) {
...
}
public void addRoundRect(RectF rect, float rx, float ry, Direction dir) {
...
}
我打算不花費長篇大論,去談?wù)撜嬲那懈钸壿嫛? 兩個層面,疊加,然后切割得到圓角啊什么的,過于乏味,面向初學者的話很難堅持讀完。
上面四種方法,直接上白話解釋,
- 第一種,貌似參數(shù)最全,分別傳入需要畫圓角的圖片view的大小,此處
left
和top
傳入0即可,這邊的四個參數(shù)表示的僅僅是區(qū)域大小,而非被切割I(lǐng)mageView的區(qū)域。
float rx
,float ry
分別表示,在x軸和y軸上的圓角切割度數(shù), 因為實際上圓角切割我們涉及到兩個方向,一個是左右方向,x方向,一個是上下方向,y方向。這里我們傳入兩個8.0f
就行了。
最后的Direction
表示曲線的閉合方向,這里由于我們只是畫圓角曲線,并不涉及到曲線上的文字,那么隨便傳哪個都一樣,直接傳入Direction.CW
即可 - 第二種,直接傳入包含l,t,r,b的rect對象,和radii數(shù)組,以及Direction對象,如何去理解這個叫radii的float數(shù)組,我們看到在具體的方法中,有這么一段
```
if (radii.length < 8) {
throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values");
}
再看看,方法注釋
* @param radii Array of 8 values, 4 pairs of [X,Y] radii
很顯然,只能傳size為8的float數(shù)組,分別是4對[X,Y]的角度切割度數(shù),那么很容易理解了,這個數(shù)組可以然我們對Rect的四個角且出不同的角度。
* 第三種和第四種同上,自己YY吧。
好了,我們開始畫曲線
final RectF rectF = new RectF(0, 0, bitmapWidth, bitmapHeight);
int radius = getResources().getDimensionPixelSize(R.dimen.radius);
Path path = new Path();
path.addRoundRect(rectF, radius, radius, Path.Direction.CW);
先new一個Path對象出來,并且傳入所需要切割的范圍大小,以及切割圓角角度,以及方向參數(shù)。
* 曲線畫完了,怎么切割
我們已經(jīng)把模具準備好了,那么接下去我們就開始要準備澆灌了,我們還需要兩樣東西,機床和原料。
原料很明顯就是ImageView的Bitmap本身,機床是什么呢?
實際上我們在繪制中最終使用的繪制操作都是來自于Canvas類,這回也不例外,機床就是`Canvas`類。
* 先把機床拿出來
Bitmap result = Bitmap.createBitmap(imageviewWidth, viewHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(result);
因為機床創(chuàng)建需要傳入Bitmap對象,因此此處我們就直接偽造一個Bitmap,大小為ImageView的大小,配置這種無所謂,傳入32位即可。
* 把原料整理好
```
Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
獲得了直角矩形的原料。
* 將模具再整理一下
我們之前已經(jīng)得到了模具Path,現(xiàn)在我們需要將它安放到機床上去(在這里吧上面的代碼又鐵了一遍,省的你再往上翻了)
final RectF rectF = new RectF(0, 0, bitmapWidth, bitmapHeight);
int radius = getResources().getDimensionPixelSize(R.dimen.radius);
Path path = new Path();
path.addRoundRect(rectF, radius, radius, Path.Direction.CW);
canvas.clipPath(path);
這樣一來,我們就在機床上擺上了圓角的模型。
* 開動機床,開始切割
```
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
canvas.drawBitmap(bitmap, rect, rectF, paint);
這里還需要傳入畫筆,因為切割鋸齒以及bitmap填充需要使用到paint來實現(xiàn)。
這樣一來,result就是我們所需要的自帶圓角的bitmap了


> 如果你看到現(xiàn)在還是一頭霧水,既然你真心誠意的問了,那我就大發(fā)慈悲的再來幫你梳理一遍(火箭隊?)
* 首先搞一個空的bitmap,大小傳入你需要的尺寸,質(zhì)量32位即可
* 將這個空的bitmap放到機床canvas上
* 獲取真實圖片的矩形rect
* 獲取圓角模具的矩形rectF
* 創(chuàng)建畫筆Paint,控制鋸齒和圖形填充(理解成機床上的潤滑劑比較不錯)
* 機床按動開關(guān),通過drawBitmap方法將bitmap繪制成我們需要圓角bitmap

那么我們直接在自定義ImageView的onDraw方法中做一下手腳,
@Override
protected void onDraw(Canvas canvas) {
Path path = new Path();
int w = this.getWidth();
int h = this.getHeight();
/向路徑中添加圓角矩形。radii數(shù)組定義圓角矩形的四個圓角的x,y半徑。radii長度必須為8/
path.addRoundRect(new RectF(0,0,w,h), 8.0f, 8.0f, Path.Direction.CW);
canvas.clipPath(path);
super.onDraw(canvas);
}
## 代碼中動態(tài)實現(xiàn)
陰差陽錯,上面反而已經(jīng)把如何在代碼中動態(tài)實現(xiàn)講出來了,我們可以直接用一個方法來概括,
/**設(shè)置圖片圓角
* @param bitmap 原圖
* @param radius 圓角角度
* @param viewWidth 需要展示所在view的寬度
* @param viewHeight
* @return
*/
public Bitmap getRoundRectBitmap(Bitmap bitmap, int radius, int viewWidth, int viewHeight) {
Bitmap result = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(result);
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
final RectF rectF = new RectF(0, 0, viewWidth, viewHeight);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
float[] rids = {10.0f,10.0f,10.0f,10.0f,0.0f,0.0f,0.0f,0.0f,};
Path path = new Path();
path.addRoundRect(rectF, rids, Path.Direction.CW);
canvas.clipPath(path);
canvas.drawBitmap(bitmap, rect, rectF, paint);
return result;
}
這里使用到了float數(shù)組,只將圖片的左上和右上修改為圓角。僅供參考。
------
## 注意點以及拓展
* 如果ImageView的本身尺寸不是固定的,比如你寫了wrap_content,而后又動態(tài)的改變了它的尺寸,記得要同步到切割方法的Rect中去,否則會出現(xiàn)各種切得的bitmap不是你要的問題。
* 由于如上方法可以對指定的角進行切割圓角,所以最終選擇如上如上方法在此闡述。其實如果單單實現(xiàn)圓角,方法有很多,比如如下方法
paint.setXfermode(null);
canvas.drawRoundRect(rectF, radius, radius, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
在此不做過多展開,若有疑問可直接私信或留言交流。