萬惡的國內(nèi)設(shè)計,都喜歡偏向于ios的扁平化風(fēng)格,這里涉及到很多原因,國內(nèi)的網(wǎng)路環(huán)境導(dǎo)致大家無法使用國外的優(yōu)秀app狮鸭;長期的ios一家獨大以及前喬布斯時代的老本至今仍未吃完給ios用戶和蹩腳的應(yīng)用設(shè)計者一發(fā)強心劑坠宴,讓他們以為ios的風(fēng)格就是最好的風(fēng)格曾掂;硬件的審美被同樣帶到了軟件中。
在此不排除扁平化風(fēng)格存在一定的情景優(yōu)勢顽腾,但作為Material Design的擁護者近零,希望大家可以去如下地址下載個把國外的應(yīng)用把玩一番,打開國門抄肖,看看外面的世界久信,外面的風(fēng)格。
2017 Google Play Awards:https://play.google.com/store/info/topic?id=merch_topic_30028d2_playwards2017_nomineesTP
以上純屬牢騷漓摩,扁平化狂熱者請繞道裙士。。幌甘。
今天看到一個ios跟主管表明自己對于擬物風(fēng)格的向往和對扁平風(fēng)格的厭惡潮售。痊项。
言歸正傳,由于ios帶來的圓角風(fēng)格酥诽,導(dǎo)致產(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)造方法,當(dāng)然在新api中會出現(xiàn)第四種虾宇,此處寫三種即可搓彻。
- 先去搞個模具
想想一下,你要做一個臉盆嘱朽,可能你先想到的不是去直接打造旭贬,而是想著先去制造一個模具,然后我們只需要往里面澆灌就可以得到我們的臉盆搪泳。
而這個圓角恰巧也可能通過這種方式來實現(xiàn)骑篙,我們就先來看下如何去搞一個圓角的模具。
在此原諒我認(rèn)為你是一個完全不具備java環(huán)境下繪畫基礎(chǔ)的或者說只具備一丟丟的開發(fā)者森书。
在Android的graphics包下有許多幾何繪圖相關(guān)的類靶端,我們平時見得比較多的諸如Canvas
,Paint
都在這個類下面,平時我們會使用canvas參數(shù)傳遞給畫筆Paint凛膏,做一些字體的繪制等等杨名,我們通常設(shè)置它的粗細(xì),大小猖毫,鋸齒等等之后交給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ù)撜嬲那懈钸壿嫛? 兩個層面蓖柔,疊加,然后切割得到圓角啊什么的风纠,過于乏味,面向初學(xué)者的話很難堅持讀完牢贸。
上面四種方法竹观,直接上白話解釋,
- 第一種潜索,貌似參數(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)把模具準(zhǔn)備好了臀蛛,那么接下去我們就開始要準(zhǔn)備澆灌了亲桦,我們還需要兩樣?xùn)|西,機床和原料浊仆。
原料很明顯就是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了
![切割.png](http://upload-images.jianshu.io/upload_images/1305996-b3b3f2c4a76be84b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![切割細(xì)節(jié).png](http://upload-images.jianshu.io/upload_images/1305996-52535f1c30273acc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
> 如果你看到現(xiàn)在還是一頭霧水襟锐,既然你真心誠意的問了,那我就大發(fā)慈悲的再來幫你梳理一遍(火箭隊膛锭?)
* 首先搞一個空的bitmap粮坞,大小傳入你需要的尺寸,質(zhì)量32位即可
* 將這個空的bitmap放到機床canvas上
* 獲取真實圖片的矩形rect
* 獲取圓角模具的矩形rectF
* 創(chuàng)建畫筆Paint初狰,控制鋸齒和圖形填充(理解成機床上的潤滑劑比較不錯)
* 機床按動開關(guān)莫杈,通過drawBitmap方法將bitmap繪制成我們需要圓角bitmap
![切圓角流程.png](http://upload-images.jianshu.io/upload_images/1305996-ea6c2721d7f7c0a7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
那么我們直接在自定義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));
在此不做過多展開说铃,若有疑問可直接私信或留言交流访惜。