Android自定义视图是否需要所有三个构造函数?

Android自定义视图是否需要所有三个构造函数?,android,android-custom-view,Android,Android Custom View,创建自定义视图时,我注意到许多人似乎是这样做的: public MyView(Context context) { super(context); // this constructor used when programmatically creating view doAdditionalConstructorWork(); } public MyView(Context context, AttributeSet attrs) { super(context, attrs

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

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(上下文上下文、属性集attrs、int-defStyle)
怎么样?我不确定它在哪里使用,但我在超级课堂上看到了它。我需要它吗?它在哪里使用


如果您要从
xml
添加自定义的
视图
,则会出现。

,如:

 <com.mypack.MyView
      ...
      />
在应用显式XML属性之前,还将调用第二个构造函数,并将样式默认为
MyCustomStyle


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

如果您必须包含三个构造函数(如现在讨论的构造函数),也可以这样做

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

}

如果覆盖所有三个构造函数,请不要级联
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.

MyView(context-context) 以编程方式实例化视图时使用

MyView(上下文、属性集属性) 由
LayoutInflater
用于应用xml属性。如果此属性中有一个名为
style
,则在布局xml文件中查找显式值之前,将先在样式中查找属性

MyView(上下文上下文、属性集属性、int-defStyleAttr) 假设您希望对所有小部件应用默认样式,而不必在每个布局文件中指定
style
。例如,默认情况下将所有复选框设为粉红色。您可以使用defStyleAttr执行此操作,框架将在主题中查找默认样式

请注意,
defstylettr
在一段时间前被错误地命名为
defStyle
,关于是否真的需要此构造函数存在一些讨论。看

MyView(上下文上下文、属性集属性、int-defStyleAttr、int-defStyleRes) 如果您能够控制应用程序的基本主题,那么第三个构造函数可以很好地工作。这对谷歌来说是可行的,因为他们会将自己的小部件放在默认主题旁边。但是,假设您正在编写一个小部件库,并且希望在用户不需要调整主题的情况下设置默认样式。现在,您可以使用
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();
}
总而言之 如果您正在实现自己的视图,那么只需要两个第一个构造函数,框架就可以调用它们

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


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

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

Support-v7
SwitchCompact
软件包支持
thumbTint
trackTint
属性,因为24版本而23版本不支持它们。现在您希望在23版本中支持它们,您将如何实现这一点

我们假设使用自定义视图
支持的SwitchCompact
扩展
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传递给第三个参数。运行代码时,您会发现
getthumbdravable()
总是返回null,这有多奇怪,因为方法
getthumbdravable()
是它的超类
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>

@style/Widget.CompoundButton.Switch

Kotlin似乎消除了很多这种痛苦:

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

@JVMLowloads将生成所有必需的构造函数(请参见注释),每个构造函数可能都调用super()。然后,简单地用Kotlin init{}块替换您的初始化方法。样板代码消失了

那么什么时候使用第一个构造函数?@OvidiuLatcu你能展示一个第三个构造函数的例子吗(有3个参数)?我能给构造函数添加额外的参数吗?我如何使用它们?关于第三个构造函数,这实际上是完全错误的。XML总是调用双参数构造函数。如果三个参数(和)构造函数想要指定一个包含默认样式的属性,或者直接指定一个默认样式(在四参数构造函数的情况下),则子类会调用它们。我刚刚提交了一个编辑以使答案正确。下面我还提出了一个备选答案。这是扩展ListView时的基本建议。作为上述级联的(先前)风扇,我记得当我为每个构造函数调用正确的超级方法时,我花了几个小时追踪一个微妙的错误,这个错误消失了。顺便说一句,我在回答中使用了代码:这似乎是基于你的答案-但请注意,作者包括了充气器的使用?我认为没有必要在所有构造函数中调用init,因为当您按照调用层次结构进行操作时,您最终将进入默认的构造函数,用于编程视图创建反正视图(上下文上下文){}我正在执行相同的操作,但未能在textview中设置值,该值在我要设置的自定义视图中可用
class MyView
@JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
    : View(context, attrs, defStyle)