android – How to parse a dimension string and convert it to a dimension value-ThrowExceptions

Exception or error:

I’m looking for a way to dynamically convert a String like "30dp" into an int that resembles the amount of pixels. This would mean that StaticClass.theMethodImSearchingFor("16px") would return 16.
My application will get these Strings dynamically and I need a way to store it as a pixel value to use later.
I’ve already looked at the Android Source code, mainly the classes Resources, TypedArray and TypedValue, but I couldn’t find anything useful.

How to solve:

If you need the android resource dimen as a int you can do this in your code:

context.getResources().getDimensionPixelSize(R.dimen.your_dimen_res);

###

I needed this myself so I wrote a class to handle it. All code in this answer is licensed under Apache License 2.0. Enjoy.

There are two static methods that mimic two TypedValue methods. DimensionConverter.stringToDimension() mimics TypedValue.complexToDimension. DimensionConverter.stringToDimensionPixelSize() mimics TypedValue.complexToDimensionPixelSize.

Supports all current units. Will accept dimension strings like “33sp”, ” 44 dp ” and throw an exception for bad formats.

Simple to use:

String dimension = "38dp";
Log.i(TAG, "Testing: " + dimension);
try {
    Log.i(TAG, "Converts to: " + DimensionConverter.stringToDimension(dimension, resources.getDisplayMetrics()));
} catch (NumberFormatException exception) {
    Log.i(TAG, "Unable to convert.");
}

Class here:

public class DimensionConverter {

    // -- Initialize dimension string to constant lookup.
    public static final Map<String, Integer> dimensionConstantLookup = initDimensionConstantLookup();
    private static Map<String, Integer> initDimensionConstantLookup() {
        Map<String, Integer> m = new HashMap<String, Integer>();  
        m.put("px", TypedValue.COMPLEX_UNIT_PX);
        m.put("dip", TypedValue.COMPLEX_UNIT_DIP);
        m.put("dp", TypedValue.COMPLEX_UNIT_DIP);
        m.put("sp", TypedValue.COMPLEX_UNIT_SP);
        m.put("pt", TypedValue.COMPLEX_UNIT_PT);
        m.put("in", TypedValue.COMPLEX_UNIT_IN);
        m.put("mm", TypedValue.COMPLEX_UNIT_MM);
        return Collections.unmodifiableMap(m);  
    }
    // -- Initialize pattern for dimension string.
    private static final Pattern DIMENSION_PATTERN = Pattern.compile("^\\s*(\\d+(\\.\\d+)*)\\s*([a-zA-Z]+)\\s*$");

    public static int stringToDimensionPixelSize(String dimension, DisplayMetrics metrics) {
        // -- Mimics TypedValue.complexToDimensionPixelSize(int data, DisplayMetrics metrics).
        InternalDimension internalDimension = stringToInternalDimension(dimension);
        final float value = internalDimension.value;
        final float f = TypedValue.applyDimension(internalDimension.unit, value, metrics);
        final int res = (int)(f+0.5f);
        if (res != 0) return res;
        if (value == 0) return 0;
        if (value > 0) return 1;
        return -1;
    }

    public static float stringToDimension(String dimension, DisplayMetrics metrics) {
        // -- Mimics TypedValue.complexToDimension(int data, DisplayMetrics metrics).
        InternalDimension internalDimension = stringToInternalDimension(dimension);
        return TypedValue.applyDimension(internalDimension.unit, internalDimension.value, metrics);
    }

    private static InternalDimension stringToInternalDimension(String dimension) {
        // -- Match target against pattern.
        Matcher matcher = DIMENSION_PATTERN.matcher(dimension);
        if (matcher.matches()) {
            // -- Match found.
            // -- Extract value.
            float value = Float.valueOf(matcher.group(1)).floatValue();
            // -- Extract dimension units.
            String unit = matcher.group(3).toLowerCase();
            // -- Get Android dimension constant.
            Integer dimensionUnit = dimensionConstantLookup.get(unit);
            if (dimensionUnit == null) {
                // -- Invalid format.
                throw new NumberFormatException();
            } else {
                // -- Return valid dimension.
                return new InternalDimension(value, dimensionUnit);
            }
        } else {
            // -- Invalid format.
            throw new NumberFormatException();
        }        
    }

    private static class InternalDimension {
        float value;
        int unit;

        public InternalDimension(float value, int unit) {
            this.value = value;
            this.unit = unit;
        }
    }
}

###

Thanks to mindriot, works great and is a lifesaver.

Here it is in C#

note: If for some reason you cannot use Integer types (vs int’s) (which will be Java Integers in Mono), I left the code that uses C# int’s in comments everywhere that is associated. Just swap the commented int code for the uncommented Integer code everywhere you see it.

Had to use Integer so that it can be determined if there is no match when checking the Dictionary/Map (TryGetValue) of suffixs (in which case it’ll be null; if ints are used instead, then the out param will be 0, which corresponds to the first entry of the map, which obviously doesn’t work. Too bad TryGetValue didn’t return a negeative value upon no match!?).

public class DimensionConverter
{
    // -- Initialize dimension string to constant lookup.     

    //public static readonly Dictionary<string, int> dimensionConstantLookup = initDimensionConstantLookup();
    public static readonly Dictionary<string, Integer> dimensionConstantLookup = initDimensionConstantLookup();

    //private static Dictionary<string, int> initDimensionConstantLookup()
    private static Dictionary<string, Integer> initDimensionConstantLookup()
    {
        //Dictionary<string, int> m = new Dictionary<string, int>();
        Dictionary<string, Integer> m = new Dictionary<string, Integer>();

        m.Add("px", (Integer)((int)ComplexUnitType.Px));
        m.Add("dip", (Integer)((int)ComplexUnitType.Dip));
        m.Add("dp", (Integer)((int)ComplexUnitType.Dip));
        m.Add("sp", (Integer)((int)ComplexUnitType.Sp));
        m.Add("pt", (Integer)((int)ComplexUnitType.Pt));
        m.Add("in", (Integer)((int)ComplexUnitType.In));
        m.Add("mm", (Integer)((int)ComplexUnitType.Mm));

        /*m.Add("px", (int)ComplexUnitType.Px);
        m.Add("dip", (int)ComplexUnitType.Dip);
        m.Add("dp", (int)ComplexUnitType.Dip);
        m.Add("sp", (int)ComplexUnitType.Sp);
        m.Add("pt", (int)ComplexUnitType.Pt);
        m.Add("in", (int)ComplexUnitType.In);
        m.Add("mm", (int)ComplexUnitType.Mm);*/

        return m;
    }

    // -- Initialize pattern for dimension string.     

    private static Regex DIMENSION_PATTERN = new Regex("^\\s*(\\d+(\\.\\d+)*)\\s*([a-zA-Z]+)\\s*$");

    public static int stringToDimensionPixelSize(string dimension, DisplayMetrics metrics)
    {
        // -- Mimics TypedValue.complexToDimensionPixelSize(int data, DisplayMetrics metrics).         

        InternalDimension internalDimension = stringToInternalDimension(dimension);

        float value = internalDimension.value;
        //float f = TypedValue.ApplyDimension((ComplexUnitType)internalDimension.unit, value, metrics);
        float f = TypedValue.ApplyDimension((ComplexUnitType)(int)internalDimension.unit, value, metrics);
        int res = (int)(f + 0.5f);

        if (res != 0) return res;
        if (value == 0) return 0;
        if (value > 0) return 1;

        return -1;
    }

    public static float stringToDimension(String dimension, DisplayMetrics metrics)
    {
        // -- Mimics TypedValue.complexToDimension(int data, DisplayMetrics metrics).         

        InternalDimension internalDimension = stringToInternalDimension(dimension);

        //return TypedValue.ApplyDimension((ComplexUnitType)internalDimension.unit, internalDimension.value, metrics);
        return TypedValue.ApplyDimension((ComplexUnitType)(int)internalDimension.unit, internalDimension.value, metrics);
    }

    private static InternalDimension stringToInternalDimension(String dimension)
    {
        // -- Match target against pattern.         

        MatchCollection matches = DIMENSION_PATTERN.Matches(dimension);

        if (matches.Count > 0)
        {
            Match matcher = matches[0];

            // -- Match found.             
            // -- Extract value.             
            float value = Float.ValueOf(matcher.Groups[1].Value).FloatValue();

            // -- Extract dimension units.             
            string unit = matcher.Groups[3].ToString().ToLower();

            // -- Get Android dimension constant.             
            //int dimensionUnit;

            Integer dimensionUnit;
            dimensionConstantLookup.TryGetValue(unit, out dimensionUnit);

            //if (dimensionUnit == ????)
            if (dimensionUnit == null)
            {
                // -- Invalid format.                 
                throw new NumberFormatException();
            }
            else
            {
                // -- Return valid dimension.                 
                return new InternalDimension(value, dimensionUnit);
            }
        }
        else
        {
            // -- Invalid format.             
            throw new NumberFormatException();
        }
    }

    private class InternalDimension
    {
        public float value;
        //public int unit;
        public Integer unit;

        //public InternalDimension(float value, int unit)
        public InternalDimension(float value, Integer unit)
        {
            this.value = value;
            this.unit = unit;
        }
    }
}

###

This link might help you figure out your conversion, however since pixels and density-independent pixels are not a 1:1 matchup, expect some (minor) distortions.

These units (dp) are relative to a 160 dpi screen, so one dp is one
pixel on a 160 dpi screen. The ratio of dp-to-pixel will change with
the screen density, but not necessarily in direct proportion.

Leave a Reply

Your email address will not be published. Required fields are marked *