Как расширить ImageView в приложении Android-Scala?

Я пробовал множество решений, найденных в google по ключевым словам: несколько конструкторов, scala, наследование, подклассы.

Похоже, что это не работает. ImageView имеет три конструктора:

ImageView(context)
ImageView(context,attribute set)
ImageView(context,attribute set, style)

В scala вы можете расширить только один из них. И решение использования более полного конструктора (ImageView(context,attribute set, style)) и передачи значений по умолчанию не работает либо потому, что конструктор ImageView(context) делает нечто совершенно иное, чем два других конструктора.

Некоторые решения использования признака или объекта-компаньона не работают, потому что CustomView должен быть классом! Я имею в виду, что я не единственный, кто использует этот класс (так что я мог бы написать код scala любым способом, который я хотел), есть также android-sdk, который использует этот класс, и да, он должен быть классом.

Цель состоит в том, чтобы иметь CustomView, который расширяет ImageView и все эти работы:

new CustomView(context)
new CustomView(context,attribute set)
new CustomView(context,attribute set, style)

Пожалуйста, дайте мне знать, если вам нужно какое-либо уточнение по этому сложному вопросу!

Ответ 1

В соответствии с Android View документация View(Context) используется при построении из кода и View(Context, AttributeSet) и View(Context, AttributeSet, int) (и поскольку уровень API 21 View(Context, AttributeSet, int, int)) используется, когда View надувается из XML.

Конструктор XML просто вызывает один и тот же конструктор, тот, который содержит большинство аргументов, которые являются единственными с любой реальной реализацией, поэтому мы можем использовать аргументы по умолчанию в Scala. С другой стороны, у "конструктора кода" может быть другая реализация, поэтому лучше вообще позвонить из Scala.

Следующая реализация может быть решением:

private trait MyViewTrait extends View {
  // implementation
} 

class MyView(context: Context, attrs: AttributeSet, defStyle: Int = 0)
    extends View(context, attrs, defStyle) with MyViewTrait {}

object MyView {
  def apply(context: Context) = new View(context) with MyViewTrait
}

Затем может быть использован "конструктор кода", например:

var myView = MyView(context)

(не реальный конструктор).

И еще один раз:

var myView2 = new MyView(context, attrs)
var myView3 = new MyView(context, attrs, defStyle)

который SDK ожидает от них.

Аналогично для уровня API 21 и выше class можно определить как:

class MyView(context: Context, attrs: AttributeSet, defStyle: Int = 0, defStyleRes: Int = 0)
    extends View(context, attrs, defStyle, defStyleRes) with MyViewTrait {}

а четвертый конструктор можно использовать как:

var myView4 = new MyView(context, attrs, defStyle, defStyleRes)

Update:

Это становится немного сложнее, если вы попытаетесь вызвать метод protected в View, например setMeasuredDimension(int, int) из trait. Защищенные Java-методы не могут быть вызваны из признаков. Обходным путем является реализация аксессора в реализациях class и object:

private trait MyViewTrait extends View {
  protected def setMeasuredDimensionAccessor(w: Int, h: Int): Unit
  def callingSetMeasuredDimensionAccessor(): Unit = {
    setMeasuredDimensionAccessor(1, 2)
  }
} 

class MyView(context: Context, attrs: AttributeSet, defStyle: Int = 0)
    extends View(context, attrs, defStyle) with MyViewTrait {
  override protected def setMeasuredDimensionAccessor(w: Int, h: Int) =
    setMeasuredDimension(w, h)
}

object MyView {
  def apply(context: Context) = new View(context) with MyViewTrait {
    override protected def setMeasuredDimensionAccessor(w: Int, h: Int) =
      setMeasuredDimension(w, h)
  }
}

Ответ 2

По словам Мартина Одерского (создателя Scala), это невозможно.

В http://scala-programming-language.1934581.n4.nabble.com/scala-calling-different-super-constructors-td1994456.html:

"существует способ вызова разных суперконструкторов в разных class-constructors - или все должны перейти к основному конструктору и только поддерживается один супер-конструктор?

Нет, он должен пройти через главный конструктор. Эта деталь, где Scala более ограничительный, чем Java.

Я думаю, что ваш лучший подход - реализовать свои взгляды на Java.

Ответ 3

Похоже, вам лучше писать подкласс в Java. В противном случае, если ваше предположение о SDK с использованием трех конструкторов аргументов корректно, вы можете использовать черту и класс со своим сопутствующим объектом. SDK будет использовать три конструктора аргументов CustomView, которые также реализуют черту, содержащую любое дополнительное поведение, которое вам нужно:

trait TCustomView {
  // additional behavior here
}

final class CustomView(context: Context, attributes: AttributeSet, style: Int)
  extends ImageView(context, attributes, style) with TCustomView

В коде приложения вы можете использовать одну, две или три версии аргументов следующим образом:

object CustomView {
  def apply(c: Context, a: AttributeSet, s: Int) =
    new ImageView(c, a, s) with TCustomView
  def apply(context: Context, attributes: AttributeSet) =
    new ImageView(context, attributes) with TCustomView
  def apply(context: Context) = new ImageView(context) with TCustomView
}

CustomView(context)
CustomView(context, attributes)
CustomView(context, attributes, style)

Кажется, что много работы. В зависимости от ваших целей вы можете добавить дополнительное поведение с implicits:

implicit def imageViewToCustomView(view: ImageView) = new {
  def foo = ...
}

Ответ 4

Этот вопрос привел меня к нескольким соображениям дизайна, которые я хотел бы поделиться с вами.

Мое первое соображение состоит в том, что если класс Java был правильно спроектирован, наличие нескольких конструкторов должно быть признаком того, что некоторые свойства класса могут иметь значение по умолчанию.

Scala предоставляет значения по умолчанию как функцию языка, поэтому вам не нужно беспокоиться о предоставлении нескольких конструкторов вообще, вы можете просто предоставить значение по умолчанию для некоторые аргументы вашего конструктора. Такой подход приводит к значительно более чистому API, чем к трем различным конструкторам, которые производят такое поведение, поскольку вы четко указываете, почему вам не нужно указывать все параметры.

Мое второе соображение состоит в том, что это не всегда применимо, если вы расширяете классы сторонней библиотеки. Однако:

  • Если вы расширяете класс библиотеки с открытым исходным кодом и класс правильно разработан, вы можете просто исследовать значения по умолчанию аргумента конструктора
  • если вы расширяете класс, который неправильно настроен (т.е. когда перегруженные конструкторы не являются API-интерфейсом по умолчанию для некоторых параметров) или где у вас нет доступа к источнику, вы можете заменить наследование композицией и обеспечить неявное преобразование.

    class CustomView(c: Context, a: Option[AttributeSet]=None, s: Option[Int]=None){
    
       private val underlyingView:ImageView = if(a.isDefined)
                                                if (s.isDefined)
                                                    new ImageView(c,a.get,s.get)
                                                else
                                                    new ImageView(c,a.get)
                                            else
                                                new ImageView(c)
    }
    
    object CustomView {
        implicit def asImageView(customView:CustomView):ImageView = customView.underlyingView
    }