Android可拖動(dòng)時(shí)間刻度軸

先看效果

效果圖.png

可左右拖動(dòng)和通過縮放來實(shí)現(xiàn)不同刻度模式的切換,下面代碼只使用了兩種刻度模式.
綠色部分代表有錄制的視頻數(shù)據(jù),該效果可根據(jù)需求自行修改.

iOS版
寫法思路完全相同

思路

繪圖主要繪制刻度線和時(shí)間數(shù)值,其中要記錄和使用的主要數(shù)值為時(shí)間戳,相應(yīng)時(shí)間戳在需要繪制的界面里對應(yīng)的x的值,時(shí)間參考為時(shí)間軸中線,而刻度線參考為0刻度即左邊界.需要處理的主要關(guān)系為時(shí)間戳和中間刻度所代表的時(shí)間刻度改變時(shí)各個(gè)時(shí)間戳所代表的相對于控件本身的x的值.
那么所有數(shù)據(jù)都可以通過中間線位置所代表的時(shí)間,控件本身的寬度,刻度線間距寬度和其所代表的時(shí)間長度進(jìn)行換算和相互轉(zhuǎn)化來解決,而繪圖為保證性能可以通過計(jì)算只去繪制需要繪制的內(nèi)容.
最終需要達(dá)到的效果是通過改變中央刻度線所代表的時(shí)間戳的值來實(shí)現(xiàn)整個(gè)時(shí)間軸的繪制.而我們改變時(shí)間軸就只要改變中間刻度線所代表的時(shí)間戳,然后重新繪制就可以實(shí)現(xiàn)各種效果,比如拖動(dòng),就是根據(jù)手指拖動(dòng)的距離換算成時(shí)間長度對中央刻度線時(shí)間戳進(jìn)行加減,然后不斷繪制達(dá)到拖動(dòng)時(shí)整個(gè)時(shí)間軸滑動(dòng)的效果.

代碼
public class ZFTimeLine extends View {

    private final int SCALE_TYPE_BIG = 1;           //大刻度
    private final int SCALE_TYPE_SMALL = 2;         //小刻度

    private int intervalValue;                    //小刻度寬度
    private int scaleType;
    private long currentInterval;                  //中間刻度對應(yīng)的時(shí)間戳

    private SimpleDateFormat formatterScale;        //日期格式化,用于時(shí)間戳和時(shí)間字符的轉(zhuǎn)換
    private SimpleDateFormat formatterProject;      //日期格式化,用于時(shí)間戳和時(shí)間字符的轉(zhuǎn)換

    private Paint paintWhite,paintGreen,paintRed;   //三種不同顏色的畫筆
    private int point = 0;                          //用于當(dāng)前觸控點(diǎn)數(shù)量
    private float moveStartX = 0;                   //用于記錄單點(diǎn)觸摸點(diǎn)位置,用于計(jì)算拖距離
    private float scaleValue = 0;                   //用于記錄兩個(gè)觸摸點(diǎn)間距,用于時(shí)間軸縮放計(jì)算

    private boolean onLock;                         //用于屏蔽時(shí)間軸拖動(dòng),為true時(shí)無法拖動(dòng)

    private OnZFTimeLineListener listener;          //時(shí)間軸拖動(dòng)監(jiān)聽,這個(gè)只在拖動(dòng)完成時(shí)返回?cái)?shù)據(jù)

    //已錄制視頻數(shù)據(jù)信息
    List<VideoInfo> calstuff;
    //設(shè)置監(jiān)聽
    public void setListener(OnZFTimeLineListener listener) {
        this.listener = listener;
    }

    //拖動(dòng)時(shí)間軸監(jiān)聽
    public interface OnZFTimeLineListener{
        void didMoveToDate(String date);
    }
    public ZFTimeLine(Context context) {
        super(context);
        init();
    }

    public ZFTimeLine(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ZFTimeLine(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    //數(shù)據(jù)數(shù)據(jù)初始化
    private void init(){

        scaleType = SCALE_TYPE_BIG;
        intervalValue = 0;
        setAlpha(0.8f);
        setBackgroundColor(Color.BLACK);
        timeNow();

        onLock = false;

        formatterScale = new SimpleDateFormat("HH:mm");
        formatterProject = new SimpleDateFormat("yyyyMMddHHmmss");

        paintWhite = new Paint();
        paintWhite.setColor(Color.WHITE);
        paintWhite.setTextSize(intDip2px(10));
        paintWhite.setTextAlign(Paint.Align.CENTER);
        paintWhite.setStrokeWidth(dip2px(0.5f));

        paintGreen = new Paint();
        paintGreen.setColor(Color.GREEN);

        paintRed = new Paint();
        paintRed.setColor(Color.RED);
    }

    //把當(dāng)前時(shí)間戳設(shè)置我中間刻度對應(yīng)的時(shí)間戳
    private void timeNow(){
        currentInterval = System.currentTimeMillis();
    }
    //寬度1所代表的毫秒數(shù)
    private long milliscondsOfIntervalValue(){
        if (scaleType == SCALE_TYPE_BIG){
            return (long) (6*60000.0/intervalValue);
        }else {
            return (long) (60000.0/intervalValue);
        }
    }

    private float dip2px(float dipValue){
        return dipValue * (getResources().getDisplayMetrics().densityDpi / 160);
    }
    private int intDip2px(float dipValue){
        return (int) (dip2px(dipValue) + 0.5);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //初始化小刻度的間隔,在init里densityDpi的數(shù)據(jù)為0,所以放到這里了
        if (intervalValue == 0) intervalValue = intDip2px(10);

        //中間線的x值
        long centerX = getWidth()/2;
        //左邊界線代表的時(shí)間戳
        long leftInterval = currentInterval - centerX * milliscondsOfIntervalValue();
        //右邊界線時(shí)間戳
        long rightInterval = currentInterval + centerX * milliscondsOfIntervalValue();

        long x;             //記錄繪制刻度線的位置
        long interval;      //記錄所繪制刻度線代表的時(shí)間戳

        //下面計(jì)算需要繪制的第一個(gè)刻度線的位置和所代表的時(shí)間戳
        if (scaleType == SCALE_TYPE_BIG){
            long a = leftInterval/(60 * 6 * 1000);
            interval =  ((a + 1) * (60 * 6 * 1000));
            x = (interval - leftInterval) / milliscondsOfIntervalValue();
        }else {
            long a = leftInterval/(60 * 1000);
            interval = ((a + 1) * (60 * 1000));
            x = (interval - leftInterval) / milliscondsOfIntervalValue();
        }

        //這里是這個(gè)項(xiàng)目特有的需求,根據(jù)視頻數(shù)據(jù)繪制綠色和紅色區(qū)域,分別代表該位置有已錄制的普通視頻和緊急視頻(行車記錄儀)
        if (calstuff != null){
            for (int i = 0;i<calstuff.size();i++){
                VideoInfo info = calstuff.get(i);
                //獲取視頻文件的開始時(shí)間戳和結(jié)束時(shí)間戳
                long startInterval = info.getStartTime().getTimeInMillis();
                long endInterval = info.getEndTime().getTimeInMillis();
                //判斷是否需要繪制
                if ((startInterval > leftInterval && startInterval < rightInterval)
                        || (endInterval > leftInterval && endInterval < rightInterval)
                        || (startInterval < leftInterval && endInterval > rightInterval)){
                    //將開始和結(jié)束時(shí)間戳轉(zhuǎn)化為對應(yīng)的x的位置
                    long startX = (startInterval - leftInterval)/milliscondsOfIntervalValue();
                    long endX = (endInterval - leftInterval)/milliscondsOfIntervalValue();
                    if (info.getFileName().contains("SOS")){
                        //緊急視頻 為紅色區(qū)域色塊
                        canvas.drawRect(startX,0,endX,getHeight()-dip2px(24),paintRed);
                    }else {
                        //普通的為綠色
                        canvas.drawRect(startX,0,endX,getHeight()-dip2px(24),paintGreen);
                    }
                }

//                Dbug.e("====>", "" + info.getStartTime().getTimeInMillis());
            }
        }
        //畫刻度線
        while (x >= 0 && x<= getWidth()){
            int a;          //長刻度線間隔所代表的時(shí)間長度,用于計(jì)算,單位是毫秒
            if (scaleType == SCALE_TYPE_BIG){
                a= 60000 * 6;
            }else {
                a = 60000;
            }
            long rem = interval % (a * 5);
            //根據(jù)時(shí)間戳值對大刻度間隔是否整除判斷畫長刻度或者短刻度
            if (rem != 0){//小刻度
                canvas.drawLine(x,getHeight() - dip2px(5),x,getHeight(),paintWhite);
            }else {//大刻度
                canvas.drawLine(x,getHeight() - dip2px(10),x,getHeight(),paintWhite);
                //大刻度繪制時(shí)間文字
                String time = formatterScale.format(interval);
                canvas.drawText(time,x,getHeight() - dip2px(12),paintWhite);
            }
            //下一個(gè)刻度
            x = x + intervalValue;
            interval = interval + a;
        }
        //畫中間線
        canvas.drawLine(centerX,0,centerX,getHeight(),paintWhite);
    }

    //通過onTouchEvent來實(shí)現(xiàn)拖動(dòng)和縮放
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction() & MotionEvent.ACTION_MASK){
            case MotionEvent.ACTION_DOWN:{
//                Log.e("touch","ACTION_DOWN" + event.getX());
                point = 1;
                moveStartX = event.getX();
            }break;
            case MotionEvent.ACTION_POINTER_DOWN:{
//                Log.e("touch","ACTION_POINTER_DOWN" + event.getX(0) + "-----" + event.getX(1));
                point = point + 1;
                if (point == 2){
                    scaleValue = Math.abs(event.getX(1) - event.getX(0));
                }
            }break;
            case MotionEvent.ACTION_MOVE:{
//                Log.e("touch","ACTION_MOVE");
                if (point == 1){
                    //拖動(dòng)
                    currentInterval = currentInterval - milliscondsOfIntervalValue() * ((long) (event.getX() -
                            moveStartX));
                    moveStartX = event.getX();
                }else if (point == 2){
                    float value = Math.abs(event.getX(1) - event.getX(0));

                    if (scaleType == SCALE_TYPE_BIG){
                        if (scaleValue - value < 0){//變大
                            intervalValue = intervalValue + ((int) ((value - scaleValue)/dip2px(100)));
                            if (intervalValue >= intDip2px(15)){
                                scaleType = SCALE_TYPE_SMALL;
                                intervalValue = intDip2px(10);
                            }
                        }else {//變小
                            intervalValue = intervalValue + ((int) ((value - scaleValue)/dip2px(100)));
                            if (intervalValue < intDip2px(10)){
                                intervalValue = intDip2px(10);
                            }
                        }
                    }else {
                        if (scaleValue - value < 0){//變大
                            intervalValue = intervalValue + ((int) ((value - scaleValue)/dip2px(100)));
                            if (intervalValue >= intDip2px(15)){
                                intervalValue = intDip2px(15);
                            }
                        }else {//變小
                            intervalValue = intervalValue + ((int) ((value - scaleValue)/dip2px(100)));
                            if (intervalValue < intDip2px(10)){
                                scaleType = SCALE_TYPE_BIG;
                                intervalValue = intDip2px(10);
                            }
                        }
                    }
                }else {
                    return true;
                }
            }break;
            case MotionEvent.ACTION_POINTER_UP:{
//                Log.e("touch","ACTION_POINTER_UP");
                point = point - 1;
            }break;
            case MotionEvent.ACTION_UP:{
//                Log.e("touch","ACTION_UP");
                point = 0;
                //拖動(dòng)結(jié)束  這里應(yīng)該有Bug沒有區(qū)分移動(dòng)可縮放狀態(tài) 不過影響不大
                if (listener != null){
                    listener.didMoveToDate(formatterProject.format(currentInterval));
                }
            }break;
        }
        //重新繪制
        invalidate();
        return true;
    }

    //所有暴露的刷新方法使用不當(dāng)會(huì)引起崩潰(在時(shí)間軸創(chuàng)建之后但是沒有顯示的時(shí)候調(diào)用),解決辦法是使用handel來調(diào)用該方法
    //刷新,重新繪制
    public void refresh(){
        invalidate();
    }

    //刷新到當(dāng)前時(shí)間
    public void refreshNow(){
        if (onLock || point != 0){
            return;
        }
        timeNow();
        refresh();
    }

    //移動(dòng)到某時(shí)間  傳入?yún)?shù)格式舉例 20170918120000
    public void moveTodate(String timeStr){
        if (onLock || point != 0){
            return;
        }
        try {
            currentInterval = formatterProject.parse(timeStr).getTime();
            invalidate();
            if (listener != null){
                listener.didMoveToDate(formatterProject.format(currentInterval));
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
    //移動(dòng)到某時(shí)間 傳入時(shí)間戳
    public void moveTodate(long timeInterval){
        if (onLock || point != 0){
            return;
        }
        if (timeInterval == 0)return;
        currentInterval = timeInterval;
        invalidate();
    }

    //獲取當(dāng)前時(shí)間軸指向的時(shí)間 返回參數(shù)格式舉例 20170918120000
    public String currentTimeStr(){
        return formatterProject.format(currentInterval);
    }

    //鎖定,不可拖動(dòng)和縮放
    public void lockMove(){
        onLock = true;
    }

    //解鎖,可以拖動(dòng)和縮放
    public void unLockMove(){
        onLock = false;
    }

    //獲取當(dāng)前時(shí)間軸指向的時(shí)間的時(shí)間戳
    public long getCurrentInterval(){
        return currentInterval;
    }
    //把時(shí)間數(shù)據(jù)轉(zhuǎn)化為時(shí)間戳
    public long timeIntervalFromStr(String str){
        try {
            return formatterProject.parse(str).getTime();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return 0;
    }
    //把時(shí)間戳轉(zhuǎn)化為時(shí)間字符串
    public String timeStrFromInterval(long interval){
        return formatterProject.format(interval);
    }

    //寫入視頻數(shù)據(jù)
    public void setCalstuff(List<VideoInfo> mcalstuff) {
        this.calstuff = mcalstuff;
        refresh();
    }

    //清除視頻信息
    public void clearVideoInfos(){
        this.calstuff = null;
        refresh();
    }
}
使用
        <com.gushang.ZFTimeLine
            android:id="@+id/scalePanel"
            android:layout_alignParentBottom="true"
            android:background="@android:color/transparent"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </com.gushang.ZFTimeLine>

因?yàn)轫?xiàng)目用到,而且這種效果的資料不太好找,所以在解決之后記錄和分享一下.
也方便我以后處理繪圖需求時(shí)用作參考.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 效果 可左右拖動(dòng)和通過縮放來實(shí)現(xiàn)不同刻度模式的切換,下面代碼只使用了兩種刻度模式.綠色部分代表有錄制的視頻數(shù)據(jù),該...
    夢里風(fēng)吹過閱讀 4,693評(píng)論 8 8
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,765評(píng)論 25 709
  • Matplotlib 入門教程 來源:Introduction to Matplotlib and basic l...
    布客飛龍閱讀 32,238評(píng)論 5 162
  • 大家對優(yōu)惠券應(yīng)該都比較熟悉,有過網(wǎng)購經(jīng)驗(yàn)的人應(yīng)該都用過,優(yōu)惠券也是商家促銷一種常用的手段,今天來跟大家介紹下B2C...
    游社長閱讀 925評(píng)論 0 16
  • 第三天 下一天清晨,我將再一次迎接黎明,急于尋找新的喜...
    聚錦緣閱讀 669評(píng)論 0 0

友情鏈接更多精彩內(nèi)容