This has been bothering me for a while, and none of my searching has yielded results. If I have a custom GUI element, I can use a LayoutInflater to inflate it as I would a normal component. The inflation call results in a call to my custom GUI element's constructor, and all is well.
However, what if I want to add a custom parameter to my element's constructor? Is there a way I can pass this parameter in using LayoutInflater?
For example:
In main xml, I have a holder for my layout:
<LinearLayout
android:id="#+id/myFrameLayoutHolder"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
</LinearLayout>
and a MyFrameLayout.xml file:
<com.example.MyFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/MyFLayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1 >
<!-- Cool custom stuff -->
</com.example.MyFrameLayout>
and an inflater call:
LayoutInflater MyInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout myFLayoutHolder = (LinearLayout) findViewById(R.id.myFrameLayoutHolder);
MyFrameLayout L = ((MyFrameLayout) MyInflater.inflate(R.layout.MyFLayout, myFLayoutHolder, false));
myFLayoutHolder.addView(L);
If, in my class that extends FrameLayout, I add a parameter to my constructor, I get a crash:
public class MyFrameLayout extends FrameLayout {
private int myInt;
public MyFrameLayout(Context context) {
this(context, null);
}
public MyFrameLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0, 0);
}
public MyFrameLayout(Context context, AttributeSet attrs, int defStyle, int myParameter) {
super(context, attrs, defStyle);
myInt = myParameter;
//Amazing feats of initialization
}
}
Now, it's easy enough to work around this issue by defining a custom init method that I call right after layout inflation, but that seems clumsy to me. Is there a better way?
You cant define a constructor with your own parameter because your constructor signature conflicts with FrameLayout's own constructor signature and you are not calling super(context, attrs, defStyle);, instead you are calling super(context, attrs); which is incomplete for this constructor.
You must need to define all three native constructors exactly as they are:
FrameLayout(Context context)
FrameLayout(Context context, AttributeSet attrs)
FrameLayout(Context context, AttributeSet attrs, int defStyle)
What you can do is to use your own (custom) attributes in xml and then retrieve them in your MyFrameLayout's attrs object
If the custom component is inflate by XML file or inflate method. You dont´t pass elemnts in the construct because this is not support in android.
Related
I am having custom view which will take attribute set(xml value) as constructor value
public CustomView(Context context) // No Attributes in this one.
{
super(context);
this(context, null, 0);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
this(context, attrs, 0)
}
public CustomView(Context context, AttributeSet attrs, int default_style) {
super(context, attrs, default_style);
readAttrs(context, attrs, defStyle);
init();
}
In Fragment class i am setting the view as
CustomView customView = (CustomView) view.findViewById(R.id.customView);
where custom view contains various value such as height,width,padding etc.
i want to modify those values based on required condition and set it back to custom view.
I placed setting width height code in onDraw method and called invalidte view.
But above method will set the every time if i called invalidate method in CustomView class.
how to overcome this so that i can pass modified attribute set value in constructor only.?
Edit: I need to modify the view values(initialize with new values) which is set during attribute constructor so that i will get a refreshed view with a new values.
Override #OnDraw or 'Invalidate' is not a good function for me where inside invalidate i have written the methods which will execute in each second interval.
I see that your CustomView can have multiple attributes and you want to modify some of these attributes based on some condition and pass this in the constructor.
Few best practices while designing a custom view:
If you have custom attributes, make sure that you expose them via setters and getters. In your setter method, call invalidate();
Don't try modifying any attributes inside onDraw() or onMeasure() methods.
Try your best to avoid writing Custom constructors for your Custom View.
So the ideal way to solve your problem is to instantiate your CustomView and then modify the attributes, either externally (in your Activity or Fragment), or have a method inside the CustomView.java and then invoke it externally. Doing this will still give you the same result you are looking for.
So lets say you declared your custom attributes like this for view named StarsView
<declare-styleable name="StarsView">
<attr name="stars" format="integer" />
<attr name="score" format="float" />
</declare-styleable>
And you want to read attributes from something like this
<my.package..StarsView
app:stars="5"
app:score="4.6"
You do just this in constructor
public StarsView(Context context, AttributeSet attrs) {
super(context, attrs);
if(attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.StarsView, defStyleAttr, 0);
stars = Tools.MathEx.clamp(1, 10, a.getInt(R.styleable.StarsView_stars, 5));
score = (int)Math.floor(a.getFloat(R.styleable.StarsView_score, stars) * 2f);
a.recycle(); // its important to call recycle after we are done
}
}
It's probably not the solution you were hoping for, but put a FrameLayout in your xml instead of the CustomView, and then create your CustomView programmatically with the FrameLayout as it's parent
I'm having trouble understanding why my extended layout isn't working. I made a class which goes
public class MyLayout extends RelativeLayout {
public MyLayout(Context context) {
super(context);
}
}
I wrote the XML as
<package.MyLayout
. . .
</package.MyLayout>
The method where I get the error is in the activity where I have called setContentView(R.layout.layout_relative).
I don't understand what I'm doing wrong because surely you inflate in the activity from the XML layout which is building on the custom class where I can make my overrides?
you need the other constructor, the one that takes two parameters:
public MyLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
which is used when the layout is inflated from the layout
You have specified the wrong constructors!
Android xml inflation uses
public RelativeLayout (Context context, AttributeSet attrs, int
defStyleAttr)
public RelativeLayout (Context context, AttributeSet
attrs, int defStyleAttr, int defStyleRes) since api 21
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
does anybody know, how to get a referenced xml layout, programmatically (in code) for my custom widget. I have already created a custom declare-styleable, with my desired attributes and I know how to get ohter xml attribute values, like string or integers.
What I want to do is something like this:
<MyCustomView
xmlns:my="http://schemas.android.com/apk/res-auto"
id="#+id/view"
my:headerLayout="#layout/my_fancy_layout"
/>
So I want to retrieve my_fancy_layout programmatically and inflate that layout in the code of MyCustomView.
Any idea how to do that?
Edit: I guess I can retreive the resource id with
int resId = attrs.getAttributeResourceValue(androidns, "headerLayout", 0);
But whats the correct namespace if I MyCustomView is a library project and if I would like to use
xmlns:my="http://schemas.android.com/apk/res-auto"
Ok, i found the solution by myself:
you have to retrieve a TypedArray from yout AttributeSet.
than you can access your desired resource id with something like this:
TypedArray attrs = ... ;
int headerRes = attrs.getResourceId(R.styleable.MyCustomWidget_headerLayout, -1);
than you can inflate like usually:
LayoutInflater.from(context).inflate(headerRes, this);
You can indeed inflate your layout in the constructor of your custom view:
public class MyCustomView extends /* LinearLayout, RelativeLayout, etc. */ {
public MyCustomView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context, attrs);
}
public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView(context, attrs);
}
protected void initView(Context context, attrs) {
LayoutInflater.from(context).inflate(attrs.getAttributeResourceValue("http://schemas.android.com/apk/res-auto", "headerLayout", 0), this, true);
}
}
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.