电子数显控件,隔壁产品都馋哭了

856 阅读4分钟

废不说,有图

模拟电子手表的时钟数显,适合用在时钟、数字电子风格显示等场景。

可以注意到,本控件还原了电子手表上数字显示的残影效果,在显示某个具体数字时,仍然可以隐约看见数字“8”的形状。

将这个控件用在大屏全面屏手机上,作为一个工作时钟也是个不错的选择。

一、设计思路

模拟现实生活中的上古时代电子手表进行设计还原。为更加贴近现实中的电子手表,也提供了像素栅格的绘制

是不是更有那味儿了?

二、实现方式

2.1 UI拆解

2.1.1 形状分析

可以观察到,控件由同一个基础形状通过不同位置的摆放组合而成

2.1.2 数字分类

所有的数字都由数字“8”的形状组成,不同部位涂抹不同颜色而呈现不同数字

2.2 UI绘制

2.2.1 绘制基本形状

定义全局的mPath变量用于绘制基本形状,封装一个绘制基本形状的方法

/**
 * 绘制骨架单元
 *
 * @param w 宽
 * @param h 高
 */
private void drawUnit(Canvas canvas, int w, int h) {
    if (mPath == null) {
        mPath = new Path();
        // 间隔,相当于padding
        int d = 5;
        mPath.moveTo(d, h / 2);
        mPath.lineTo(h / 2 + d, 0);
        mPath.lineTo(w - h / 2 - d, 0);
        mPath.lineTo(w - d, h / 2);
        mPath.lineTo(w - h / 2 - d, h);
        mPath.lineTo(h / 2 + d, h);
        mPath.lineTo(d, h / 2);
        mPath.close();
    }

    canvas.drawPath(mPath, unitPaint);

    //todo:绘制网格
}

2.2.2 绘制数字“8”

为表现出数显残影效果,所有的数字由数字“8”的形状组成,涂抹不同位置的颜色显示不同的数字。绘制完基本形状后,对基本形状进行旋转、平移等操作即可组成数字“8”形状。

/**
 * 绘制数字
 *
 * @param canvas
 * @param num    数字
 */
private void drawNum(Canvas canvas, int num) {
    //绘制第1个
    unitPaint.setColor(NumColors.getTargetNumColors(num).colors[0]);
    canvas.translate(uintH / 2, 0);
    drawUnit(canvas, uintW, uintH);

    //绘制第2个
    unitPaint.setColor(NumColors.getTargetNumColors(num).colors[1]);
    canvas.translate(uintW + uintH / 2, 0);
    canvas.rotate(90);
    canvas.translate(uintH / 2, 0);
    drawUnit(canvas, uintW, uintH);

    //绘制第3个
    unitPaint.setColor(NumColors.getTargetNumColors(num).colors[2]);
    canvas.translate(uintW, 0);
    drawUnit(canvas, uintW, uintH);

    //绘制第4个
    unitPaint.setColor(NumColors.getTargetNumColors(num).colors[3]);
    canvas.translate(uintW + uintH / 2, 0);
    canvas.rotate(90);
    canvas.translate(uintH / 2, 0);
    drawUnit(canvas, uintW, uintH);

    //绘制第5个
    unitPaint.setColor(NumColors.getTargetNumColors(num).colors[4]);
    canvas.translate(uintW + uintH / 2, 0);
    canvas.rotate(90);
    canvas.translate(uintH / 2, 0);
    drawUnit(canvas, uintW, uintH);

    //绘制第6个
    unitPaint.setColor(NumColors.getTargetNumColors(num).colors[5]);
    canvas.translate(uintW, 0);
    drawUnit(canvas, uintW, uintH);

    //绘制第7个
    unitPaint.setColor(NumColors.getTargetNumColors(num).colors[6]);
    canvas.rotate(90);
    canvas.translate(uintH / 2, -uintH / 2);
    drawUnit(canvas, uintW, uintH);
}

2.2.3 数字枚举

有了数字“8”形状的骨架,定义不同的数字枚举即可实现不同数字的绘制。同时定义好残影颜色和数显颜色,方便日后定制扩展。

 /**
* 默认骨架颜色
*/
@ColorInt
public static int uintBgColor = Color.parseColor("#e8e8e8");

/**
* 选中的骨架颜色
*/
@ColorInt
public static int uintSelectedColor = Color.DKGRAY;


/**
 * 每个数字对应的颜色数组枚举
 */
enum NumColors {

    NUM_0(new int[]{uintSelectedColor, uintSelectedColor, uintSelectedColor, uintSelectedColor, uintSelectedColor, uintSelectedColor, uintBgColor}),
    NUM_1(new int[]{uintBgColor, uintSelectedColor, uintSelectedColor, uintBgColor, uintBgColor, uintBgColor, uintBgColor}),
    NUM_2(new int[]{uintSelectedColor, uintSelectedColor, uintBgColor, uintSelectedColor, uintSelectedColor, uintBgColor, uintSelectedColor}),
    NUM_3(new int[]{uintSelectedColor, uintSelectedColor, uintSelectedColor, uintSelectedColor, uintBgColor, uintBgColor, uintSelectedColor}),
    NUM_4(new int[]{uintBgColor, uintSelectedColor, uintSelectedColor, uintBgColor, uintBgColor, uintSelectedColor, uintSelectedColor}),
    NUM_5(new int[]{uintSelectedColor, uintBgColor, uintSelectedColor, uintSelectedColor, uintBgColor, uintSelectedColor, uintSelectedColor}),
    NUM_6(new int[]{uintSelectedColor, uintBgColor, uintSelectedColor, uintSelectedColor, uintSelectedColor, uintSelectedColor, uintSelectedColor}),
    NUM_7(new int[]{uintSelectedColor, uintSelectedColor, uintSelectedColor, uintBgColor, uintBgColor, uintBgColor, uintBgColor}),
    NUM_8(new int[]{uintSelectedColor, uintSelectedColor, uintSelectedColor, uintSelectedColor, uintSelectedColor, uintSelectedColor, uintSelectedColor}),
    NUM_9(new int[]{uintSelectedColor, uintSelectedColor, uintSelectedColor, uintSelectedColor, uintBgColor, uintSelectedColor, uintSelectedColor});

    /**
     * 数字对应的颜色数组
     */
    int colors[];

    NumColors(int[] colors) {
        this.colors = colors;
    }

    public static NumColors getTargetNumColors(int num) {
        switch (num) {
            case 0:
                return NUM_0;
            case 1:
                return NUM_1;
            case 2:
                return NUM_2;
            case 3:
                return NUM_3;
            case 4:
                return NUM_4;
            case 5:
                return NUM_5;
            case 6:
                return NUM_6;
            case 7:
                return NUM_7;
            case 8:
                return NUM_8;
            case 9:
                return NUM_9;
            default:
                return NUM_0;
        }
    }
}

2.3 栅格绘制

栅格绘制使用到了Paint绘制时的叠加方式,思路是这样的,先绘制基本形状,再在裁剪成基本形状的画布上绘制栅格,最后排除重叠的栅格绘制内容

2.3.1 绘制的叠加方式简介

通过Paint.setXfermode进行设置,参数通过PorterDuff.Mode枚举进行选取。

2.3.2 代码绘制

确定好叠加模式后,裁剪出基本形状的画布,继而绘制栅格。需要注意的是调用restore方法,方便其它基本形状位置的摆放绘制。

//绘制网格
if (isDrawGrid) {
    canvas.save();
    canvas.clipPath(mPath);
    canvas.drawPath(mPath, unitPaint);
    unitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN));
    canvas.drawPath(getGridPath(w, h), unitPaint);
    unitPaint.setXfermode(null);
    canvas.restore();
} else {
    canvas.drawPath(mPath, unitPaint);
}

最后的效果

三、后记

所有的UI交互控件都应该是基于现实的抽象与改进,集可用性、美观性、便捷性于一体。

从控件设计和实现一窥相关行业方向。控件设计可能会经历以下三个阶段:

第一阶段:对现实生活中的工具进行抽象,演化成不同的UI提供给用户;

第二阶段:在抽象的基础上进行派生,新增更适合手机屏幕的平面交互方式;

第三阶段:回归现实,所有的控件成为现实生活中的一部分,它可能在你家的沙发上,也可能在你家的浴室里,完全融入现实生活而不显突兀。

控件地址在gitee

点我点我点我

既然看到这里了,是时候亮出我的公众号了,才怪...