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

在创build自定义视图时,我注意到很多人似乎都这样做:

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)呢? 我不确定它在哪里使用,但我在超级课堂上看到它。 我需要它,它在哪里使用?

这个问题还有另外一部分 。

如果你将添加自定义Viewxml也像:

  <com.mypack.MyView ... /> 

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

如果你添加了从xmlView ,并且还指定了android:style属性,如:

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

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

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

如果你重写所有三个构造函数,请不要CASCADE 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作为样式属性。

MyView(上下文上下文)

以编程方式使用instanciating Views时使用。

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

LayoutInflater来应用xml属性。 如果其中一个属性被命名为style ,则在查找layout xml文件中的显式值之前,将查找属性。

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

假设您想要将默认样式应用于所有小部件,而无需在每个布局文件中指定style 。 举个例子,默认情况下,所有checkbox都是粉红色的。 你可以用defStyleAttr来做到这一点,框架将在你的主题中查找默认样式。

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

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

如果你能控制应用程序的基本主题,第三个构造函数可以很好地工作。 这是谷歌工作,因为他们将他们的小部件边默认主题。 但是,假设您正在编写一个小部件库,并且您希望设置一个默认样式,而无需用户调整其主题。 您现在可以使用defStyleRes将其设置为2个第一个构造函数中的默认值来执行此操作:

 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软件包支持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传递给第三个参数,一切都会好的。那么为什么呢?

第三个参数是一个简单的属性。 该属性指向一个样式资源。在上面的情况下,系统会在当前主题中findswitchStyle属性,幸运的是系统find它。

frameworks/base/core/res/res/values/themes.xml ,你会看到:

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

如果你现在需要包含三个构造函数,你也可以这样做。

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