1

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
JJD
  • 50,076
  • 60
  • 203
  • 339
  • Looks like you're not defining whether the values are doubles or floats, but doubles are default. Double is 64 bits, float 32 so you get the exception. Either change everything to double or specify the values as floats. – G_V Dec 08 '14 at 15:03
  • @user3427079 This float definition: `` is not enough? – JJD Dec 08 '14 at 15:24
  • I think that's actually what is causing the problem. A float is expected as specified in the attributes, but what is actually passed is a double. Possibly the NumberFormatException is caused by putting a larger sized value into a smaller box as it's passed as a String, then directly cast to Float but a String is also 64 bits. The other way around will be caught by autoboxing. If you change those to double or specify the values you pass as floats, I think it should work. Try 1.0f for weight for example. I am not sure how android's internal attribute casting is handled though, hope it works. – G_V Dec 08 '14 at 15:46
  • @user3427079 Android `attrs` do not offer `double` a format type. - In the first part of my question you can see that the `0.55` value works fine without any type qualifier as in the suggested `0.55f`. Nonetheless, I tried `string` as the format type and appended the `f` as you suggested. This ends with the same error though. – JJD Dec 08 '14 at 16:06
  • Hmm, that's too bad. Have you considered scaling up the values? Instead of 0.75 and 0.25 use them like percentages as weight divides by the total of defined weights. So using 75 and 25 as weights instead of floats/doubles. – G_V Dec 08 '14 at 16:32

1 Answers1

1

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.

JJD
  • 50,076
  • 60
  • 203
  • 339
Informatic0re
  • 6,300
  • 5
  • 41
  • 56