如何解析尺寸字符串并将其转换为尺寸值

21
我正在寻找一种方法,将像"30dp"这样的字符串动态地转换为类似于像素数量的整数。这意味着StaticClass.theMethodImSearchingFor("16px")会返回16
我的应用程序会动态获取这些字符串,并且需要一种将其存储为像素值以便以后使用的方法。
我已经查看了Android源代码,主要是ResourcesTypedArrayTypedValue类,但我找不到有用的东西。
4个回答

60

如果你需要将 Android 资源 dimen 转换为 int 类型,你可以在代码中这样做:

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

我知道,但我要转换的字符串是动态非资源字符串。 - ArnoldOverwater

17

我自己需要这个功能,所以我编写了一个处理它的类。此答案中的所有代码都是根据Apache License 2.0许可的。祝使用愉快。

有两个静态方法模仿了两个TypedValue方法。DimensionConverter.stringToDimension() 模仿 TypedValue.complexToDimension。DimensionConverter.stringToDimensionPixelSize() 模仿TypedValue.complexToDimensionPixelSize

支持所有当前单位。将接受类似于“33sp”,“44 dp”的维度字符串,并针对错误格式抛出异常。

使用简单:

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.");
}

这里是类:

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,+1!我在下面放置了C#端口。非常感谢你,这真的帮了我很多。 - samus
2
我认为应该将 String unit = matcher.group(2).toLowerCase(); 更改为 String unit = matcher.group(3).toLowerCase(); - Nick
@Nick - 我已经编辑了代码,使用了正确的组。不过很奇怪,因为我在我的库中拥有的代码是正确的,是从那里复制下来的。立即使用第二个组会导致我的库代码崩溃,在发布之前我进行了测试。此外,Samus Arin的代码(它是我的一个移植版本)也具有正确的组,除非他修复了这个错误但没有通知我。有点混乱,但感谢你的提醒。 - mindriot
1
似乎 DIMENSION_PATTERN 无法匹配负值。但是这个值可以使用:"^-?\s*(\d+(.\d+))\s([a-zA-Z]+)\s*$" - Nick
@Nick 可能应该是 ^-?\s*(\d+(.\d+))\s([a-zA-Z]+)\s* - sandrstar

1

感谢mindriot,它非常好用,是一个救命稻草。

这是C#版本的代码:

注意:如果由于某些原因您无法使用整数类型(而不是int),则在与之相关的所有地方,我都将使用C# int的代码注释掉。只需将注释的int代码替换为您看到的未注释的Integer代码即可。

必须使用Integer,以便在检查后缀的Dictionary/Map(TryGetValue)时确定是否没有匹配项(在这种情况下,它将为null;如果使用int,则out参数将为0,这对应于映射的第一个条目,显然不起作用。太糟糕了,TryGetValue没有返回负值来表示没有匹配项!?)。

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

0

这个链接可能会帮助你解决转换问题,但由于像素和密度无关像素不是一对一的匹配,所以可能会出现一些(轻微)的扭曲。

这些单位(dp)是相对于160 dpi屏幕的,因此在160 dpi屏幕上,一个dp就是一个像素。 dp到像素的比率将随着屏幕密度而改变,但不一定成正比例。



网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接