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

先看效果

效果圖.png

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

iOS版
寫法思路完全相同

思路

繪圖主要繪制刻度線和時(shí)間數(shù)值,其中要記錄和使用的主要數(shù)值為時(shí)間戳,相應(yīng)時(shí)間戳在需要繪制的界面里對(duì)應(yīng)的x的值,時(shí)間參考為時(shí)間軸中線,而刻度線參考為0刻度即左邊界.需要處理的主要關(guān)系為時(shí)間戳和中間刻度所代表的時(shí)間刻度改變時(shí)各個(gè)時(shí)間戳所代表的相對(duì)于控件本身的x的值.
那么所有數(shù)據(jù)都可以通過中間線位置所代表的時(shí)間,控件本身的寬度,刻度線間距寬度和其所代表的時(shí)間長(zhǎng)度進(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í)間長(zhǎng)度對(duì)中央刻度線時(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;                  //中間刻度對(duì)應(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è)置我中間刻度對(duì)應(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)化為對(duì)應(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;          //長(zhǎng)刻度線間隔所代表的時(shí)間長(zhǎng)度,用于計(jì)算,單位是毫秒
            if (scaleType == SCALE_TYPE_BIG){
                a= 60000 * 6;
            }else {
                a = 60000;
            }
            long rem = interval % (a * 5);
            //根據(jù)時(shí)間戳值對(duì)大刻度間隔是否整除判斷畫長(zhǎng)刻度或者短刻度
            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)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末接箫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖秒啦,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怜庸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡藏斩,警方通過查閱死者的電腦和手機(jī)他匪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門菇存,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人邦蜜,你說我怎么就攤上這事依鸥。” “怎么了畦徘?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵毕籽,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我井辆,道長(zhǎng)关筒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任杯缺,我火速辦了婚禮蒸播,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘萍肆。我一直安慰自己袍榆,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布塘揣。 她就那樣靜靜地躺著包雀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪亲铡。 梳的紋絲不亂的頭發(fā)上才写,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音奖蔓,去河邊找鬼赞草。 笑死,一個(gè)胖子當(dāng)著我的面吹牛吆鹤,可吹牛的內(nèi)容都是我干的厨疙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼疑务,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼沾凄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起知允,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤搭独,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后廊镜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體牙肝,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年嗤朴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了配椭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡雹姊,死狀恐怖股缸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吱雏,我是刑警寧澤敦姻,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布瘾境,位于F島的核電站,受9級(jí)特大地震影響镰惦,放射性物質(zhì)發(fā)生泄漏迷守。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一旺入、第九天 我趴在偏房一處隱蔽的房頂上張望兑凿。 院中可真熱鬧,春花似錦茵瘾、人聲如沸礼华。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽圣絮。三九已至,卻和暖如春雕旨,著一層夾襖步出監(jiān)牢的瞬間晨雳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國打工奸腺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留餐禁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓突照,卻偏偏與公主長(zhǎng)得像帮非,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子讹蘑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容