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
Related
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
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);
}
}
There are several constructors available for defining an ImageView.
For Example
1) public ImageView (Context context)
2) public ImageView (Context context, AttributeSet attrs)
3) public ImageView (Context context, AttributeSet attrs, int defStyle)**
I am confused in using 2nd and 3rd type of constructor.
basically i don't know what to pass in place of AttributeSet.
Kindly provide a coding example.
These constructors are defined in the View documentation. Here is a description of the parameters from View(Context, AttributeSet, int):
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.
defStyle The default style to apply to this view. If 0, no style will be applied (beyond what is included in the theme). This may
either be an attribute resource, whose value will be retrieved from
the current theme, or an explicit style resource.
It's worth noting that you can pass null in place of an AttributeSet if you have no attributes to pass.
In terms of coding the AttributeSet, here's a bit of code I use for a custom TextView class I have:
public EKTextView(Context context, AttributeSet attrs) {
super(context, attrs);
// ...
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LocalTextView);
determineAttrs(context, a);
}
// ...
}
private void determineAttrs(Context c, TypedArray a) {
String font = a.getString(R.styleable.fontName);
if (font != null)
mTypeface = Typeface.createFromAsset(c.getAssets(), "fonts/" + font);
mCaps = a.getBoolean(R.styleable.allCaps, false);
}
As you can see, once you get a TypedArray from the attributes, you can just use its various methods to collect each of the attributes. Other code you may want to review is that of View(Context, AttributeSet, int) or Resources.obtainStyledAttributes(AttributeSet, int[], int, int).
Ways of creating imageView, ImageView with Context
ImageView image= new ImageView(context);
Here when you want set the values like height, width gravity etc you need to set
image.set****();
based on the number of attributes you need to use no of setXXX() methods,.
2.Using Attribute set
you can define set of attributes like height, width etc in your res/values folder in separate xml file, pass the xml file to getXml()
XmlPullParser parser = resources.getXml(yourxmlfilewithattribues);
AttributeSet attributes = Xml.asAttributeSet(parser);
ImageView image=new ImageView(context,attributes);
Here you can also define your custom attributes in your xml . and you can access the by using the methods provided by AttributeSet class example
getAttributeFloatValue(int index, float defaultValue)
//Return the float value of attribute at 'index'
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.