NumberFormatException when using styleable attrs - android

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.

Related

Custom view default style not wokring without style tag

I have researched a lot and find many sources to this problem but no solutions working for me I don't know what's going wrong.
Some resources i have look into
Custom view style, android's attributes are ignored
https://androidpedia.net/en/tutorial/1446/creating-custom-views
https://infinum.com/the-capsized-eight/how-to-support-themes-in-custom-views-for-android-apps
This is repo for this problem
https://github.com/burhankhanzada199888/CustomView-Style-StackOverflow-Question/tree/master/app
I have 2 custom view in my activity first without style tag and second with style tag but cardView style only apply when using in xml
custom_view.xml
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:parentTag="com.google.android.material.card.MaterialCardView"
tools:style="#style/CustomCardView">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="4dp"
android:background="?colorSurface"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:id="#+id/imageView"
android:layout_width="100dp"
android:layout_height="100dp"
android:padding="8dp"
android:src="#mipmap/ic_launcher"
app:srcCompat="#mipmap/ic_launcher" />
<TextView
android:id="#+id/textView"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?colorAccent"
android:gravity="center"
android:text="Lorem ipsum"
android:textColor="#android:color/white"
android:textSize="25sp" />
</LinearLayout>
</merge>
activity_main.xml
<com.myapp.CustomCardView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<com.myapp.CustomCardView
style="#style/CustomCardView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
CustomCardView.java
public class CustomCardView extends MaterialCardView {
public CustomCardView(Context context) {
this(context, null);
}
public CustomCardView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomCardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomCardView, defStyleAttr, R.style.CustomCardView);
float imageSize = a.getDimension(R.styleable.CustomCardView_imageSize, 0);
float textSize = a.getDimension(R.styleable.CustomCardView_textSize, 0);
a.recycle();
inflate(context, R.layout.custom_view, this);
ImageView imageView = findViewById(R.id.imageView);
imageView.getLayoutParams().height = (int) imageSize;
imageView.getLayoutParams().width = (int) imageSize;
TextView textView = findViewById(R.id.textView);
textView.setTextSize(textSize);
}
}
styles.xml
<style name="CustomCardView" parent="Widget.MaterialComponents.CardView">
<item name="cardElevation">8dp</item>
<item name="cardCornerRadius">16dp</item>
<item name="android:layout_margin">8dp</item>
<item name="cardBackgroundColor">?colorPrimary</item>
<item name="imageSize">150dp</item>
<item name="textSize">50sp</item>
</style>
attrs.xml
<declare-styleable name="CustomCardView">
<attr name="imageSize" format="dimension|reference" />
<attr name="textSize" format="dimension|reference" />
</declare-styleable>
You can add in the attrs.xml:
<attr format="reference" name="customCardViewStyle"/>
Change the constructor:
public CustomCardView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
to
public CustomCardView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.customCardViewStyle);
}
Then in your app theme add:
<item name="customCardViewStyle">#style/CustomCardView</item>
Just pay attention to <item name="android:layout_margin">...</item>. The LayoutParams are specific to the parent view type and you can't apply it with a theme.You can set the layout attributes in a style on in the layout.
It happens because you've removed super calls in public CustomCardView(Context context) and public CustomCardView(Context context, AttributeSet attrs) constructors. Indeed your constructors must be like these:
public CustomCardView(Context context) {
super(context)
this(context, null);
}
and
public CustomCardView(Context context, AttributeSet attrs) {
super(context, attrs)
this(context, attrs, 0);
}

Robolectric inflating custom views for tests

I'm trying to write some tests for a custom view, but I am having trouble inflating the custom view in my test case. The error I get is
android.view.InflateException: <merge /> can be used only with a valid ViewGroup root and attachToRoot=true
at android.view.LayoutInflater.inflate(LayoutInflater.java:458)
at android.view.LayoutInflater.inflate(LayoutInflater.java:397)
at android.view.LayoutInflater.inflate(LayoutInflater.java:353)
at com.androidas.models.ui.order.MyLocationViewTest.setUp(MyLocationViewTest.java:45)
I even tried making a dummy activity (TestActivity) with a tag in it to try to inflate it
public class MyLocationView extends RelativeLayout {
private TextView mTextEta;
private TextView mTextOnTime;
private Date mMeetTime;
private CalendarManager mCalendarManager;
public MyLocationView(Context context) {
this(context, null);
}
public MyLocationView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyLocationView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
LayoutInflater.from(context)
.inflate(R.layout.v_mylocation, this, true);
mTextEta = (TextView) findViewById(R.id.order_statusTracking_txt_myEta);
mTextOnTime = (TextView) findViewById(R.id.order_statusTracking_txt_myDelay);
}
}
layout
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
>
<ImageView
android:id="#+id/order_statusTracking_img_walking"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_walk"
/>
<TextView
android:id="#+id/order_statusTracking_txt_myEta"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="#dimen/spacing_small"
android:layout_toRightOf="#+id/order_statusTracking_img_walking"
android:layout_toEndOf="#+id/order_statusTracking_img_walking"
android:gravity="center_vertical"
tools:text="Arrive in 7min"
style="#style/HeaderText"
/>
<TextView
android:id="#+id/order_statusTracking_txt_myDelay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignLeft="#+id/order_statusTracking_txt_myEta"
android:layout_below="#+id/order_statusTracking_txt_myEta"
android:layout_marginTop="#dimen/spacing_normal"
tools:text="On Time"
style="#style/InfoText.Black"
/>
</merge>
and Test case:
#RunWith(RobolectricTestRunner.class)
#Config(emulateSdk = 18)
public class MyLocationViewTest {
MyLocationView mLocation;
TextView mTextDelay;
Trip mTrip;
CalendarManager mCalendarManager;
Date meetupTime;
#Before
public void setUp() {
Activity activity = Robolectric.buildActivity(TestActivity.class).create().get();
mLocation = (MyLocationView) LayoutInflater.from(activity).inflate(R.layout.v_mylocation, null);
mTextDelay = (TextView) mLocation.findViewById(R.id.order_statusTracking_txt_myDelay);
}
OK, 1 year later but here is the weird thing that worked for me:
MyCustomView mView = (MyCustomView) LayoutInflater
.from(RuntimeEnvironment.application)
.inflate(R.layout.my_custom_view_layout,
new MyCustomView(RuntimeEnvironment.application),
true);

Get animation from custom attribute in constructor

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);

Custom xml attributes without attrs.xml file

Recently I've faced with adding custom xml parameters to my views in xml layout. I know that I should use attrs.xml file for this purpose, but... I have found, that I can use custom parameters without any attrs.xml file at all. Can somebody explain this ? Is this a bug or is this a valid case ?
here is my custom view:
public class TestView extends View {
public TestView(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
public TestView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
final String scheme = "http://red.com/ui/scheme";
if (attrs != null) {
Log.d("TestView", "custom param value: " + attrs.getAttributeValue(scheme, "cutom"));
}
}
}
and here is the main layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:red="http://red.com/ui/scheme"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/hello" />
<com.red.ui.TestView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#ffAABBCC"
red:cutom="customvalue"
/>
</LinearLayout>
It is a simple scratch project, created by Android wizard.
The custom attribute that you added is not available in R.java
I think the main motto of making custom attributes is to use it at multiple places.
But through this code we cann't accomplish the same scenario.
Here is the sample code - attrs.xml file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyLayout">
<attr name="text" format="string" />
</declare-styleable>
</resources>
I am changing main.xml to add the the text attribute
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:red="http://red.com/ui/scheme"
xmlns:myapp="http://schemas.android.com/apk/res/com.psl"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/hello"
myapp:text="Text String" />
<com.psl.TestView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#ffAABBCC"
myapp:text="My Special Text String"
red:cutom="customvalue" />
</LinearLayout>
TestView.java -
public class TestView extends View {
public TestView(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
public TestView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
final String scheme = "http://red.com/ui/scheme";
if (attrs != null) {
Log.d("TestView", "custom param value: " + attrs.getAttributeValue(scheme, "cutom"));
}
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.MyLayout);
CharSequence s = a.getString(R.styleable.MyLayout_text);
Log.d("MyTestView", "attrs param value: " + s.toString());
}
}
If you noticed after making the attr in attrs.xml. It is available everywhere.
But the attr defined in xml itself through custom namespace is available only through the namespace that you have to define everywhere.
May be its a bug because the attribute is getting added to some custom namespace and not in the application itself.
This is not a "bug" of course. This is how you use a custom view in your xml.
refer to this : http://developer.android.com/guide/topics/ui/custom-components.html

declare-styleable drawableTop

I try implement compound view for my dashboard activity. It's Button with TextView. Need for my theme.
I have attr.xml file
<declare-styleable name="DashboardButton">
<attr name="drawableTop" format="reference"/> <!-- i want set android:drawableTop of button-->
<attr name="btnText" format="string" /> <!-- text about button functionality-->
<attr name="tvText" format="string" /> <!-- additional information -->
</declare-styleable>
also have dashboard_button.xml layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<Button
android:id="#+id/btn"
style="#style/DashboardButton"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" />
<TextView
style="#style/btn_add_info"
android:id="#+id/txtView"
android:text="0"/>
</RelativeLayout>
also have custom component DashboardButton
public class DashboardButton extends RelativeLayout {
private TextView txtView;
private Button btn;
public DashboardButton(Context context) {
super(context);
}
public DashboardButton(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.dashboard_button, this);
loadViews(attrs);
}
public DashboardButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.dashboard_button, this);
loadViews(attrs);
}
private void loadViews(AttributeSet attrs) {
txtView = (TextView) findViewById(R.id.txtView);
btn = (Button) findViewById(R.id.btn);
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.DashboardButton);
txtView.setText(a.getString(R.styleable.DashboardButton_tvText));
btn.setText(a.getString(R.styleable.DashboardButton_btnText));
//i think issue here but can't understand what i should do
Drawable topDrawable = a.getDrawable(R.styleable.DashboardButton_drawableTop);
btn.setCompoundDrawables(null, topDrawable, null, null);
a.recycle();
}
public void setTxtViewText(CharSequence text) {
txtView.setText(text);
}
}
and finally i have
<my.package.name.DashboardButton
android:id="#+id/Btn"
style="#style/DashboardButton"
app:btnText="#string/my_function"
app:tvText="#string/add_info"
app:drawableTop="#drawable/function_logo">
</my.package.name.DashboardButton>
All work fine, except app:drawableTop drawable not load at run time, I dont see drawable. Can you help me by advice?
You need to set the bounds for your drawable:
topDrawable.setBounds( new Rect( 0, 0, w, h ) );
or alternatively use:
btn.setCompoundDrawablesWithIntrinsicBounds(null, topDrawable, null, null);

Categories

Resources