我正在尝试在我的 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
Kotlin 自 2015 年 3 月 19 日发布 M11 以来支持多个构造函数。语法如下:
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)
但请注意,这种方法有时可能会导致意外结果,具体取决于您继承的类如何定义其构造函数。 那篇文章中对可能发生的情况给出了很好的解释。
你应该使用注释
JvmOverloads
(就像Kotlin 1.0中的那样),你可以编写这样的代码:
class CustomView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : View(context, attrs, defStyle)
这将生成 3 个构造函数,正如您最可能想要的那样。
引自文档:
对于每个具有默认值的参数,这将生成一个 额外的重载,它有这个参数和所有参数 参数列表中右侧已删除。
Custome
View
使用 kotlin 这里是示例代码。
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)
}
}
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
类时。
您可以使用带有默认参数的主构造函数,否则:
class MyView(
context: Context,
attrs: AttributeSet? = null
) : View(context, attrs)
如果需要从 Java 调用它,可以将
@JvmOverloads
添加到 constructor
。
并且 如果您只从 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)
这似乎确实是一个问题。我从来没有遇到过这个问题,因为我的自定义视图要么仅在 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 中并通过代码向其中添加视图。
有多种方法可以覆盖构造函数,
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()
}
}
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>
看起来,构造函数参数是由类型和顺序固定的,但我们可以像这样添加自己的:
class UpperMenu @JvmOverloads
constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0,parentLayout: Int,seeToolbar: Boolean? = false)
: Toolbar(context, attrs, defStyleAttr) {}
其中
parentLayout ,seeToolbar
添加到其中,因此:
val upper= UpperMenu (this,null,0,R.id.mainParent, true)
当你有一些视图(BottomSheetDialog)已经可以显示文本并想要添加格式化字符串时,你应该添加两个构造函数。
class SomeDialog : BottomSheetDialog {
private val binding = DialogSomeBinding.inflate(layoutInflater)
// Base constructor that cannot be called directly
private constructor(
context: Context,
title: CharSequence
) : super(context) {
setContentView(binding.root)
binding.title.text = title
}
// Constructor with simple CharSequence message
constructor(
context: Context,
title: CharSequence,
message: CharSequence
) : this(context, title) {
binding.message.text = message
}
// Constructor with formatted SpannableString message
constructor(
context: Context,
title: CharSequence,
message: SpannableString
) : this(context, title) {
binding.message.text = message
}
}
用途:
val span = SpannableString(getString(R.string.message, name))
...
SomeDialog(
context = requireContext(),
title = getString(R.string.title),
message = span
).show()