兩行代碼實現任意View圓角 - RCView

背景

前段時間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的通用性來實現了一個很是通用的圓角佈局學習

ViewOutlineProvider介紹

​ 根據官方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

view.outlineProvider = object : ViewOutlineProvider() {
    override fun getOutline(view: View, outline: Outline) {
        outline.setRoundRect(
            0,
            0,
            view.width,
            view.height,
            radius
        )
    }
}
view.clipToOutline = true
複製代碼

剪切圓形View

view.outlineProvider = object : ViewOutlineProvider() {
    override fun getOutline(view: View, outline: Outline) {
        outline.setOval(
            0,
            0,
            view.width,
            view.height
        )
    }
}
view.clipToOutline = true
複製代碼

兩句代碼咱們就實現了一個四周圓角的View, 是否是很簡單方便.

有些同窗會想要直接在xml中設置屬性, 因此就有下面的封裝

RCView

1.使用DataBinding

使用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
                    )
                }
            }
        }
    }
}
複製代碼

xml中使用

<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>
複製代碼

優勢:

  1. 無需多餘的封裝, 使用自定義View, 便可適用於任意View切割圓角
  2. 使用簡單, 執行效率高

缺點:

  1. 由於BindingAdapter的緣由, 在xml使用的必需要用 @{} 括起來

  2. 指定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又不會自動提示

2.使用自定義View

可能有些同窗在項目中並無使用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" />
複製代碼

優勢:

  1. 無需使用DataBinding
  2. 寫法與一般寫的xml相同

缺點:

  1. 沒須要給一個View圓角都要自定義一個View, 不夠靈活

3. kotlin代碼實現

代碼實現上方已經寫過了, 代碼實現優勢就是也是很靈活的, 缺點就是仍是要在代碼中寫的

原理

​ 上方那麼多全都是基於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真的能學到不少.

相關文章
相關標籤/搜索