I would like to make a custom view with own xml attributes. I would like to specify a header layout that will be inflated in my custom xml view, something like this:
<com.example.MyCustomWidget
android:layout_width="match_parent"
android:layout_height="match_parent"
app:headerLayout="#layout/my_header"
/>
Is that possible and how to i get the layout resource from TypedArray?
So at the end I would like to do something like this:
class MyCustomWidget extends FrameLayout {
public ProfileTabLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ProfileTabLayout);
int headerLayout = a.getLayout(R.styleable.MyCustomView_headerLayout, 0); // There is no such method
a.recycle();
LayoutInflater.from(context)
.inflate(headerLayout, this, true);
}
}
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyCustomWidget">
<attr name="headerLayout" format="reference" />
</declare-styleable>
</resources>
First you have to make your custom field. Yo do this by adding code below to res/values/attrs.xml
<declare-styleable name="MyCustomView">
<attr name="headerLayout" format="reference" />
</declare-styleable>
Then in your custom view you can get this value in constructor
public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
...
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.MyCustomView,
defStyle, 0
);
try {
int headerLayout = a.getResourceId(R.styleable.MyCustomView_headerLayout, 0);
} finally {
a.recycle();
}
...
}
From here on you can inflate headerLayout with LayoutInflater
Related
I am trying to use two properties from attrs.xml such as: content and handle. When I use them while building the layout the view goes away. I have been trying the bug for 3 days and nothing worked.
Help is much appreciated!
Code in Context
<hh.hhh.app.android.hhhh.widget.ExpandablePanel
android:id="#+id/expandablePanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:handle="#+id/expandButton"
app:content="#+id/listViewProduct"
app:collapsedHeight="50dip"
app:animationDuration="25">
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/listViewProduct"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/expandButton"/>
</hh.hhh.app.android.hhhh.widget.ExpandablePanel>
attrs.xml code
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ExpandablePanel">
<attr name="handle" format="reference" />
<attr name="content" format="reference" />
<attr name="collapsedHeight" format="dimension"/>
<attr name="animationDuration" format="integer"/>
</declare-styleable>
</resources>
I am sharing my own custom class as an example. So for all custom function you need to first check if it is in EditMode or not. isInEditMode() tells us is the layout is being render by IDE preview or by phone. IDE preview is not so much powerful to run our custom codes, so it is recommended to do not run our custom while rendering by preview. I am attaching my custom class to just understand the exact scenario.
public class DynamicImageView extends ImageView {
static DynamicImageTypeListener masterImageTypeListener;
DynamicImageTypeListener imageTypeListener;
int imageResourceArray;
public DynamicImageView(Context context, AttributeSet attr, int defStyle) {
super(context, attr, defStyle);
initValues(attr, defStyle);
}
public DynamicImageView(Context context, AttributeSet attr) {
super(context, attr);
initValues(attr, 0);
}
public DynamicImageView(Context context) {
super(context);
}
public static void setMasterImageTypeListener(DynamicImageTypeListener masterImageTypeListener) {
DynamicImageView.masterImageTypeListener = masterImageTypeListener;
}
public void setImageTypeListener(DynamicImageTypeListener imageTypeListener) {
this.imageTypeListener = imageTypeListener;
}
private void initValues(AttributeSet attr, int defStyle) {
if (!isInEditMode()) { //This code will run only on phone not by preview.
TypedArray a = getContext().obtainStyledAttributes(attr, R.styleable.DynamicImageView, defStyle, 0);
imageResourceArray = a.getResourceId(0, 0);
updateImage();
a.recycle();
}
}
public void updateImage() {
TypedArray imgs = getResources().obtainTypedArray(imageResourceArray);
int imageIndex = imageTypeListener != null ? imageTypeListener.getImageType(getContext()) : (masterImageTypeListener != null ? masterImageTypeListener.getImageType(getContext()) : 0);
setImageResource(imgs.getResourceId(imageIndex, -1));
}
}
Hope it will help :)
I extended the TextView class to "CustomTextView" that's why I needed to set a custom font. So it's the result:
public class CustomTextView extends TextView {
public CustomTextView(Context context, AttributeSet attrs) {
super(context, attrs);
this.setTypeface(Typeface.createFromAsset(context.getAssets(), "fonts/nn.otf"));
this.setTextSize(30);
}
}
You can see that I setted a default textSize that it's 30:
When I want this CustomTextView, I use this code:
<com.calendar.CustomTextView
android:id="#+id/edData"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="DOM 21 GIU"
android:textSize="45dp"/>
If you notice, I setted the textSize value to 45dp, but the it remains 30 (from the custom class).
How do I set a different textSize? Also, for bold style?
you should remove the this.setTextSize(30); from the constructor, because the xml layout do the resize in during the super(context, attrs) call, and the bold font should be included in the otf file (usually are different otf files for different styles)
This is my solution: (I made tests with textColor and not textSize, but it's the same).
I edited the res/values/atrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomTextView">
<attr name="textColor" format="string" />
</declare-styleable>
</resources>
Then, I edited my class like:
public class CustomTextView extends TextView {
public CustomTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
public CustomTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public CustomTextView(Context context) {
super(context);
init(null);
}
private void init(AttributeSet attrs) {
if (attrs != null) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.CustomTextView);
String fontName = a.getString(R.styleable.CustomTextView_fontName);
if (textColor != null) {
this.setTextColor(Color.parseColor(textColor));
} else {
this.setTextColor(Color.parseColor("#000000"));
}
a.recycle();
}
}
}
So this work:
<com.imgspa.listviewadapter.CustomTextView
android:id="#+id/ed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
listviewadapter:textColor="#FF0000"/>
<com.imgspa.listviewadapter.CustomTextView
android:id="#+id/edRis"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="35dp" />
The first custom textview text is red, the second one is black
To set the style just use android:textStyle="bold". For your other problem, you can check the textSize from the attributes I believe using attrs.getAttribute(NAME OF ATTRIBUTE) method and then you can set it to that value or set it to 30 depending on what you want.
I created a custom view for Android which renders two inner views to store a key and a value in two columns. The class looks like this:
public class KeyValueRow extends RelativeLayout {
protected TextView mLabelTextView;
protected TextView mValueTextView;
public KeyValueRow(final Context context) {
super(context);
init(context, null, 0);
}
public KeyValueRow(final Context context, final AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public KeyValueRow(final Context context,
final AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs, defStyle);
}
protected void init(final Context context,
final AttributeSet attrs, int defStyle) {
View layout = ((LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE))
.inflate(R.layout.key_value_row, this, true); // line 46
mLabelTextView = (TextView) layout.findViewById(
R.id.key_value_row_label);
mValueTextView = (TextView) layout.findViewById(
R.id.key_value_row_value);
}
public void setLabelText(final String text) {
mLabelTextView.setText(text);
}
public void setValueText(final String text) {
mValueTextView.setText(text);
}
}
The associated layout file layout/key_value_row.xml looks like this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="1.0">
<TextView
android:id="#+id/key_value_row_label"
style="#style/KeyValueLabel"
android:layout_weight=".55"
tools:text="Label"/>
<TextView
android:id="#+id/key_value_row_value"
style="#style/KeyValueValue"
android:layout_weight=".45"
tools:text="Value"/>
</LinearLayout>
This can be used as follows in a layout:
<com.example.myapp.customview.KeyValueRow
android:id="#+id/foobar"
style="#style/KeyValueRow" />
Until here everything works fine!
The challenge
Now, I want to allow custom settings for the layout_weight attributes of both inner TextViews. Therefore, I prepared the attributes in values/attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="KeyValueRow">
<attr name="label_layout_weight" format="float" />
<attr name="value_layout_weight" format="float" />
</declare-styleable>
</resources>
First question would be: is float the correct format for layout_weight?
Next, I would apply them in the layout file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="1.0">
<TextView
android:id="#+id/key_value_row_label"
style="#style/KeyValueLabel"
android:layout_weight="?label_layout_weight"
tools:text="Label"/>
<TextView
android:id="#+id/key_value_row_value"
style="#style/KeyValueValue"
android:layout_weight="?value_layout_weight"
tools:text="Value"/>
</LinearLayout>
Then they can be used in the example:
<com.example.myapp.customview.KeyValueRow
android:id="#+id/foobar"
style="#style/KeyValueRow"
custom:label_layout_weight=".25"
custom:value_layout_weight=".75" />
When I run this implementation the following exception is thrown:
Caused by: java.lang.NumberFormatException: Invalid float: "?2130772062"
at java.lang.StringToReal.invalidReal(StringToReal.java:63)
at java.lang.StringToReal.parseFloat(StringToReal.java:310)
at java.lang.Float.parseFloat(Float.java:300)
at android.content.res.TypedArray.getFloat(TypedArray.java:288)
at android.widget.LinearLayout$LayoutParams.<init>(LinearLayout.java:1835)
at android.widget.LinearLayout.generateLayoutParams(LinearLayout.java:1743)
at android.widget.LinearLayout.generateLayoutParams(LinearLayout.java:58)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:757)
at android.view.LayoutInflater.inflate(LayoutInflater.java:492)
at android.view.LayoutInflater.inflate(LayoutInflater.java:397)
at com.example.myapp.customview.KeyValueRow.init(KeyValueRow.java:46)
at com.example.myapp.customview.KeyValueRow.<init>(KeyValueRow.java:28)
... 33 more
You can use
private void applyAttributes(Context context, AttributeSet attrs) {
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs, R.styleable.KeyValueRow, 0, 0);
try {
labelWeight = a.getFloat(
R.styleable.KeyValueRow_label_layout_weight, 0.55f);
valueWeight = a.getFloat(
R.styleable.KeyValueRow_value_layout_weight, 0.45f);
} finally {
a.recycle();
}
}
and after this you can apply this via:
mLabelTextView = (TextView) layout.findViewById(R.id.key_value_row_label);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, labelWeight);
mLabelTextView.setLayoutParams(layoutParams);
To get it running replace android:layout_weight="?label_layout_weight" with some default value such as android:layout_weight="0.5" in layout/key_value_row.xml. It will be overwritten.
I am trying to extend an android.widget.Button and added a styleable attribute to my custom widget, which should hold a reference to a value in res/values/strings.xml.
<resources>
<attr name="infoText" format="reference" />
<declare-styleable name="FooButton">
<attr name="infoText" />
</declare-styleable>
</resources
In my layout I have something like this:
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="horizontal">
<com.example.FooButton
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="#+id/fooButton"
infoText="#string/fooButtonInfoText" />
</LinearText>
My res/values/strings.xml looks like this:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="fooButtonInfoText">BAR</string>
</resources>
The extraction of the attribute value in my custom FooButton looks like this:
TypedArray typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.FooButton);
Integer infoTextId = typedArray.getResourceId(R.styleable.FooButton_infoText, 0);
if (infoTextId > 0) {
infoText = context.getResources().getString(infoTextId);
}
typedArray.recycle();
I've got these three constructors implemented:
public FooButton(Context context) {
super(context);
}
public FooButton(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
setInfoText(context, attributeSet);
}
public FooButton(Context context, AttributeSet attributeSet, int defStyle) {
super(context, attributeSet, defStyle);
setInfoText(context, attributeSet);
}
The method FooButton.setInfoText(context, attributeSet) is called every time there is a FooButton declared.
I am fighting this problem for too long and read dozens of Stackoverflow questions... why does this not work?
You must declare the namespace for custom attributes. It should look like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/auto"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="horizontal">
<com.example.FooButton
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="#+id/fooButton"
app:infoText="#string/fooButtonInfoText" />
</LinearText>
I have a custom view, extending LinearLayout and im trying to add some custom attributes, like this:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- other stuff -->
<declare-styleable name="UIPlayer">
<attr name="team" format="integer" />
<attr name="playAnimation" format="reference" />
</declare-styleable>
</resources>
My custom view is like this:
public class UIPlayer extends LinearLayout
{
// . . .
public UIPlayer(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
initAttrs(context, attrs);
}
public UIPlayer(Context context, AttributeSet attrs)
{
super(context, attrs);
initAttrs(context, attrs);
}
public void initAttrs(Context context, AttributeSet attrs)
{
TypedArray taPlayer = context.obtainStyledAttributes(attrs, R.styleable.UIPlayer);
int team = taPlayer.getInt(R.styleable.UIPlayer_team, -1);
player.setTeam(team);
int animPlayId = taPlayer.getResourceId(R.styleable.UIPlayer_playAnimation, -1);
try
{
animPlay = AnimationUtils.loadAnimation(context, animPlayId);
}
catch (NotFoundException e)
{
d("anim", "onPlay not found! " + animPlayId);
}
taPlayer.recycle();
}
// . . .
}
My layout file is like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:devn="http://schemas.android.com/apk/res/devN.games.mygame"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clipChildren="false"
android:orientation="vertical"
android:weightSum="3.0" >
<devN.games.UIPlayer
android:id="#+id/player"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:clipChildren="false"
android:gravity="top"
devn:playAnimation="#anim/play"
devn:team="2" >
</devN.games.UIPlayer>
<!-- ... -->
</LinearLayout>
What im doing wrong and i cant retrive the animation from within the contstructor? (the "strange fact" is that the team attribute is working fine)
After a lot of studies in android source code, i tried the overload methode:
obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
and it worked like a charm!! :)
So the correct code is:
TypedArray taPlayer = context.obtainStyledAttributes(attrs, R.styleable.UIPlayer, 0, 0);