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

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

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)怎么样?我不知道它在哪里使用,但我看到它在超级类。我需要它吗? 它在哪里使用?

这是 这个问题的另一部分

39204 次浏览

长话短说,No, 但是如果您覆盖了任何构造函数,那么请确保使用完全相同数量的参数调用 super(...)(例如,请参阅 Jin 的回答为什么)。


如果您将添加您的自定义 Viewxml还喜欢:

 <com.mypack.MyView
...
/>

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

如果从 xml中添加 View并指定 android:style属性,如下所示:

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

在应用显式的 XML 属性之前,第2个构造函数也将被调用,并将样式默认为 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 (上下文)

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

MyView (上下文上下文,AttributeSet attrs)

LayoutInflater用于应用 xml 属性。如果其中一个属性名为 style,则在布局 xml 文件中查找显式值之前,将查找样式中的属性。

MyView (上下文上下文,AttributeSet attrs,int defStyleAttr)

假设您希望对所有小部件应用默认样式,而不必在每个布局文件中指定 style。例如,默认情况下将所有复选框设置为粉红色。可以使用 defStyleAttr 实现这一点,框架将在主题中查找默认样式。

请注意,defStyleAttr在一段时间前被错误地命名为 defStyle,并且有一些关于是否真正需要这个构造函数的讨论。见 https://code.google.com/p/android/issues/detail?id=12683

MyView (上下文上下文,AttributeSet attrs,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();
}

总而言之

如果您正在实现自己的视图,那么应该只需要前两个构造函数,并且框架可以调用它们。

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

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

第三个构造函数要复杂得多。

Support-v7 SwitchCompact包支持 thumbTinttrackTint属性,因为24个版本,而23个版本不支持它们。现在你想支持他们在23个版本,你将如何做到这一点?

我们假设使用自定义视图 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>

科特林似乎带走了很多痛苦:

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

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