android画布裁剪圆角,Android 视图圆角化处理方案
前言
最近项目中突然要将用到图片(项目使用Fresco)及视频(项目使用TextureView绘制纹理,SurfaceView不在本文讨论之列,绝大部分播放器为了视图可控,现在都会采用TextureView而不是SurfaceView。原因的话那又是另一片大海,自行脑补)的地方都进行圆角化,且需支持可控实验,即开关开启时圆角关闭时非圆角。由于工程运行已久,图片及视频的地方甚多,除了考虑技术方案外,还需考虑人工成本。
各种圆角化方案探索对比
方案一 : 直接采用Canvas.clipxxx 相关api,裁剪出一个圆角区域
emmmmmm...... 该方案简单暴力,通用性强。然后,全文终。
黑脸
然后你就会发现,你的页面也终了。如果只是一个静态的单图视图,该方法问题不大,但如果是复杂页面,滚动的时候,测试就会跟你说,页面卡顿了,要优化。
原因就是 Canvas.clip的相关api损耗相对较大。
方案二 :直接Fresco自带功能
1: 直接使用 roundedCornerRadius属性,然后你就会惊奇地发现,静图很完美,动图无效了......
2: 经过一番查找,发现fresco已经给出动图的解决方案,加上roundWithOverlayColor属性,该属性支持传drawable类型,然后动图可以圆角化了
...泪大普奔,可以下班了。
但是 我们工程原来很多地方是类似头条这样,item有点击背景的.
item点击背景改变.gif
而圆角只是在图片的各个角上,使用该属性实现的话,你会发现如下现象
overlayColor透出4个角不同颜色.gif
问题一:外层背景颜色改变时,盖住的4个角的颜色,并无随外层点击颜色变化,4个角还是上次的默认颜色透出
这个现象,如果圆角不是很大,且大item的不同状态间颜色差异不大时,不是很明显。原因roundWithOverlayColor属性采用的是一个普通的静态drawable,当外部背景按下时OverlayDrawable,并无刷新。
那我们就在外部背景按下时,重新设置roundWithOverlayColor色值,然后重新调用加载图片?
原理可行,但通过fresco二次加载的方式,性能还是有点浪费。如果项目中不用处理视频,或允许视频与图片2套的化,图片圆角化可以采用该方案。
该方案应该有点可以优化,就是不要调用二次加载方式,而是按下去时
获取OverlayDrawable(没再认真去看源码,是否叫这名字,暂时这样叫)然后根据颜色刷新该层drawable就好。Fresco的原理就是一层层的drawable,然后控制器根据当前状态,来显示对应层的drawable,猜测roundWithOverlayColor应该是有单独对应一层drawable的。由于我没采用该方案,具体细节不再细究。
如果采用该方案,那接下来就又要开启视频的圆角方案之旅了
方案三 :最终大招 CardView
经过一番思考后,我们终于想到了 系统提供的CardView,然后我们就开始了 全工程改造。
把原来全工程各个视频控件和图片控件的外层,都加上一层CardView,经过多个日夜不停地加班奋战,几天过去了,你就会发现,一切运行完美,视频控件也完美支持圆角化了
问题二:每个视频控件和图片控件外层都加上个cardview,做为父layout的话,成本实在太高了。而且个别地方,原来如果是通过childview.getLayoutParams操作原子控件LayoutParams的话,那代码和布局同时改起来,简直是...
问题三:套一层的话相当于多一层布局,布局层级更深一层,layout时间加长,性能上面你懂的。在开关关(无需圆角)的情况下,该cardview纯属浪费
问题四:android 5.0 以下的机子你会发现神奇的现象,就是api 21以下的机子,圆角化并不是你想象中的样子
直接偷懒网上盗下效果图,如下
API21及以上.jpg
api21以下.jpg
初步一看,虽然加上了圆角属性,但是图片边上是方的。将左下角和左上角放大仔细看下:
左下角细节图.jpg
左上角.jpg
可以看到,CardView本身是圆角效果了,但是里边的内容却还是方的,并且出现了多余的白边。
看来是时候撸一把cardview源码(基于support 26.0.1其余版本大同小异,这里只分析粘贴最主要代码)了
public class CardView extends FrameLayout {
...
static {
if (Build.VERSION.SDK_INT >= 21) {
IMPL = new CardViewApi21Impl();
} else if (Build.VERSION.SDK_INT >= 17) {
IMPL = new CardViewApi17Impl();
} else {
IMPL = new CardViewBaseImpl();
}
IMPL.initStatic();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!(IMPL instanceof CardViewApi21Impl)) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
switch (widthMode) {
case MeasureSpec.EXACTLY:
case MeasureSpec.AT_MOST:
final int minWidth = (int) Math.ceil(IMPL.getMinWidth(mCardViewDelegate));
widthMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(minWidth,
MeasureSpec.getSize(widthMeasureSpec)), widthMode);
break;
}
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
switch (heightMode) {
case MeasureSpec.EXACTLY:
case MeasureSpec.AT_MOST:
final int minHeight = (int) Math.ceil(IMPL.getMinHeight(mCardViewDelegate));
heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(minHeight,
MeasureSpec.getSize(heightMeasureSpec)), heightMode);
break;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
private void initialize(Context context, AttributeSet attrs, int defStyleAttr) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CardView, defStyleAttr,
R.style.CardView);
ColorStateList backgroundColor;
if (a.hasValue(R.styleable.CardView_cardBackgroundColor)) {
backgroundColor = a.getColorStateList(R.styleable.CardView_cardBackgroundColor);
} else {
// There isn't one set, so we'll compute one based on the theme
final TypedArray aa = getContext().obtainStyledAttributes(COLOR_BACKGROUND_ATTR);
final int themeColorBackground = aa.getColor(0, 0);
aa.recycle();
...
IMPL.initialize(mCardViewDelegate, context, backgroundColor, radius,
elevation, maxElevation);
}
...
};
最主要就是
1:初始化获取一些xml属性,变成本地变量方便后续使用
2: onMeasure方法,在api21以下做了特殊处理(一直很想吐槽这个处理方式),具体处理下文分析
3: 根据sdk版本生成不同的实现类 ,cardview只是做为一个空壳,在各种方法被系统调用的时候,调用对应实现类的对应方法。这就是为啥不同api版本,效果不一样的地方了。
既然问题出现在21以下,我们就先看下CardViewApi17Impl的实现。
其实17~20(CardViewApi17Impl)及17以下(CardViewBaseImpl)的差别很小,仅是在如何绘制圆角上方法(drawRoundRect)不同而已。原因如下,不再详细分析
// Draws a round rect using 7 draw operations. This is faster than using
// canvas.drawRoundRect before JBMR1 because API 11-16 used alpha mask textures to draw
// shapes.
所以我们直接看CardViewBaseImpl,重点在以下3个方法
@Override
public void initialize(CardViewDelegate cardView, Context context,
ColorStateList backgroundColor, float radius, float elevation, float maxElevation) {
RoundRectDrawableWithShadow background = createBackground(context, backgroundColor, radius,
elevation, maxElevation);
background.setAddPaddingForCorners(cardView.getPreventCornerOverlap());
cardView.setCardBackground(background);
updatePadding(cardView);
}
private RoundRectDrawableWithShadow createBackground(Context context,
ColorStateList backgroundColor, float radius, float elevation,
float maxElevation) {
return new RoundRectDrawableWithShadow(context.getResources(), backgroundColor, radius,
elevation, maxElevation);
}
@Override
public void updatePadding(CardViewDelegate cardView) {
Rect shadowPadding = new Rect();
getShadowBackground(cardView).getMaxShadowAndCornerPadding(shadowPadding);
cardView.setMinWidthHeightInternal((int) Math.ceil(getMinWidth(cardView)),
(int) Math.ceil(getMinHeight(cardView)));
cardView.setShadowPadding(shadowPadding.left, shadowPadding.top,
shadowPadding.right, shadowPadding.bottom);
}
其实就是又把大部分工作交给了RoundRectDrawableWithShadow处理,然后将创建该drawable对象设为背景。然后本类中只处理一些外层间距问题,主要是外层阴影在21以下的实现方式(又是一个坑),最后我们跟踪RoundRectDrawableWithShadow,重点在
@Override
public void draw(Canvas canvas) {
if (mDirty) {
buildComponents(getBounds());
mDirty = false;
}
canvas.translate(0, mRawShadowSize / 2);
drawShadow(canvas);
canvas.translate(0, -mRawShadowSize / 2);
sRoundRectHelper.drawRoundRect(canvas, mCardBounds, mCornerRadius, mPaint);
}
通过buildComponents及drawShadow方法,就会发现,其实在旧版本上,是采用设padding,讲cardview包裹的原布局缩小2层(1层用于显示阴影、1层用于绘制上图看到的原视频外的白色圆角部分),个人感觉这种方式灰常坑,首先强制将原来控件尺寸(ui要找麻烦了)改了不说,空出来绘制得出的阴影也是很呵呵,与21以上的效果完全不是一个级别;圆角效果也是相当于外层多了个圆角,而不是原视图上做的改动。这就明白了,为啥会出现上图的样子了。
那为啥5.0以上的效果会没有问题呢? 接下来就是重点CardViewApi21Impl,其实大概流程与CardViewApi17Impl 干的事差不多,区别仅在于drawable不一样,他是使用RoundRectDrawable做背景。继续跟踪RoundRectDrawable发现其实做的事与RoundRectDrawableWithShadow差不多。重点在于
/**
* Ensures the tint filter is consistent with the current tint color and
* mode.
*/
private PorterDuffColorFilter createTintFilter(ColorStateList tint, PorterDuff.Mode tintMode) {
if (tint == null || tintMode == null) {
return null;
}
final int color = tint.getColorForState(getState(), Color.TRANSPARENT);
return new PorterDuffColorFilter(color, tintMode);
}
该PorterDuff.Mode为PorterDuff.Mode.SRC_IN(这方面知识,自行脑补,赋上一张简图)
PorterDuff.jpg
该drawble 默认在4个角用透明像素绘制了4个圆角,然后结合PorterDuff.Mode.SRC_IN模式。
最后最关键的是要配合上view的setClipToOutline方法,就可以实现视图圆角了,但是这些api都是21及以后才有的,所以你懂的。
虽然cardview的方式,不适合我们。但是,api 21以后的这种方式给我们提供了一种思路,只需要设个RoundRectDrawable(support包中该类不对外开发,我们可以自行复制实现)当背景,然后打开view的setClipToOutline方法。2行代码即可搞定,瞬间解决了以上所有遇到问题
方案四 :痛苦的兼容及及全通用方案处理
如果项目可以不用兼容5.0以下机子,该部分可以不看了
原理:自己造轮子在view的最上层绘制一层与背景一样颜色的圆角,挡住下面的视图
原理很简单,但实现起来有有几个点要注意:
1:必须能先知道外层布局各种状态的底色,且外层背景颜色如果并非单色,那就凉凉了
像这种外层有相应点击事件的情况,外层view还需要通知里层view 刷新对应圆角颜色;而且里层view无论有没单独的点击事件。盖上去的这层都不能根据本身view的状态变色,否则也会出现问题一的情况
工程中有换肤功能,还必须兼容各种换肤情况
2:技术选型上,绘制覆盖层的圆角是否直接在view上绘制。直接在view上绘制可行,但通用性相对较低,相当于各种需要圆角的view都要去自定义一个原来的子类,在原子类上绘制一层。并且外层view状态的传递给里层view的方式代码写起来也会相对抠脚。建议采用drawable的方式,在各个view上层绘制一次该drawable即可。然后内外两层view用户操作状态的传递及如何刷新,实现方案不一,代码及工程量也不一
在这里就不再详细对比各种细节,直接上个人思考良久,综合各方面考量,最终觉得比较合理的方式,转换成demo,有兴趣的同学,自行点击链接查看
总结
总的来说各种实现圆角的方案大概原理可概括为以下几种
1:直接裁剪视图型,简单暴力
2:利用各种图形重叠区域的api及模式,产生效果,但可能会有api版本问题
3:直接在原视图上盖层底色圆角。方案通用,但实现方式不一样,代码量及通用性可以相差不少
android画布裁剪圆角,Android 视图圆角化处理方案相关推荐
- android 画布裁剪,一种基于Android系统对UI控件进行轮廓剪裁及美化的方法与流程...
本发明涉及Android应用的技术领域,特别涉及一种基于Android系统对UI控件进行轮廓剪裁及美化的方法. 背景技术: 目前,随着智能电视的普及,Android应用层出不穷,而那些表现形式单一.传 ...
- android 画布心形,Android CustomShapeImageView对图片进行各种样式裁剪:圆形、星形、心形、花瓣形等...
Android CustomShapeImageView对图片进行各种样式裁剪:圆形.星形.心形.花瓣形等 Android CustomShapeImageView是github上一个第三方开源 ...
- android中图片圆角,Android中实现圆角图片的几种方法
Android中实现圆角图片有多种姿势,不知你解锁了几种? 方法一:setXfermode法 此种方式就是再new一个相同尺寸的bitmap,然后使用paint.setXfermode(new Por ...
- android画布大小设置,Android:定义onDraw画布大小
我已经删除了旧的代码,因为它没有按照我的意图工作.我试图在屏幕的某个部分使用onDraw画布功能,这样我的绘图周围仍然可以有按钮和其他文字.Android:定义onDraw画布大小 ***编辑* ** ...
- android 画布实现签名,Android 自定义View手写签名并保存图片
1.自定义View--支撑设置画笔色彩,画笔宽度,画板色彩,铲除画板,查看是否有签名,保存画板图片(仿制粘贴可直接使用) /***CreatedbyYyyyQon2020/3/5. *电子签名*/pu ...
- android 简单实现圆角,Android 实现圆角图片的简单实例
Android 实现圆角图片的简单实例 实现效果图: 本来想在网上找个圆角的例子看一看,不尽人意啊,基本都是官方的Demo的那张原理图,稍后会贴出.于是自己自定义了个View,实现图片的圆角以及圆形效 ...
- android 画布实现签名,Android实现屏幕手写签名
Android屏幕手写签名的原理就是把手机屏幕当作画板,把用户手指当作画笔,手指在屏幕上在屏幕上划来划去,屏幕就会显示手指的移动轨迹,就像画笔在画板上写字一样.实现手写签名需要结合绘图的路径工具Pat ...
- 小米7.0 android 图片裁剪失败,Android 7.0适配 -- FileProvider 拍照、选择相册、裁切图片, 小米机型适配...
需求: 最近把APP的TargetSdk从21提高至25后,测试时, 在Android7.0以上的系统上,爆出了一些异常. 在个别小米等机型也存在一些异常. 问题分析: FileUriExposedE ...
- android 布局加圆角,Android通用圆角布局
名称 rclayout 语言 Android 平台 GitHub 作者 GcsSloop 该布局是一个可以任意设定布局圆角的大小,宽度,颜色等.可满足基于的开发工作中遇到的圆角布局需求. 效果图 效果 ...
最新文章
- OPPO这次的新旗舰,开始“搞颜色”了,当然不是外壳的那种
- 201521123029《Java程序设计》第1周学习总结
- mysql 分页有数据没了_mysql分页丢数据的分析
- 阿里开源Euler:国内首个工业级图表征学习框架
- mysql客户端 mariadb_配置MariaDB允许客户端远程连接
- python——类别型数据标签二值化-one_hot编码
- PHP中的unset的使用问题
- java web项目个人博客_javaWeb项目个人博客系统
- pdf如何在线旋转?PDF旋转的方法
- 阿里云视频点播(java)
- 神舟战神调节风扇热键_神舟GX9电脑热键驱动
- 蓝牙(四)LMP协议解析之二
- docker2-镜像原理及创建新的镜像
- 【继承系列】JS中的组合继承
- Havok VS PhysX 漫谈物理加速世界!
- 软考高级系统架构设计师系列之:深入掌握软考高级系统架构设计师考试的知识分布点,轻松应对高级系统架构设计师考试
- 菜鸟教程学习——html5 表单
- 分享一个超级Nice的美食小程序
- 纪念币有没有收藏价值?
- 险绝冠天下---华山