Android:自定義View之番茄鍾

閒來無事,回顧了一下以前寫的項目,把番茄鍾從裏面整理出來了。android

該View經過上下滑動設置倒計時的時間,調用start()方法開始倒計時,stop()方法中止計時。canvas

效果圖以下:api

核心代碼:app

  1 import android.animation.ValueAnimator;
  2 import android.content.Context;
  3 import android.graphics.Canvas;
  4 import android.graphics.Color;
  5 import android.graphics.Paint;
  6 import android.graphics.RectF;
  7 import android.os.Build;
  8 import android.os.CountDownTimer;
  9 import android.util.AttributeSet;
 10 import android.util.DisplayMetrics;
 11 import android.util.Log;
 12 import android.view.MotionEvent;
 13 import android.view.View;
 14 import android.view.ViewGroup;
 15 import android.view.WindowManager;
 16 
 17 import androidx.annotation.Nullable;
 18 import androidx.annotation.RequiresApi;
 19 
 20 public class TomatoClockView extends View {
 21     private static final String TAG = "TomatoClockView";
 22     
 23     private Paint arcPaint;  //圓弧畫筆
 24     private Paint textPaint;  //時間文本畫筆
 25     private int backgroundColor = Color.parseColor("#D1D1D1");
 26     private int arcColor = Color.BLUE;
 27 
 28     private int width;  //View的寬
 29     private int height;  //View的高
 30     private float centerX;  //View中心點的X座標
 31     private float centerY;  //View中心點的Y座標
 32 
 33     private float oldOffsetY;  //上一次MOVE事件結束位置和DOWN事件落點之間Y座標的偏移量
 34     private float offsetY;  //本次MOVE事件結束位置和DOWN事件落點之間Y座標的偏移量
 35     float touchedY;  //本次DOWN事件落點的Y座標
 36 
 37     private static final int MAX_TIME = 60;  //最大倒計時長
 38     private static String textTime = "00:00";  //時間文本
 39     private long countDownTime;  //倒計時時長(毫秒)
 40     private float time;  //倒計時時長(分鐘)
 41 
 42     private float sweepVelocity = 0;  //動畫執行的完成度
 43     private ValueAnimator valueAnimator;  //屬性動畫對象
 44 
 45     private boolean isStarted;  //倒計時是否已開始
 46 
 47     private MyTimer mTimeCounter;  //倒計時器
 48 
 49     private class MyTimer extends CountDownTimer{
 50 
 51         /**
 52          * @param millisInFuture    The number of millis in the future from the call
 53          *                          to {@link #start()} until the countdown is done and {@link #onFinish()}
 54          *                          is called.
 55          * @param countDownInterval The interval along the way to receive
 56          *                          {@link #onTick(long)} callbacks.
 57          */
 58         public MyTimer(long millisInFuture, long countDownInterval) {
 59             super(millisInFuture, countDownInterval);
 60         }
 61 
 62         @Override
 63         public void onTick(long millisUntilFinished) {
 64 
 65             textTime = formatCountTime(millisUntilFinished);
 66             invalidate();
 67 
 68         }
 69 
 70         @Override
 71         public void onFinish() {
 72             textTime = "00:00";
 73             invalidate();
 74         }
 75     }
 76 
 77     //view是在JAVA代碼中new的,則調用此構造函數
 78     public TomatoClockView(Context context) {
 79         super(context);
 80         init();
 81     }
 82 
 83     //View是在.xml文件中聲明的,則調用此構造函數
 84     public TomatoClockView(Context context, @Nullable AttributeSet attrs) {
 85         super(context, attrs);
 86         init();
 87     }
 88 
 89     public TomatoClockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
 90         super(context, attrs, defStyleAttr);
 91         init();
 92     }
 93 
 94     @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
 95     public TomatoClockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
 96         super(context, attrs, defStyleAttr, defStyleRes);
 97     }
 98 
 99     @Override
100     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
101         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
102 
103         width = MeasureSpec.getSize(widthMeasureSpec);
104         height = MeasureSpec.getSize(heightMeasureSpec);
105 
106         //定義LayoutParams爲warp_content時的測量寬高,不然wrap_content會失效
107         if(getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT
108         && getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT){
109             width = 400;
110             height = 500;
111         } else if(getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT){
112             width = 400;
113         } else if(getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT){
114             height = 400;
115         }
116 
117 
118         //計算番茄鐘的中心點
119         centerX = getLeft() + width/2;
120         centerY = getTop() + height/2;
121 
122         setMeasuredDimension(width, height);  //保存測量寬高
123         Log.d(TAG, "onMeasure: ");
124     }
125 
126     @Override
127     protected void onDraw(Canvas canvas) {
128         super.onDraw(canvas);
129 
130         //對padding進行處理,不然padding屬性將會失效
131         int paddingLeft = getPaddingLeft();
132         int paddingRight = getPaddingRight();
133         int paddingTop = getPaddingTop();
134         int paddingBottom = getPaddingBottom();
135 
136         //int radius = Math.min(width-paddingLeft-paddingRight, height-paddingTop-paddingBottom)/2;  //計算半徑
137 
138         RectF rectF = new RectF();
139         rectF.set(centerX-width/2 + paddingLeft, centerY-height/2 + paddingTop, centerX+width/2 - paddingRight, centerY+height/2 - paddingBottom);
140 
141         //繪製底部圓弧
142         canvas.save();
143         arcPaint.setColor(backgroundColor);
144         canvas.drawArc(rectF, -90, 360, false, arcPaint);
145         canvas.restore();
146 
147         //繪製倒計時圓弧
148         canvas.save();
149         arcPaint.setColor(arcColor);
150         canvas.drawArc(rectF, -90, 360 * sweepVelocity, false, arcPaint);
151         canvas.restore();
152 
153         //繪製時間文本
154         canvas.save();
155         Paint.FontMetrics metrics = textPaint.getFontMetrics();
156         float baseline = (metrics.bottom - metrics.top)/2 + centerY - metrics.bottom;
157         canvas.drawText(textTime, centerX, baseline, textPaint );
158         canvas.restore();
159     }
160 
161     @Override
162     public boolean onTouchEvent(MotionEvent event) {
163         if(isStarted){
164             return true;
165         }
166 
167         //獲取屏幕高度
168         WindowManager manager = (WindowManager) (getContext().getApplicationContext().getSystemService(Context.WINDOW_SERVICE));
169         DisplayMetrics metrics = new DisplayMetrics();
170         manager.getDefaultDisplay().getMetrics(metrics);
171         float screenHeight = metrics.heightPixels;
172 
173         float y = event.getY();  //獲取觸摸事件發生位置的y座標
174 
175         //經過上下滑動來設置倒計時時間
176         //原理:MOVE事件結束時的y座標 - DOWN事件發生的y座標,MAX_TIME*(所得值/屏幕高度)即爲倒計時時間,負減正增
177         switch (event.getAction()){
178 
179             case MotionEvent.ACTION_DOWN:
180                 touchedY = y;
181                 break;
182 
183             case MotionEvent.ACTION_MOVE:
184                 offsetY = y - touchedY;
185 
186                 //能夠經過屢次滑動來調整時間
187                 float totalOffsetY = oldOffsetY + offsetY;
188                 if(totalOffsetY <= 0){
189                     totalOffsetY = 0;
190                 } else if(totalOffsetY >= screenHeight){
191                     totalOffsetY = screenHeight;
192                 }
193 
194                 time = totalOffsetY/screenHeight*MAX_TIME;  //分鐘
195 
196                 textTime = formatTime((long)time);
197 
198                 invalidate();
199                 break;
200 
201             case MotionEvent.ACTION_UP:
202                 oldOffsetY = offsetY;  //記錄上次滑動的位移量,用以實現屢次滑動調整時間
203                 countDownTime = (long)time * 60 * 1000;  //倒計時時長,毫秒
204                 break;
205         }
206 
207         return true;
208     }
209 
210     private void init(){
211         initPaint();
212         initValueAnimation();
213     }
214 
215     private void initPaint(){
216         arcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
217         arcPaint.setStyle(Paint.Style.STROKE);  //描邊
218         arcPaint.setStrokeWidth(30);
219 
220 
221         textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
222         textPaint.setColor(Color.BLACK);
223         textPaint.setStrokeWidth(20);
224         textPaint.setTextSize(180);
225         textPaint.setTextAlign(Paint.Align.CENTER);
226     }
227 
228     private void initValueAnimation(){
229         valueAnimator = ValueAnimator.ofFloat(0f, 1f);
230         valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
231             @Override
232             public void onAnimationUpdate(ValueAnimator animation) {
233                 sweepVelocity = (float) animation.getAnimatedValue();
234                 invalidate();
235             }
236         });
237     }
238 
239     /**
240      * 開始倒計時
241      */
242     @RequiresApi(api = Build.VERSION_CODES.KITKAT)
243     public void start(){
244         if(!isStarted){
245             isStarted = true;
246 
247             //設置動畫時間並開始動畫
248             valueAnimator.setDuration(countDownTime);
249             valueAnimator.start();
250 
251             //設置倒計時時間並開始倒計時
252             mTimeCounter = new MyTimer(countDownTime, 1000);
253             mTimeCounter.start();
254         }
255 
256     }
257 
258     /**
259      * 中止倒計時
260      */
261     public void stop(){
262         mTimeCounter.cancel();
263         valueAnimator.end();
264 
265         isStarted = false;
266         time = 0f;
267         textTime = "00:00";
268         sweepVelocity = 0;
269         oldOffsetY = 0;
270 
271         invalidate();
272     }
273 
274     /**
275      * 倒計時開始前格式化時間文本
276      * @param time
277      * @return
278      */
279     private String formatTime(long time){
280         StringBuilder sb = new StringBuilder();
281 
282         if(time < 10){
283             sb.append("0" + time + ":00");
284         } else {
285             sb.append(time + ":00");
286         }
287 
288         return sb.toString();
289     }
290 
291     /**
292      * 在倒計時過程當中格式化時間文本
293      * @param time
294      * @return
295      */
296     private static String formatCountTime(long time){
297 
298         StringBuilder sb = new StringBuilder();
299 
300         time = time/1000;  //毫秒轉秒
301 
302         long min = time/60;  //分鐘
303         long second = time - min*60;  //
304 
305         if(min < 10){
306             sb.append("0" + min + ":");
307         } else {
308             sb.append(min + ":");
309         }
310 
311         if(second < 10){
312             sb.append("0" + second);
313         } else {
314             sb.append(second);
315         }
316 
317 
318         return sb.toString();
319     }
320 
321     public boolean isStarted() {
322         return isStarted;
323     }
324 
325     public void setStarted(boolean started) {
326         isStarted = started;
327     }
328 }

 

在佈局文件中直接調用便可:ide

<androidx.appcompat.widget.LinearLayoutCompat
        android:layout_centerInParent="true"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.example.viewtest.view.TomatoClockView <--注意要使用全限定名!-->
            android:id="@+id/m_view"
            android:layout_width="300dp"
            android:layout_height="300dp"
            android:layout_gravity="center"
            android:padding="10dp" />

        <Button
            android:id="@+id/btn_start_clock"
            android:layout_margin="20dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignBottom="@id/m_view"
            android:text="START" />

    </androidx.appcompat.widget.LinearLayoutCompat>

番茄鐘的相關屬性能夠根據須要自行設置。函數

相關文章
相關標籤/搜索