Android自定义控件--时间标尺控件

1、自定义刻度尺控件

     时间标尺可以左右滑动,并支持手势缩放

     点击日期,时间标尺可以跳转到对应的的日期的00:00

     点击实时,时间标尺可以跳转到当前时间

     实现的最终效果如下图所示:

 

2、在values文件夹下新建attrs_time_rule_pressure.xml文件,设置自定义属性:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="rule_bgColor" format="color|reference"/>
    <attr name="rule_gradationColor" format="color|reference"/>
    <attr name="rule_indicatorLineColor" format="reference|color" />
    <attr name="rule_indicatorLineWidth" format="reference|dimension" />
    <attr name="rule_textColor" format="reference|color" />
    <attr name="rule_textSize" format="reference|dimension" />
    <color name="test">#eb4c1c</color>
    <declare-styleable name="TimeRulePressure">
        <attr name="rule_bgColor" />
        <attr name="rule_gradationColor"/>
        <attr name="trv_partHeight" format="dimension|reference" />
        <attr name="trv_partColor" format="color|reference" />
        <attr name="trv_gradationWidth" format="dimension|reference" />
        <attr name="trv_secondLen" format="dimension|reference" />
        <attr name="trv_minuteLen" format="dimension|reference" />
        <attr name="trv_hourLen" format="dimension|reference" />
        <attr name="trv_gradationTextColor" format="color|reference" />
        <attr name="trv_gradationTextSize" format="dimension|reference" />
        <attr name="trv_gradationTextGap" format="dimension|reference" />
        <attr name="trv_currentTime" format="integer|reference" />
        <attr name="rule_indicatorLineColor" />
        <attr name="rule_indicatorLineWidth" />
        <attr name="trv_indicatorTriangleSideLen" format="dimension|reference" />
        <attr name="trv_dateLayoutHeight" format="dimension|reference"/>
        <attr name="trv_dateTextSize" format="dimension|reference"/>
        <attr name="trv_dateHeight" format="dimension|reference"/>
        <attr name="trv_dateWidth" format="dimension|reference"/>
        <attr name="trv_dateTextColor" format="color|reference"/>
        <attr name="trv_dateBgColor" format="color|reference"/>
        <attr name="trv_dateDistance" format="dimension|reference"/>
    </declare-styleable>
</resources>

3、创建TimeRulePressure.java文件:

package com.sharetronic.ffmpegvideo.utils;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Build;
import android.support.annotation.IntRange;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.Scroller;

import com.sharetronic.ffmpegvideo.BuildConfig;
import com.sharetronic.ffmpegvideo.R;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * TimeRulePressure
 * <p>
 * 时间尺控件
 * <p>
 * 功能:
 * - 可选择多天(00:00 ~ 24:00)内的任一时刻,精确到秒级
 * - 可显示多个时间块
 * - 支持滑动及惯性滑动
 * - 支持缩放时间间隔
 * - 支持滑动与缩放的连续切换
 * <p>
 * 思路:
 * - 时间绘制思路参考{@link }
 * - 时间缩放,采用缩放手势检测器 ScaleGestureDetector
 * - 缩放的等级估算方式:进入默认比例为1,根据每隔所占的秒数与宽度,可估算出每个等级的宽度范围,再与默认等级对应的宽度相除,即可算出缩放比例
 * - 惯性滑动,使用速度追踪器 VelocityTracker
 * - 缩放与滑动之间的连续操作,ScaleGestureDetector 开始与结束的条件是第二个手指按下与松开,
 * 所以onTouchEvent()中应该使用 getActionMasked()来监听第二个手指的 DOWN(ACTION_POINTER_DOWN) 与 UP(ACTION_POINTER_UP) 事件,
 * MOVE 都是一样的
 * - 时间块,由起始时间与终止时间组成,采用一个有序的集合来装入即可
 * <p>
 * Author: tiger-Y
 * Description:
 * Date 2018/12/21
 */
public class TimeRulePressure extends View {

    private static final boolean LOG_ENABLE = BuildConfig.DEBUG;
    public static final int MAX_TIME_VALUE = 24 * 3600;

    private int bgColor;
    /**
     * 刻度颜色
     */
    private int gradationColor;
    /**
     * 时间块的高度
     */
    private float partHeight;
    /**
     * 时间块的颜色
     */
    private int partColor;
    /**
     * 刻度宽度
     */
    private float gradationWidth;
    /**
     * 秒、分、时刻度的长度
     */
    private float secondLen;
    private float minuteLen;
    private float hourLen;
    /**
     * 刻度数值颜色、大小、与时刻度的距离
     */
    private int gradationTextColor;
    private float gradationTextSize;
    private float gradationTextGap;

    /**
     * 当前时间,单位:s
     */
    private @IntRange(from = 0, to = MAX_TIME_VALUE)
    int currentTime;
    /**
     * 指针颜色
     */
    private int indicatorColor;
    /**
     * 指针上三角形的边长
     */
    private float indicatorTriangleSideLen;
    /**
     * 指针的宽度
     */
    private float indicatorWidth;

    /**
     * 最小单位对应的单位秒数值,一共四级: 10s、1min、5min、15min
     * 与 {@link #mPerTextCounts} 和 {@link #mPerCountScaleThresholds} 对应的索引值
     * <p>
     * 可以组合优化成数组
     */
    private static int[] mUnitSeconds = {
            10, 10, 10, 10,
            60, 60,
            5 * 60, 5 * 60,
            15 * 60, 15 * 60, 15 * 60, 15 * 60, 15 * 60, 15 * 60
    };

    /**
     * 数值显示间隔。一共13级,第一级最大值,不包括
     */
    @SuppressWarnings("all")
    private static int[] mPerTextCounts = {
            60, 60, 2 * 60, 4 * 60, // 10s/unit: 最大值, 1min, 2min, 4min
            5 * 60, 10 * 60, // 1min/unit: 5min, 10min
            20 * 60, 30 * 60, // 5min/unit: 20min, 30min
            3600, 2 * 3600, 3 * 3600, 4 * 3600, 5 * 3600, 6 * 3600 // 15min/unit
    };

    /**
     * 与 {@link #mPerTextCounts} 对应的阈值,在此阈值与前一个阈值之间,则使用此阈值对应的间隔数值
     * 如:1.5f 代表 4*60 对应的阈值,如果 mScale >= 1.5f && mScale < 1.8f,则使用 4*60
     * <p>
     * 这些数值,都是估算出来的
     */
    @SuppressWarnings("all")
    private float[] mPerCountScaleThresholds = {
            6f, 3.6f, 1.8f, 1.5f, // 10s/unit: 最大值, 1min, 2min, 4min
            0.8f, 0.4f,   // 1min/unit: 5min, 10min
            0.25f, 0.125f, // 5min/unit: 20min, 30min
            0.07f, 0.04f, 0.03f, 0.025f, 0.02f, 0.015f // 15min/unit: 1h, 2h, 3h, 4h, 5h, 6h
    };
    /**
     * 默认mScale为1
     */
    private float mScale = 1;
    /**
     * 1s对应的间隔,比较好估算
     */
    private final float mOneSecondGap = dp2px(12) / 60f;
    /**
     * 当前最小单位秒数值对应的间隔
     */
    private float mUnitGap = mOneSecondGap * 60;
    /**
     * 默认索引值
     */
    private int mPerTextCountIndex = 4;
    /**
     * 一格代表的秒数。默认1min
     */
    private int mUnitSecond = mUnitSeconds[mPerTextCountIndex];

    /**
     * 数值文字宽度的一半:时间格式为“00:00”,所以长度固定
     */
    private final float mTextHalfWidth;

    private final int SCROLL_SLOP;
    private final int MIN_VELOCITY;
    private final int MAX_VELOCITY;

    /**
     * 当前时间与 00:00 的距离值
     */
    private float mCurrentDistance;


    private Paint mPaint;
    private TextPaint mTextPaint;
    private Path mTrianglePath;
    private Scroller mScroller;
    private VelocityTracker mVelocityTracker;

    /**
     * 缩放手势检测器
     */
    private ScaleGestureDetector mScaleGestureDetector;

    /**
     * 日期列表
     */
    private ArrayList<String> dateArray;

    /**
     * 日期布局的高度
     */
    private float dateHeight;

    /**
     * 日期控件字体、高度、宽度、颜色、控件背景颜色
     */
    private float dateTextSize;
    private float dateItemHeight;
    private float dateItemWidth;
    private int dateItemColor;
    private int dateBgColor;

    /**
     * 被点击的日期控件索引
     */
    private int dateClickIndex = 0;

    /**
     * 日期间控件的间隔
     */
    private float dateItemToItemDistance;

    /**
     * 总时间,日期的天数 * 一天的秒数
     */
//    private int TOTAL_TIME_VALUE = MAX_TIME_VALUE * dateArray.size();

    private int mWidth, mHeight;
    private int mHalfWidth;

    private int mInitialX;
    private int mLastX, mLastY;
    private boolean isMoving;
    private boolean isScaling;

    private List<TimePart> mTimePartList;
    private OnTimeChangedListener mListener;

    public interface OnTimeChangedListener {
        void onTimeChanged(int newTimeValue);
    }

    /**
     * 时间片段
     */
    public static class TimePart {
        /**
         * 起始时间,单位:s,取值范围∈[0, 86399]
         * 0       —— 00:00:00
         * 86399   —— 23:59:59
         */
        public int startTime;

        /**
         * 结束时间,必须大于{@link #startTime}
         */
        public int endTime;
    }

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

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

    public TimeRulePressure(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttrs(context, attrs);

        init(context);
        initScaleGestureDetector(context);

        mTextHalfWidth = mTextPaint.measureText("00:00") * .5f;
        ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
        SCROLL_SLOP = viewConfiguration.getScaledTouchSlop();
        MIN_VELOCITY = viewConfiguration.getScaledMinimumFlingVelocity();
        MAX_VELOCITY = viewConfiguration.getScaledMaximumFlingVelocity();

        calculateValues();
    }

    private void initAttrs(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TimeRulePressure);
        bgColor = ta.getColor(R.styleable.TimeRulePressure_rule_bgColor, Color.parseColor("#EEEEEE"));
        gradationColor = ta.getColor(R.styleable.TimeRulePressure_rule_gradationColor, Color.GRAY);
        partHeight = ta.getDimension(R.styleable.TimeRulePressure_trv_partHeight, dp2px(20));
        partColor = ta.getColor(R.styleable.TimeRulePressure_trv_partColor, Color.parseColor("#F58D24"));
        gradationWidth = ta.getDimension(R.styleable.TimeRulePressure_trv_gradationWidth, 1);
        secondLen = ta.getDimension(R.styleable.TimeRulePressure_trv_secondLen, dp2px(3));
        minuteLen = ta.getDimension(R.styleable.TimeRulePressure_trv_minuteLen, dp2px(5));
        hourLen = ta.getDimension(R.styleable.TimeRulePressure_trv_hourLen, dp2px(10));
        gradationTextColor = ta.getColor(R.styleable.TimeRulePressure_trv_gradationTextColor, Color.parseColor("#FF888888"));
        gradationTextSize = ta.getDimension(R.styleable.TimeRulePressure_trv_gradationTextSize, sp2px(12));
        gradationTextGap = ta.getDimension(R.styleable.TimeRulePressure_trv_gradationTextGap, dp2px(2));
        currentTime = ta.getInt(R.styleable.TimeRulePressure_trv_currentTime, 0);
        indicatorTriangleSideLen = ta.getDimension(R.styleable.TimeRulePressure_trv_indicatorTriangleSideLen, dp2px(15));
        indicatorWidth = ta.getDimension(R.styleable.TimeRulePressure_rule_indicatorLineWidth, dp2px(1));
        indicatorColor = ta.getColor(R.styleable.TimeRulePressure_rule_indicatorLineColor, Color.RED);
        dateHeight = ta.getDimension(R.styleable.TimeRulePressure_trv_dateLayoutHeight, dp2px(40));
        dateTextSize = ta.getDimension(R.styleable.TimeRulePressure_trv_dateTextSize, sp2px(12));
        dateItemHeight = ta.getDimension(R.styleable.TimeRulePressure_trv_dateHeight, dp2px(20));
        dateItemWidth = ta.getDimension(R.styleable.TimeRulePressure_trv_dateWidth, dp2px(40));
        dateItemColor = ta.getColor(R.styleable.TimeRulePressure_trv_dateTextColor, Color.parseColor("#FF888888"));
        dateBgColor = ta.getColor(R.styleable.TimeRulePressure_trv_dateBgColor, Color.parseColor("#AE592D"));
        dateItemToItemDistance = ta.getDimension(R.styleable.TimeRulePressure_trv_dateDistance, dp2px(20));
        ta.recycle();
    }

    private void calculateValues() {
        mCurrentDistance = currentTime / mUnitSecond * mUnitGap;
    }

    private void init(Context context) {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setTextSize(gradationTextSize);
        mTextPaint.setColor(gradationTextColor);

        mTrianglePath = new Path();

        mScroller = new Scroller(context);

        //日期
        dateArray = new ArrayList<>();
        dateArray.add("12/19");
        dateArray.add("12/20");
        dateArray.add(" 实时");
    }

    private void initScaleGestureDetector(Context context) {
        mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.OnScaleGestureListener() {

            /**
             * 缩放被触发(会调用0次或者多次),
             * 如果返回 true 则表示当前缩放事件已经被处理,检测器会重新积累缩放因子
             * 返回 false 则会继续积累缩放因子。
             */
            @Override
            public boolean onScale(ScaleGestureDetector detector) {
                final float scaleFactor = detector.getScaleFactor();
                logD("onScale...focusX=%f, focusY=%f, scaleFactor=%f",
                        detector.getFocusX(), detector.getFocusY(), scaleFactor);

                final float maxScale = mPerCountScaleThresholds[0];
                final float minScale = mPerCountScaleThresholds[mPerCountScaleThresholds.length - 1];
                if (scaleFactor > 1 && mScale >= maxScale) {
                    // 已经放大到最大值
                    return true;
                } else if (scaleFactor < 1 && mScale <= minScale) {
                    // 已经缩小到最小值
                    return true;
                }

                mScale *= scaleFactor;
                mScale = Math.max(minScale, Math.min(maxScale, mScale));
                mPerTextCountIndex = findScaleIndex(mScale);

                mUnitSecond = mUnitSeconds[mPerTextCountIndex];
                mUnitGap = mScale * mOneSecondGap * mUnitSecond;
                logD("onScale: mScale=%f, mPerTextCountIndex=%d, mUnitSecond=%d, mUnitGap=%f",
                        mScale, mPerTextCountIndex, mUnitSecond, mUnitGap);

                mCurrentDistance = (float) currentTime / mUnitSecond * mUnitGap;
                invalidate();
                return true;
            }

            @Override
            public boolean onScaleBegin(ScaleGestureDetector detector) {
                logD("onScaleBegin...");
                isScaling = true;
                return true;
            }

            @Override
            public void onScaleEnd(ScaleGestureDetector detector) {
                isScaling = false;
                logD("onScaleEnd...");
            }
        });

        // 调整最小跨度值。默认值27mm(>=sw600dp的32mm),太大了,效果不好
        Class clazz = ScaleGestureDetector.class;
        int newMinSpan = ViewConfiguration.get(context).getScaledTouchSlop();
        try {
            Field mMinSpanField = clazz.getDeclaredField("mMinSpan");
            mMinSpanField.setAccessible(true);
            mMinSpanField.set(mScaleGestureDetector, newMinSpan);
            mMinSpanField.setAccessible(false);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 二分法查找缩放值对应的索引值
     */
    private int findScaleIndex(float scale) {
        final int size = mPerCountScaleThresholds.length;
        int min = 0;
        int max = size - 1;
        int mid = (min + max) >> 1;
        while (!(scale >= mPerCountScaleThresholds[mid] && scale < mPerCountScaleThresholds[mid - 1])) {
            if (scale >= mPerCountScaleThresholds[mid - 1]) {
                // 因为值往小区,index往大取,所以不能为mid -1
                max = mid;
            } else {
                min = mid + 1;
            }
            mid = (min + max) >> 1;
            if (min >= max) {
                break;
            }
            if (mid == 0) {
                break;
            }
        }
        return mid;
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mWidth = MeasureSpec.getSize(widthMeasureSpec);
        mHeight = MeasureSpec.getSize(heightMeasureSpec);

        // 只处理wrap_content的高度,设置为80dp
        if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
            mHeight = dp2px(60);
        }
        mHalfWidth = mWidth >> 1;

        setMeasuredDimension(mWidth, mHeight);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final int actionIndex = event.getActionIndex();
        int pointerId = event.getPointerId(actionIndex);
        final int actionMasked = event.getActionMasked();
        final int action = event.getAction();
        final int pointerCount = event.getPointerCount();
        logD("onTouchEvent: isScaling=%b, actionIndex=%d, pointerId=%d, actionMasked=%d, action=%d, pointerCount=%d",
                isScaling, actionIndex, pointerId, actionMasked, action, pointerCount);
        final int x = (int) event.getX();
        final int y = (int) event.getY();
        mScaleGestureDetector.onTouchEvent(event);

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);
        switch (actionMasked) {
            case MotionEvent.ACTION_DOWN:
                //点击日期控件
                eventDateClicked(event);

                isMoving = false;
                mInitialX = x;
                if (!mScroller.isFinished()) {
                    mScroller.forceFinished(true);
                }
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                // 只要第二手指按下,就禁止滑动
                isScaling = true;
                isMoving = false;
                break;
            case MotionEvent.ACTION_MOVE:
                if (isScaling) {
                    break;
                }
                int dx = x - mLastX;
                if (!isMoving) {
                    final int dy = y - mLastY;
                    if (Math.abs(x - mInitialX) <= SCROLL_SLOP || Math.abs(dx) <= Math.abs(dy)) {
                        break;
                    }
                    isMoving = true;
                }
                mCurrentDistance -= dx;
                computeTime();
                break;
            case MotionEvent.ACTION_UP:
                if (isScaling || !isMoving) {
                    break;
                }
                mVelocityTracker.computeCurrentVelocity(1000, MAX_VELOCITY);
                final int xVelocity = (int) mVelocityTracker.getXVelocity();
                if (Math.abs(xVelocity) >= MIN_VELOCITY) {
                    // 惯性滑动
                    final int maxDistance = (int) (MAX_TIME_VALUE * (dateArray.size()-1) / mUnitGap * mUnitGap);
                    mScroller.fling((int) mCurrentDistance, 0, -xVelocity, 0, 0, maxDistance, 0, 0);
                    invalidate();
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
                // 两个中的有一个手指被抬起,允许滑动。同时把未抬起的手机当前位置赋给初始X
                isScaling = false;
                int restIndex = actionIndex == 0 ? 1 : 0;
                mInitialX = (int) event.getX(restIndex);
                break;
            default:
                break;
        }
        mLastX = x;
        mLastY = y;
        return true;
    }

    private void computeTime() {
        // 不用转float,肯定能整除
        float maxDistance = MAX_TIME_VALUE * (dateArray.size()-1) / mUnitSecond * mUnitGap;
        // 限定范围
        mCurrentDistance = Math.min(maxDistance, Math.max(0, mCurrentDistance));
        currentTime = (int) (mCurrentDistance / mUnitGap * mUnitSecond);
        if (mListener != null) {
            mListener.onTimeChanged(currentTime);
        }
        //滑动时,更改到对应的日期
        movingChangeDate(currentTime);
        invalidate();
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    protected void onDraw(final Canvas canvas) {
        // 背景
        canvas.drawColor(bgColor);

        //日期
        drawDate(canvas);

        // 刻度
        drawRule(canvas);

        // 时间段
        drawTimeParts(canvas);

        // 当前时间指针
        drawTimeIndicator(canvas);
    }

    /**
     * 绘制日期控件
     *
     * @param canvas
     */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void drawDate(Canvas canvas) {
        //日期控件一半的宽度
        float dateHalfWidth = dateItemWidth / 2;
        //日期一半高度
        //绘制日期控件布局
//        mPaint.setColor(Color.parseColor("#595B5D"));
        for (int i = 0; i < dateArray.size(); i++) {
            if(i == dateClickIndex){
                mPaint.setColor(dateBgColor);
                mPaint.setStyle(Paint.Style.FILL);
                canvas.drawRoundRect(dateItemToItemDistance + (dateItemWidth + dateItemToItemDistance) * i, dp2px(5), (dateItemToItemDistance + dateItemWidth) * (i + 1), dp2px(5) + dateItemHeight, dp2px(10), dp2px(10), mPaint);

            }else {
//                mPaint.setColor(bgColor);
                mPaint.setColor(Color.parseColor("#595B5D"));
                mPaint.setStyle(Paint.Style.STROKE);
                canvas.drawRoundRect(dateItemToItemDistance + (dateItemWidth + dateItemToItemDistance) * i, dp2px(5), (dateItemToItemDistance + dateItemWidth) * (i + 1), dp2px(5) + dateItemHeight, dp2px(10), dp2px(10), mPaint);

            }
//            canvas.drawRoundRect(dateItemToItemDistance + (dateItemWidth + dateItemToItemDistance) * i, dp2px(5), (dateItemToItemDistance + dateItemWidth) * (i + 1), dp2px(5) + dateItemHeight, dp2px(10), dp2px(10), mPaint);
            canvas.drawText(dateArray.get(i), dateItemToItemDistance + dateHalfWidth - mTextHalfWidth + (dateItemToItemDistance + dateItemWidth) * i, dp2px(5) + dateHalfWidth - dateTextSize / 2, mTextPaint);
        }

        //绘制日期下面的分割线
        mPaint.setColor(Color.parseColor("#595B5D"));
        mPaint.setStrokeWidth(1);
        canvas.drawLine(0, dateHeight - dp2px(10), mWidth, dateHeight - dp2px(10), mPaint);
    }

    /**
     * 点击日期控件
     */
    private void eventDateClicked(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        for (int i = 0; i < dateArray.size(); i++) {
            if (x >= (dateItemToItemDistance + (dateItemWidth + dateItemToItemDistance) * i) && x <= ((dateItemToItemDistance + dateItemWidth) * (i + 1)) && y >= dp2px(5) && y <= (dp2px(5) + dateItemHeight)) {
                Log.d("TimeRulePresure", "x =" + x + " y =" + y + " i =" + i);
                dateClickIndex = i;
                setCurrentTime(MAX_TIME_VALUE * i);
                postInvalidate();
            }
        }
    }

    /**
     * 滑动时,更改到对应的日期
     */
    private void movingChangeDate(int distance){
        for (int i = 0; i < dateArray.size()-1; i++) {
            if(distance >= MAX_TIME_VALUE * i && distance < MAX_TIME_VALUE * (i + 1)){
                dateClickIndex = i;
            }
        }
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            mCurrentDistance = mScroller.getCurrX();
            computeTime();
        }
    }

    /**
     * 绘制刻度
     */
    private void drawRule(Canvas canvas) {
        // 移动画布坐标系
        canvas.save();
//        canvas.translate(0, partHeight);
        canvas.translate(0, dateHeight);
        mPaint.setColor(gradationColor);
        mPaint.setStrokeWidth(gradationWidth);

        // 刻度
//        int start = 0;
        float offset = mHalfWidth - mCurrentDistance;
        final int perTextCount = mPerTextCounts[mPerTextCountIndex];
        for (int i = 0; i < dateArray.size()-1; i++) {
            int start = 0;
            while (start <= MAX_TIME_VALUE - 1) {
                // 刻度
                if (start % 3600 == 0) {
                    // 时刻度
                    canvas.drawLine(offset, 0, offset, hourLen, mPaint);
                } else if (start % 60 == 0) {
                    // 分刻度
                    canvas.drawLine(offset, 0, offset, minuteLen, mPaint);
                } else {
                    // 秒刻度
                    canvas.drawLine(offset, 0, offset, secondLen, mPaint);
                }

                // 时间数值
                if (start % perTextCount == 0) {
                    String text = formatTimeHHmm(start);
                    canvas.drawText(text, offset - mTextHalfWidth, hourLen + gradationTextGap + gradationTextSize, mTextPaint);
                }

                start += mUnitSecond;
                offset += mUnitGap;
            }
        }

        canvas.restore();
    }

    /**
     * 绘制当前时间指针
     */
    private void drawTimeIndicator(Canvas canvas) {
        // 指针
        mPaint.setColor(indicatorColor);
        mPaint.setStrokeWidth(indicatorWidth);
        canvas.drawLine(mHalfWidth, dateHeight - dp2px(10), mHalfWidth, mHeight, mPaint);

        // 正三角形
        if (mTrianglePath.isEmpty()) {
            //
            final float halfSideLen = indicatorTriangleSideLen * .5f;
            mTrianglePath.moveTo(mHalfWidth - halfSideLen, dp2px(80));
            mTrianglePath.rLineTo(indicatorTriangleSideLen, 0);
            mTrianglePath.rLineTo(-halfSideLen, -(float) (Math.sin(Math.toRadians(60)) * halfSideLen));
            mTrianglePath.close();
        }
        mPaint.setStrokeWidth(1);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawPath(mTrianglePath, mPaint);
        mPaint.setStyle(Paint.Style.STROKE);
    }

    /**
     * 绘制时间段
     */
    private void drawTimeParts(Canvas canvas) {
        if (mTimePartList == null) {
            return;
        }
        // 不用矩形,直接使用直线绘制
        mPaint.setStrokeWidth(partHeight);
        mPaint.setColor(partColor);
        float start, end;
        final float halfPartHeight = partHeight * .5f;
        final float secondGap = mUnitGap / mUnitSecond;
        for (int i = 0, size = mTimePartList.size(); i < size; i++) {
            TimePart timePart = mTimePartList.get(i);
            start = mHalfWidth - mCurrentDistance + timePart.startTime * secondGap;
            end = mHalfWidth - mCurrentDistance + timePart.endTime * secondGap;
            canvas.drawLine(start, halfPartHeight, end, halfPartHeight, mPaint);
        }
    }

    /**
     * 格式化时间 HH:mm
     *
     * @param timeValue 具体时间值
     * @return 格式化后的字符串,eg:3600 to 01:00
     */
    public static String formatTimeHHmm(@IntRange(from = 0, to = MAX_TIME_VALUE) int timeValue) {
        if (timeValue < 0) {
            timeValue = 0;
        }
        int hour = timeValue / 3600;
        int minute = timeValue % 3600 / 60;
        StringBuilder sb = new StringBuilder();
        if (hour < 10) {
            sb.append('0');
        }
        sb.append(hour).append(':');
        if (minute < 10) {
            sb.append('0');
        }
        sb.append(minute);
        return sb.toString();
    }

    /**
     * 格式化时间 HH:mm:ss
     *
     * @param timeValue 具体时间值
     * @return 格式化后的字符串,eg:3600 to 01:00:00
     */
    public static String formatTimeHHmmss(@IntRange(from = 0, to = MAX_TIME_VALUE) int timeValue) {
        int hour = timeValue / 3600;
        int minute = timeValue % 3600 / 60;
        int second = timeValue % 3600 % 60;
        StringBuilder sb = new StringBuilder();

        if (hour < 10) {
            sb.append('0');
        }
        sb.append(hour).append(':');

        if (minute < 10) {
            sb.append('0');
        }
        sb.append(minute);
        sb.append(':');

        if (second < 10) {
            sb.append('0');
        }
        sb.append(second);
        return sb.toString();
    }

    private int dp2px(float dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
    }

    private int sp2px(float sp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
    }

    @SuppressWarnings("all")
    private void logD(String format, Object... args) {
        if (LOG_ENABLE) {
            Log.d("MoneySelectRuleView", String.format("zjun@" + format, args));
        }
    }

    /**
     * 设置时间变化监听事件
     *
     * @param listener 监听回调
     */
    public void setOnTimeChangedListener(OnTimeChangedListener listener) {
        this.mListener = listener;
    }

    /**
     * 设置时间块(段)集合
     *
     * @param timePartList 时间块集合
     */
    public void setTimePartList(List<TimePart> timePartList) {
        this.mTimePartList = timePartList;
        postInvalidate();
    }

    /**
     * 设置当前时间
     *
     * @param currentTime 当前时间
     */
    public void setCurrentTime(@IntRange(from = 0, to = MAX_TIME_VALUE*3) int currentTime) {
        this.currentTime = currentTime;
        calculateValues();
        postInvalidate();
    }

    /**
     * 设置日期列表
     * @param list
     */
    public void setDateArray(ArrayList<String> list) {
        if (dateArray.isEmpty()) {
            dateArray = new ArrayList<>();
        }
        dateArray.clear();
        dateArray.addAll(list);
        dateArray.add("实时");
        postInvalidate();
    }
}

4、在布局文件中引用

<com.sharetronic.ffmpegvideo.utils.TimeRulePressure
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:layout_below="@id/ll_play"
        app:rule_bgColor="#2B2B2B" />

参考:

https://github.com/zjun615/RulerView

https://www.jianshu.com/p/5d1fa50298b3

https://www.jb51.net/article/140917.htm

https://blog.csdn.net/u014005316/article/details/54667576

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值