38

I made my custom component just putting few TextViews together. Now I want to be able to init my custom control directly from code, passing text sizes independently for each of of TV's

My attributes definition:

<resources>
    <declare-styleable name="BasicGauge">
        <attr name="valueTextSize" format="dimension" />
        <attr name="titleTextSize" format="dimension" />
        <attr name="unitsTextSize" format="dimension" />
    </declare-styleable>
</resources>

Sample initialization of component:

<pl.com.digita.BikeComputerUi.customviews.BasicGauge
    android:id="@+id/basicGauge1"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    android:padding="10dp"
    valueTextSize="40sp">

</pl.com.digita.BikeComputerUi.customviews.BasicGauge>

How I try to read those attributes in component's constructor:

final int N = typedArray.getIndexCount();
for (int i = 0; i < N; i++) {
    int attribute = typedArray.getIndex(i);
    switch (attribute) {
        case R.styleable.BasicGauge_valueTextSize:
            valueTextSize = typedArray.getString(attribute);
            break;
        case R.styleable.BasicGauge_titleTextSize:
            titleTextSize = typedArray.getString(attribute);
            break;
        case R.styleable.BasicGauge_unitsTextSize:
            unitsTextSize = typedArray.getString(attribute);
            break;
    }
    typedArray.recycle();
}

Problem: After creation all of my values are still null. 40sp is exactly my desired value.

piotrpo
  • 12,398
  • 7
  • 42
  • 58

2 Answers2

72

A few things to say :

  • first you need a xml name space declaration line at the top of your xml, exactly in the same way as you do with android xmlns : xmlns:foo="http://schemas.android.com/apk/res-auto"
  • then you need to prefix valueTextSize with your xmlns : foo:valueTextSize="40sp"

After that, it's not a very good idea to get a string, android offers more powerfull solution to deal with dimensions :

int unitsTextSize = typedArray.getDimensionPixelSize(R.styleable.BasicGauge_unitsTextSize, textSize);

then there are some subtleties :

  • for a Paint, or a TextPaint, you can this value as is : paint.setTextSize(unitTextSize):
  • for a textview, the above approach would fail, and you have to use an overload of setText to get the correct result : textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, unitTextSize);

The difference comes from what is called "raw pixels" (unscaled according to density, just raw) and "scaled pixels" (the opposite).

treesAreEverywhere
  • 3,722
  • 4
  • 28
  • 51
Snicolas
  • 37,840
  • 15
  • 114
  • 173
  • first of all - thanks for solving my problem. What is the best way to pass dimension value to TextView inside of compound component? – piotrpo May 16 '13 at 21:04
  • The way you do it : use a custom attribute, retrieve it during construction of the compound view, then pass it to views when created or inflated. – Snicolas May 17 '13 at 05:20
38

For a little more context to the accepted answer:

attrs.xml

Set the custom attribute with the dimension format.

<resources>
    <declare-styleable name="CustomView">
        <attr name="valueTextSize" format="dimension" />
        <attr name="titleTextSize" format="dimension" />
        <attr name="unitsTextSize" format="dimension" />
    </declare-styleable>
</resources>

activity.xml

Reference your attributes with xmlns:app=... and set them in your xml with app:....

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

    <com.example.myproject.CustomView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:valueTextSize="40sp"
        app:titleTextSize="20sp"
        app:unitsTextSize="24sp" /> 

</RelativeLayout>

CustomView.java

Obtain the values from the TypedArray using getDimensionPixelSize. Within your custom view just work with pixels. See this answer if you need to convert to a different dimension.

public class CustomView extends View {

    private float mValueTextSize; // pixels
    private float mTitleTextSize; // pixels
    private float mUnitsTextSize; // pixels

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.getTheme().obtainStyledAttributes(
                attrs, R.styleable.CustomView, 0, 0);
        try {
            mValueTextSize = a.getDimensionPixelSize(R.styleable.CustomView_valueTextSize, 0);
            mTitleTextSize = a.getDimensionPixelSize(R.styleable.CustomView_titleTextSize, 0);
            mUnitsTextSize = a.getDimensionPixelSize(R.styleable.CustomView_unitsTextSize, 0);
        } finally {
            a.recycle();
        }
    }

    // ...
}
Community
  • 1
  • 1
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
  • 1
    Can someone explain why we would set the default value to 0 in the call to `getDimensionPixelSize()`? – Jantzilla Feb 08 '19 at 03:07
  • 1
    @Jantzilla, I'm not sure why I set it to `0`. You're right, though. It would be better to choose a more reasonable default. Remember to do the [sp to px conversion](https://stackoverflow.com/a/42108115/3681880) at runtime on whatever default value you choose. – Suragch Feb 08 '19 at 05:19