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)