万恶的国内设计,都喜欢偏向于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的大小,此处
left
和top
传入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());
获得了直角矩形的原料。
* 将模具再整理一下
我们之前已经得到了模具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);
这样一来,我们就在机床上摆上了圆角的模型。
* 开动机床,开始切割
```
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));
在此不做过多展开,若有疑问可直接私信或留言交流。