AndroidShapeableImageView使用详解,告别shape三方库
效果
前言
先来看一下ShapeableImageView是什么
由上图可以看到ShapeableImageView也没有什么神秘的,不过是ImageView的一个子类而已,但是从效果图来看,在不写shape、不引入三方库的情况下,还是挺容易实现预期效果的,而且扩展性良好。使用引入material包implementation "com.google.android.material:material:1.2.1" 常规
和ImageView正常使用没有区别圆角
没有直接设置圆角的属性,需要用到app:shapeAppearance,后面会说cornerFamily 角的处理方式,rounded圆角,cut裁剪cornerSize 圆角大小圆
圆角的大小可以用百分比,也可以自己计算,比如宽高100dp,圆角50dp描边
app:strokeColor 描边颜色app:strokeWidth 描边宽度注意这里padding的数值是描边宽度的一半,后面会说切角
cornerFamily:cut 处理模式变为裁剪菱形
同样,裁剪模式下圆角大小也可以计算叶子
cornerSizeTopLeft 左上圆角cornerSizeBottomRight 右下圆角以此类推,左上、左下、右上、右下等半圆
六边形
author:yechaoa属性
关于xml属性,我也做了一个整理,属性不多,只有4个
扩展
前面为了整体的排版,埋了几个伏笔,下面来一一解答。
会涉及到源码,但是经过去繁从简,看起来也非常轻松的。shapeAppearance
Shape appearance overlay style reference for ShapeableImageView.ShapeableImageView的形状外观覆盖样式参考。
前面可以看到我们设置圆角其实是用的style,那为什么不直接用attrs呢,不是更加直观方便吗,带着疑问来看看源码是怎么处理的。
直接看ShapeableImageView的次构造方法:
public class ShapeableImageView extends AppCompatImageView implements Shapeable { ... public ShapeableImageView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(wrap(context, attrs, defStyle, DEF_STYLE_RES), attrs, defStyle); // Ensure we are using the correctly themed context rather than the context that was passed in. context = getContext(); clearPaint = new Paint(); clearPaint.setAntiAlias(true); clearPaint.setColor(Color.WHITE); clearPaint.setXfermode(new PorterDuffXfermode(Mode.DST_OUT)); destination = new RectF(); maskRect = new RectF(); maskPath = new Path(); TypedArray attributes = context.obtainStyledAttributes( attrs, R.styleable.ShapeableImageView, defStyle, DEF_STYLE_RES); strokeColor = MaterialResources.getColorStateList( context, attributes, R.styleable.ShapeableImageView_strokeColor); strokeWidth = attributes.getDimensionPixelSize(R.styleable.ShapeableImageView_strokeWidth, 0); borderPaint = new Paint(); borderPaint.setStyle(Style.STROKE); borderPaint.setAntiAlias(true); shapeAppearanceModel = ShapeAppearanceModel.builder(context, attrs, defStyle, DEF_STYLE_RES).build(); shadowDrawable = new MaterialShapeDrawable(shapeAppearanceModel); if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { setOutlineProvider(new OutlineProvider()); } } }
常规操作,获取自定义属性。
关键的两行代码:
shapeAppearanceModel = ShapeAppearanceModel.builder(context, attrs, defStyle, DEF_STYLE_RES).build(); shadowDrawable = new MaterialShapeDrawable(shapeAppearanceModel);
也就是说我们给shapeAppearance设置的style,并不是ShapeableImageView自己来处理的,而是由ShapeAppearanceModel来构建的,然后又交给MaterialShapeDrawable来绘制的。ShapeAppearanceModel
这个类就厉害了,有点像Flutter中的Decoration,可以构建出花里胡哨的效果。
来看ShapeAppearanceModel部分源码:
public class ShapeAppearanceModel { /** Builder to create instances of {@link ShapeAppearanceModel}s. */ public static final class Builder { @NonNull private CornerTreatment topLeftCorner = MaterialShapeUtils.createDefaultCornerTreatment(); @NonNull private CornerTreatment topRightCorner = MaterialShapeUtils.createDefaultCornerTreatment(); @NonNull private CornerTreatment bottomRightCorner = MaterialShapeUtils.createDefaultCornerTreatment(); @NonNull private CornerTreatment bottomLeftCorner = MaterialShapeUtils.createDefaultCornerTreatment(); @NonNull private CornerSize topLeftCornerSize = new AbsoluteCornerSize(0); @NonNull private CornerSize topRightCornerSize = new AbsoluteCornerSize(0); @NonNull private CornerSize bottomRightCornerSize = new AbsoluteCornerSize(0); @NonNull private CornerSize bottomLeftCornerSize = new AbsoluteCornerSize(0); @NonNull private EdgeTreatment topEdge = MaterialShapeUtils.createDefaultEdgeTreatment(); @NonNull private EdgeTreatment rightEdge = MaterialShapeUtils.createDefaultEdgeTreatment(); @NonNull private EdgeTreatment bottomEdge = MaterialShapeUtils.createDefaultEdgeTreatment(); @NonNull private EdgeTreatment leftEdge = MaterialShapeUtils.createDefaultEdgeTreatment(); public Builder() {} ... } ... }
可以看到有各种边和角的属性,这里注意两个点:
MaterialShapeUtils.createDefaultCornerTreatment() 创建默认角的处理方式MaterialShapeUtils.createDefaultEdgeTreatment() 创建默认边的处理方式
也就意味着,边和角除了默认,是可以自定义的,这就有极大的想象空间了,比如这样:
// 代码设置 角和边 val shapeAppearanceModel2 = ShapeAppearanceModel.builder().apply { setAllCorners(RoundedCornerTreatment()) setAllCornerSizes(50f) setAllEdges(TriangleEdgeTreatment(50f, false)) }.build() val drawable2 = MaterialShapeDrawable(shapeAppearanceModel2).apply { setTint(ContextCompat.getColor(this@ShapeableImageViewActivity, R.color.colorPrimary)) paintStyle = Paint.Style.FILL_AND_STROKE strokeWidth = 50f strokeColor = ContextCompat.getColorStateList(this@ShapeableImageViewActivity, R.color.red) } mBinding.text2.setTextColor(Color.WHITE) mBinding.text2.background = drawable2
再比如这样:
// 代码设置 聊天框效果 val shapeAppearanceModel3 = ShapeAppearanceModel.builder().apply { setAllCorners(RoundedCornerTreatment()) setAllCornerSizes(20f) setRightEdge(object : TriangleEdgeTreatment(20f, false) { // center 位置 , interpolation 角的大小 override fun getEdgePath(length: Float, center: Float, interpolation: Float, shapePath: ShapePath) { super.getEdgePath(length, 35f, interpolation, shapePath) } }) }.build() val drawable3 = MaterialShapeDrawable(shapeAppearanceModel3).apply { setTint(ContextCompat.getColor(this@ShapeableImageViewActivity, R.color.colorPrimary)) paintStyle = Paint.Style.FILL } (mBinding.text3.parent as ViewGroup).clipChildren = false // 不限制子view在其范围内 mBinding.text3.setTextColor(Color.WHITE) mBinding.text3.background = drawable3 MaterialShapeDrawable
源码(有删减):
public class MaterialShapeDrawable extends Drawable implements TintAwareDrawable, Shapeable { ... @Override public void draw(@NonNull Canvas canvas) { fillPaint.setColorFilter(tintFilter); final int prevAlpha = fillPaint.getAlpha(); fillPaint.setAlpha(modulateAlpha(prevAlpha, drawableState.alpha)); strokePaint.setColorFilter(strokeTintFilter); strokePaint.setStrokeWidth(drawableState.strokeWidth); final int prevStrokeAlpha = strokePaint.getAlpha(); strokePaint.setAlpha(modulateAlpha(prevStrokeAlpha, drawableState.alpha)); if (pathDirty) { calculateStrokePath(); calculatePath(getBoundsAsRectF(), path); pathDirty = false; } maybeDrawCompatShadow(canvas); if (hasFill()) { drawFillShape(canvas); } if (hasStroke()) { drawStrokeShape(canvas); } ... static final class MaterialShapeDrawableState extends ConstantState { ... public MaterialShapeDrawableState(@NonNull MaterialShapeDrawableState orig) { shapeAppearanceModel = orig.shapeAppearanceModel; elevationOverlayProvider = orig.elevationOverlayProvider; strokeWidth = orig.strokeWidth; colorFilter = orig.colorFilter; fillColor = orig.fillColor; strokeColor = orig.strokeColor; tintMode = orig.tintMode; tintList = orig.tintList; alpha = orig.alpha; scale = orig.scale; shadowCompatOffset = orig.shadowCompatOffset; shadowCompatMode = orig.shadowCompatMode; useTintColorForShadow = orig.useTintColorForShadow; interpolation = orig.interpolation; parentAbsoluteElevation = orig.parentAbsoluteElevation; elevation = orig.elevation; translationZ = orig.translationZ; shadowCompatRadius = orig.shadowCompatRadius; shadowCompatRotation = orig.shadowCompatRotation; strokeTintList = orig.strokeTintList; paintStyle = orig.paintStyle; if (orig.padding != null) { padding = new Rect(orig.padding); } } ... } ... }
没什么特别的,你只需要知道除了可以设置描边之外,还可以设置背景、阴影等其他属性。说明ShapeAppearanceModel只能是实现Shapeable接口的View才可以设置,比如Chip、MaterialButtom等。而MaterialShapeDrawable其实就是Drawable,是所有View都可以设置的。描边问题
这里借github一张图
又是自定义view的常规操作,有一半画笔是在边界外面的,所以需要设置padding为strokeWidth的一半。默认圆角问题
有细心的同学会发现啊,第一个常规的ShapeableImageView还是有一点圆角的,没错,属于默认的,跟踪一下源码来看一下:
第一个是颜色,很明显不是我们要找的,继续看shapeAppearanceMediumComponent
只是一个简单的属性,继续查找关联引用
- @style/ShapeAppearance.MaterialComponents.MediumComponent
又引用了一个style,继续看ShapeAppearance.MaterialComponents.MediumComponent这个style
哦豁,看到了熟悉的属性cornerSize,藏的还挺深,继续看看数值是多少
4dp
默认4dp。
那如果不想要这个圆角怎么办呢,可以学习源码仿写一个,不过上面也看到了,有点绕,不如直接写个style搞定:
然后引用
app:shapeAppearance="@style/Corner0Style"
效果:
ok,到这里就差不多了,虽然还有很多相关知识点没有提及,但是也不少了,不如自己去尝试一番,慢慢消化。Github
https://github.com/yechaoa/MaterialDesign感谢ShapeableImageView 官方文档ShapeAppearanceModel 官方文档Android Material组件使用详解Android Notes 玩转 ShapeableImageViewMaterial Components——Shape的处理最后
写作不易,如果对你有一丢丢帮助或启发,感谢点赞支持 ^ - ^
当红数播北京师旷AP20到底是哪些地方更优秀北京师旷的AP10一直以来以工作稳定性不错的声音表现以及操作方便和超高的性价比等等优势受到许多音乐爱好者的喜欢。而品牌方最新官宣的升级换代型号AP20相对比于AP10有哪些升级的地
冬日幸存者开发日志UI重制与新增视觉效果大幅提升我们深知视觉设计对于游戏的重要性。因此,在这篇开发日志中,我们想要聊一聊最近一段时间里,我们对冬日幸存者UI的一些改动。我们的新UI可以分成两个部分重制与新增。关于重制部分,我们将
这些千奇百怪的场馆太适合遛娃了!开飞机学木工做动画片逛公园看大展游泳嬉水,日常遛娃玩来玩去也就这么几个主题,小爱已经给大家推荐过无数遍了,有时也会自己问自己,这么大个北京城,难道就没有其他玩法了吗?能让老母亲心甘情愿带娃去,孩子也能
宝妈说带娃抑郁了,监控下却是另一副面孔,宝爸有些懵世界卫生组织研究发现,约6080的女性,会在产后出现不同程度的抑郁情绪。不就是带个孩子吗,有什么好抑郁的,这或许是宝妈最不想听到的一句话了。产后抑郁不是简单地不开心,而且许多女性自
暑期出游莫忘做好风险防范近日,在天津湖北两地同时发生景区游客坠落事故,造成一死一伤,其中一名游客为10岁儿童。当下正值暑假旅游旺季,也是汛期灾害易发时期,景区游乐场安全事故时有发生,这给计划出游的家长和孩
骑次自行车,才更懂荷兰来源环球时报环球时报特约记者葛莉娜自行车王国荷兰多年来不断建设完善自行车道网络和配套设施打造优美宜人的骑行环境,使人们乐于骑行享受骑行。到了荷兰,游客们喜欢租一辆自行车,在城市乡村
奥利教户外徒步篇36户外的不同天气穿衣法则我们要到达的地点和时间在很大程度上决定了我们的户外衣橱的构成在炎热的一天之后,沙漠之夜会变得异常寒冷。我们可能在高山雪原上,在令人惊叹的正午炎热中汗流浃背,然后第二天早上起来发现瓶
图文并茂带你一起探秘长江之源沱沱河美景从唐古拉兵站到达唐古拉山镇大概下午四点左右,著名的沱沱河发源于唐古拉山脉格拉丹东雪山之中,是一些冰川融水逐步汇集而成的小溪流,也就是长江发源地的源头沱沱河,河水从唐古拉山镇贯穿而过
唐锐公使访问马六甲州8月7日至8日,唐锐公使访问马六甲州,其间会见马六甲州旅游文化及遗产事务行政议员贾拉尼,并赴马六甲科技大学(UTeM)出席一带一路大讲堂活动。唐公使在一带一路大讲堂开幕式上致辞表示
哈佛医学院分享的大脑健康饮食方法(全文译自美国CNBC)作为一名营养精神科医生,我一直强调维持均衡的饮食。这在很大程度上与确保我获得所有正确的维生素,特别是因为它对于防止认知能力下降至关重要。鉴于随着年龄的增长,
幼儿园将有大调整,有望明年全部落实,家长听后感到安心现在的社会特别重视学历,没有学历寸步难行,将来的发展也很受限,所以为了让自己的孩子将来可以更好地发展,很多的家长都特别重视孩子的教育问题。甚至在孩子上幼儿园时期就要为孩子挑选好的幼