Android开发之时间刻度盘

[复制链接]
来自: suisui0614 分类: Android精品源码 上传时间: 2016-4-25 15:13:15
Tag:Android 刻度盘 开发

项目介绍:

一、最近的一个项目中有遇到时间刻度盘的需求,在网上没找到合适的,于是自己就花点时间实现了,现在分享出来,效果如下图:

在介绍如何实现之前,先大概介绍一个这个时间刻度盘的功能:

1、显示当前时间,并且可以左右拖动至上一天或者下一天,

2、根据传入的时间块来绘制蓝色部分

二、代码实现


[Java] 查看源文件 复制代码
public class ScalePanel extends View {  
  
    public interface OnValueChangeListener {  
        public void onValueChange(float value);  
  
        /** 
         * value不再变化,终点 
         *  
         * @param mCalendar 
         *            刻度盘上当前时间 
         */  
        public void onValueChangeEnd(Calendar mCalendar);  
    }  
  
    public static final int MOD_TYPE_HALF = 2;  
    public static final int MOD_TYPE_ONE = 10;  
  
    private static final int ITEM_HALF_DIVIDER = 60;  
  
    private static final int ITEM_MAX_HEIGHT = 10;  
  
    private static final int TEXT_SIZE = 14;  
  
    private float mDensity;  
    /** 
     * 当前刻度值 
     */  
    private int mValue = 12;  
    private int mLineDivider = ITEM_HALF_DIVIDER;  
  
    private float mLastX;  
    /** 
     * 记录刻度盘滑动的偏移量 
     */  
    private float mMove;  
    private float mWidth, mHeight;  
  
    private int mMinVelocity;  
    private Scroller mScroller;  
    private VelocityTracker mVelocityTracker;  
  
    private OnValueChangeListener mListener;  
    /** 
     * 日期文字的宽度 
     */  
    float textWidth = 0;  
    private TextPaint textPaint, dateAndTimePaint;  
    private Paint linePaint;  
    private boolean isNeedDrawableLeft, isNeedDrawableRight;  
    private Calendar mCalendar;  
    private Paint middlePaint, bgColorPaint;  
    /** 
     * 
     */  
    private boolean isChangeFromInSide;  
    public boolean isEnd;  
    // 为了画背景色,从左向右画,记录下屏幕最左,最右处的时间点  
    private Calendar leftCalendar, rightCalendar;  
    private List data;  
    private int hour, minute, second;  
    int gap = 12, indexWidth = 4, indexTitleWidth = 24, indexTitleHight = 10,  
            shadow = 6;  
    String color = "#FA690C";  
    String dateStr, timeStr;  
  
    public ScalePanel(Context context, AttributeSet attrs) {  
        super(context, attrs);  
  
        mScroller = new Scroller(getContext());  
        mDensity = getContext().getResources().getDisplayMetrics().density;  
  
        mMinVelocity = ViewConfiguration.get(getContext())  
                .getScaledMinimumFlingVelocity();  
        linePaint = new Paint();  
        linePaint.setStrokeWidth(2);  
        linePaint.setColor(Color.parseColor("#464646"));  
  
        bgColorPaint = new Paint();  
        bgColorPaint.setStrokeWidth(2);  
        bgColorPaint.setColor(Color.parseColor("#00a3dd"));  
  
        textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);  
        textPaint.setTextSize(TEXT_SIZE * mDensity);  
  
        dateAndTimePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);  
        dateAndTimePaint.setTextSize(18 * mDensity);  
  
        middlePaint = new Paint();  
        scaleUnit = mLineDivider * mDensity;  
        mCalendar = Calendar.getInstance();  
        initDateAndTime(mCalendar);  
  
        leftCalendar = Calendar.getInstance();  
        rightCalendar = Calendar.getInstance();  
    }  
  
    /** 
     * 根据时间来计算偏差,(minute*60+second)*scaleUnit/3600 
     */  
    private void initOffSet() {  
        mMove = (minute * 60 + second) * scaleUnit / 3600;  
    }  
  
    private void initDateAndTime(Calendar mCalendar) {  
        this.mCalendar = mCalendar;  
        hour = mCalendar.get(Calendar.HOUR_OF_DAY);  
        minute = mCalendar.get(Calendar.MINUTE);  
        second = mCalendar.get(Calendar.SECOND);  
        mValue = hour;  
        initOffSet();  
    }  
  
    /** 
     * 通过设置calendar来设置刻度盘当前的时间 
     *  
     * @param mCalendar 
     */  
    public void setCalendar(Calendar mCalendar) {  
        // 用户手指拖动刻度盘的时候,不接收外部的更新,以免冲突  
        if (!isChangeFromInSide) {  
            initDateAndTime(mCalendar);  
            initOffSet();  
            invalidate();  
        }  
    }  
  
    /** 
     * 设置用于接收结果的监听器 
     *  
     * @param listener 
     */  
    public void setValueChangeListener(OnValueChangeListener listener) {  
        mListener = listener;  
    }  
  
    /** 
     * 获取当前刻度值 
     *  
     * @return 
     */  
    public float getValue() {  
        return mValue;  
    }  
  
    @Override  
    protected void onLayout(boolean changed, int left, int top, int right,  
            int bottom) {  
        mWidth = getWidth();  
        mHeight = getHeight();  
        super.onLayout(changed, left, top, right, bottom);  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
  
        drawMiddleLine(canvas);  
        drawScaleLine(canvas);  
    }  
  
    private float offsetPercent;  
    private float scaleUnit;  
    private boolean isChange = false;  
    /** 
     * 线条底部的位置 
     */  
    float lineBottom;  
    /** 
     * 线条顶部得到位置 
     */  
    float lineTop;  
  
    /** 
     * 从中间往两边开始画刻度线 
     *  
     * @param canvas 
     */  
    private void drawScaleLine(Canvas canvas) {  
        canvas.save();  
        isNeedDrawableLeft = true;  
        isNeedDrawableRight = true;  
        float width = mWidth;  
        float xPosition = 0;  
        lineBottom = mHeight - getPaddingBottom();  
        lineTop = lineBottom - mDensity * ITEM_MAX_HEIGHT;  
        if (data != null && data.size() > 0) {  
            calulateDrawPosition(canvas);  
        }  
        //mValue的值控制在0~23之间  
        if (mValue > 0) {  
            mValue = mValue % 24;  
        } else if (mValue < 0) {  
            mValue = mValue % 24 + 24;  
        }  
        if (mMove < 0) {//向左滑动  
            if (mValue == 0 && hour != 23) {  
                mCalendar.set(Calendar.DAY_OF_MONTH,  
                        mCalendar.get(Calendar.DAY_OF_MONTH) - 1);  
            }  
              
            hour = mValue - 1;  
            //滑到上一日23点  
            if (hour == -1) {  
                hour = 23;  
            }  
            offsetPercent = 1 + mMove / scaleUnit;  
        } else if (mMove >= 0) {//向右滑动,  
            offsetPercent = mMove / scaleUnit;  
            hour = mValue;  
            //滑到次日0点,  
            if (hour == 0 && !isChange) {  
                //如果没有ischange,那么在hour==0时,day会重复加一  
                mCalendar.set(Calendar.DAY_OF_MONTH,  
                        mCalendar.get(Calendar.DAY_OF_MONTH) + 1);  
                // 避免重复把day+1  
                isChange = true;  
            }  
        }  
        if (hour != 0) {  
            // 在hour切换成别的值的时候再把标志设为默认值  
            isChange = false;  
        }  
        countMinAndSecond(offsetPercent);  
  
        drawTimeText(canvas);  
        for (int i = 0; true; i++) {  
            // 往右边开始画  
            xPosition = (width / 2 - mMove) + i * scaleUnit;  
            if (isNeedDrawableRight && xPosition + getPaddingRight() < mWidth) {// 在view范围内画刻度  
                canvas.drawLine(xPosition, lineTop, xPosition, lineBottom,  
                        linePaint);  
                textWidth = Layout.getDesiredWidth(int2Str(mValue + i),  
                        textPaint);  
                canvas.drawText(int2Str(mValue + i), xPosition  
                        - (textWidth / 2), lineTop - 5, textPaint);  
            } else {  
                isNeedDrawableRight = false;  
            }  
            // 往左边开始画  
            if (i > 0) {// 防止中间的刻度画两遍  
                xPosition = (width / 2 - mMove) - i * scaleUnit;  
                if (isNeedDrawableLeft && xPosition > getPaddingLeft()) {  
                    canvas.drawLine(xPosition, lineTop, xPosition, lineBottom,  
                            linePaint);  
                    textWidth = Layout.getDesiredWidth(int2Str(mValue - i),  
                            textPaint);  
                    canvas.drawText(int2Str(mValue - i), xPosition  
                            - (textWidth / 2), lineTop - 5, textPaint);  
                } else {  
                    isNeedDrawableLeft = false;  
                }  
            }  
            // 当不需要向左或者向右画的时候就退出循环,结束绘制操作  
            if (!isNeedDrawableLeft && !isNeedDrawableRight) {  
                break;  
            }  
        }  
        canvas.restore();  
    }  
  
    /** 
     * 还存在问题,如果data数据量过大,也就是用户搜索的时间跨度过大,这种方式肯定不行会卡死。 
     * 所以以后得通过获得当前回放所处的位置,然后选择前后一天左右的时间,这样数据量就不会太大 
     * 现在本着先做出来再优化的原则,记录下此问题,以后再做修改优化 
     *  
     * @param canvas 
     */  
    private void calulateDrawPosition(Canvas canvas) {  
        // 距离和时间对应起来 ((mWidth/2/scaleUnit)*3600*1000)  
        long timeOffset = (long) ((mWidth / 2 / scaleUnit) * 3600 * 1000);  
        long middleTime = mCalendar.getTimeInMillis();  
        // 根据时间偏移算出左右的时间  
        leftCalendar.setTimeInMillis(middleTime - timeOffset);  
        rightCalendar.setTimeInMillis(middleTime + timeOffset);  
        // 找到时间开始点,然后顺序向右画,直到画到屏幕最右侧,关键是找到时间开始点  
        // 时间开始点就是从什么地方开始画背景色  
        for (int position = 0; position < data.size(); position++) {  
            TVideoFile tVideoFile = data.get(position);  
            Calendar startCalendar = tVideoFile.startTime;  
            Calendar endCalendar = tVideoFile.endTime;  
            if (leftCalendar.before(startCalendar)  
                    && rightCalendar.after(startCalendar)) {  
                // 从start从开始画  
                drawBgColor(canvas, startCalendar, endCalendar, position);  
                break;  
            } else if (leftCalendar.after(startCalendar)  
                    && leftCalendar.before(endCalendar)) {  
                // 从left从开始画  
                drawBgColor(canvas, leftCalendar, endCalendar, position);  
                break;  
            }  
        }  
    }  
  
    /** 
     *  
     * @param canvas 
     * @param start 
     *            第一块背景色开始的位置 
     * @param distance 
     *            第一块背景色的长度 
     * @param position 
     *            第一块背景色所在时间片段在data中所处的position,下一块从position+1开始 
     */  
    public void drawBgColor(Canvas canvas, Calendar startTime,  
            Calendar endTime, int position) {  
        // 根据时间获得在刻度盘上具体的位置  
        float startPosition = getPositionByTime(startTime);  
        float endPosition = getPositionByTime(endTime);  
        drawBgColorRect(startPosition, lineTop, endPosition, lineBottom, canvas);  
        for (int i = position + 1; i < data.size(); i++) {  
            TVideoFile tVideoFile = data.get(i);  
            Calendar startCalendar = tVideoFile.startTime;  
            Calendar endCalendar = tVideoFile.endTime;  
            startPosition = getPositionByTime(startCalendar);  
            endPosition = getPositionByTime(endCalendar);  
            if (startPosition <= mWidth) {// 只画屏幕屏幕区域以内的  
                drawBgColorRect(startPosition, lineTop, endPosition,  
                        lineBottom, canvas);  
            } else {  
                break;  
            }  
        }  
    }  
  
    /** 
     * 画背景色 
     *  
     * @param canvas 
     */  
    private void drawBgColorRect(float left, float top, float right,  
            float bottom, Canvas canvas) {  
        canvas.drawRect(left, top, right, bottom, bgColorPaint);  
  
    }  
  
    /** 
     * 根据时间获得在刻度盘上具体的位置 
     *  
     * @param calendar 
     * @return 
     */  
    public float getPositionByTime(Calendar calendar) {  
        long middleTime = mCalendar.getTimeInMillis();  
        float position = 0;  
        long timeOffset = middleTime - calendar.getTimeInMillis();  
        if (timeOffset >= 0) {  
            position = (float) (mWidth / 2 - (1.0 * timeOffset / 3600 / 1000)  
                    * scaleUnit);  
        } else {  
            position = (float) (mWidth / 2 - (1.0 * timeOffset / 3600 / 1000)  
                    * scaleUnit);  
        }  
        return position;  
    }  
  
    /** 
     * 准备画背景色的数据 
     */  
    public void setTimeData(List data) {  
        this.data = data;  
    }  
  
    /** 
     * 画日期时间的文字 
     *  
     * @param canvas 
     */  
    private void drawTimeText(Canvas canvas) {  
        mCalendar.set(Calendar.HOUR_OF_DAY, hour);  
        mCalendar.set(Calendar.MINUTE, minute);  
        mCalendar.set(Calendar.SECOND, second);  
        timeStr = date2timeStr(mCalendar.getTime());  
        textWidth = Layout.getDesiredWidth(timeStr, textPaint);  
        canvas.drawText(timeStr, mWidth / 2 + 15 * mDensity, 50,  
                dateAndTimePaint);  
        drawDateText(canvas);  
    }  
  
    private void drawDateText(Canvas canvas) {  
        dateStr = date2DateStr(mCalendar.getTime());  
        textWidth = Layout.getDesiredWidth(dateStr, textPaint);  
        canvas.drawText(dateStr, mWidth / 2 - textWidth - 35 * mDensity, 50,  
                dateAndTimePaint);  
    }  
    /** 
     * 计算分钟和秒钟 
     * @param percent 
     * @return 
     */  
    public int[] countMinAndSecond(float percent) {  
        minute = (int) (3600 * percent / 60);  
        second = (int) (3600 * percent % 60);  
        return new int[] { minute, second };  
    }  
  
    /** 
     * 画中间的红色指示线、阴影等。指示线两端简单的用了两个矩形代替 
     *  
     * @param canvas 
     */  
    private void drawMiddleLine(Canvas canvas) {  
        canvas.save();  
  
        middlePaint.setStrokeWidth(indexWidth);  
        middlePaint.setColor(Color.parseColor(color));  
        canvas.drawLine(mWidth / 2, 0, mWidth / 2, mHeight, middlePaint);  
        canvas.restore();  
    }  
  
    @Override  
    public boolean onTouchEvent(MotionEvent event) {  
        int action = event.getAction();  
        int xPosition = (int) event.getX();  
  
        if (mVelocityTracker == null) {  
            mVelocityTracker = VelocityTracker.obtain();  
        }  
        mVelocityTracker.addMovement(event);  
  
        switch (action) {  
        case MotionEvent.ACTION_DOWN:  
            mScroller.forceFinished(true);  
            mLastX = xPosition;  
            isChangeFromInSide = true;  
            break;  
        case MotionEvent.ACTION_MOVE:  
            mMove += (mLastX - xPosition);  
            changeMoveAndValue();  
            break;  
        case MotionEvent.ACTION_UP:  
        case MotionEvent.ACTION_CANCEL:  
            countMoveEnd();  
            countVelocityTracker(event);  
            return false;  
        default:  
            break;  
        }  
        mLastX = xPosition;  
        return true;  
    }  
  
    private void changeMoveAndValue() {  
        float fValue = mMove / scaleUnit;  
        int tValue = (int) fValue;  
        //滑动超过一格以后,记录下当前刻度盘上的值  
        if (Math.abs(fValue) > 0) {  
            mValue += tValue;  
            //偏移量永远都小于一格  
            mMove -= tValue * scaleUnit;  
            notifyValueChange();  
            postInvalidate();  
        }  
    }  
  
    private void countVelocityTracker(MotionEvent event) {  
        mVelocityTracker.computeCurrentVelocity(1000, 1500);  
        float xVelocity = mVelocityTracker.getXVelocity();  
        if (Math.abs(xVelocity) > mMinVelocity) {  
            mScroller.fling(0, 0, (int) xVelocity, 0, Integer.MIN_VALUE,  
                    Integer.MAX_VALUE, 0, 0);  
        } else {  
            notifyChangeOver();  
        }  
    }  
  
    private void countMoveEnd() {  
        mLastX = 0;  
        notifyValueChange();  
        postInvalidate();  
    }  
  
    private void notifyValueChange() {  
        if (null != mListener) {  
            mListener.onValueChange(mValue);  
        }  
    }  
  
    private void notifyChangeOver() {  
        if (null != mListener) {  
            mListener.onValueChangeEnd(mCalendar);  
        }  
        isChangeFromInSide = false;  
    }  
  
    @Override  
    public void computeScroll() {  
        super.computeScroll();  
        if (mScroller.computeScrollOffset()) {  
            if (mScroller.getCurrX() == mScroller.getFinalX()) { // over  
                countMoveEnd();  
                notifyChangeOver();  
            } else {  
                int xPosition = mScroller.getCurrX();  
                mMove += (mLastX - xPosition);  
                changeMoveAndValue();  
                mLastX = xPosition;  
            }  
        }  
    }  
  
    public String int2Str(int i) {  
        if (i > 0) {  
            i = i % 24;  
        } else if (i < 0) {  
            i = i % 24 + 24;  
        }  
        String str = String.valueOf(i);  
        if (str.length() == 1) {  
            return "0" + str + ":00";  
        } else if (str.length() == 2) {  
            return str + ":00";  
        }  
        return "";  
    }  
  
    public String date2DateStr(Date date) {  
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");  
        return dateFormat.format(date);  
    }  
  
    public String date2timeStr(Date date) {  
        SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");  
        return dateFormat.format(date);  
    }  
}  


我提供了setCalendar方法供外界来设置刻度盘的当前时间,并且提供了onValueChange(float value)和onValueChangeEnd(Calendar mCalendar)来分别提供实时监听和滑动结束的监听,如果想要绘制时间块的背景色可以这样


[Java] 查看源文件 复制代码
public class MainActivity extends Activity implements OnValueChangeListener {  
    /** 
     * 时间刻度盘 
     */  
    private ScalePanel scalePanel;  
    List data = new ArrayList();  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        initData();  
        scalePanel = (ScalePanel) findViewById(R.id.scalePanel);  
        scalePanel.setValueChangeListener(this);  
        Calendar mCalendar = Calendar.getInstance();  
        //设置时间块数据  
        scalePanel.setTimeData(data);  
        //设置当前时间  
        scalePanel.setCalendar(mCalendar);  
    }  
  
    private void initData() {  
        for (int hourOffset = -5; Math.abs(hourOffset) <= 5; hourOffset++) {  
            addTimeBloack(hourOffset);  
        }  
    }  
  
    private void addTimeBloack(int hourOffset) {  
        TVideoFile file = new TVideoFile();  
        Calendar startTime = Calendar.getInstance();  
        startTime.set(Calendar.HOUR_OF_DAY, startTime.get(Calendar.HOUR_OF_DAY) + hourOffset);  
        startTime.set(Calendar.MINUTE, 0);  
        file.startTime = startTime;  
  
        Calendar endTime = Calendar.getInstance();  
        endTime.set(Calendar.HOUR_OF_DAY, endTime.get(Calendar.HOUR_OF_DAY) + hourOffset);  
        endTime.set(Calendar.MINUTE, 50);  
        file.endTime = endTime;  
        data.add(file);  
    }  
  
    @Override  
    public void onValueChange(float value) {  
  
    }  
  
    @Override  
    public void onValueChangeEnd(Calendar mCalendar) {  
  
    }  
}  


具体的实现可以细看代码和注释,代码中有些关于scroller的使用我没有做任何说明



相关源码推荐:

我来说两句
所有评论(27)
never。boy 2016-4-26 08:43:56
楼主用心了,内容非常精彩。
回复
hsc931702526 2016-4-27 16:32:20
虽不明,但觉厉!
回复
笨@小孩 2016-5-13 15:45:16
正需要啊,感谢楼主无私分享!
回复
pcpopdyn 2016-6-16 18:04:22
膜拜大神~
回复
endless 2016-6-16 18:05:28
膜拜大神~
回复
xukun 2016-7-19 10:51:55
感谢分享,安卓巴士有你更精彩:)
回复
never。boy 2016-9-13 09:51:27
楼主威武啊,安卓巴士有你更给力!
回复
1234下一页
提取码:  下载次数:19 状态:已购或VIP 售价:10(原价:10)金钱 下载权限:初级码农 
2321 0 19
代码贡献英雄榜
用户名 下载数
联系我们
首页/微信公众账号投稿
帖子代码编辑/版权问题
QQ:435399051,1294855032
如何获得代码达人称号?
如何成为签约作者?
领先的中文移动开发者社区
18620764416
7*24全天服务
意见反馈:1294855032@qq.com

扫一扫关注我们

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粤ICP备15117877号 )