64

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

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

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}

MyView(Context)用于在代码中实例化视图,并MyView(Context, AttributeSet)在从 XML 膨胀布局时由布局膨胀器调用。

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

工厂方法:

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

或者

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) { ... }

具有默认值的构造函数:

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)

如何解决这个难题?


更新:似乎我们可以使用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) { ... }

或者

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
4

8 回答 8

90

自 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) {}
}

更多信息在这里这里

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

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

但请注意,因为这种方法有时可能会导致意外结果,具体取决于您继承的类如何定义其构造函数。那篇文章很好地解释了可能发生的情况。

于 2015-03-20T14:11:29.623 回答
66

您应该使用注解JvmOverloads(就像在 Kotlin 1.0 中一样),您可以编写如下代码:

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

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

引用自文档

对于每个具有默认值的参数,这将生成一个额外的重载,该重载会删除该参数以及参数列表中该参数右侧的所有参数。

于 2015-07-29T13:50:48.957 回答
12

使用kotlinView定制这里的示例代码。

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)
}

}
于 2018-02-04T20:42:27.987 回答
7

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

class MyView(context: Context, attrs: AttributeSet?) : FooView(context, attrs)

鉴于此 Java 代码:

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

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}

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

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

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
}

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

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

class MyView @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null
) : View(context, attrs)

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

如果你只从 XML 中扩展视图,那么你可以使用最简单的

class MyView(context: Context, attrs: AttributeSet?) : View(context, attrs)

如果您的类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)
}

但是如果你想要一个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)
于 2019-09-27T00:08:03.277 回答
5

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

据我所知,有两种方法可以解决这个问题:

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

val parser = resources.getXml(R.xml.my_view_attrs)
val attrs = Xml.asAttributeSet(parser)
val view = MyView(context, attrs)

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

于 2013-12-19T16:16:47.490 回答
1

有几种方法可以覆盖你的构造函数,

当您需要默认行为时

class MyWebView(context: Context): WebView(context) {
    // code
}

当您需要多个版本时

class MyWebView(context: Context, attr: AttributeSet? = null): WebView(context, attr) {
    // code
}

当你需要在里面使用参数时

class MyWebView(private val context: Context): WebView(context) {
    // you can access context here
}

当您想要更简洁的代码以获得更好的可读性时

class MyWebView: WebView {

    constructor(context: Context): super(context) {
        mContext = context
        setup()
    }

    constructor(context: Context, attr: AttributeSet? = null): super(context, attr) {
        mContext = context
        setup()
    }
}
于 2019-06-11T11:16:49.350 回答
0

添加了通过使用多个构造函数扩展 XML 布局来创建自定义视图的完整示例

class MyCustomView : FrameLayout {
    private val TAG = MyCustomView ::class.simpleName

    constructor(context: Context): super(context) {
        initView()
    }

    constructor(context: Context, attr: AttributeSet? = null): super(context, attr) {
        initView()
    }

    constructor(
        context: Context,
        attrs: AttributeSet?,
        defStyleAttr: Int
    ):   super(context, attrs, defStyleAttr) {
        initView()
    }

    /**
     * init View Here
     */
    private fun initView() {
       val rootView = (context
            .getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater)
            .inflate(R.layout.layout_custom_view, this, true)

       // Load and use rest of views here
       val awesomeBG= rootView.findViewById<ImageView>(R.id.awesomeBG)
      
}

在 XML 中添加您的layout_custom_view视图文件

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

  
    <ImageView
        android:id="@+id/awesomeBG"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:contentDescription="@string/bg_desc"
        android:fitsSystemWindows="true"
        android:scaleType="centerCrop" />

    <!--ADD YOUR VIEWs HERE-->
 
   </FrameLayout>
于 2020-07-24T12:03:45.463 回答
-6

您可以从 JetBrains 尝试新的 Kotlin 库Anko(也可以在github上贡献)。目前它处于测试阶段,但您可以使用此类代码创建视图

    button("Click me") {
         textSize = 18f
         onClick { toast("Clicked!") }
    }

看看这个库

于 2015-04-09T14:18:51.543 回答