Android development time dial, android development dial
I. In a recent project, I encountered a time dial requirement. I didn't find a proper one on the Internet, so I took some time to implement it. Now I want to share it with you. The effect is as follows:
Before introducing how to implement it, we will introduce the functions of a time dial:
1. The current time is displayed. You can drag the cursor to the previous day or to the next day,
2. Draw the blue part based on the input time block
Ii. Code Implementation
Public class ScalePanel extends View {public interface OnValueChangeListener {public void onValueChange (float value);/*** value does not change, destination ** @ param mCalendar * Current Time on the dial */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; pri Vate static final int TEXT_SIZE = 14; private float mDensity;/*** current scale value */private int mValue = 12; private int mLineDivider = ITEM_HALF_DIVIDER; private float mLastX; /*** record the offset of the dial sliding */private float mMove; private float mWidth, mHeight; private int mMinVelocity; private Scroller mScroller; private VelocityTracker mVelocityTracker; private OnValueChangeListener mListener; /*** width of the date text */float textWidth = 0; private TextPaint textPaint, dateAndTimePaint; private Paint linePaint; private boolean values, isNeedDrawableRight; private Calendar mCalendar; private Paint middlePaint, bgColorPaint;/*****/private boolean isChangeFromInSide; public boolean isEnd; // For the purpose of painting the background color, draw from left to right and record the time points on the leftmost and rightmost sides of the screen: private Calendar leftCalendar, rightCalendar; private List <TVideoFile> data; private int hour, minute, second; I Nt gap = 12, indexWidth = 4, indexTitleWidth = 24, indexTitleHight = 10, shadow = 6; String color = "# FA690C"; String dateStr, timeStr; public ScalePanel (Context context Context, attributeSet attrs) {super (context, attrs); mScroller = new Scroller (getContext (); mDensity = getContext (). getResources (). getDisplayMetrics (). density; mMinVelocity = ViewConfiguration. get (getContext ()). getScaledMinimumFlingVelocity (); l InePaint = 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 ();}/*** calculate the deviation based on time (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 () ;}/ *** set the current time of the dial by setting the calendar ** @ param mCalendar */public void setCalendar (Calendar mCalendar) {// when the user's finger is dragging the dial, external updates are not received to avoid conflict with if (! IsChangeFromInSide) {initDateAndTime (mCalendar); initOffSet (); invalidate ();}} /*** set the listener for receiving results ** @ param listener */public void setValueChangeListener (OnValueChangeListener listener) {mListener = listener ;} /*** get the current scale value ** @ return */public float getValue () {return mValue;} @ Overrideprotected void onLayout (boolean changed, int left, int top, int right, int bottom) {mWidth = getWidth (); mHeight = getH Eight (); super. onLayout (changed, left, top, right, bottom) ;}@ Overrideprotected void onDraw (Canvas canvas) {super. onDraw (canvas); drawMiddleLine (canvas); drawScaleLine (canvas);} private float offsetPercent; private float scaleUnit; private boolean isChange = false; /*** position at the bottom of the line */float lineBottom;/*** position obtained at the top of the line */float lineTop; /*** draw the dial line from the middle to the two sides ** @ param canvas */private void drawScaleLine (Canvas canva S) {canvas. save (); outputs = true; isNeedDrawableRight = true; float width = mWidth; float xPosition = 0; lineBottom = mHeight-getPaddingBottom (); lineTop = lineBottom-mDensity * item_maxheight; if (data! = Null & data. size ()> 0) {calulateDrawPosition (canvas);} // The mValue is controlled between 0 and ~ Between 23 if (mValue> 0) {mValue = mValue % 24;} else if (mValue <0) {mValue = mValue % 24 + 24;} if (mMove <0) {// sliding left if (mValue = 0 & hour! = 23) {mCalendar. set (Calendar. DAY_OF_MONTH, mCalendar. get (Calendar. DAY_OF_MONTH)-1);} hour = mValue-1; // slide to the previous day if (hour =-1) {hour = 23 ;} offsetPercent = 1 + mMove/scaleUnit;} else if (mMove> = 0) {// slide to the right, offsetPercent = mMove/scaleUnit; hour = mValue; // slide to 0 the next day, if (hour = 0 &&! IsChange) {// if there is no ischange, when hour = 0, the mCalendar will be added to the day. set (Calendar. DAY_OF_MONTH, mCalendar. get (Calendar. DAY_OF_MONTH) + 1); // avoid repeating day + 1 isChange = true;} if (hour! = 0) {// set the flag to isChange = false when hour is switched to another value;} countMinAndSecond (offsetPercent); drawTimeText (canvas ); for (int I = 0; true; I ++) {// draw xPosition = (width/2-mMove) + I * scaleUnit to the right; if (isNeedDrawableRight & xPosition + getPaddingRight () <mWidth) {// draw a scale canvas within the view range. drawLine (xPosition, lineTop, xPosition, lineBottom, linePaint); textWidth = Layout. getDesiredWidth (int2Str (mValue + I), textPa Int); canvas. drawText (int2Str (mValue + I), xPosition-(textWidth/2), lineTop-5, textPaint);} else {isNeedDrawableRight = false ;} // draw if (I> 0) from the left {// prevent the middle scale from drawing xPosition = (width/2-mMove)-I * scaleUnit; if (isNeedDrawableLeft & xPosition> getPaddingLeft () {canvas. drawLine (xPosition, lineTop, xPosition, lineBottom, linePaint); textWidth = Layout. getDesiredWidth (int2Str (mValue-I), textPa Int); canvas. drawText (int2Str (mValue-I), xPosition-(textWidth/2), lineTop-5, textPaint);} else {isNeedDrawableLeft = false ;}} // exit the loop if (! IsNeedDrawableLeft &&! IsNeedDrawableRight) {break;} canvas. restore ();}/*** still has a problem. If the data volume is too large, that is, the time span of the user's search is too large, this method will definitely not be stuck. * In the future, you must obtain the position of the current playback, and then select a day or so, so that the data volume will not be too large * Now record this problem based on the principle of first-out and optimization, then modify and optimize ** @ param canvas */private void calulateDrawPosition (Canvas canvas) {// The distance corresponds to the time (mWidth/2/scaleUnit) * 3600*1000) long timeOffset = (long) (mWidth/2/scaleUnit) * 3600*1000); long middleTime = mCalendar. getTimeInMillis (); // calculate the time leftCalendar Based on the Time Offset. setTimeInMillis (middleTime-timeOffset); rightCalendar. setTimeInM Illis (middleTime + timeOffset); // locate the start point of the time and draw it to the right until it is painted to the far right of the screen, the key is to find the start point of the time. // the start point of the time is to draw the background color 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) {// draw drawBgColor (canvas, startCa from start Lendar, endCalendar, position); break;} else if (leftCalendar. after (startCalendar) & leftCalendar. before (endCalendar) {// draw drawBgColor (canvas, leftCalendar, endCalendar, position); break ;}} from left ;}}} /***** @ param canvas * @ param start * position where the first background color starts * @ param distance * length of the first background color * @ param position * First background color position of the time segment in data, the next part starts from position + 1 */public void drawBgColor (Canvas canvas, Calendar start Time, Calendar endTime, int position) {// obtain the specific 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. endTim E; startPosition = getPositionByTime (startCalendar); endPosition = getPositionByTime (endCalendar); if (startPosition <= mWidth) {// only draw drawBgColorRect (startPosition, lineTop, endPosition, lineBottom, canvas) ;}else {break ;}}/*** specifies the background color ** @ param canvas */private void drawBgColorRect (float left, float top, float right, float bottom, Canvas canvas) {canvas. drawRect (left, top, right, bottom, bgCo LorPaint);}/*** obtain the specific position on the dial by time ** @ param calendar * @ return */public float getPositionByTime (Calendar calendar AR) {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;}/*** data for preparing the background color */public void setTimeData (List <TVideoFile> data) {this. data = data;}/*** draw the text of the date and time ** @ 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. getDesiredWi Dth (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);}/*** computing minutes and seconds * @ param percent * @ return */public in T [] countMinAndSecond (float percent) {minute = (int) (3600 * percent/60); second = (int) (3600 * percent % 60 ); return new int [] {minute, second };}/*** specifies the red indicator line and shadow in the middle. Two rectangles are used at both ends of the indicator line to replace ** @ 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 () ;}@ Overridepublic boolean onTouchEvent (MotionEvent event) {int action = event. getAction (); int xPosition = (int) event. getX (); if (mVelocityTr Acker = 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 (); countVelocityTrac Ker (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; // The offset is always less than one cell mMove-= tValue * scaleUnit; yyvaluechange (); postInvalidate ();}} private void countVelocityTracker (MotionEvent event) {mVelocityTracker. computeCurrentVeloc Ity (1000,150 0); 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 {policychangeover () ;}} private void countMoveEnd () {mLastX = 0; policyvaluechange (); postInvalidate ();} private void policyvaluechange () {if (null! = MListener) {mListener. onValueChange (mValue) ;}} private void policychangeover () {if (null! = MListener) {mListener. onValueChangeEnd (mCalendar);} isChangeFromInSide = false;} @ Overridepublic void computeScroll () {super. computeScroll (); if (mScroller. computescroloffset () {if (mScroller. getCurrX () = mScroller. getFinalX () {// overcountMoveEnd (); yychangeover ();} 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 );}}
I have provided the setCalendar Method for external users to set the current time of the dial, and provided onValueChange (float value) and onValueChangeEnd (Calendar mCalendar) to provide real-time listening and sliding end listening respectively, if you want to draw the background color of the time block, you can do this:
Public class MainActivity extends Activity implements OnValueChangeListener {/*** time dial */private ScalePanel scalePanel; List <TVideoFile> data = new ArrayList <TVideoFile> (); @ Overrideprotected 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 (); // set the time block data scalePanel. setTimeData (data); // set the current time to 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) ;}@ Overridepublic void onValueChange (float value) {}@ Overridepublic void onValueChangeEnd (Calendar mCalendar ){}}
The specific implementation can be detailed in the Code and comments. I have not provided any instructions on the use of scroroller in the Code. If you are not familiar with the use of scroroller, read this article to learn more about Scroller in Android development.
If you have any questions, you can discuss them with me.
Finally, we will leave a demo. If you need it, please check it out. Thank you for your valuable comments.