前段时间UI提了一个需求,要把RecyclerView的第一个Item始终切成上方圆角java
由于页面是可配置的并不知道RecyclerView的第一个Item是哪个View,android
若是要每一个item都判断的话, 会显得很繁琐,git
因此咱们要寻找一种通用的切割圆角的方式.github
本项目地址RCVIew, 该项目demo是模仿了Android官方Demo- Sunflower写的markdown
在各个博客寻找已有的方案时, 发现现有的切割的方案都有必定的局限性,app
好比GcsSloop大佬 的rclayout, 大佬只封装了两个View, ImageView
以及RelativeLayout
,ide
公司项目中新写的layout
已经大部分都是ConstraintLayout
, 若是要修改全部的布局的话代价太大, 并且自定义View不够灵活.oop
在这时候找到了**API 21+**官方提供的一个API, 并且是View类里面的方法--setOutlineProvider(ViewOutlineProvider provider)
,布局
而后就使用该API的通用性来实现了一个很是通用的圆角布局学习
根据官方APi介绍, 咱们能够了解到ViewOutlineProvider
是设置VIew的投影以及剪切View轮廓, 而且是在Android 5.0添加的.
如今把源码贴上来:
/** * Interface by which a View builds its {@link Outline}, used for shadow casting and clipping. */ public abstract class ViewOutlineProvider { /** * Called to get the provider to populate the Outline. * * This method will be called by a View when its owned Drawables are invalidated, when the * View's size changes, or if {@link View#invalidateOutline()} is called * explicitly. * * The input outline is empty and has an alpha of <code>1.0f</code>. * * @param view The view building the outline. * @param outline The empty outline to be populated. */ public abstract void getOutline(View view, Outline outline); } 复制代码
咱们使用的就是getOutline
来规定View的轮廓, 来实现剪切View, 而且若是view的size变化了, 也会回调该方法, 这样也就能够剪切RecyclerView等size会变化的View
view.outlineProvider = object : ViewOutlineProvider() { override fun getOutline(view: View, outline: Outline) { outline.setRoundRect( 0, 0, view.width, view.height, radius ) } } view.clipToOutline = true 复制代码
view.outlineProvider = object : ViewOutlineProvider() { override fun getOutline(view: View, outline: Outline) { outline.setOval( 0, 0, view.width, view.height ) } } view.clipToOutline = true 复制代码
两句代码咱们就实现了一个四周圆角的View, 是否是很简单方便.
有些同窗会想要直接在xml中设置属性, 因此就有下面的封装
使用DataBiding提供的BIndingAdapter, 能够最灵活, 最通用性的实现圆角布局, 直接贴上代码
@BindingAdapter( "app:usePadding", "app:cornerRadiusSize", "app:cornerRadiusType", requireAll = false ) fun setRoundCornerOutline( view: View, cornerRadiusSize: Float, cornerRadiusType: Int = 0, usePadding: Boolean = false ) { view.outlineProvider = if (usePadding) { getRoundCornerOutlineWithPadding(view.context, cornerRadiusSize, sRadiusType[cornerRadiusType]) } else { getRoundCornerOutline(view.context, cornerRadiusSize, sRadiusType[cornerRadiusType]) } view.clipToOutline = true } 复制代码
ViewUtils.kt
val sRadiusType = arrayOf( RadiusType.ALL, RadiusType.TOP, RadiusType.LEFT, RadiusType.BOTTOM, RadiusType.RIGHT ) fun getRoundCornerOutline( radius: Float, radiusType: RadiusType ): ViewOutlineProvider { val ceilRadius = ceil(radius).toInt() return object : ViewOutlineProvider() { override fun getOutline(view: View, outline: Outline) { when (radiusType) { RadiusType.ALL -> { outline.setRoundRect( 0, 0, view.width - view.paddingLeft, view.height - view.paddingTop, radius ) } RadiusType.TOP -> { outline.setRoundRect( 0, 0, view.width - view.paddingLeft, view.height - view.paddingTop + ceilRadius, radius ) } RadiusType.BOTTOM -> { outline.setRoundRect( 0, -ceilRadius, view.width - view.paddingLeft, view.height - view.paddingTop, radius ) } RadiusType.LEFT -> { outline.setRoundRect( 0, 0, view.width - view.paddingLeft + ceilRadius, view.height - view.paddingTop, radius ) } RadiusType.RIGHT -> { outline.setRoundRect( -ceilRadius, 0, view.width - view.paddingLeft, view.height - view.paddingTop, radius ) } } } } } fun getRoundCornerOutlineWithPadding( radius: Float, radiusType: RadiusType ): ViewOutlineProvider { val ceilRadius = ceil(radius).toInt() return object : ViewOutlineProvider() { override fun getOutline(view: View, outline: Outline) { when (radiusType) { RadiusType.ALL -> { outline.setRoundRect( view.paddingLeft, view.paddingTop, view.width - view.paddingLeft, view.height - view.paddingTop, radius ) } RadiusType.TOP -> { outline.setRoundRect( view.paddingLeft, view.paddingTop, view.width - view.paddingLeft, view.height - view.paddingTop + ceilRadius, radius ) } RadiusType.BOTTOM -> { outline.setRoundRect( view.paddingLeft, view.paddingTop - ceilRadius, view.width - view.paddingLeft, view.height - view.paddingTop, radius ) } RadiusType.LEFT -> { outline.setRoundRect( view.paddingLeft, view.paddingTop, view.width - view.paddingLeft + ceilRadius, view.height - view.paddingTop, radius ) } RadiusType.RIGHT -> { outline.setRoundRect( view.paddingLeft - ceilRadius, view.paddingTop, view.width - view.paddingLeft, view.height - view.paddingTop, radius ) } } } } } 复制代码
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="20dp" android:layout_marginTop="20dp" android:layout_marginEnd="20dp" android:background="#ffffff" app:cornerRadiusSize="@{@dimen/dp4}" app:cornerRadiusType="@{@integer/corner_radius_all}"> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/product_image" android:layout_width="100dp" android:layout_height="100dp" android:layout_marginStart="4dp" android:layout_marginTop="4dp" android:layout_marginBottom="4dp" android:scaleType="centerCrop" app:cornerRadiusSize="@{@dimen/dp4}" app:cornerRadiusType="@{@integer/corner_radius_all}" app:imageUrl="@{product.imageUrl}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> 复制代码
优势:
缺点:
由于BindingAdapter的缘由, 在xml使用的必需要用 @{}
括起来
指定conerRadiusType和cornerRadiusSize的要使用提早定义好的value, 若是直接使用Float, 或者Int,好比
<androidx.appcompat.widget.AppCompatImageView android:id="@+id/product_image" android:layout_width="100dp" android:layout_height="100dp" android:layout_marginStart="4dp" android:layout_marginTop="4dp" android:layout_marginBottom="4dp" android:scaleType="centerCrop" app:cornerRadiusSize="@{8f}" app:cornerRadiusType="@{0}" app:imageUrl="@{product.imageUrl}" /> 复制代码
这样就不便于理解, 因此必须使用提早定义好的value, 可是这样Android Studio又不会自动提示
可能有些同窗在项目中并无使用DataBinding,那么这时候就须要自定义VIew
好比
class RoundCornerImageView( context: Context, attrs: AttributeSet ) : AppCompatImageView(context, attrs) { @Px private var radius: Float = 0f private var radiusType: RadiusType = RadiusType.ALL init { val ta = context.obtainStyledAttributes(attrs, R.styleable.RoundCornerView) radius = ta.getDimension(R.styleable.RoundCornerView_cornerRadiusSize, 0f) val index = ta.getInt(R.styleable.RoundCornerView_cornerRadiusType, 0) radiusType = sRadiusType[index] setRadiusType(radiusType) ta.recycle() } fun setRadiusType(radiusType: RadiusType) { this.radiusType = radiusType outlineProvider = getRoundCornerOutlineWithPadding(radius, radiusType) clipToOutline = true } fun setRadius(@Px radius: Float) { this.radius = radius outlineProvider = getRoundCornerOutline(radius, radiusType) clipToOutline = true } } 复制代码
<io.kailuzhang.github.rcview.RoundCornerImageView android:id="@+id/product_image" android:layout_width="0dp" android:layout_height="0dp" android:layout_margin="8dp" android:scaleType="centerCrop" app:imageUrl="@{product.imageUrl}" app:layout_constraintDimensionRatio="1:1" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:cornerRadiusSize="4dp" app:cornerRadiusType="top" tools:background="#aaa" /> 复制代码
优势:
缺点:
代码实现上方已经写过了, 代码实现优势就是也是很灵活的, 缺点就是仍是要在代码中写的
上方那么多全都是基于ViewOutlineProvider来实现的, 关于该圆角切割的原理我仍是不是很清楚, 看了源码, 最后调用的是方法
@CriticalNative private static native boolean nSetOutlineRoundRect(long renderNode, int left, int top, int right, int bottom, float radius, float alpha); 复制代码
这里是调用的native层的方法, 具体原理也不得而知, 但愿有大神能够告知
Android其实还有更多好用便捷的API, 咱们尚未发现, 因此咱们在学习技术的时候 更多的去看谷歌官方提供的API与文档. 还有看Android官方Demo真的能学到不少.