我是否需要所有三个构造函数才能使用Android自定义视图?


142

创建自定义视图时,我注意到许多人似乎是这样的:

public MyView(Context context) {
  super(context);
  // this constructor used when programmatically creating view
  doAdditionalConstructorWork();
}

public MyView(Context context, AttributeSet attrs) {
  super(context, attrs);
  // this constructor used when creating view through XML
  doAdditionalConstructorWork();
}

private void doAdditionalConstructorWork() {

  // init variables etc.
}

我的第一个问题是,构造函数MyView(Context context, AttributeSet attrs, int defStyle)呢?我不确定在哪里使用它,但是我在超类中看到了它。我需要它吗?在哪里使用?

这个问题还有另外一部分

Answers:


144

如果您也要View从添加自定义,xml例如:

 <com.mypack.MyView
      ...
      />

您将需要构造器public MyView(Context context, AttributeSet attrs),否则Exception当Android试图给您的容器充气时,您会得到一个View

如果您添加Viewfrom xml并且还指定android:style属性,例如:

 <com.mypack.MyView
      style="@styles/MyCustomStyle"
      ...
      />

第二个构造函数也将被调用,并MyCustomStyle在应用显式XML属性之前将样式默认为。

当您希望应用程序中的所有视图具有相同的样式时,通常使用第三个构造函数。


3
什么时候使用第一个构造函数呢?
Android Killer

@OvidiuLatcu您可以显示第三个CTOR的示例(带有3个参数)吗?
android开发人员

如何在构造函数中添加额外的参数,以及如何使用它们?
Mohammed Subhi Sheikh Quroush

24
关于第三个构造函数,这实际上是完全错误的。XML 始终调用两个参数的构造函数。如果三参数(和四参数)构造函数要指定包含默认样式或直接包含默认样式的属性(对于四参数构造函数),则由子类调用它们
imgx64 2015年

我刚刚提交了修改以使答案正确。我还在下面提出了另一种答案。
mbonnin

117

如果覆盖所有三个构造函数,请不要进行级联this(...)调用。您应该改为这样做:

public MyView(Context context) {
    super(context);
    init(context, null, 0);
}

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

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

private void init(Context context, AttributeSet attrs, int defStyle) {
    // do additional work
}

原因是父类可能在其自己的构造函数中包含默认属性,而您可能会意外覆盖它们。例如,这是用于的构造函数TextView

public TextView(Context context) {
    this(context, null);
}

public TextView(Context context, @Nullable AttributeSet attrs) {
    this(context, attrs, com.android.internal.R.attr.textViewStyle);
}

public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    this(context, attrs, defStyleAttr, 0);
}

如果不调用super(context),则不会正确设置R.attr.textViewStyle为样式attr。


12
这是扩展ListView时的基本建议。作为上述级联的(先前)粉丝,我记得花了数小时来追踪一个细微的错误,当我为每个构造函数调用正确的super方法时,该错误便消失了。
Groovee60 2015年

顺便说一句@Jin我在此答案中使用了代码:stackoverflow.com/a/22780035/294884,这似乎是基于您的答案的-但请注意,作者包括使用充气机吗?
Fattie

1
我认为并不需要在所有构造函数中都调用init,因为当您遵循调用层次结构时,无论如何,您最终都会使用默认的构造函数来创建编程视图View(Context context){}
MarianKlühspies16

即时通讯在做同样的事情,但未能在textview中设置值,这在我的自定义视图中可用,我想从活动中设置值
Erum,

1
我怎么不知道
Suragch

49

MyView(上下文上下文)

以编程方式实例化视图时使用。

MyView(上下文上下文,AttributeSet属性)

由所使用,LayoutInflater以应用xml属性。如果此属性之一名为style,则将在样式中查找属性,然后在布局xml文件中查找显式值。

MyView(Context context,AttributeSet attrs,int defStyleAttr)

假设您要对所有小部件应用默认样式,而不必style在每个布局文件中指定。例如,将所有复选框默认设置为粉红色。您可以使用defStyleAttr进行此操作,框架将在您的主题中查找默认样式。

请注意,它defStyleAttr是在前defStyle一段时间被错误命名的,关于是否确实需要此构造函数有一些讨论。请参阅https://code.google.com/p/android/issues/detail?id=12683

MyView(Context context,AttributeSet attrs,int defStyleAttr,int defStyleRes)

如果您可以控制应用程序的基本主题,则第3个构造函数可以很好地工作。对于Google来说,这是可行的,因为他们将其小部件与默认主题一起运送。但是假设您正在编写一个小部件库,并且希望设置默认样式而无需用户调整其主题。现在,您可以defStyleRes通过将其设置为前两个构造函数中的默认值来使用它:

public MyView(Context context) {
  super(context, null, 0, R.style.MyViewStyle);
  init();
}

public MyView(Context context, AttributeSet attrs) {
  super(context, attrs, 0, R.style.MyViewStyle);
  init();
}

总而言之

如果要实现自己的视图,则只需要两个第一个构造函数即可,并且可以由框架调用。

如果希望View是可扩展的,则可以为类的子类实现第4个构造函数,以便能够使用全局样式。

我没有看到第3个构造函数的实际用例。如果您不为小部件提供默认样式,但仍希望用户能够提供默认样式,则可能是一种快捷方式。应该不会发生那么多。


7

Kotlin似乎减轻了许多痛苦:

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

@JvmOverloads将生成所有必需的构造函数(请参见该注释的文档),每个构造函数都可能调用super()。然后,只需将您的初始化方法替换为Kotlin init {}块。样板代码不见了!


1

第三个构造函数要复杂得多。让我举个例子。

Support-v7 SwitchCompact软件包从24版本开始支持thumbTinttrackTint属性,而23版本不支持它们。现在,您想在23版本中支持它们,您将如何实现呢?

我们假设使用自定义View SupportedSwitchCompact扩展SwitchCompact

public SupportedSwitchCompat(Context context) {
    this(context, null);
}

public SupportedSwitchCompat(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public SupportedSwitchCompat(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
}

private void init(){
    mThumbDrawable = getThumbDrawable();
    mTrackDrawable = getTrackDrawable();
    applyTint();
}

这是传统的代码样式。注意,我们在这里将0传递给第三个参数。运行代码时,您会发现getThumbDrawable()总是返回null多么奇怪,因为该方法getThumbDrawable()是其超类SwitchCompact的方法。

如果您通过R.attr.switchStyle了第三个参数,一切都会顺利进行,那为什么呢?

第三个参数是一个简单的属性。属性指向样式资源。在上述情况下,系统会switchStyle在当前主题中找到属性,所幸系统会找到它。

在中frameworks/base/core/res/res/values/themes.xml,您将看到:

<style name="Theme">
    <item name="switchStyle">@style/Widget.CompoundButton.Switch</item>
</style>

-2

如果您必须像现在讨论的那样包括三个构造函数,那么您也可以这样做。

public MyView(Context context) {
  this(context,null,0);
}

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

public MyView(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
  doAdditionalConstructorWork();

}

2
@Jin在许多情况下是个好主意,但在许多情况下也很安全(例如:RelativeLayout,FrameLayout,RecyclerView等)。因此,我想这可能是一个逐案的要求,在决定是否进行级联之前应检查基类。本质上,如果基类中的2-param构造函数只是调用this(context,attrs,0),那么在自定义视图类中这样做也是安全的。
ejw

@IanWong当然会被调用,因为第一个和第二个方法正在调用第三个方法。
CoolMind
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.