自定义刻度尺进度条总结

应项目需要我这边要定义一个播放录音的控件,效果如图:

这边先讲一下具体的初版思路吧

1、首先我的思路是复杂的问题简单化,先把这个控件拆分为:上面的刻度和下面的进度条

2、刻度可以根据录音的具体时间来画,下面的进度可以继承SeekBar来实现

3、然后将两个控件的组合在一起放在一个自定义的Relativelayout中,组合成一个控件

先上一下效果动图


下面是上代码时间:

一:首先来画刻度尺的部分

刻度尺的部分是比较简单的这里我自定义了一个控件继承View然后同onDraw来画出这个刻度尺来

如果要画出刻度尺则必须知道自定义控件在屏幕的中位置,下面是获取控件在屏幕中具体位置的方法

下面是获取屏幕宽度的方法
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(dm);
screenWidth = dm.widthPixels;
下面是获取视图绝对坐标和相对坐标的方法
getLocationOnScreen ,计算该视图在全局坐标系中的xy值,(注意这个值是要从屏幕顶端算起,也就是索包括了通知栏的高度)//获取在当前屏幕内的绝对坐标
getLocationInWindow ,计算该视图在它所在的widnow的坐标xy值,//获取在整个窗口内的绝对坐标 (不是很理解= =)
getLeft ,getTop,getBottom,getRight,这一组是获取相对在它父亲里的坐标
如果在ActivityOnCreate()事件输出那些参数,是全为0,要等UI控件都加载完了才能获取到这些。
可在onMeasure中中设置自定义控件的高度
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int width;
    int height;

    if (heightMode == MeasureSpec.EXACTLY) {
        height = heightSize;
    } else {
        height = Math.min(mMinHeight, heightSize);
    }
    width = widthSize;
    setMeasuredDimension(width, height);
}
控件的宽和高确定好之后接下来就是要画刻度值了,刻度值分为两部分,一部分是水平的横线另一部分是

竖直的竖杠,横线比较简单至于竖线考虑到录音的时间是从一到无穷大的,于是这竖线的部分就需要分一分了

首先是1-6s的时候分别画录取时间加一的竖线,大于6的时候则要将对录音的时间进行三等分或者两等分,把时间分的均匀一点就行了,上面的刻度只是个大概的显示所以尽力做到平均就行了。

线画完之后就要画时间的显示了时间的显示如下

将时间进行等分的处理函数

private int courseTime(int record, int time) {
    if (record % 6 == 0) {//整除6则返回6的整倍数
        return record / 6 * time;
    } else if (record % 3 == 0) {//整除3
        if (time % 2 == 0) {//三等分的位置
            return (record / 3) * (time / 2);
        } else {//其他的位置
            return ((record / 3) * ((time + 1) / 2) - ((record / 3) * ((time - 1) / 2))) / 2 + ((record / 3) * ((time - 1) / 2));
        }
    } else if (record % 2 == 0) {//能整除2的数值
        if (time % 3 == 0) {//进行二等分的位置
            return (record / 2) * (time / 3);
        } else {//其他位置
            if ((record / 2) % 3 == 1) {//二等分之后再三等分进行余数的分配
                if (time < 3) {//前半部分的二等分
                    return (record / 2 / 3 * time);
                } else {//后半部分的二等分
                    return (record / 2 + record / 2 / 3 * (time - 3));
                }
            } else {//同上
                if ((record / 2) % 3 == 2) {
                    if (time < 3) {
                        return (record / 2 / 3 * time + time);
                    } else {
                        return (record / 2 + record / 2 / 3 * (time - 3) + (time - 3));
                    }
                }
            }
        }
    } else {//质数的分配
        if (time == 0) {
            return 0;
        } else if (time == 3) {
            return record / 2;
        } else if (time == 6) {
            return record;
        } else if (time < 3) {
            if ((record / 2) % 3 == 1) {
                return (record / 2 / 3 * time);
            } else if ((record / 2) % 3 == 2) {
                return (record / 2 / 3 * time + time);
            } else {
                return record / 2 / 3 * time;
            }
        } else {
            if ((record / 2 + 1) % 3 == 1) {
                return (record / 2 + record / 2 / 3 * (time - 3));
            } else if ((record / 2) % 3 == 2) {
                return (record / 2 + record / 2 / 3 * (time - 3) + (time - 3));
            } else {
                return record / 2 + (record / 2 + 1) / 3 * (time - 3);
            }
        }
    }
    return 0;
}

直接获取绘制字符串的宽度
textPaint.measureText(str)
将int值的秒级数转换为时分秒格式
private String getCourseTime(Integer seconds) {
    int day = seconds / (60 * 60 * 24);//换成天
    int hour = (seconds - (60 * 60 * 24 * day)) / 3600;//总秒数-换算成天的秒数=剩余的秒数    剩余的秒数换算为小时
    int minute = (seconds - 60 * 60 * 24 * day - 3600 * hour) / 60;//总秒数-换算成天的秒数-换算成小时的秒数=剩余的秒数    剩余的秒数换算为分
    int second = seconds - 60 * 60 * 24 * day - 3600 * hour - 60 * minute;//总秒数-换算成天的秒数-换算成小时的秒数-换算为分的秒数=剩余的秒数
    if (hour != 0) {
        return (hour >= 10 ? hour : "0" + hour) + ":" + (minute >= 10 ? minute : "0" + minute) + ":" + (second >= 10 ? second : "0" + second);
    } else if (minute != 0) {
        return (minute >= 10 ? minute : "0" + minute) + ":" + (second >= 10 ? second : "0" + second);
    } else {
        return "00:" + (second >= 10 ? second : "0" + second);
    }
}
然后直接在几道竖线下画平分的时间就行了,至此刻度尺就制作完毕了。

二:接下来就是下面的进度条部分了,这个部分我是继承了SeekBar的自定义View制作的

我们也分为三步

1、首先是红色的游标

把默认的游标去除

android:duplicateParentState="true"
android:thumb="@null"
要画出完整的图标就必须把进度条的背景放大

//设置控件的padding 给提示指示图标留出位置
setPadding(linePadding, (int) Math.ceil(imageHeight) - 30, linePadding, 25);

2、就是进度条的颜色、进度和上面的刻度尺要保持两端相同,我的思路是:①进度条的长度保持和上面的刻度尺长度相同 ②画一个图片来覆盖进度条默认的进度图片 ③画一个半透明的灰色横线来作为进度

rectSeek = this.getProgressDrawable().getBounds();
float bm_x = rectSeek.width() * getProgress() / getMax() + (linePadding - (int) Math.ceil(imageWidth) / 2);

//设置控件的padding 给提示指示图标留出位置
setPadding(linePadding, (int) Math.ceil(imageHeight) - 30, linePadding, 25);

图片使用一个xml格式的shape做的可以添加起始、中部和结尾的颜色

canvas.drawBitmap(seekbarThumbBitmap, 0, (int) Math.ceil(imageHeight) - 28, cursorImagePaint);//绘制假的进度条背景色

canvas.drawLine(0, (int) Math.ceil(imageHeight) - 28, bm_x + (int) Math.ceil(imageWidth) / 2, (int) Math.ceil(imageHeight) - 28, seekbarThumbPaint);

3、下面一个就是画tag的功能了,这个主要就是画一个红色的矩形

根据打印tag的时间来确定红色矩形的位置就可以了

mBarRect.left = getPaddingLeft();
mBarRect.right = getWidth() - getPaddingRight();
//final boolean isLayoutRtl = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
float cx;
//float cy = getHeight() / 2 - getPaddingBottom() / 2 - 2;
float barRectwidth = mBarRect.width() - 50;
cx = mBarRect.left + barRectwidth
        * (mMarks.get(i).postion / (float) mDuration);
    //}
    //radius = getResources().getDimension(R.dimen.panit_line_space) / 3;
//canvas.drawCircle(cx, cy, radius, mShapePaint);
    //canvas.drawBitmap(mBitmap, cx, cy, mPaintTag);
canvas.drawRect(cx, (int) Math.ceil(imageHeight) - 28, cx + tagWidth, (int) Math.ceil(imageHeight) - 28 + tagHeight, tagPaint);
至此这个控件就基本结束了,下面就是要合入record的代码中了。