AI智能
改变未来

Android:自定义View之番茄钟

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

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

效果图如下:

核心代码:

import android.animation.ValueAnimator;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.RectF;import android.os.Build;import android.os.CountDownTimer;import android.util.AttributeSet;import android.util.DisplayMetrics;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import android.view.WindowManager;import androidx.annotation.Nullable;import androidx.annotation.RequiresApi;public class TomatoClockView extends View {private static final String TAG = \"TomatoClockView\";private Paint arcPaint;  //圆弧画笔private Paint textPaint;  //时间文本画笔private int backgroundColor = Color.parseColor(\"#D1D1D1\");private int arcColor = Color.BLUE;private int width;  //View的宽private int height;  //View的高private float centerX;  //View中心点的X坐标private float centerY;  //View中心点的Y坐标private float oldOffsetY;  //上一次MOVE事件结束位置和DOWN事件落点之间Y坐标的偏移量private float offsetY;  //本次MOVE事件结束位置和DOWN事件落点之间Y坐标的偏移量float touchedY;  //本次DOWN事件落点的Y坐标private static final int MAX_TIME = 60;  //最大倒计时长private static String textTime = \"00:00\";  //时间文本private long countDownTime;  //倒计时时长(毫秒)private float time;  //倒计时时长(分钟)private float sweepVelocity = 0;  //动画执行的完成度private ValueAnimator valueAnimator;  //属性动画对象private boolean isStarted;  //倒计时是否已开始private MyTimer mTimeCounter;  //倒计时器private class MyTimer extends CountDownTimer{/*** @param millisInFuture    The number of millis in the future from the call*                          to {@link #start()} until the countdown is done and {@link #onFinish()}*                          is called.* @param countDownInterval The interval along the way to receive*                          {@link #onTick(long)} callbacks.*/public MyTimer(long millisInFuture, long countDownInterval) {super(millisInFuture, countDownInterval);}@Overridepublic void onTick(long millisUntilFinished) {textTime = formatCountTime(millisUntilFinished);invalidate();}@Overridepublic void onFinish() {textTime = \"00:00\";invalidate();}}//view是在JAVA代码中new的,则调用此构造函数public TomatoClockView(Context context) {super(context);init();}//View是在.xml文件中声明的,则调用此构造函数public TomatoClockView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);init();}public TomatoClockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)public TomatoClockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);width = MeasureSpec.getSize(widthMeasureSpec);height = MeasureSpec.getSize(heightMeasureSpec);//定义LayoutParams为warp_content时的测量宽高,否则wrap_content会失效if(getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT&& getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT){width = 400;height = 500;} else if(getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT){width = 400;} else if(getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT){height = 400;}//计算番茄钟的中心点centerX = getLeft() + width/2;centerY = getTop() + height/2;setMeasuredDimension(width, height);  //保存测量宽高Log.d(TAG, \"onMeasure: \");}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//对padding进行处理,否则padding属性将会失效int paddingLeft = getPaddingLeft();int paddingRight = getPaddingRight();int paddingTop = getPaddingTop();int paddingBottom = getPaddingBottom();//int radius = Math.min(width-paddingLeft-paddingRight, height-paddingTop-paddingBottom)/2;  //计算半径RectF rectF = new RectF();rectF.set(centerX-width/2 + paddingLeft, centerY-height/2 + paddingTop, centerX+width/2 - paddingRight, centerY+height/2 - paddingBottom);//绘制底部圆弧canvas.save();arcPaint.setColor(backgroundColor);canvas.drawArc(rectF, -90, 360, false, arcPaint);canvas.restore();//绘制倒计时圆弧canvas.save();arcPaint.setColor(arcColor);canvas.drawArc(rectF, -90, 360 * sweepVelocity, false, arcPaint);canvas.restore();//绘制时间文本canvas.save();Paint.FontMetrics metrics = textPaint.getFontMetrics();float baseline = (metrics.bottom - metrics.top)/2 + centerY - metrics.bottom;canvas.drawText(textTime, centerX, baseline, textPaint );canvas.restore();}@Overridepublic boolean onTouchEvent(MotionEvent event) {if(isStarted){return true;}//获取屏幕高度WindowManager manager = (WindowManager) (getContext().getApplicationContext().getSystemService(Context.WINDOW_SERVICE));DisplayMetrics metrics = new DisplayMetrics();manager.getDefaultDisplay().getMetrics(metrics);float screenHeight = metrics.heightPixels;float y = event.getY();  //获取触摸事件发生位置的y坐标//通过上下滑动来设置倒计时时间//原理:MOVE事件结束时的y坐标 - DOWN事件发生的y坐标,MAX_TIME*(所得值/屏幕高度)即为倒计时时间,负减正增switch (event.getAction()){case MotionEvent.ACTION_DOWN:touchedY = y;break;case MotionEvent.ACTION_MOVE:offsetY = y - touchedY;//可以通过多次滑动来调整时间float totalOffsetY = oldOffsetY + offsetY;if(totalOffsetY <= 0){totalOffsetY = 0;} else if(totalOffsetY >= screenHeight){totalOffsetY = screenHeight;}time = totalOffsetY/screenHeight*MAX_TIME;  //分钟textTime = formatTime((long)time);invalidate();break;case MotionEvent.ACTION_UP:oldOffsetY = offsetY;  //记录上次滑动的位移量,用以实现多次滑动调整时间countDownTime = (long)time * 60 * 1000;  //倒计时时长,毫秒break;}return true;}private void init(){initPaint();initValueAnimation();}private void initPaint(){arcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);arcPaint.setStyle(Paint.Style.STROKE);  //描边arcPaint.setStrokeWidth(30);textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);textPaint.setColor(Color.BLACK);textPaint.setStrokeWidth(20);textPaint.setTextSize(180);textPaint.setTextAlign(Paint.Align.CENTER);}private void initValueAnimation(){valueAnimator = ValueAnimator.ofFloat(0f, 1f);valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {sweepVelocity = (float) animation.getAnimatedValue();invalidate();}});}/*** 开始倒计时*/@RequiresApi(api = Build.VERSION_CODES.KITKAT)public void start(){if(!isStarted){isStarted = true;//设置动画时间并开始动画valueAnimator.setDuration(countDownTime);valueAnimator.start();//设置倒计时时间并开始倒计时mTimeCounter = new MyTimer(countDownTime, 1000);mTimeCounter.start();}}/*** 停止倒计时*/public void stop(){mTimeCounter.cancel();valueAnimator.end();isStarted = false;time = 0f;textTime = \"00:00\";sweepVelocity = 0;oldOffsetY = 0;invalidate();}/*** 倒计时开始前格式化时间文本* @param time* @return*/private String formatTime(long time){StringBuilder sb = new StringBuilder();if(time < 10){sb.append(\"0\" + time + \":00\");} else {sb.append(time + \":00\");}return sb.toString();}/*** 在倒计时过程中格式化时间文本* @param time* @return*/private static String formatCountTime(long time){StringBuilder sb = new StringBuilder();time = time/1000;  //毫秒转秒long min = time/60;  //分钟long second = time - min*60;  //秒if(min < 10){sb.append(\"0\" + min + \":\");} else {sb.append(min + \":\");}if(second < 10){sb.append(\"0\" + second);} else {sb.append(second);}return sb.toString();}public boolean isStarted() {return isStarted;}public void setStarted(boolean started) {isStarted = started;}}

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

<androidx.appcompat.widget.LinearLayoutCompatandroid: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\" /><Buttonandroid: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>

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

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » Android:自定义View之番茄钟