• Android:自定义View之番茄钟


    闲来无事,回顾了一下之前写的项目,把番茄钟从里面整理出来了。

    该View通过上下滑动设置倒计时的时间,调用start()方法开始倒计时,stop()方法停止计时。

    效果图如下:

    核心代码:

      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 }

    在布局文件中直接调用即可:

    <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>

    番茄钟的相关属性可以根据需要自行设置。

  • 相关阅读:
    cmd命令操作Mysql数据库
    可编程作息时间控制器设计
    博客第一天
    PRML 1: Gaussian Distribution
    KMP String Matching Algorithm
    Reinstall Ubuntu 14.04
    Computability 4: Decidability and R.E. Sets (I)
    Consumer-Producer Problem
    Compile the Linux Kernel
    Introduction to Linux Kernel
  • 原文地址:https://www.cnblogs.com/Seraph1999/p/14540670.html
Copyright © 2020-2023  润新知