前言

最近项目中突然要将用到图片(项目使用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 视图圆角化处理方案相关推荐

  1. android 画布裁剪,一种基于Android系统对UI控件进行轮廓剪裁及美化的方法与流程...

    本发明涉及Android应用的技术领域,特别涉及一种基于Android系统对UI控件进行轮廓剪裁及美化的方法. 背景技术: 目前,随着智能电视的普及,Android应用层出不穷,而那些表现形式单一.传 ...

  2. android 画布心形,Android CustomShapeImageView对图片进行各种样式裁剪:圆形、星形、心形、花瓣形等...

     Android CustomShapeImageView对图片进行各种样式裁剪:圆形.星形.心形.花瓣形等 Android CustomShapeImageView是github上一个第三方开源 ...

  3. android中图片圆角,Android中实现圆角图片的几种方法

    Android中实现圆角图片有多种姿势,不知你解锁了几种? 方法一:setXfermode法 此种方式就是再new一个相同尺寸的bitmap,然后使用paint.setXfermode(new Por ...

  4. android画布大小设置,Android:定义onDraw画布大小

    我已经删除了旧的代码,因为它没有按照我的意图工作.我试图在屏幕的某个部分使用onDraw画布功能,这样我的绘图周围仍然可以有按钮和其他文字.Android:定义onDraw画布大小 ***编辑* ** ...

  5. android 画布实现签名,Android 自定义View手写签名并保存图片

    1.自定义View--支撑设置画笔色彩,画笔宽度,画板色彩,铲除画板,查看是否有签名,保存画板图片(仿制粘贴可直接使用) /***CreatedbyYyyyQon2020/3/5. *电子签名*/pu ...

  6. android 简单实现圆角,Android 实现圆角图片的简单实例

    Android 实现圆角图片的简单实例 实现效果图: 本来想在网上找个圆角的例子看一看,不尽人意啊,基本都是官方的Demo的那张原理图,稍后会贴出.于是自己自定义了个View,实现图片的圆角以及圆形效 ...

  7. android 画布实现签名,Android实现屏幕手写签名

    Android屏幕手写签名的原理就是把手机屏幕当作画板,把用户手指当作画笔,手指在屏幕上在屏幕上划来划去,屏幕就会显示手指的移动轨迹,就像画笔在画板上写字一样.实现手写签名需要结合绘图的路径工具Pat ...

  8. 小米7.0 android 图片裁剪失败,Android 7.0适配 -- FileProvider 拍照、选择相册、裁切图片, 小米机型适配...

    需求: 最近把APP的TargetSdk从21提高至25后,测试时, 在Android7.0以上的系统上,爆出了一些异常. 在个别小米等机型也存在一些异常. 问题分析: FileUriExposedE ...

  9. android 布局加圆角,Android通用圆角布局

    名称 rclayout 语言 Android 平台 GitHub 作者 GcsSloop 该布局是一个可以任意设定布局圆角的大小,宽度,颜色等.可满足基于的开发工作中遇到的圆角布局需求. 效果图 效果 ...

最新文章

  1. OPPO这次的新旗舰,开始“搞颜色”了,当然不是外壳的那种
  2. 201521123029《Java程序设计》第1周学习总结
  3. mysql 分页有数据没了_mysql分页丢数据的分析
  4. 阿里开源Euler:国内首个工业级图表征学习框架
  5. mysql客户端 mariadb_配置MariaDB允许客户端远程连接
  6. python——类别型数据标签二值化-one_hot编码
  7. PHP中的unset的使用问题
  8. java web项目个人博客_javaWeb项目个人博客系统
  9. pdf如何在线旋转?PDF旋转的方法
  10. 阿里云视频点播(java)
  11. 神舟战神调节风扇热键_神舟GX9电脑热键驱动
  12. 蓝牙(四)LMP协议解析之二
  13. docker2-镜像原理及创建新的镜像
  14. 【继承系列】JS中的组合继承
  15. Havok VS PhysX 漫谈物理加速世界!
  16. 软考高级系统架构设计师系列之:深入掌握软考高级系统架构设计师考试的知识分布点,轻松应对高级系统架构设计师考试
  17. 菜鸟教程学习——html5 表单
  18. 分享一个超级Nice的美食小程序
  19. 纪念币有没有收藏价值?
  20. 险绝冠天下---华山

热门文章

  1. 随机码生成器[10位字母+数字]
  2. 听说go语言越来越火了?那么请收下这一份go语言书单吧!
  3. 关于redis 小白从入门到大神
  4. bit(比特)和byte(字节的关系)以及现实应用场景(存储单位和网速单位)
  5. 如何将 Visual Paradigm 桌面客户端连接到不同的 VP Online 存储库丨使用教程
  6. 基于计算机底层基础设计一个高性能的单机管理主机的心跳服务
  7. 计算机手动配置信息,手动配置 IPv6
  8. 图解数据在网络中的传输过程
  9. 硕士毕业论文写多少字
  10. Oracle数据库同义词详解