Подтвердить что ты не робот

Как создать конструктор нестандартного представления с Kotlin

Я пытаюсь использовать Kotlin в своем Android-проекте. Мне нужно создать собственный класс представления. Каждый пользовательский вид имеет два важных конструктора:

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.

Ответ на этот вопрос предполагает, что я использую конструктор со значениями по умолчанию или factory. Но вот что мы имеем:

Factory:

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)

Как решить эту проблему?


UPDATE: Похоже, мы можем использовать конструктор View(Context, null) суперкласса вместо View(Context), поэтому метод метода factory представляется решением. Но даже тогда я не могу заставить свой код работать:

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
4b9b3361

Ответ 1

Kotlin поддерживает несколько конструкторов начиная с M11, который был выпущен 19.03.2015. Синтаксис выглядит следующим образом:

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)

Однако остерегайтесь, так как этот подход может иногда приводить к неожиданным результатам, в зависимости от того, как класс, который вы наследуете, определяет его конструкторы. Хорошее объяснение того, что может произойти, дается в этой статье.

Ответ 2

Вы должны использовать аннотацию JvmOverloads (как это выглядит в Kotlin 1.0), вы можете написать код следующим образом:

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

Это создаст 3 конструктора, как вы, скорее всего, захотите.

Цитата из docs:

Для каждого параметра со значением по умолчанию это будет генерировать один дополнительная перегрузка, которая имеет этот параметр и все параметры для справа от него в списке параметров удалено.

Ответ 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)
}

}

Ответ 4

Это похоже на проблему. Я никогда не сталкивался с этим, потому что мои пользовательские представления либо были созданы только в xml, либо только в коде, но я вижу, где это будет выглядеть.

Насколько я вижу, есть два способа обойти это:

1) Используйте конструктор с attrs. Использование представления в 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 и добавить представление к нему через код.

Ответ 5

Есть несколько способов переопределить ваши конструкторы,

Когда вам нужно поведение по умолчанию

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

Ответ 6

Учитывая этот код Java:

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

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

его котлинский эквивалент будет использовать вторичные конструкторы:

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)

И если вы только раздуваете представления из 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)

Ответ 7

Вы можете попробовать новую библиотеку Anko для Kotlin из JetBrains (также вы можете внести вклад в github). В настоящее время он находится в бета-версии, но вы можете создавать представления с таким кодом

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

Посмотрите на эту библиотеку