使用Kotlin自定义Android视图

And*_*nko 41 android constructor kotlin

我正在尝试在我的Android项目中使用Kotlin.我需要创建自定义视图类.每个自定义视图都有两个重要的构造函数

public class MyView extends View {
    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}
Run Code Online (Sandbox Code Playgroud)

MyView(Context)用于在代码中实例化视图,并MyView(Context, AttributeSet)在从XML 扩展布局时由布局inflater调用.

这个问题的回答表明我使用默认值或工厂方法的构造函数.但这就是我们所拥有的:

工厂方法:

fun MyView(c: Context) = MyView(c, attrs) //attrs is nowhere to get
class MyView(c: Context, attrs: AttributeSet) : View(c, attrs) { ... }
Run Code Online (Sandbox Code Playgroud)

要么

fun MyView(c: Context, attrs: AttributeSet) = MyView(c) //no way to pass attrs.
                                                        //layout inflater can't use 
                                                        //factory methods
class MyView(c: Context) : View(c) { ... }
Run Code Online (Sandbox Code Playgroud)

具有默认值的构造方法:

class MyView(c: Context, attrs: AttributeSet? = null) : View(c, attrs) { ... }
//here compiler complains that 
//"None of the following functions can be called with the arguments supplied."
//because I specify AttributeSet as nullable, which it can't be.
//Anyway, View(Context,null) is not equivalent to View(Context,AttributeSet)
Run Code Online (Sandbox Code Playgroud)

如何解决这个难题?


更新:似乎我们可以使用View(Context, null)超类构造函数代替View(Context),因此工厂方法方法似乎是解决方案.但即使这样我也无法使用我的代码:

fun MyView(c: Context) = MyView(c, null) //compilation error here, attrs can't be null
class MyView(c: Context, attrs: AttributeSet) : View(c, attrs) { ... }
Run Code Online (Sandbox Code Playgroud)

要么

fun MyView(c: Context) = MyView(c, null) 
class MyView(c: Context, attrs: AttributeSet?) : View(c, attrs) { ... }
//compilation error: "None of the following functions can be called with 
//the arguments supplied." attrs in superclass constructor is non-null
Run Code Online (Sandbox Code Playgroud)

col*_*iot 52

你应该使用注释JvmOverloads(就像在Kotlin 1.0中看起来一样),你可以编写如下代码:

class CustomView @JvmOverloads constructor(
    context: Context, 
    attrs: AttributeSet? = null, 
    defStyle: Int = 0
) : View(context, attrs, defStyle)
Run Code Online (Sandbox Code Playgroud)

这将生成3个构造函数,就像您最想要的那样.

文档引用:

对于具有默认值的每个参数,这将生成一个额外的重载,该重载具有此参数,并且删除了参数列表中右侧的所有参数.

  • 这种方式比接受的答案更优雅. (4认同)
  • "...考虑到这个解决方案对于所有类型的视图都不可行.其中一些(例如`TextView`)需要调用`super` parent来初始化样式." - [使用Kotlin安装自定义视图(KAD 06)](https://antonioleiva.com/custom-views-android-kotlin/) (4认同)

aga*_*aga 43

自2015年3月19日发布的M11以来,Kotlin支持多个构造函数.语法如下:

class MyView : View {
    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
        // ...
    }

    constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0) {}
}
Run Code Online (Sandbox Code Playgroud)

更多信息在这里这里.

编辑:您还可以使用@JvmOverloads注释,以便Kotlin为您自动生成所需的构造函数:

class MyView @JvmOverloads constructor(
    context: Context, 
    attrs: AttributeSet? = null, 
    defStyle: Int = 0
) : View(context, attrs, defStyle)
Run Code Online (Sandbox Code Playgroud)

但要注意,因为这种方法有时会导致意外结果,具体取决于您继承的类如何定义其构造函数.在该文章中给出了可能发生的事情的良好解释.


Sag*_*hva 6

Custome View这里科特林的示例代码.

class TextViewLight : TextView {

constructor(context: Context) : super(context){
    val typeface = ResourcesCompat.getFont(context, R.font.ccbackbeat_light_5);
    setTypeface(typeface)
}

constructor(context: Context, attrs : AttributeSet) : super(context,attrs){
    val typeface = ResourcesCompat.getFont(context, R.font.ccbackbeat_light_5);
    setTypeface(typeface)
}

constructor(context: Context,  attrs: AttributeSet , defStyleAttr : Int) : super(context, attrs, defStyleAttr){
    val typeface = ResourcesCompat.getFont(context, R.font.ccbackbeat_light_5);
    setTypeface(typeface)
}

}
Run Code Online (Sandbox Code Playgroud)


ajs*_*vig 5

这确实是一个问题.我从来没有遇到过这种情况,因为我的自定义视图要么只在xml中创建,要么只在代码中创建,但我可以看到它会出现在哪里.

据我所知,有两种解决方法:

1)使用带有attrs的构造函数.使用xml中的视图可以正常工作.在代码中,您需要使用所需的视图标记为xml资源充气,并将其转换为属性集:

val parser = resources.getXml(R.xml.my_view_attrs)
val attrs = Xml.asAttributeSet(parser)
val view = MyView(context, attrs)
Run Code Online (Sandbox Code Playgroud)

2)使用没有attrs的构造函数.您不能将视图直接放在xml中,但很容易将FrameLayout放在xml中并通过代码将视图添加到其中.

  • 为了它的价值,Kotlin M11增加了对多个构造函数的支持:http://blog.jetbrains.com/kotlin/2015/03/kotlin-m11-is-out/ (2认同)

are*_*lek 5

TL;DR大多数时候,只需将自定义视图定义为:

class MyView(context: Context, attrs: AttributeSet?) : FooView(context, attrs)
Run Code Online (Sandbox Code Playgroud)

鉴于此 Java 代码:

public final class MyView extends View {
    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}
Run Code Online (Sandbox Code Playgroud)

它的 Kotlin 等效项将使用辅助构造函数:

class MyView : View {
    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
}
Run Code Online (Sandbox Code Playgroud)

当您确实想根据视图是在代码中创建还是从 XML 膨胀来调用不同的超类构造函数时,该语法很有用。我所知道的唯一情况是当您View直接扩展类时。

您可以使用带有默认参数和@JvmOverloads注释的主构造函数,否则:

class MyView @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null
) : View(context, attrs)
Run Code Online (Sandbox Code Playgroud)

你并不需要@JvmOverloads constructor,如果你不打算从Java调用它。

如果你只从XML膨胀的观点,那么你可以去从最简单的

class MyView(context: Context, attrs: AttributeSet?) : View(context, attrs)
Run Code Online (Sandbox Code Playgroud)

如果您的类open用于扩展并且您需要保留父类的样式,则您想回到仅使用辅助构造函数的第一个变体:

open class MyView : View {
    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
}
Run Code Online (Sandbox Code Playgroud)

但是如果你想要一个open覆盖父样式并让它的子类也覆盖它的类,你应该没问题@JvmOverloads

open class MyView @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = R.attr.customStyle,
        defStyleRes: Int = R.style.CustomStyle
) : View(context, attrs, defStyleAttr, defStyleRes)
Run Code Online (Sandbox Code Playgroud)