21

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.

ArnoldOverwater
  • 235
  • 1
  • 2
  • 5

4 Answers4

60

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);
Cata
  • 11,133
  • 11
  • 65
  • 86
17

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;
        }
    }
}
mindriot
  • 14,149
  • 4
  • 29
  • 40
  • Really slick mindriot, +1! I put the C# port down below. Thanks a lot for this, it really helped me out. – samus Oct 03 '12 at 17:28
  • 2
    I believe String unit = matcher.group(2).toLowerCase(); should be changed to String unit = matcher.group(3).toLowerCase(); – Nick Oct 09 '12 at 03:16
  • @Nick - I edited the code to use the correct group. It's odd though, because the code I have in my library is correct which is where I copied it from. Using group 2 immediately crashes my library code and I tested it before posting. Also, Samus Arin's code (which is a port of mine) also has the correct group, unless he fixed the bug as well without notifying me. Rather confusing but thanks for the heads up. – mindriot Oct 24 '12 at 00:37
  • 1
    Also looks like DIMENSION_PATTERN won't match negative values. This value works though: "^\-?\s*(\d+(\.\d+))\s([a-zA-Z]+)\s*$" – Nick Sep 26 '14 at 04:31
  • @Nick probably it should be ^\-?\s*(\d+(\.\d+)*)\s*([a-zA-Z]+)\s* – sandrstar Oct 05 '16 at 12:44
1

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;
        }
    }
}
samus
  • 6,102
  • 6
  • 31
  • 69
0

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.

Community
  • 1
  • 1
Phil
  • 35,852
  • 23
  • 123
  • 164
  • I've read this on http://developer.android.com/, however ít would still be hard to extract for example the `"dp"` substring from the `String` `"12dp"`, verify that `"dp"` is a valid conversion unit and then convert it. I know the Android framework must be capable to to it, since it also does so with XML resources, but doesn't seem to be open for Non-resource strings. – ArnoldOverwater Dec 01 '11 at 15:56