先看效果
可左右拖動(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í)用作參考.