Android中的艺术数字

1,562 阅读2分钟

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动。

本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

效果

艺术数字

使用代码如下:

<com.lloydfinch.mooneffect.NumberView
    android:id="@+id/tv_number"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="#FF0000"
    android:padding="8dp"

    app:nv_num0="@drawable/ic_0"
    app:nv_num1="@drawable/ic_1"
    app:nv_num2="@drawable/ic_2"
    app:nv_num3="@drawable/ic_3"
    app:nv_num4="@drawable/ic_4"
    app:nv_num5="@drawable/ic_5"
    app:nv_num6="@drawable/ic_6"
    app:nv_num7="@drawable/ic_7"
    app:nv_num8="@drawable/ic_8"
    app:nv_num9="@drawable/ic_9"

    app:nv_number="12345689" />

通过nv_num指定对应的数字图片,然后通过nv_number指定数字即可。

分析&代码

我们不可能绘制出这样的文字,太费劲了,所以我们可以使用0-9这10张数字图片 跟 数字建立个对应关系,然后根据数字去找图片 并 绘制出来即可。

所以我们需要:

  • 1 提供0-9共10张数字图片。
  • 2 建立每一位数字到图片的映射。
  • 3 将数字拆解成一位一位的,并根据每一位找到对应的图片。
  • 4 将对应的图片绘制出来即可。

好,逻辑有了,接下来就是实现:

  • 1 我们提供ic_0到ic_9这10张数字图,并且我们定义一个style让用户可以手动指定这些图片。
 <!--自定义数字图片-->
<declare-styleable name="NumberView">
    <!--要展示的数字-->
    <attr name="nv_number" format="string" />
    <!--数字们对应的图片们-->
    <attr name="nv_num1" format="reference" />
    <attr name="nv_num2" format="reference" />
    <attr name="nv_num3" format="reference" />
    <attr name="nv_num4" format="reference" />
    <attr name="nv_num5" format="reference" />
    <attr name="nv_num6" format="reference" />
    <attr name="nv_num7" format="reference" />
    <attr name="nv_num8" format="reference" />
    <attr name="nv_num9" format="reference" />
    <attr name="nv_num0" format="reference" />
</declare-styleable>
  • 2 我们建立一个Map来存储数字到图片的映射关系:
// 存放数字对应的图片
private Map<Integer, Integer> numsMap = new HashMap<>();

// 将用户在xml中指定的数字图片和数字建立映射
numsMap.put(0, array.getResourceId(R.styleable.NumberView_nv_num0, 0));
numsMap.put(1, array.getResourceId(R.styleable.NumberView_nv_num1, 0));
numsMap.put(2, array.getResourceId(R.styleable.NumberView_nv_num2, 0));
numsMap.put(3, array.getResourceId(R.styleable.NumberView_nv_num3, 0));
numsMap.put(4, array.getResourceId(R.styleable.NumberView_nv_num4, 0));
numsMap.put(5, array.getResourceId(R.styleable.NumberView_nv_num5, 0));
numsMap.put(6, array.getResourceId(R.styleable.NumberView_nv_num6, 0));
numsMap.put(7, array.getResourceId(R.styleable.NumberView_nv_num7, 0));
numsMap.put(8, array.getResourceId(R.styleable.NumberView_nv_num8, 0));
numsMap.put(9, array.getResourceId(R.styleable.NumberView_nv_num9, 0));
  • 3 将数字拆解成一位一位的,并找到每位数字对应的图片保存下来,以备绘制,这里要注意顺序。
private void refreshNumber() {
    int length = text.length();
    // 创建数字对应的图片数组
    pics = new Bitmap[length];

    try {
        // 倒序把数字对应的图片存放到pics里面
        int number = Integer.parseInt(text);
        while (number != 0) {
            // 我们这里是后往前依次取个位数,所以要从后往前放图片
            pics[--length] = BitmapFactory.decodeResource(getResources(), numsMap.get(number % 10));
            number /= 10;
        }
    } catch (NumberFormatException e) {
        e.printStackTrace();
    }
}
  • 4 绘制图片,这里要考虑padding。
@Override
protected void onDraw(Canvas canvas) {
    float left = getPaddingStart();
    float top = getPaddingTop();

    // 直接遍历图片并从左到右绘制出来
    for (Bitmap pic : pics) {
        canvas.drawBitmap(pic, left, top, mPaint);
        left += pic.getWidth();
    }
}

这里有个注意点,就是需要处理onMeasure()函数,防止用户给定的数字图片不规则的情况。

完整的代码如下所示:

public class NumberView extends View {

    // 显示的数字
    private String text = "0";

    private Paint mPaint;

    // 数字-图片 映射
    private Map<Integer, Integer> numsMap = new HashMap<>();

    // 用来存放需要绘制的图片
    private Bitmap[] pics;

    public NumberView(Context context) {
        this(context, null);
    }

    public NumberView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public NumberView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.NumberView);
        text = array.getString(R.styleable.NumberView_nv_number);
        numsMap.put(0, array.getResourceId(R.styleable.NumberView_nv_num0, 0));
        numsMap.put(1, array.getResourceId(R.styleable.NumberView_nv_num1, 0));
        numsMap.put(2, array.getResourceId(R.styleable.NumberView_nv_num2, 0));
        numsMap.put(3, array.getResourceId(R.styleable.NumberView_nv_num3, 0));
        numsMap.put(4, array.getResourceId(R.styleable.NumberView_nv_num4, 0));
        numsMap.put(5, array.getResourceId(R.styleable.NumberView_nv_num5, 0));
        numsMap.put(6, array.getResourceId(R.styleable.NumberView_nv_num6, 0));
        numsMap.put(7, array.getResourceId(R.styleable.NumberView_nv_num7, 0));
        numsMap.put(8, array.getResourceId(R.styleable.NumberView_nv_num8, 0));
        numsMap.put(9, array.getResourceId(R.styleable.NumberView_nv_num9, 0));

        array.recycle();
        init();
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);

        refreshNumber();
    }

    private void refreshNumber() {
        // 初始化图片数组
        int length = text.length();
        pics = new Bitmap[length];

        try {
            // 倒序把数字对应的图片存放到pics里面
            int number = Integer.parseInt(text);
            while (number != 0) {
                pics[--length] = BitmapFactory.decodeResource(getResources(), numsMap.get(number % 10));
                number /= 10;
            }
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMeasureMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMeasureMode = MeasureSpec.getMode(heightMeasureSpec);

        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        // 如果宽度是wrap_content,就取图片的总宽度
        if (widthMeasureMode == MeasureSpec.AT_MOST) {
            widthSize = 0;
            widthSize += (getPaddingStart() + getPaddingEnd());
            for (Bitmap pic : pics) {
                widthSize += pic.getWidth();
            }
        }

        // 如果高度是wrap_content,就取所有图片中的最大高度
        if (heightMeasureMode == MeasureSpec.AT_MOST) {
            heightSize = 0;
            heightSize += (getPaddingTop() + getPaddingBottom());
            int maxHeight = 0;
            for (Bitmap pic : pics) {
                maxHeight = Math.max(pic.getHeight(), maxHeight);
            }
            heightSize += maxHeight;
        }

        setMeasuredDimension(MeasureSpec.makeMeasureSpec(widthSize, widthMeasureMode), MeasureSpec.makeMeasureSpec(heightSize, heightMeasureMode));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        float left = getPaddingStart();
        float top = getPaddingTop();

        // 直接遍历图片并从左到右绘制出来
        for (Bitmap pic : pics) {
            canvas.drawBitmap(pic, left, top, mPaint);
            left += pic.getWidth();
        }
    }

    /**
     * 设置数字
     *
     * @param text 要设置的数字
     */
    public void setText(String text) {
        this.text = text;
        refreshNumber();
    }
}

总结

核心思想就一个: 这个玩意儿一看就不是一时半会画出来的,就不要再纠结,就算最后终于画出来了,也就是装个X,浪费时间精力。说白了就是不做收支比不高的事

知识点也是一个: 对直接继承自View的自定义View,需要手动处理wrap_content和padding这两个关键点。