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);
}
}
Related
I am creating a view in my Kotlin file and setting some properties like text color and typeface.
class TestTextView(context: Context) : AppCompatTextView(context) {
init{
setTextColor(ContextCompat.getColor(context, android.R.color.holo_blue_dark))
}
}
Now when I use this view in an XML and try to render it, it does not show blue color
<com.mindvalley.core.view.TestTextView
android:id="#+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test"
android:layout_centerInParent="true"/>
Adding screenshot for the same. Is there a way to render the exact color, size and typeface TextView created in Kotlin file in an XML file?
Views in Android have multiple constructors.
public AppCompatTextView(Context context) {
this(context, null);
}
public AppCompatTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
}
public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);
mTextHelper = new AppCompatTextHelper(this);
mTextHelper.loadFromAttributes(attrs, defStyleAttr);
mTextHelper.applyCompoundDrawablesTints();
mTextClassifierHelper = new AppCompatTextClassifierHelper(this);
}
When the view is inflated from xml file, the constructor with -at least- attributes is used, and you don't override this one.
When defining a custom view you should override each of the super class constructors, or just the richest one putting #JvmOverloads annotation before.
⚠️ It's an annotation to use with caution:
https://medium.com/#mmlodawski/https-medium-com-mmlodawski-do-not-always-trust-jvmoverloads-5251f1ad2cfe
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'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
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.
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();
}