Kotlin实战填坑

1,828 阅读17分钟

话说,怎么才算实战?在此之前我已经使用Kotlin写过几个项目了,从最初的一边看文档一边面向QQ群编程,到现在自己也可以和众人侃侃而谈Kotlin的基础语法,那么问题来了——我为什么现在才开始叫“Kotlin实战”呢?因为我之前敲代码都是规规矩矩没有任何问题,但是最近在使用一个项目的时候,遇到了不少问题,因此做一个总结,希望可以帮到其他人。

项目简介

先给大家简单介绍一下项目的来源:重构UIWidgetRadiusView.为什么要重构呢?因为在使用过程中觉得这个模块的功能很强大,但是在使用的过程中有一些默认属性不是很熟悉,甚至觉得是bug,于是打算熟悉此此项目并重构并放到自己的项目模板中。但是我在重构过程中遇到一些困难:

  • 泛型使用问题
  • 类的继承问题

泛型使用问题

我对泛型的了解,仅限于Java中的泛型方法、泛型类,觉得没有什么难度,于是看见代码直接干。先看看java代码是什么样的?

public class RadiusViewDelegate<T extends RadiusViewDelegate> {
    ...
}

soSeay,这个类的声明确实很简单,于是我三下五除二的很快写好了,代码如下:

咦?咋不行呢?没事,我还有一招,javaKotlin代码,巴拉巴拉魔法变:

咋这招也不灵呢?不是说好java可以做的事情,Kotlin也可以做的吗?原来童话都是骗人的?

童话没有骗人,是讲童话的人骗了你!我们尝试修改一下泛型呢?

open class RadiusViewDelegate<T : RadiusViewDelegate<T>>(
    val a: View,
    open val b: Context,
    open val c: AttributeSet?
)
open class B<T : RadiusViewDelegate<T>>(a: View, b: Context, c: AttributeSet?) :RadiusViewDelegate<T >(a,b,c){

}

看来童话确实是存在的,Java可以干的活Kotlin照样能干!但是还有一个问题,如何声明B这个对象呢?直接写肯定是不行的,不信的话你可以试一下,当然,我也可以给你截一张图:

那么我们应该怎么解决呢?不是说好童话是存在的吗?我们尝试一下声明C继承B然后使用C这个对象,代码如下:

class C(a: View, b: Context, c: AttributeSet?):B<C>(a, b, c){

}
val b = C(radiusSwitch,this,null)

看来这样是可以的,那么我们为什么要通过继承来实现呢?或者,我们为什么要使用这样的泛型呢?回答了第二个问题,第一个问题都不是问题了。此处使用泛型的原因是为了使子类可以链式调用父类的方法。众所周知,为了链式调用,我们可以对调用的方法增加一个返回值this,这样就可以实现链式调用了。但是涉及子类继承呢?子类调用父类的方法返回的是父类,虽然可以强转也不会报错,但是这样强转以后的“链式调用”还算链式调用吗?话说回来,我们怎么通过增加一个泛型实现链式调用呢? 我们在顶层父类增加一个返回this的方法,然后包括子类所有需要返回this的地方都调用这个方法即可。

open class RadiusViewDelegate<T : RadiusViewDelegate<T>>(
    val a: View,
    open val b: Context,
    open val c: AttributeSet?
){
      fun back(): T {
        return this as T
    }
}

到这里我们就解决了为什么使用泛型、怎么使用泛型,但是我们还没有认真去了解一下什么是泛型?知其然,知其所以然才能更好的了解童话是不是骗人的,因此我们去看看官方文档

Java 类型系统中最棘手的部分之一是通配符类型(参见 Java Generics FAQ)。 而 Kotlin 中没有。 相反,它有两个其他的东西:声明处型变(declaration-site variance)与类型投影(type projections)。

看完以后,第一个感觉就是我们此处的泛型属于声明处型变,并且我们的泛型约束还可以换一种写法,因此修改一下代码:

open class RadiusViewDelegate<out T> @JvmOverloads constructor(
    val view: View,
    val context: Context,
    val attrs: AttributeSet? = null
) where T : RadiusViewDelegate<T> {
    fun back(): T {
        return this as T
    }
}

这里的链式调用有童鞋建议说可以使用构造者模式和接口来实现,优点是在back方法中可以避免这种强行转换的方式,但是这种写法基本上我们的RadiusViewDelegate及其子类都需要一个单独的接口,我认为代码可读性有下降,因此没有继续纠结,有兴趣的朋友可以尝试一下。

类的继承

Kotlin当中,一个类允许被继承只需要在class前添加一个关键字open,使用的时候用:替换java中的extends即可。一句话的事情,我为什么会单独拿出来说事呢?因为在使用过程中我遇到了两个坑。

构造方法参数的关键字

我写了一个类RadiusTextDelegate继承自上文中的RadiusViewDelegate,在使用的时候发现一个非常奇葩的问题:其父类RadiusViewDelegate的代码报错,但是单独使用其父类RadiusViewDelegate的时候一切正常。报错代码如下:

 val mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.RadiusView)
 
 //日志
 java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.TypedArray android.content.Context.obtainStyledAttributes(android.util.AttributeSet, int[])' on a null object reference
        at com.vincent.baselibrary.widget.radius.delegate.RadiusViewDelegate.<init>(RadiusViewDelegate.kt:96)

添加断点检查的时候,父类和子类的参数都一样,貌似均不为空。

就是这个断点,让我误以为contextattrs均不为空,后来经过请教朱凯大佬才明白attrs已经为空了! 我们再来看看这个源码:

open class RadiusViewDelegate<out T> @JvmOverloads constructor(
    val view: View,
    open val context: Context,
    open val attrs: AttributeSet? = null
) where T : RadiusViewDelegate<T> {
      init {
        val mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.RadiusView)
        initAttributes(mTypedArray)

        ...
    }
    ...
}

open class RadiusTextDelegate<T> constructor(
    private val textView: TextView,
    override val context: Context,
    override val attrs: AttributeSet?
) :
    RadiusViewDelegate<T>(textView, context, attrs) where T : RadiusViewDelegate<T> {
        
    }

看了一下源码更加不明白,为什么contextattrs传给父类后就空了呢?通过面向QQ群编程发现无效,后来尝试将Kotlin代码转化为Java代码后发现了问题:

package com.vincent.baseproject.ui;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@Metadata(
   mv = {1, 1, 15},
   bv = {1, 0, 3},
   k = 1,
   d1 = {"\u0000 \n\u0002\u0018\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0006\b\u0016\u0018\u0000*\u000e\b\u0000\u0010\u0001*\b\u0012\u0004\u0012\u0002H\u00010\u00022\b\u0012\u0004\u0012\u0002H\u00010\u0002B\u001f\u0012\u0006\u0010\u0003\u001a\u00020\u0004\u0012\u0006\u0010\u0005\u001a\u00020\u0006\u0012\b\u0010\u0007\u001a\u0004\u0018\u00010\b¢\u0006\u0002\u0010\tR\u0016\u0010\u0007\u001a\u0004\u0018\u00010\bX\u0096\u0004¢\u0006\b\n\u0000\u001a\u0004\b\n\u0010\u000bR\u0014\u0010\u0005\u001a\u00020\u0006X\u0096\u0004¢\u0006\b\n\u0000\u001a\u0004\b\f\u0010\rR\u000e\u0010\u0003\u001a\u00020\u0004X\u0082\u0004¢\u0006\u0002\n\u0000¨\u0006\u000e"},
   d2 = {"Lcom/vincent/baseproject/ui/RadiusTextDelegate;", "T", "Lcom/vincent/baseproject/ui/RadiusViewDelegate;", "textView", "Landroid/widget/TextView;", "context", "Landroid/content/Context;", "attrs", "Landroid/util/AttributeSet;", "(Landroid/widget/TextView;Landroid/content/Context;Landroid/util/AttributeSet;)V", "getAttrs", "()Landroid/util/AttributeSet;", "getContext", "()Landroid/content/Context;", "app_debug"}
)
public class RadiusTextDelegate extends RadiusViewDelegate {
   private final TextView textView;
   @NotNull
   private final Context context;
   @Nullable
   private final AttributeSet attrs;

   @NotNull
   public Context getContext() {
      return this.context;
   }

   @Nullable
   public AttributeSet getAttrs() {
      return this.attrs;
   }

   public RadiusTextDelegate(@NotNull TextView textView, @NotNull Context context, @Nullable AttributeSet attrs) {
      Intrinsics.checkParameterIsNotNull(textView, "textView");
      Intrinsics.checkParameterIsNotNull(context, "context");
      super((View)textView, context, attrs);
      this.textView = textView;
      this.context = context;
      this.attrs = attrs;
   }
}
// RadiusViewDelegate.java
package com.vincent.baseproject.ui;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import com.vincent.baseproject.R.styleable;
import kotlin.Metadata;
import kotlin.jvm.JvmOverloads;
import kotlin.jvm.internal.DefaultConstructorMarker;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@Metadata(
   mv = {1, 1, 15},
   bv = {1, 0, 3},
   k = 1,
   d1 = {"\u0000,\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\b\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\b\u0016\u0018\u0000*\u0010\b\u0000\u0010\u0001 \u0001*\b\u0012\u0004\u0012\u0002H\u00010\u00002\u00020\u0002B#\b\u0007\u0012\u0006\u0010\u0003\u001a\u00020\u0004\u0012\u0006\u0010\u0005\u001a\u00020\u0006\u0012\n\b\u0002\u0010\u0007\u001a\u0004\u0018\u00010\b¢\u0006\u0002\u0010\tJ\u0012\u0010\u0010\u001a\u00020\u00112\b\u0010\u0012\u001a\u0004\u0018\u00010\u0013H\u0002R\u0016\u0010\u0007\u001a\u0004\u0018\u00010\bX\u0096\u0004¢\u0006\b\n\u0000\u001a\u0004\b\n\u0010\u000bR\u0014\u0010\u0005\u001a\u00020\u0006X\u0096\u0004¢\u0006\b\n\u0000\u001a\u0004\b\f\u0010\rR\u0011\u0010\u0003\u001a\u00020\u0004¢\u0006\b\n\u0000\u001a\u0004\b\u000e\u0010\u000f¨\u0006\u0014"},
   d2 = {"Lcom/vincent/baseproject/ui/RadiusViewDelegate;", "T", "", "view", "Landroid/view/View;", "context", "Landroid/content/Context;", "attrs", "Landroid/util/AttributeSet;", "(Landroid/view/View;Landroid/content/Context;Landroid/util/AttributeSet;)V", "getAttrs", "()Landroid/util/AttributeSet;", "getContext", "()Landroid/content/Context;", "getView", "()Landroid/view/View;", "initAttributes", "", "typedArray", "Landroid/content/res/TypedArray;", "app_debug"}
)
public class RadiusViewDelegate {
   @NotNull
   private final View view;
   @NotNull
   private final Context context;
   @Nullable
   private final AttributeSet attrs;

   private final void initAttributes(TypedArray typedArray) {
   }

   @NotNull
   public final View getView() {
      return this.view;
   }

   @NotNull
   public Context getContext() {
      return this.context;
   }

   @Nullable
   public AttributeSet getAttrs() {
      return this.attrs;
   }

   @JvmOverloads
   public RadiusViewDelegate(@NotNull View view, @NotNull Context context, @Nullable AttributeSet attrs) {
      Intrinsics.checkParameterIsNotNull(view, "view");
      Intrinsics.checkParameterIsNotNull(context, "context");
      super();
      this.view = view;
      this.context = context;
      this.attrs = attrs;
      TypedArray typedArray = this.getContext().obtainStyledAttributes(this.getAttrs(), styleable.RadiusView);
      this.initAttributes(typedArray);
   }

   // $FF: synthetic method
   public RadiusViewDelegate(View var1, Context var2, AttributeSet var3, int var4, DefaultConstructorMarker var5) {
      if ((var4 & 4) != 0) {
         var3 = (AttributeSet)null;
      }

      this(var1, var2, var3);
   }

   @JvmOverloads
   public RadiusViewDelegate(@NotNull View view, @NotNull Context context) {
      this(view, context, (AttributeSet)null, 4, (DefaultConstructorMarker)null);
   }
}

仔细查看RadiusViewDelegate的构造方法RadiusViewDelegate(@NotNull View view, @NotNull Context context, @Nullable AttributeSet attrs)方法下第七行代码得知,contextattrs并不是使用构造方法的参数,而是通过get方法获取的,而问题就出在get方法。因为RadiusTextDelegate的构造方法参数context使用了两个关键字override val,所以将该文件转成Java代码后子类生成了如下方法:

 @NotNull
   public Context getContext() {
      return this.context;
   }

话说回来,父类通过getContext()方法获取Context时,子类还没有来得及将参数赋值给成员变量this.context,因此就产生了异常'android.content.res.TypedArray android.content.Context.obtainStyledAttributes(android.util.AttributeSet, int[])' on a null object reference。 解决方案很简单,将子类的构造方法参数修改一下即可:

open class RadiusViewDelegate<out T> @JvmOverloads constructor(
    val view: View,
    val context: Context,
    val attrs: AttributeSet? = null
) where T : RadiusViewDelegate<T> {
    
}

open class RadiusTextDelegate<T> constructor(
    private val textView: TextView,
    context: Context,
    attrs: AttributeSet?
) :
    RadiusViewDelegate<T>(textView, context, attrs) where T : RadiusViewDelegate<T> {
        
    }

通过上面的例子可以简单的说一下构造方法参数关键字的用法,valvar就不解释了,open代表子类会重写父类的get方法和set方法(如果是var类型参数),而子类如果使用和父类完全一致的参数名称,则不能不添加override关键字。添加override关键字以后,如果父类的参数是var则子类也不能修改为val类型,反之,子类构造方法参数的类型则不受限制了。

构造方法的关键字

一般我们在自定义View的时候,常见构造方法有如下两种有隐患的写法,如下:

/// 第一种
class CountdownView: TextView{
    constructor(context: Context) : this(context,null)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs,0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
}
/// 第二种
class CountdownView @JvmOverloads constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : TextView(context, attrs, defStyleAttr) {
    
}

我在自定义RadiusSwitch的时候使用了第二种构造方法,结果遇到了一堆异常。

java.lang.NullPointerException: Attempt to invoke interface method 'int java.lang.CharSequence.length()' on a null object reference
        at android.widget.Switch.makeLayout(Switch.java:896)
        at android.widget.Switch.onMeasure(Switch.java:815)

网上百度、谷歌找到以下几种解决方案:

  • theme设置错误——未发现错误
  • Context设置错误——断点检查是当前Activity
  • 添加textOfftextOn属性——添加以后Switch的样式显示有问题,但是能解决闪退。

通过和原生的Switch添加断点对比以后发现是mShowText未被设置为true,但是通过xml文件配置属性可解决。接下来又发现Switch的点击事件无效了。最后查看源码发现是clickable也被设置为false,导致点击事件无效。当然,也可以通过xml配置属性解决。 但是始终是没有找到问题的根源,最后再次检查源码发现原来是构造方法使用错误,因为我调用的是Switch(Context context, AttributeSet attrs, int defStyleAttr),而xml布局文件中的控件是使用的构造方法应该是Switch(Context context, AttributeSet attrs),而二者的构造方法区别在于下面:

public Switch(Context context, AttributeSet attrs) {
        this(context, attrs, com.android.internal.R.attr.switchStyle);
    }
public Switch(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

找到错误的根本原因以后,解决就很简单了:当然不再是xml配置上面提到的这些默认属性了,而是修改构造方法,如下:

class RadiusSwitch : Switch {
    var delegate: RadiusSwitchDelegateImp? = null

    constructor(context: Context) : super(context) {
        delegate = RadiusSwitchDelegateImp(this, context, null)
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs,0) {
        delegate = RadiusSwitchDelegateImp(this, context, attrs)
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr) {
        delegate = RadiusSwitchDelegateImp(this, context, attrs)
    }
}

项目使用

到此实战填坑就结束了,接下来还是给大家看看这个模块的效果图,然后告诉大家怎么使用。

效果图1

效果图2

效果图3

     <!--View默认样式-->
    
    <!-- 圆角矩形背景色 -->
    <attr name="rv_backgroundColor" format="color"/>
    <!-- 圆角矩形背景色Pressed -->
    <attr name="rv_backgroundPressedColor" format="color"/>
    <!-- 圆角矩形背景色Disabled -->
    <attr name="rv_backgroundDisabledColor" format="color"/>
    <!-- 圆角矩形背景色Selected -->
    <attr name="rv_backgroundSelectedColor" format="color"/>
    <!-- 圆角矩形背景色Checked -->
    <attr name="rv_backgroundCheckedColor" format="color"/>
    <!-- 圆角矩形背景色按下状态透明度(0-255默认0 仅当未设置backgroundPressedColor有效) -->
    <attr name="rv_backgroundPressedAlpha" format="integer"/>
    <!-- 圆角边框颜色-->
    <attr name="rv_strokeColor" format="color"/>
    <!-- 圆角边框颜色Pressed -->
    <attr name="rv_strokePressedColor" format="color"/>
    <!-- 圆角边框颜色Disabled -->
    <attr name="rv_strokeDisabledColor" format="color"/>
    <!-- 圆角边框颜色Selected -->
    <attr name="rv_strokeSelectedColor" format="color"/>
    <!-- 圆角边框颜色Checked -->
    <attr name="rv_strokeCheckedColor" format="color"/>
    <!-- 圆角矩形边框色按下状态透明度(0-255默认0 仅当未设置strokePressedColor有效) -->
    <attr name="rv_strokePressedAlpha" format="integer"/>

    <!-- 圆角线框,单位dp-->
    <attr name="rv_strokeWidth" format="dimension"/>
    <!-- 圆角线框虚线的宽度,单位dp-->
    <attr name="rv_strokeDashWidth" format="dimension"/>
    <!-- 圆角线框虚线的间隙,单位dp-->
    <attr name="rv_strokeDashGap" format="dimension"/>

    <!-- 圆角弧度是高度一半-->
    <attr name="rv_radiusHalfHeightEnable" format="boolean"/>
    <!-- 圆角矩形宽高相等,取较宽高中大值-->
    <attr name="rv_widthHeightEqualEnable" format="boolean"/>
    <!-- 圆角弧度,单位dp-->
    <attr name="rv_radius" format="dimension"/>
    <!-- 圆角弧度,单位dp,TopLeft-->
    <attr name="rv_topLeftRadius" format="dimension"/>
    <!-- 圆角弧度,单位dp,TopRight-->
    <attr name="rv_topRightRadius" format="dimension"/>
    <!-- 圆角弧度,单位dp,BottomLeft-->
    <attr name="rv_bottomLeftRadius" format="dimension"/>
    <!-- 圆角弧度,单位dp,BottomRight-->
    <attr name="rv_bottomRightRadius" format="dimension"/>
    <!-- 水波纹颜色-->
    <attr name="rv_rippleColor" format="color"/>
    <!-- 是否有Ripple效果,api21+有效-->
    <attr name="rv_rippleEnable" format="boolean"/>
    <!--View选中状态-->
    <attr name="rv_selected" format="boolean"/>
    <!--View背景状态进入状态延时(非水波纹)-->
    <attr name="rv_enterFadeDuration" format="integer"/>
    <!--View背景状态退出状态延时(非水波纹)-->
    <attr name="rv_exitFadeDuration" format="integer"/>

    <!-- 文字颜色-->
    <attr name="rv_textColor" format="color"/>
    <!-- 文字颜色Pressed-->
    <attr name="rv_textPressedColor" format="color"/>
    <!-- 文字颜色Disabled-->
    <attr name="rv_textDisabledColor" format="color"/>
    <!-- 文字颜色Selected-->
    <attr name="rv_textSelectedColor" format="color"/>
    <!-- 文字颜色Checked-->
    <attr name="rv_textCheckedColor" format="color"/>

    <!--设置EditText在调用setText后默认光标置于末尾-->
    <attr name="rv_selectionEndEnable" format="boolean"/>
    <!--设置EditText在调用setText后默认光标置于末尾只执行一次-->
    <attr name="rv_selectionEndOnceEnable" format="boolean"/>

    <!--以下为TextView Drawable Left Top Right Bottom 宽高及各种状态属性-->
    <!--是否系统自带drawableLeft样式-->
    <attr name="rv_leftDrawableSystemEnable" format="boolean"/>
    <!--设置drawable为颜色值(ColorDrawable)时的圆角弧度-->
    <attr name="rv_leftDrawableColorRadius" format="dimension"/>
    <!--设置drawable为颜色值(ColorDrawable)时是否圆形-->
    <attr name="rv_leftDrawableColorCircleEnable" format="boolean"/>
    <!--drawable宽高属性当drawable为颜色值(ColorDrawable)时必须设置否则显示不出-->
    <attr name="rv_leftDrawableWidth" format="dimension"/>
    <attr name="rv_leftDrawableHeight" format="dimension"/>
    <attr name="rv_leftDrawable" format="reference|color"/>
    <attr name="rv_leftPressedDrawable" format="reference|color"/>
    <attr name="rv_leftDisabledDrawable" format="reference|color"/>
    <attr name="rv_leftSelectedDrawable" format="reference|color"/>
    <attr name="rv_leftCheckedDrawable" format="reference|color"/>

    <!--drawable宽高属性当drawable为颜色值(ColorDrawable)时必须设置否则显示不出-->
    <!--是否系统自带drawableTop样式-->
    <attr name="rv_topDrawableSystemEnable" format="boolean"/>
    <!--设置drawable为颜色值(ColorDrawable)时的圆角弧度-->
    <attr name="rv_topDrawableColorRadius" format="dimension"/>
    <!--设置drawable为颜色值(ColorDrawable)时是否圆形-->
    <attr name="rv_topDrawableColorCircleEnable" format="boolean"/>
    <!--drawable宽高属性当drawable为颜色值(ColorDrawable)时必须设置否则显示不出-->
    <attr name="rv_topDrawableWidth" format="dimension"/>
    <attr name="rv_topDrawableHeight" format="dimension"/>
    <attr name="rv_topDrawable" format="reference|color"/>
    <attr name="rv_topPressedDrawable" format="reference|color"/>
    <attr name="rv_topDisabledDrawable" format="reference|color"/>
    <attr name="rv_topSelectedDrawable" format="reference|color"/>
    <attr name="rv_topCheckedDrawable" format="reference|color"/>

    <!--drawable宽高属性当drawable为颜色值(ColorDrawable)时必须设置否则显示不出-->
    <!--是否系统自带drawableRight样式-->
    <attr name="rv_rightDrawableSystemEnable" format="boolean"/>
    <!--设置drawable为颜色值(ColorDrawable)时的圆角弧度-->
    <attr name="rv_rightDrawableColorRadius" format="dimension"/>
    <!--设置drawable为颜色值(ColorDrawable)时是否圆形-->
    <attr name="rv_rightDrawableColorCircleEnable" format="boolean"/>
    <!--drawable宽高属性当drawable为颜色值(ColorDrawable)时必须设置否则显示不出-->
    <attr name="rv_rightDrawableWidth" format="dimension"/>
    <attr name="rv_rightDrawableHeight" format="dimension"/>
    <attr name="rv_rightDrawable" format="reference|color"/>
    <attr name="rv_rightPressedDrawable" format="reference|color"/>
    <attr name="rv_rightDisabledDrawable" format="reference|color"/>
    <attr name="rv_rightSelectedDrawable" format="reference|color"/>
    <attr name="rv_rightCheckedDrawable" format="reference|color"/>

    <!--drawable宽高属性当drawable为颜色值(ColorDrawable)时必须设置否则显示不出-->
    <!--是否系统自带drawableBottom样式-->
    <attr name="rv_bottomDrawableSystemEnable" format="boolean"/>
    <!--设置drawable为颜色值(ColorDrawable)时的圆角弧度-->
    <attr name="rv_bottomDrawableColorRadius" format="dimension"/>
    <!--设置drawable为颜色值(ColorDrawable)时是否圆形-->
    <attr name="rv_bottomDrawableColorCircleEnable" format="boolean"/>
    <!--drawable宽高属性当drawable为颜色值(ColorDrawable)时必须设置否则显示不出-->
    <attr name="rv_bottomDrawableWidth" format="dimension"/>
    <attr name="rv_bottomDrawableHeight" format="dimension"/>
    <attr name="rv_bottomDrawable" format="reference|color"/>
    <attr name="rv_bottomPressedDrawable" format="reference|color"/>
    <attr name="rv_bottomDisabledDrawable" format="reference|color"/>
    <attr name="rv_bottomSelectedDrawable" format="reference|color"/>
    <attr name="rv_bottomCheckedDrawable" format="reference|color"/>
    <!--以上为TextView Drawable Left Top Right Bottom 宽高及各种状态属性-->

    <!--以下为CompoundButton ButtonDrawable 宽高及不同状态属性-->
    <!--drawable宽高属性当drawable为颜色值(ColorDrawable)时方有效-->
    <!--是否系统自带Button样式-->
    <attr name="rv_buttonDrawableSystemEnable" format="boolean"/>
    <!--设置drawable为颜色值(ColorDrawable)时的圆角弧度-->
    <attr name="rv_buttonDrawableColorRadius" format="dimension"/>
    <!--设置drawable为颜色值(ColorDrawable)时是否圆形-->
    <attr name="rv_buttonDrawableColorCircleEnable" format="boolean"/>
    <attr name="rv_buttonDrawableWidth" format="dimension"/>
    <attr name="rv_buttonDrawableHeight" format="dimension"/>
    <attr name="rv_buttonDrawable" format="reference|color"/>
    <attr name="rv_buttonPressedDrawable" format="reference|color"/>
    <attr name="rv_buttonDisabledDrawable" format="reference|color"/>
    <attr name="rv_buttonSelectedDrawable" format="reference|color"/>
    <attr name="rv_buttonCheckedDrawable" format="reference|color"/>

以上属性也可以通过代码来设置,如下:

 rtv_javaBg.delegate?.run {
            this.setTextCheckedColor(Color.BLUE)
            .setBackgroundCheckedColor(Color.WHITE)
            .setRadius(resources.getDimension(R.dimen.dp_radius))
            .setStrokeWidth(resources.getDimensionPixelSize(R.dimen.dp_stroke_width))
            .setStrokeColor(ContextCompat.getColor(this@RadiusActivity,android.R.color.holo_purple))
            .setStrokeDashWidth(resources.getDimension(R.dimen.dp_dash_width))
            .setStrokeDashGap(resources.getDimension(R.dimen.dp_dash_gap))
            .initShape()
        }

以上为个人理解,水平有限,还请各位斧正!

源码

示例

参考:泛型