簡體   English   中英

如何使用 Kotlin 創建自定義視圖的構造函數

[英]How to create constructor of custom view with Kotlin

我正在嘗試在我的 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

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

但是請注意,這種方法有時可能會導致意外結果,具體取決於您繼承的類如何定義其構造函數。 那篇文章對可能發生的事情進行了很好的解釋。

您應該使用注釋JvmOverloads (就像在 Kotlin 1.0 中一樣),您可以編寫如下代碼:

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

這將按照您最可能想要的方式生成 3 個構造函數。

來自文檔的引用:

對於每個具有默認值的參數,這將生成一個額外的重載,該重載將刪除該參數及其右側的所有參數列表中的參數。

帶有 kotlin 的自定義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)
}

}

這似乎是一個問題。 我從來沒有遇到過這種情況,因為我的自定義視圖要么只在 xml 中創建,要么只在代碼中創建,但我可以看到這會出現在哪里。

據我所知,有兩種方法可以解決這個問題:

1) 使用帶屬性的構造函數。 使用 xml 中的視圖可以正常工作。 在代碼中,您需要使用您的視圖所需的標簽來擴充 xml 資源,並將其轉換為屬性集:

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

2) 使用沒有 attrs 的構造函數。 您不能將視圖直接放置在 xml 中,但是很容易將 FrameLayout 放置在 xml 中並通過代碼將視圖添加到其中。

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)

如果您不打算從 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)

有幾種方法可以覆蓋構造函數,

當您需要默認行為時

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

當您需要多個版本時

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

當你需要在里面使用 params 時

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

super()給了我Primary constructor call expected (Kotlin 1.3.61 )。

class SomeView(
    context: Context,
    attrs: AttributeSet?,
    defStyleAttr: Int,
    defStyleRes: Int
): View(context, attrs, defStyleAttr, defStyleRes) {
    constructor(context: Context) : this(context, null, 0, 0)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : this(context, attrs, defStyleAttr, 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>

看起來,構造函數參數是由類型和順序固定的,但我們可以這樣添加自己的:

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

您可以從 JetBrains 嘗試 Kotlin 的新庫Anko (您也可以在github 上做出貢獻)。 目前它處於測試階段,但您可以使用此類代碼創建視圖

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

看看這個圖書館

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM