I have a code something like this:
public class CannonView extends SurfaceViewimplements SurfaceHolder.Callback{
Activity activity;
And its constructor:
public CannonView(Context context, AttributeSet attrs){
Super(context,attrs)
activity = (Activity) context;
but apparently the AttributeSet is doing nothing, I dont know why is there, so my questions are: 1.-what is AttributeSet? 2.-why do we need to provide AttributeSet attrs as a second argument? by the way the rest of code is for painting using canvas. Thanks.
http://developer.android.com/training/custom-views/create-view.html
- this is an explanation.
Shortly, attributeSet is needed for GUI editor. AttributeSet is a set of parameters like
layout_width, layour_height and so on.
It you need you new custom attributes, the you need to extend to expand AttributSet class
Views have 3 constructors:
SurfaceView(Context context)
SurfaceView(Context context, AttributeSet attrs)
SurfaceView(Context context, AttributeSet attrs, int defStyle)
NOTE: The third style was added in API Level 11. But if you want to create a custom view for newer versions of the API you should implement it.
When implementing a custom view, if you want it to be widely usable, then you should implement the three Constructors - as another use of your View in a different part of the code or another app could instantiate it using any of the constructors.
If you are constructing the view programatically, then you can decide which constructor you use.
But, the Android Framework instantiates your view when they are referenced from XML.
<com.me.Common.MyView
android:layout_width="wrap_contents"
...
/>
etc.
These XML declarations that instantiate your view can include many Attributes, some of them the standard android ones in the "android:" namespace. If you pass these to the SuperClass you are extending (if you are extending a View class - as you are) then it will parse them and use them and you don't need to do much.
But you can also define and use custom attributes in your own name name
<com.me.Common.MyView
android:layout_width="wrap_contents"
...
com.me:num_elements="10"
/>
and then you should parse the attribute set passed in the constructor and change the behaviour of your View object to respect the settings in the XML. The "android:" attributes will be parsed and used by the Superclass.
So, as you donĀ“t know exactly how your custom view will be instantiated by the Android Framework (it will depend on the XML tag declaring it), you should implement the three constructors.
NOTE: It's tempting to do the standard Java override style and have each constructor use the more complex one via super:
MyView(Context context, AttributeSet attrs) {
super(context, attrs, 0);
}
But I have seen this lead to problems, as 0 is not always a valid style.
So, I recommend you implement a init() method that does your own customer code, and that you call the constructor of the Superclass that corresponds to the parameters of the constructor the Framework use for your customer view:
public AnimationController(Context context) {
super(context);
initUI(context, null, -1);
}
public AnimationController(Context context, AttributeSet attrs) {
super(context, attrs);
initUI(context, attrs, -1);
}
public AnimationController(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initUI(context, attrs, defStyle);
}
Especially as the super() with the three parameters may not exist on a device with API < 11.
Implementing custom views this way makes them much more configurable and reusable, as you can lay them out in different XML files with different attributes set, or use a style that defines a set of attributes - just as the Android views are used.
There are 3 constructors for SurfaceView:
SurfaceView(Context context)
SurfaceView(Context context, AttributeSet attrs)
SurfaceView(Context context, AttributeSet attrs, int defStyle)
I believe you should be able to override any of these so technically you don't have to provide an AttributeSet as a second parameter.
As for why there is an AttributeSet you can refer to the View documentation for that: http://developer.android.com/reference/android/view/View.html. The constructor SurfaceView(Context context, AttributeSet attrs) is "called when inflating a view from XML" according to that site.
As for what an AttributeSet is: it is "a collection of attributes, as found associated with a tag in an XML document" according to http://developer.android.com/reference/android/util/AttributeSet.html
Related
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.
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 from xml also like :
<com.mypack.MyView
...
/>
you will need the constructor public MyView(Context context, AttributeSet attrs), otherwise you will get an Exception when Android tries to inflate your View.
If you add your View from xml and also specify the android:style attribute like :
<com.mypack.MyView
style="#styles/MyCustomStyle"
...
/>
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.
If you override all three constructors, please DO NOT CASCADE this(...) CALLS. You should instead be doing 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
}
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:
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);
}
If you did not call super(context), you would not have properly set R.attr.textViewStyle as the style attr.
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 named style, 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 named defStyle 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=12683
MyView(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:
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();
}
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 seems to take away a lot of this pain:
class MyView
#JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
: View(context, attrs, defStyle)
#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!
The third constructor is much more complicated.Let me hold an example.
Support-v7 SwitchCompact package supports thumbTint and trackTint 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 extends 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();
}
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 method getThumbDrawable() is its super class SwitchCompact'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:
<style name="Theme">
<item name="switchStyle">#style/Widget.CompoundButton.Switch</item>
</style>
If you have to include three constructors like the one under discussion now, you could do this too.
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();
}
I created a custom view:
public class SomeView extends View
The custom view constructors:
public SomeView (Context context)
{
super(context);
}
// Called when view is inflated from xml
public SomeView (Context context, AttributeSet attrs)
{
super(context, attrs);
}
// Perform inflation from XML and apply a class-specific base style from a theme attribute.
public SomeView (Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
I also tried the 4th constructor from api 21 with no luck:
public VeediView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
{
super(context, attrs,defStyleAttr, defStyleRes);
}
In the xml layout i am defining this view and things work fine.
Testing on Galaxy S2 works fine and the view constructor are called but when running the app on Nexus-7 android 5.0.2 the constructors do not get called at all.
Any idea why?
Could it be related to rooted devices?
The related xml view:
<com.package.name
android:id="#+id/scene"
android:onClick="startx"
style="#style/txt_money_style"
android:layout_width="72dp"
android:layout_height="72dp"
android:background="#drawable/wtbtn"
android:layout_gravity="right"
android:gravity="center_vertical|right"
/>
I think you should use this constructor for bestway:
public SomeView (Context context)
{
this(context , null);
}
// Called when view is inflated from xml
public SomeView (Context context, AttributeSet attrs)
{
this(context, attrs , 0);
}
// Perform inflation from XML and apply a class-specific base style from a theme attribute.
public SomeView (Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
// Initialize customize constructor here
}
In API 21 theres now a 4th constructor it could be that your XML is calling this.
From the docs:
public View (Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
Added in API level 21
Perform inflation from XML and apply a class-specific base style from a theme attribute or style resource. This constructor of View allows subclasses to use their own base style when they are inflating.
When determining the final value of a particular attribute, there are four inputs that come into play:
Any attribute values in the given AttributeSet.
The style resource specified in the AttributeSet (named "style").
The default style specified by defStyleAttr.
The default style specified by defStyleRes.
The base values in this theme.
Each of these inputs is considered in-order, with the first listed taking precedence over the following ones. In other words, if in the AttributeSet you have supplied , then the button's text will always be black, regardless of what is specified in any of the styles.
Parameters
context The Context the view is running in, through which it can access the current theme, resources, etc.
attrs The attributes of the XML tag that is inflating the view.
defStyleAttr An attribute in the current theme that contains a reference to a style resource that supplies default values for the view. Can be 0 to not look for defaults.
defStyleRes A resource identifier of a style resource that supplies default values for the view, used only if defStyleAttr is 0 or can not be found in the theme. Can be 0 to not look for defaults.
Here is source code of referred View.java class. If you check it out you will see, that public View(Context context) is always called. If you think it's not called but you see the view, then the issue is rather in the part detecting whether it gets called, than in Android code. You should look in there. It could be logging code or some wrong filters in AS, or similar.
From the source code you can also see, that this is the new constructor, used in Android 5.0 an higher, which has the most implementation.
public View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
The thing is i got this code and didnt develop it myself and after trying everything it turns out that the app have multiple layout files:
layout-large, layout-small etc...
I only defined the custom view on the layout folder so switching to other screen sizes invoked the regular view.
I guess others can learn from my mistake , i wish Android Studio or Eclipse can support some kind of setContentView(R.layout.activity_scene) and the related file debug option
So the answer is to make sure all layouts have the custom view defined
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.
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 from xml also like :
<com.mypack.MyView
...
/>
you will need the constructor public MyView(Context context, AttributeSet attrs), otherwise you will get an Exception when Android tries to inflate your View.
If you add your View from xml and also specify the android:style attribute like :
<com.mypack.MyView
style="#styles/MyCustomStyle"
...
/>
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.
If you override all three constructors, please DO NOT CASCADE this(...) CALLS. You should instead be doing 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
}
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:
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);
}
If you did not call super(context), you would not have properly set R.attr.textViewStyle as the style attr.
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 named style, 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 named defStyle 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=12683
MyView(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:
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();
}
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 seems to take away a lot of this pain:
class MyView
#JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
: View(context, attrs, defStyle)
#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!
The third constructor is much more complicated.Let me hold an example.
Support-v7 SwitchCompact package supports thumbTint and trackTint 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 extends 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();
}
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 method getThumbDrawable() is its super class SwitchCompact'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:
<style name="Theme">
<item name="switchStyle">#style/Widget.CompoundButton.Switch</item>
</style>
If you have to include three constructors like the one under discussion now, you could do this too.
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();
}
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 problem with this is that it stops me from making my variables final. Any reason not to do the following?
public MyView(Context context) {
this(context, null);
// this constructor used when programmatically creating view
}
public MyView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
// this constructor used when creating view through XML
}
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// this constructor used where?
// init variables
}
I've been able to create the view just fine through XML and through code, but I'm not sure if there are any drawbacks to this approach. Will this work in all cases?
There is another part to this question
The only drawback I can see (that no one seems to have mentioned) is that your second constructor loses the defStyle of the superclass, because you set it to zero. Look at the source code for any of Android's View classes, and you'll notice that the second constructor always has a specific defStyle defined.
For example, this is the second constructor of ListView:
public ListView(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.listViewStyle);
}
If you were to extend ListView using the second approach that you describe, com.android.internal.R.attr.listViewStyle would no longer be the defStyle, because you'd be bypassing that second super constructor and making it zero instead. I suppose you could resolve this by using the same defstyle as ListView, like so:
public MyView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.listViewStyle);
}
But it's not exactly the "purist" way, because you're artificially forcing it to have the same defStyle as ListView.
So, contrary to what the others said, I actually think you're better off using the first doAdditionalConstructorWork() approach outlined in your post, because that at least makes sure that the defStyle is set correctly.
Copied this from my answer for a similar question.
If you override all three constructors, please DO NOT CASCADE this(...) CALLS. You should instead be doing 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
}
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:
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);
}
If you did not call super(context), you would not have properly set R.attr.textViewStyle as the style attr.
Yup, that's a reasonable pattern to use so you don't have to repeat the custom work in every one of your constructors. And no, there don't appear to be any drawbacks to the method.
It purely depends on your requirement. Let us say if you want to use any methods in parent class without overriding their functionality in your custom view, then you need to use super() and instantiate parent class. If you dont need to invoke any methods in parent class all implementations are overridden in your custom view, then you don't need. Read A custom View Example section in this link.
Edit:
This is not Okay. See other answers to this question for reasons.
Original Answer:
It is Ok.
When we look at the source of TextView.java.
They have used the same hierarchy.
So you are Okay with this approach.
I want to create a custom view TestView class for which I can create object via new TestView().
A new view class however needs a AttributeSet object. From where do I get that AttributeSet and what does it have to include?
It's not mandatory, and most times you don't even have to worry about it as long as you provide constructors from View that pass them along to super().
public CustomView(Context context) // No Attributes in this one.
{
super(context);
// Your code here
}
public CustomView(Context context, AttributeSet attrs)
{
super(context, attrs);
// Your code here
}
public CustomView(Context context, AttributeSet attrs, int default_style)
{
super(context, attrs, default_style);
// Your code here
}
View takes care of doing the heavy lifting for dealing with all of the android:* attributes that you'd usually pass in when adding the view to a layout. Your constructors could make use of those attributes or your own if you've defined them:
<com.blrfl.CustomView
android:id="#+id/customid"
android:layout_weight="1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="center"
blrfl:foo="bar"
blrfl:quux="bletch"
/>
Either of 3 constructor provided by view class can be implemented.. so providing constructor with attributeset is not mandatory.