图片的圆角处理

万恶的国内设计,都喜欢偏向于ios的扁平化风格,这里涉及到很多原因,国内的网路环境导致大家无法使用国外的优秀app;长期的ios一家独大以及前乔布斯时代的老本至今仍未吃完给ios用户和蹩脚的应用设计者一发强心剂,让他们以为ios的风格就是最好的风格;硬件的审美被同样带到了软件中。
在此不排除扁平化风格存在一定的情景优势,但作为Material Design的拥护者,希望大家可以去如下地址下载个把国外的应用把玩一番,打开国门,看看外面的世界,外面的风格。
2017 Google Play Awards:https://play.google.com/store/info/topic?id=merch_topic_30028d2_playwards2017_nomineesTP

以上纯属牢骚,扁平化狂热者请绕道。。。
今天看到一个ios跟主管表明自己对于拟物风格的向往和对扁平风格的厌恶。。


言归正传,由于ios带来的圆角风格,导致产品对着我指手画脚,想让我把前人未完成的圆角图片坑给添上,做出如下的效果:

请拿走阴影,走你的扁平化

如果下面的两个圆角你不会处理,请先移步Android背景圆角的实现

很明显这是一个拼接的控件,你可以使用dialog,可以使用activity去实现,这不是重点,重点是上方的圆角图片怎样去实现。

实现方式无非两种,一种是自定义view实现,一种是直接通过动态实现。但是两者的宗旨是一样的

自定义view实现

既然是图片,那么很显然,最先想到的肯定是ImageView,这里我们就去自定义一个ImageView

  • 创建自定义类
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的一些注意点,如上是必须实现的三种构造方法,当然在新api中会出现第四种,此处写三种即可。

  • 先去搞个模具
    想想一下,你要做一个脸盆,可能你先想到的不是去直接打造,而是想着先去制造一个模具,然后我们只需要往里面浇灌就可以得到我们的脸盆。
    而这个圆角恰巧也可能通过这种方式来实现,我们就先来看下如何去搞一个圆角的模具。

在此原谅我认为你是一个完全不具备java环境下绘画基础的或者说只具备一丢丢的开发者。

在Android的graphics包下有许多几何绘图相关的类,我们平时见得比较多的诸如Canvas,Paint都在这个类下面,平时我们会使用canvas参数传递给画笔Paint,做一些字体的绘制等等,我们通常设置它的粗细,大小,锯齿等等之后交给canvas对象来进行绘画。

在paint的源码搜索了一番发现并没有出现round关键字的方法,这个类基本宣告无法实现圆角的东西。抱着几何包中肯定存在实现圆角的类和函数的执着,决定继续在graphic包中搜寻。

   好了注意了,接下去要进入到诡异的自圆其说了。。。

很庆幸的在graphic包下发现了Path类,顾名思义就是路线类,感觉很有可能能跟圆角的东西挂上钩,让我们去源码里面看一下。。。
然后你会发现它有包含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) {
      ...
  }
   我打算不花费长篇大论,去谈论真正的切割逻辑。
   两个层面,叠加,然后切割得到圆角啊什么的,过于乏味,面向初学者的话很难坚持读完。

上面四种方法,直接上白话解释,

  • 第一种,貌似参数最全,分别传入需要画圆角的图片view的大小,此处lefttop传入0即可,这边的四个参数表示的仅仅是区域大小,而非被切割ImageView的区域。
    float rx, float ry分别表示,在x轴和y轴上的圆角切割度数, 因为实际上圆角切割我们涉及到两个方向,一个是左右方向,x方向,一个是上下方向,y方向。这里我们传入两个8.0f就行了。
    最后的Direction表示曲线的闭合方向,这里由于我们只是画圆角曲线,并不涉及到曲线上的文字,那么随便传哪个都一样,直接传入Direction.CW即可
  • 第二种,直接传入包含l,t,r,b的rect对象,和radii数组,以及Direction对象,如何去理解这个叫radii的float数组,我们看到在具体的方法中,有这么一段
```

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数组,分别是4对[X,Y]的角度切割度数,那么很容易理解了,这个数组可以然我们对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对象出来,并且传入所需要切割的范围大小,以及切割圆角角度,以及方向参数。

* 曲线画完了,怎么切割
      我们已经把模具准备好了,那么接下去我们就开始要准备浇灌了,我们还需要两样东西,机床和原料。
   原料很明显就是ImageView的Bitmap本身,机床是什么呢?

   实际上我们在绘制中最终使用的绘制操作都是来自于Canvas类,这回也不例外,机床就是`Canvas`类。
   * 先把机床拿出来
Bitmap result = Bitmap.createBitmap(imageviewWidth, viewHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(result);
    因为机床创建需要传入Bitmap对象,因此此处我们就直接伪造一个Bitmap,大小为ImageView的大小,配置这种无所谓,传入32位即可。
   
    * 把原料整理好
     ```
     Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
获得了直角矩形的原料。
原料.png
* 将模具再整理一下
 我们之前已经得到了模具Path,现在我们需要将它安放到机床上去(在这里吧上面的代码又铁了一遍,省的你再往上翻了)
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);
这样一来,我们就在机床上摆上了圆角的模型。
圆角模具.png
* 开动机床,开始切割
```        

Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
canvas.drawBitmap(bitmap, rect, rectF, paint);

    这里还需要传入画笔,因为切割锯齿以及bitmap填充需要使用到paint来实现。
    这样一来,result就是我们所需要的自带圆角的bitmap了
    
![切割.png](http://upload-images.jianshu.io/upload_images/1305996-b3b3f2c4a76be84b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

![切割细节.png](http://upload-images.jianshu.io/upload_images/1305996-52535f1c30273acc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

   > 如果你看到现在还是一头雾水,既然你真心诚意的问了,那我就大发慈悲的再来帮你梳理一遍(火箭队?)
   * 首先搞一个空的bitmap,大小传入你需要的尺寸,质量32位即可
   * 将这个空的bitmap放到机床canvas上
   * 获取真实图片的矩形rect
   * 获取圆角模具的矩形rectF
   * 创建画笔Paint,控制锯齿和图形填充(理解成机床上的润滑剂比较不错)
   * 机床按动开关,通过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数组定义圆角矩形的四个圆角的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);
}




 ## 代码中动态实现
   阴差阳错,上面反而已经把如何在代码中动态实现讲出来了,我们可以直接用一个方法来概括,

/**设置图片圆角
* @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数组,只将图片的左上和右上修改为圆角。仅供参考。

------
## 注意点以及拓展
   * 如果ImageView的本身尺寸不是固定的,比如你写了wrap_content,而后又动态的改变了它的尺寸,记得要同步到切割方法的Rect中去,否则会出现各种切得的bitmap不是你要的问题。
   * 由于如上方法可以对指定的角进行切割圆角,所以最终选择如上如上方法在此阐述。其实如果单单实现圆角,方法有很多,比如如下方法

paint.setXfermode(null);
canvas.drawRoundRect(rectF, radius, radius, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

     在此不做过多展开,若有疑问可直接私信或留言交流。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容