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)
怎么样?我不确定它在哪里使用,但我在超级类中看到它。我需要它吗?它在哪里使用?
When creating a custom view, I have noticed that many people seem to do it like this:
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.
}
My first question is, what about the constructor MyView(Context context, AttributeSet attrs, int defStyle)
? I'm not sure where it is used, but I see it in the super class. Do I need it, and where is it used?
There's another part to this question.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
长话短说,不,但是如果您确实重写了任何构造函数,那么请确保使用完全相同数量的参数调用
super(...)
(例如,请参阅 < a href="https://stackoverflow.com/a/34301725/8740349">Jin 的回答 例如为什么)。如果您要从
xml
添加自定义View
也类似:您将需要构造函数
public MyView(Context context, AttributeSet attrs)
,否则您当 Android 尝试膨胀您的View
时,将会收到Exception
。如果您从
xml
添加View
并指定android:style
属性,例如:还将调用第二个构造函数,并将样式默认为应用显式 XML 属性之前的
MyCustomStyle
。当您希望应用程序中的所有视图具有相同的样式时,通常使用第三个构造函数。
Long story short, No, but if you do override any constructor, then ensure to call
super(...)
with the exact same number of arguments (like, see Jin's answer for example why).If you will add your custom
View
fromxml
also like :you will need the constructor
public MyView(Context context, AttributeSet attrs)
, otherwise you will get anException
when Android tries to inflate yourView
.If you add your
View
fromxml
and also specify theandroid:style
attribute like :the 2nd constructor will also be called and default the style to
MyCustomStyle
before applying explicit XML attributes.The third constructor is usually used when you want all of the Views in your application to have the same style.
如果您重写所有三个构造函数,请不要级联
this(...)
调用。相反,您应该这样做:原因是父类可能在其自己的构造函数中包含默认属性,您可能会意外覆盖这些默认属性。例如,这是
TextView
的构造函数:如果您没有调用
super(context)
,则无法正确设置R.attr.textViewStyle 作为样式属性。
If you override all three constructors, please DO NOT CASCADE
this(...)
CALLS. You should instead be doing this:The reason is that the parent class might include default attributes in its own constructors that you might be accidentally overriding. For example, this is the constructor for
TextView
:If you did not call
super(context)
, you would not have properly setR.attr.textViewStyle
as the style attr.MyView(Context context)
以编程方式实例化视图时使用。
MyView(Context context, AttributeSet attrs)
由 LayoutInflater 用来应用 xml 属性。如果此属性之一名为
style
,则在布局 xml 文件中查找显式值之前,将先查找样式属性。MyView(Context context, AttributeSet attrs, int defStyleAttr)
假设您想要将默认样式应用于所有小部件,而不必在每个布局文件中指定
style
。例如,默认情况下将所有复选框设置为粉红色。您可以使用 defStyleAttr 来执行此操作,框架将查找主题中的默认样式。请注意,
defStyleAttr
不久前被错误地命名为defStyle
,并且存在一些关于是否确实需要此构造函数的讨论。请参阅 https://code.google.com/p/android/issues/ detail?id=12683MyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
如果您可以控制应用程序的基本主题,则第三个构造函数可以很好地工作。这对谷歌来说很有效,因为他们将小部件与默认主题一起发布。但是假设您正在编写一个小部件库,并且您希望设置默认样式,而不需要用户调整他们的主题。现在,您可以使用 defStyleRes 来执行此操作,方法是将其设置为前 2 个构造函数中的默认值:
总而言之,
如果您要实现自己的视图,则只需要前 2 个构造函数,并且可以将其设置为由框架调用。
如果您希望视图可扩展,您可以为类的子级实现第四个构造函数,以便能够使用全局样式。
我没有看到第三个构造函数的真正用例。如果您没有为小部件提供默认样式但仍希望用户能够这样做,这可能是一个快捷方式。不应该发生那么多。
MyView(Context context)
Used when instanciating Views programmatically.
MyView(Context context, AttributeSet attrs)
Used by the
LayoutInflater
to apply xml attributes. If one of this attribute is namedstyle
, attributes will be looked up the the style before looking for explicit values in the layout xml file.MyView(Context context, AttributeSet attrs, int defStyleAttr)
Suppose you want to apply a default style to all widgets without having to specify
style
in each layout file. For an example make all checkboxes pink by default. You can do this with defStyleAttr and the framework will lookup the default style in your theme.Note that
defStyleAttr
was incorrectly nameddefStyle
some time ago and there is some discussion about whether this constructor is really needed or not. See https://code.google.com/p/android/issues/detail?id=12683MyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
The 3rd constructor works well if you have control over the base theme of the applications. That is working for google because they ship their widgets along side the default Themes. But suppose you're writing a widget library and you want a default style to be set without your users needing to tweak their theme. You can now do this using
defStyleRes
by setting it to the default value in the 2 first constructors:All in all
If you're implementing your own views, only the 2 first constructors should be needed and can be called by the framework.
If you want your Views to be extensible, you might implement the 4th constructor for children of your class to be able to use global styling.
I don't see a real use case for the 3rd constructor. Maybe a shortcut if you don't provide a default style for your widget but still want your users to be able to do so. Shouldn't happen that much.
Kotlin 似乎消除了很多这种痛苦:
@JvmOverloads 将生成所有必需的构造函数(请参阅该注释的 文档),其中每个都可能调用 super()。然后,只需将初始化方法替换为 Kotlin init {} 块即可。样板代码不见了!
Kotlin seems to take away a lot of this pain:
@JvmOverloads will generate all required constructors (see that annotation's documentation), each of which presumably calls super(). Then, simply replace your initialization method with a Kotlin init {} block. Boilerplate code gone!
第三个构造函数要复杂得多。让我举个例子。
Support-v7
SwitchCompact
包从 24 版本开始支持thumbTint
和trackTint
属性,而 23 版本不支持它们。现在你想在 23 中支持它们版本以及您将如何实现这一目标?我们假设使用自定义视图
SupportedSwitchCompact
扩展SwitchCompact
。这是传统的代码风格。请注意,我们将 0 传递给此处的第三个参数。当你运行代码时,你会发现
getThumbDrawable()
总是返回 null 这是多么奇怪,因为方法getThumbDrawable()
是它的超类SwitchCompact
的方法。如果将
R.attr.switchStyle
传递给第三个参数,一切都会顺利。那为什么呢?第三个参数是一个简单的属性。该属性指向样式资源。在上面的情况下,系统会在当前主题中找到
switchStyle
属性,幸运的是系统找到了它。在
frameworks/base/core/res/res 中/values/themes.xml
,您将看到:The third constructor is much more complicated.Let me hold an example.
Support-v7
SwitchCompact
package supportsthumbTint
andtrackTint
attribute since 24 version while 23 version does not support them.Now you want to support them in 23 version and how will you do to achieve this?We assume to use custom View
SupportedSwitchCompact
extendsSwitchCompact
.It's a traditional code style.Note we pass 0 to the third param here. When you run the code, you will find
getThumbDrawable()
always return null how strange it is because the methodgetThumbDrawable()
is its super classSwitchCompact
's method.If you pass
R.attr.switchStyle
to the third param, everything goes well.So why?The third param is a simple attribute. The attribute points to a style resource.In above case, the system will find
switchStyle
attribute in current theme fortunately system finds it.In
frameworks/base/core/res/res/values/themes.xml
, you will see:如果您必须包含三个构造函数(就像现在讨论的那样),您也可以这样做。
If you have to include three constructors like the one under discussion now, you could do this too.