抖音上炫酷的网红文字时钟

看了鸿洋推荐的一位兄弟的这个时钟,感觉很帅,因为他是用kt写的,这里我把它改写成了java的,写了一下,也算跟着作者走一下流程与思路。
我这里直接上代码了,流程与思路请参看

原作者的文章

TextClock.java


import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

public class TextClock extends View {

    private Paint mPaint;
    private Paint mHelperPaint;

    float mWidth;                 //控件宽
    float mHeight;                //控件高
    float mHourR;                 //小时的半径
    float mMinuteR;               //分钟的半径
    float mSecondR;               //秒的半径

    float mHourDeg = 0f;               //小时的角度
    float mMinuteDeg = 0f;             //分钟的角度
    float mSecondDeg = 0f;             //秒的角度

    private List<String> list_china;    //中文显示日期

    private ValueAnimator mAnimator;

    public TextClock(Context context) {
        this(context,null);
    }

    public TextClock(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public TextClock(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);

        mHelperPaint = new Paint();
        mHelperPaint.setAntiAlias(true);
        mHelperPaint.setColor(Color.RED);
        mHelperPaint.setStyle(Paint.Style.FILL);

        list_china = new ArrayList<>();
        list_china.add("日");
        list_china.add("一");
        list_china.add("二");
        list_china.add("三");
        list_china.add("四");
        list_china.add("五");
        list_china.add("六");
        list_china.add("七");
        list_china.add("八");
        list_china.add("九");
        list_china.add("十");

        //处理动画,声明全局的处理器
        mAnimator = ValueAnimator.ofFloat(6f, 0f);//由6降到1
        mAnimator.setDuration(150);
        mAnimator.setInterpolator(new LinearInterpolator()); //插值器设为线性

        doInvalidate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = measureWidth(widthMeasureSpec);
        int height = measureHeight(heightMeasureSpec);
        setMeasuredDimension(width, height);

        mWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
        mHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
    }

    private int measureHeight(int measureSpec)
    {
        int mode = MeasureSpec.getMode(measureSpec);
        int val = MeasureSpec.getSize(measureSpec);
        int result = 0;
        switch (mode)
        {
            case MeasureSpec.EXACTLY:
                result = val;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                break;
        }
        result = mode == MeasureSpec.AT_MOST ? Math.min(result, val) : result;
        return result + getPaddingTop() + getPaddingBottom();
    }

    private int measureWidth(int measureSpec)
    {
        int mode = MeasureSpec.getMode(measureSpec);
        int val = MeasureSpec.getSize(measureSpec);
        int result = 0;
        switch (mode)
        {
            case MeasureSpec.EXACTLY:
                result = val;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                break;
        }
        result = mode == MeasureSpec.AT_MOST ? Math.min(result, val) : result;
        return result + getPaddingLeft() + getPaddingRight();
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        //后文会涉及到
        //统一用View宽度*系数来处理大小,这样可以联动适配样式
        mHourR = mWidth * 0.143f;
        mMinuteR = mWidth * 0.35f;
        mSecondR = mWidth * 0.45f;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (canvas == null)
            return;
        canvas.drawColor(Color.BLACK);//填充背景
        canvas.save();
        canvas.translate(mWidth / 2, mHeight / 2);//原点移动到中心

        //绘制各元件,后文会涉及到
        drawCenterInfo(canvas);
        drawHour(canvas, mHourDeg);
        drawMinute(canvas, mMinuteDeg);
        drawSecond(canvas, mSecondDeg);

        //从原点处向右画一条辅助线,之后要处理文字与x轴的对齐问题,稍后再说
        canvas.drawLine(0f, 0f, mWidth, 0f, mHelperPaint);

        canvas.restore();
    }

    //绘制圆中的信息
    private void drawCenterInfo(Canvas canvas)
    {
        Calendar mCalendar = Calendar.getInstance();
        int hour = mCalendar.get(Calendar.HOUR_OF_DAY);
        int minute = mCalendar.get(Calendar.MINUTE);
        StringBuilder strMiute = new StringBuilder();
        strMiute.append(minute);
        if(minute < 10)
        {
            strMiute.insert(0,"0");
        }

        mPaint.setTextSize(mHourR * 0.4f); //字体大小根据「时圈」半径来计算
        mPaint.setColor(Color.WHITE);
        mPaint.setAlpha(255);
        mPaint.setTextAlign(Paint.Align.CENTER);
        canvas.drawText(hour + ":" + minute, 0f, getBottomY(), mPaint);

        //绘制月份、星期
        int month = mCalendar.get(Calendar.MONTH) + 1;
        StringBuilder monthstr = new StringBuilder();
        monthstr.append(month);

        if(month < 10)
        {
            monthstr.insert(0,"0");
        }
        int day = mCalendar.get(Calendar.DAY_OF_MONTH);
        int dayOfWeek = mCalendar.get(Calendar.DAY_OF_WEEK) - 1;//私有的扩展方法,将Int数字转换为 一、十一、二十等,后文绘制三个文字圈都会用该方法
        String strDayOfWeek = IntToChinaText(dayOfWeek);

        mPaint.setTextSize(mHourR * 0.16f); //字体大小根据「时圈」半径来计算
        mPaint.setAlpha(255);
        mPaint.setTextAlign(Paint.Align.CENTER);
        canvas.drawText(monthstr + "." + day +" 星期" + strDayOfWeek, 0f, getCenteredY(), mPaint);
    }

    //绘制圆中的小时信息
    private void drawHour(Canvas canvas, float degrees)
    {
        mPaint.setTextSize(mHourR * 0.16f);

        //处理整体旋转
        canvas.save();
        canvas.rotate(degrees);

        for (int i=0;i<12;i++) {
            canvas.save();

            //从x轴开始旋转,每30°绘制一下「几点」,12次就画完了「时圈」
            float iDeg = 360 / 12f * i;
            canvas.rotate(iDeg);

            //这里处理当前时间点的透明度,因为degrees控制整体逆时针旋转
            //iDeg控制绘制时顺时针,所以两者和为0时,刚好在x正半轴上,也就是起始绘制位置。
            int mAlpha = 0;
            if((iDeg + degrees) == 0f)
            {
                mAlpha = 255;
            }else{
                mAlpha = (int)(0.6f * 255);
            }

            mPaint.setAlpha(mAlpha);
            mPaint.setTextAlign(Paint.Align.LEFT);

            canvas.drawText(IntToChinaText((i + 1)) + "点", mHourR, getCenteredY(), mPaint);
            canvas.restore();
        }

        canvas.restore();
    }

    /**
     * 绘制圆中的分钟信息
     * @param canvas
     * @param degrees
     */
    private void drawMinute(Canvas canvas, float degrees)
    {
        mPaint.setTextSize(mHourR * 0.16f);

        //处理整体旋转
        canvas.save();
        canvas.rotate(degrees);

        for (int i=0;i<60;i++) {
            canvas.save();

            float iDeg = 360 / 60f * i;
            canvas.rotate(iDeg);

            int mAlpha = 0;
            if((iDeg + degrees) == 0f)
            {
                mAlpha = 255;
            }else{
                mAlpha = (int)(0.6f * 255);
            }

            mPaint.setAlpha(mAlpha);
            mPaint.setTextAlign(Paint.Align.RIGHT);

            if (i < 59) {
                canvas.drawText(IntToChinaText(i+1) + "分", mMinuteR, getCenteredY(), mPaint);
            }
            canvas.restore();
        }

        canvas.restore();
    }


    private void drawSecond(Canvas canvas, float degrees)
    {
        mPaint.setTextSize(mHourR * 0.16f);

        //处理整体旋转
        canvas.save();
        canvas.rotate(degrees);

        for (int i=0;i<60;i++) {
            canvas.save();

            float iDeg = 360 / 60f * i;
            canvas.rotate(iDeg);

            int mAlpha = 0;
            if((iDeg + degrees) == 0f)
            {
                mAlpha = 255;
            }else{
                mAlpha = (int)(0.6f * 255);
            }

            mPaint.setAlpha(mAlpha);
            mPaint.setTextAlign(Paint.Align.RIGHT);

            if (i < 59) {
                canvas.drawText(IntToChinaText(i+1) +"秒", mSecondR, getCenteredY(), mPaint);
            }
            canvas.restore();
        }

        canvas.restore();
    }


    public void doInvalidate()
    {
        Calendar mCalendar = Calendar.getInstance();
        int hour = mCalendar.get(Calendar.HOUR_OF_DAY);
        final int minute = mCalendar.get(Calendar.MINUTE);
        StringBuilder strMiute = new StringBuilder();
        strMiute.append(minute);
        if(minute < 10)
        {
            strMiute.insert(0,"0");
        }
        final int second = mCalendar.get(Calendar.SECOND);

        mHourDeg = -360 / 12f * (hour - 1);
        mMinuteDeg = -360 / 60f * (minute - 1);
        mSecondDeg = -360 / 60f * (second - 1);

        //记录当前角度,然后让秒圈线性的旋转6°
        final float hd = mHourDeg;
        final float md = mMinuteDeg;
        final float sd = mSecondDeg;



        //处理动画
        mAnimator.removeAllUpdateListeners();//需要移除先前的监听
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float av = (Float)animation.getAnimatedValue();

                if (minute == 0 && second == 0) {
                    mHourDeg = hd + av * 5;//时圈旋转角度是分秒的5倍,线性的旋转30°
                }

                if (second == 0) {
                    mMinuteDeg = md + av;//线性的旋转6°
                }

                mSecondDeg = sd + av;//线性的旋转6°

//
                invalidate();
            }
        });


        mAnimator.start();
    }

    /**
     * 取Y轴的值,让其底部对齐
     * @return
     */
    private float getBottomY()
    {
        Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();

        return -fontMetrics.bottom;
    }

    /**
     * 中间对齐
     * @return
     */
    private float getCenteredY()
    {
        Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
        return mPaint.getFontSpacing()/2 - fontMetrics.bottom;
    }


    /**
     * 将数字转化为中文
     * @param number
     * @return
     */
    private String IntToChinaText(int number)
    {
        StringBuilder str = new StringBuilder();
        char[] sNumber = String.valueOf(number).toCharArray();
        if(sNumber.length > 1)
        {
            int one = Integer.valueOf(sNumber[0] - '0');
            if(one != 1)
            {
                str.append(list_china.get(one));
            }
            str.append("十");
            int num = Integer.valueOf(sNumber[1] - '0');
            if(num > 0) {

                str.append(list_china.get(num));
            }
        }else{
            str.append(list_china.get(number));
        }

        return str.toString();
    }


}

MainActivity.java

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import com.cg.textclockview.customs.TextClock;

import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends AppCompatActivity {

    private TextClock mTextClock;
    private Timer mTimer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextClock = (TextClock)findViewById(R.id.mTextClock);

        mTimer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Log.e("MainActivity.java(run)", "行数: 32  走");
                        mTextClock.doInvalidate();
                    }
                });

            }
        };
        mTimer.schedule(task,1000,1000);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mTimer.cancel();
    }
}

发布了305 篇原创文章 · 获赞 261 · 访问量 184万+

猜你喜欢

转载自blog.csdn.net/chenguang79/article/details/99676293