问题 如何在Android-Scala应用程序中扩展ImageView?


我通过关键字在谷歌尝试了许多解决方案:多个构造函数,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,是的,它必须是一个类。

target是一个扩展ImageView的CustomView,所有这些工作:

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

如果您需要进一步澄清这个棘手的事情,请告诉我!


4237
2017-12-11 16:59


起源

您能提供需要CustomView成为类的特定示例吗? - yakshaver
当然是!在android中的常规布局中你写了一些xml。 Google Android sdk会解析此xml并创建一个CustomView实例。它通过执行(可能)这样做 new CustomView(context, attribute set)。在我自己的部分代码中,我必须创建一个没有属性的CustomView,所以我必须调用 new CustomView(context)。 (不!将null传递给属性集将无法完成工作) - Georgios Pligoropoulos


答案:


根据 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)

更新:

如果你试着打电话给它会有点复杂 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)
  }
}

0
2017-08-04 20:02





根据马丁奥德斯基(Scala的创造者)的说法,这是不可能的。

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

“有没有办法在不同的范围内调用不同的超级构造函数   class-constructors - 或者只需要进入main-constructor   支持一个超级构造函数?

不,它必须通过主构造函数。这是一个细节   Scala比Java更具限制性。“

我认为你最好的方法是用Java实现你的观点。


7
2017-12-16 16:27





听起来你可能只是更好地在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)

好像很多工作。根据您的目标,您可以使用implicit添加其他行为:

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

5
2017-12-12 17:53



我看到......使用三参数构造函数会抛出异常,因为在sdk中有一个这样的调用: new CustomView(context, attributes)。所以你肯定需要为sdk定制CustomView(2 params)和CustomView(3 params)。值得庆幸的是,第三个参数的默认值只是零,所以你可以拥有这两个参数。现在,如果sdk中有一些代码,我还没有使用过那些调用 new CustomView(context) ?! - Georgios Pligoropoulos
你将失去运气并留下两个更明智的选择:Java或暗示。 - yakshaver


这个问题让我想到几个设计考虑因素,我想与大家分享。

我首先要考虑的是,如果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
    }
    

1
2017-12-20 10:52



我完全同意你提到的设计注意事项。但是,在这种情况下,它不会对他有多大帮助,因为Android使用反射构造视图实例(这就是为什么需要所有三个构造函数)。他的班级必须实施 ImageView 为了避免 ClassCastExceptions;当然,他可以有一个他可以正确构建的内部实例,但他也必须重定向所有 ImageView该实例的方法,这是一种负担。在这之间和用Java编写他的课程...... - Rui Gonçalves
谢谢你的回答我觉得它总体上是有帮助的!但是是的,android sdk让我们: Caused by: android.view.InflateException: Binary XML file line #43: Class is not a View views.CardView 我将在其他类型的项目中牢记未来的设计。也许对android代码进行一些重构,这样我们可以按照你提到的那样正确设计一个类是最好的 - Georgios Pligoropoulos
感谢Rui的评论。你是对的 ! Android SDK是否有任何理由使用不同的构造函数创建具有反射的实例?如果您提供单个构造函数会发生什么? - Edmondo1984
@ Edmondo1984正如您所见,XML文件中的不同属性集会导致对不同构造函数的调用 这里。我不知道为什么他们没有只定义三参数构造函数,因为它足以满足我能想象的所有用例...... - Rui Gonçalves