Android 自定义控件之模仿小米秒表,完整代码注解,复制可用

细心拷贝代码,就可以运行。
易君的开发工具为:Android Studio

功能介绍:

1、自定义控件实现小米秒表表盘
2、获取当前系统的网络时间
3、可以多次计时(计时列表)【RecyclerView BaseQuickAdapter】
4、有【开始】、【计时】、【清零】 三个按钮

看效果图:

如下图,初始效果 和 清零后的效果一样
在这里插入图片描述

计时效果:
在这里插入图片描述

源码截图:

在这里插入图片描述

首先需要集成导入:RecyclerView BaseQuickAdapter

1、在App的build.gradle中添加:
implementation ‘com.android.support:recyclerview-v7:28.0.0’
implementation ‘com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.38’
implementation ‘com.google.android.gms:play-services-maps:16.1.0’
在这里插入图片描述
2、在项目 build.gradle 中添加:maven { url ‘https://jitpack.io’ }在这里插入图片描述

源码拷贝

用到的颜色

	<color name="Hei">#000000</color>
    <color name="Bai">#ffffff</color>

在style 中添加主题 AppTheme01,将背景设为黑色

<style name="AppTheme01">
        <item name="colorPrimary">@color/Hei</item>   <!--导航栏颜色-->
        <item name="colorPrimaryDark">@color/Hei</item>  <!--状态栏颜色-->
        <item name="android:windowBackground">@color/Hei</item>  <!--背景颜色-->
    </style>

activity_main.xml 布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <RelativeLayout
        android:id="@+id/RelativeLayout"
        android:layout_width="match_parent"
        android:layout_height="370dp">

        <com.example.erp_lxkun_jak.xiaomiclock.xiaomiClock
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true" />

        <TextView
            android:id="@+id/Time_TextView01"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="00:00:00"
            android:textColor="@color/Bai"
            android:textSize="60sp" />

        <com.example.erp_lxkun_jak.xiaomiclock.xiaomiClock02
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/Time_TextView01"
            android:layout_centerHorizontal="true" />

        <TextView
            android:id="@+id/Time_TextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_above="@+id/Time_TextView01"
            android:layout_centerHorizontal="true"
            android:text="000"
            android:textColor="@color/Bai"
            android:textSize="40sp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_above="@+id/Time_TextView01"
            android:layout_centerHorizontal="true"
            android:layout_marginBottom="10dp"
            android:layout_toEndOf="@+id/Time_TextView"
            android:layout_toRightOf="@+id/Time_TextView"
            android:text="小时"
            android:textColor="@color/Bai"
            android:textSize="12sp" />

    </RelativeLayout>

    <LinearLayout
        android:id="@+id/LinearLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="10dp">

        <TextView
            android:id="@+id/TextView01"
            android:layout_width="wrap_content"
            android:layout_height="40dp"
            android:layout_weight="1"
            android:background="@drawable/button_bg"
            android:gravity="center"
            android:text="暂停"
            android:textColor="@color/Bai"
            android:textSize="20sp" />

        <TextView
            android:id="@+id/TextView02"
            android:layout_width="wrap_content"
            android:layout_height="40dp"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="5dp"
            android:layout_weight="1"
            android:background="@drawable/button_bg"
            android:gravity="center"
            android:text="计时"
            android:textColor="@color/Bai"
            android:textSize="20sp" />

        <TextView
            android:id="@+id/TextView03"
            android:layout_width="wrap_content"
            android:layout_height="40dp"
            android:layout_weight="1"
            android:background="@drawable/button_bg"
            android:gravity="center"
            android:text="清零"
            android:textColor="@color/Bai"
            android:textSize="20sp" />

    </LinearLayout>

    <TextView
        android:id="@+id/System_Time_TextView"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:layout_below="@+id/RelativeLayout"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:background="@drawable/button_bg"
        android:gravity="center"
        android:text="0000/00/00 00:00:00"
        android:textColor="@color/Bai"
        android:textSize="18sp" />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/RecyclerView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/LinearLayout"
        android:layout_below="@+id/System_Time_TextView"
        android:padding="10dp"
        android:background="@drawable/button_bg"
        android:layout_margin="10dp" />

</RelativeLayout>

RecyclerView 的 item_time.xml 布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="30dp"
    android:background="@drawable/button_bg"
    android:gravity="center"
    android:layout_marginBottom="10dp"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/item_TextView01"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="10dp"
        android:layout_marginLeft="10dp"
        android:layout_weight="1"
        android:text="1"
        android:textColor="@color/Bai"
        android:textSize="18sp" />

    <TextView
        android:id="@+id/item_TextView02"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="10dp"
        android:layout_marginRight="10dp"
        android:layout_weight="1"
        android:gravity="right"
        android:text="000:00:00:00"
        android:textColor="@color/Bai"
        android:textSize="18sp" />

</LinearLayout>

按钮的背景 button_bg.xml 布局文件

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <size android:layout_width="match_parent"/>
    <size android:layout_height="match_parent"/>
    <!-- 默认背景颜色 -->
    <solid android:color="@color/Hei" />
    <!--边框-->
    <stroke android:width="0.5dp"
        android:color="@color/Bai" />
    <!--圆角-->
    <corners android:radius="5dp" />
</shape>

【BaseActivity.java】(记得在 AndroidManifest.xml 中先声明)

public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //主题设置
        setTheme(R.style.AppTheme01);
    }
}

Log的封装的调试类【L.java】

public class L {
    //开关
    public static final boolean DEBUG = true;
    //TAG
    public static final String TAG = "Smartbutler";  // Smart butler: 智能滤镜
    public static void e(String text){
        if(DEBUG){
            Log.e(TAG,text);
        }
    }
}

数据类【Static.java】

public class Static {
    public static boolean cartoon = false;   //画布旋转的默认值
    public static boolean agree_flag = false;  //秒表的默认值
}

时间记录 BaseQuickAdapter 的实体类【TimeData.java】

public class TimeData {
    private String Type;
    private String Time;
    
    public String getType() {
        return Type;
    }
    public void setType(String type) {
        Type = type;
    }
    public String getTime() {
        return Time;
    }
    public void setTime(String time) {
        Time = time;
    }
}

时间记录列表的 BaseQuickAdapter【TimeAdapter.java】

public class TimeAdapter extends BaseQuickAdapter<TimeData, BaseViewHolder> {
    public TimeAdapter(int layoutResId, List data) {
        super(layoutResId, data);
    }
    @Override
    protected void convert(BaseViewHolder helper, TimeData item) {
        helper.setText(R.id.item_TextView01,item.getType());
        helper.setText(R.id.item_TextView02,item.getTime());
    }
}

仿小米秒表 大 表盘自定义控件【xiaomiClock.java】

public class xiaomiClock extends View {
    private Paint textPaint, paint;
    private Path mTriangle;
    private Timer mTimer;
    private float agree = 1;
    private Shader mshader;
    private PathEffect mEffect;

    public xiaomiClock(Context context) {
        super(context);
    }

    public xiaomiClock(Context context, AttributeSet attrs) {
        super(context, attrs);
        //mEffect = new DashPathEffect(new float[]{1,2,5,10,50,20}, 0);    // float[]{ 虚线的厚度, 虚线的间距,虚线的厚度, 虚线的间距 ......}
        mEffect = new DashPathEffect(new float[]{5,15}, 0);    // float[]{ 虚线的厚度, 虚线的间距,虚线的厚度, 虚线的间距 ......}
        mshader = new SweepGradient(500, 500, Color.parseColor("#3b3b3b"), Color.parseColor("#ffffff"));    //渐变遮罩样式


        textPaint = new Paint();
        textPaint.setStrokeWidth(16);
        textPaint.setColor(Color.WHITE);
        textPaint.setStrokeCap(Paint.Cap.ROUND);
        textPaint.setAntiAlias(true);

        paint = new Paint();
        paint.setAntiAlias(true);

        mTriangle = new Path();
        mTriangle.moveTo(960, 500);// 此点为多边形的顶点
        //下面两个x 相等,表示底边的位置
        mTriangle.lineTo(1000, 525);  // y:底边宽的其中一个顶点
        mTriangle.lineTo(1000, 475);  //y:底边宽的其中一个顶点
        mTriangle.close();
        setmTimer();

        System.out.println("度数" + ((float) 6.0 / 10));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //绘画手表盘
        drawbeauty(canvas);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //设置宽高
        setMeasuredDimension(1000,1000);  //画布大小
    }

    public void drawbeauty(Canvas canvas) {
        int canvasWidth = canvas.getWidth();
        int canvasHeight = canvas.getHeight();
        //设置缓存层,因为下面要实用xfermode,使用xfemode必须使用缓存层,否则会出现黑色背景
        int layerId = canvas.saveLayer(0, 0, canvasWidth, canvasHeight, null, Canvas.ALL_SAVE_FLAG);

        //初始化画笔,因为上下两层做画需要的画笔属性不一样,所以只能每次重新设置一次
        paint.setStyle(Paint.Style.STROKE);     //设置画笔为不填充模式
        paint.setPathEffect(mEffect);           //设置笔画样式,这里设置的是虚线样式
        paint.setStrokeWidth(50);               //设置笔画宽度
        canvas.drawCircle(500, 500, 420, paint);    //画一个纯色表盘,虚线,空心圆形 【表盘位置和大小】

        //设置画笔属性,SRC_IN属性,让第二个图案只能花在第一个图案上面,也就是只能画在上面所说那个纯色表盘里面
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        //把画笔虚线属性去掉,因为我要的是一个实心圆形,然后让这个实心但是颜色不一样圆形画在上面所说表盘上面,因为设置了xfermode,所以显示的一样会是虚线圆形表盘,但是颜色会变成你现在的颜色
        paint.setPathEffect(null);

        //设置画笔shader属性,这里设置的是SweepGradient模式,可以让颜色过渡泾渭分明,以圆形为中心开始变化
        paint.setShader(mshader);
        paint.setStyle(Paint.Style.FILL);

        canvas.save();      //保存画布

        //旋转画布,然后你就会发现时钟表盘开始动了
        if(Static.cartoon){
            if(Static.agree_flag){
                agree = 1;
                Static.agree_flag = false;
            }
            canvas.rotate(agree, 500, 500);    //画布旋转的中心点
        }else{
            agree = 1;
        }

        canvas.drawRect(10, 10, 1000, 1100, paint);   //渐变矩形绘制
        canvas.drawPath(mTriangle, textPaint);    //绘制小三角形
        canvas.restore();

        //最后将画笔去除Xfermode
        paint.setXfermode(null);
        canvas.restoreToCount(layerId);
    }

    private void setmTimer() {
        mTimer = new Timer();
        mTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                L.e("agree = " + agree); //数值越大,旋转速度越快
                agree = agree + 0.022f;
                if (agree > 360)
                    agree = 1;
                postInvalidate();
            }
        }, 1000, 3);  //延时/周期
    }
}

仿小米秒表 小 表盘自定义控件【xiaomiClock02.java】

public class xiaomiClock02 extends View {
    private Paint textPaint, paint;
    private Path mTriangle;
    private Timer mTimer;
    private float agree = 1;
    private Shader mshader;
    private PathEffect mEffect;

    public xiaomiClock02(Context context) {
        super(context);
    }

    public xiaomiClock02(Context context, AttributeSet attrs) {
        super(context, attrs);
        mEffect = new DashPathEffect(new float[]{5,0}, 0);
        mshader = new SweepGradient(110, 110, Color.parseColor("#3b3b3b"), Color.parseColor("#ffffff"));

        textPaint = new Paint();
        textPaint.setStrokeWidth(16);
        textPaint.setColor(Color.WHITE);
        textPaint.setStrokeCap(Paint.Cap.ROUND);
        textPaint.setAntiAlias(true);

        paint = new Paint();
        paint.setAntiAlias(true);

        mTriangle = new Path();
        mTriangle.moveTo(190, 110);
        mTriangle.lineTo(110, 112);
        mTriangle.lineTo(110, 108);
        mTriangle.close();
        setmTimer();
        System.out.println("度数" + ((float) 6.0 / 10));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        drawbeauty(canvas);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(220,220);
    }

    public void drawbeauty(Canvas canvas) {
        int canvasWidth = canvas.getWidth();
        int canvasHeight = canvas.getHeight();
        int layerId = canvas.saveLayer(0, 0, canvasWidth, canvasHeight, null, Canvas.ALL_SAVE_FLAG);
        paint.setStyle(Paint.Style.STROKE);     //设置画笔为不填充模式
        paint.setPathEffect(mEffect);           //设置笔画样式,这里设置的是虚线样式
        paint.setStrokeWidth(2);               //设置笔画宽度
        canvas.drawCircle(110, 110, 100, paint);    //画一个纯色表盘,虚线,空心圆形 【表盘位置和大小】
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        paint.setPathEffect(null);
        paint.setShader(mshader);
        paint.setStyle(Paint.Style.FILL);
        canvas.save();      //保存画布

        if(Static.cartoon){
            if(Static.agree_flag){
                agree = 1;
                Static.agree_flag = false;
            }
            canvas.rotate(agree, 110, 110);    //画布旋转的中心点
        }else{
            agree = 1;
        }
        canvas.drawRect(1, 1, 220, 220, paint);   //渐变矩形绘制
        canvas.drawPath(mTriangle, textPaint);    //绘制小三角形
        canvas.restore();

        paint.setXfermode(null);
        canvas.restoreToCount(layerId);
    }

    private void setmTimer() {
        mTimer = new Timer();
        mTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                L.e("agree = " + agree); //数值越大,旋转速度越快
                //agree++;
                agree = agree + 0.2f + 1f;
                if (agree > 360)
                    agree = 1;
                postInvalidate();
            }
        }, 1000, 3);  //延时/周期
    }
}

主页面【MainActivity.java】

public class MainActivity extends BaseActivity implements View.OnClickListener {

    private TextView mTextView;
    private TextView Time_TextView;

    //三个按钮
    private TextView TextView01;
    private TextView TextView02;
    private TextView TextView03;

    private RecyclerView Recycler_View;
    private List<TimeData> Data;
    private BaseQuickAdapter Timedapter;

    private int number = 1;  //用来统计计时的次数

    //变化的时间数据  HH:mm:ss
    private int H = 0, m = 0, n = 0, s = 0;
    private String num_m, num_n, num_s;

    private boolean type = true;  //用于避免连续点击开始时出错
    Thread TimeThread = new Thread(new DataThread());  //创建启动时间控制线程

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

        Time_TextView = findViewById(R.id.Time_TextView);

        TextView01 = findViewById(R.id.TextView01);
        TextView01.setText("开始");
        TextView02 = findViewById(R.id.TextView02);
        TextView03 = findViewById(R.id.TextView03);

        TextView01.setOnClickListener(this);
        TextView02.setOnClickListener(this);
        TextView03.setOnClickListener(this);

        Recycler_View = findViewById(R.id.RecyclerView);
        //设置 Recycler_View 布局样式
        Recycler_View.setLayoutManager(new GridLayoutManager(MainActivity.this, 1, GridLayoutManager.VERTICAL, false));
        Data = new ArrayList<>();  //新建数据对象
        initAdapter();  //设置刷新适配器

    }

    //Recycler_View 适配器
    private void initAdapter() {
        Timedapter = new TimeAdapter(R.layout.item_time, Data);
        Timedapter.openLoadAnimation();
        Recycler_View.setAdapter(Timedapter);
    }

    //时间显示逻辑
    private String getWebData() {
        if (s >= 100) {  s = 0;  n++;
            if (n >= 60) { n = 0;  m++;
                if (m >= 60) { m = 0;  H++;
                }
            }
        }

        if (s <= 9) {
            num_s = String.valueOf("0" + s);    s++;
        } else {
            num_s = String.valueOf(s);  s++;
        }
        if (n <= 9)
            num_n = String.valueOf("0" + n + "." + num_s);
        else
            num_n = String.valueOf(n + "." + num_s);
        if (m <= 9)
            num_m = String.valueOf("0" + m + ":" + num_n);
        else
            num_m = String.valueOf(m + ":" + num_n);
        L.e("-- Time :"+ H +":"+ num_m);
        return num_m;
    }


    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            //确定计时变化的控件
            mTextView = findViewById(R.id.Time_TextView01);
            mTextView.setText((String) msg.obj);
            if (H <= 99) {
                if (H <= 9)
                    Time_TextView.setText("00" + H);
                else
                    Time_TextView.setText("0" + H);
            } else {
                Time_TextView.setText("" + H);
            }
        }
    };

    @Override
    public void onClick(View v) {

        switch (v.getId()) {
            case R.id.TextView01:   /** 开始按钮 */
                TextView01.setText("开始");
                if(type){
                    TimeThread.start();//启动时间控制线程
                    type = false;
                    Static.cartoon = true;
                    Static.agree_flag = false;
                }
                break;
            case R.id.TextView02:   /** 计时按钮 */
                if(TextUtils.isEmpty(num_m)){   //判断是否为空
                    Toast.makeText(MainActivity.this,"开始后才能计时",Toast.LENGTH_SHORT).show();
                }else{
                    //设置计时记录
                    TimeData item = new TimeData();
                    item.setType("第"+ number + "次计时");
                    item.setTime(H + ":" + num_m);
                    Data.add(item);
                    number++;  //计时次数递增

                    Timedapter.notifyItemInserted(0);  //在第0的位置 添加一条新数据数据
                    initAdapter();    //刷新适配器
                    Recycler_View.smoothScrollToPosition(0);   //添加后定位到第一条

                    //设置倒序 RecyclerView 列表
                    LinearLayoutManager layout = new LinearLayoutManager(this);
                    layout.setStackFromEnd(true);  //列表再底部开始展示,反转后由上面开始展示
                    layout.setReverseLayout(true);  //列表翻转
                    Recycler_View.setLayoutManager(layout);
                }
                break;
            case R.id.TextView03:   /** 清零按钮 */
                type = true;
                Static.cartoon = false;
                ResetActivity();
                 break;
        }
    }


    private class DataThread extends Thread {
        @Override
        public void run() {
            while (true) {    //无限循环
                try {
                    Thread.sleep(9);   //每一毫秒变化一次
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                final String getWebData = getWebData();
                final String sysTime = getTimeData();
                mHandler.sendMessage(mHandler.obtainMessage(0, getWebData));  //秒表
                mHandlerTime.sendMessage(mHandlerTime.obtainMessage(0, sysTime));  //系统时间
            }
        }
    }

    //设定显示系统时间
    private String getTimeData() {
        long sysTime = System.currentTimeMillis();
        CharSequence sysTimeStr = DateFormat.format("" + "yyyy/MM/dd HH:mm:ss", sysTime);
        return (String) sysTimeStr;
    }

    @SuppressLint("HandlerLeak")
    private Handler mHandlerTime = new Handler() {

        private TextView mTextView;

        public void handleMessage(android.os.Message msg) {
            //确定系统时间变化的控件
            mTextView = findViewById(R.id.System_Time_TextView);
            mTextView.setText((String) msg.obj);
        }
    };

    //重启MainActivity
    private void ResetActivity(){
        startActivity(new Intent(this, MainActivity.class));
        finish();//关闭自己
        overridePendingTransition(0, 0); //去掉Activity切换间的动画
    }
}

App下载:https://aifabu.com/RzM3

源码下载:https://download.csdn.net/download/erp_lxkun_jak/11243775
【注意:使用开发工具是 Android Studio 哦!】
.
.
感谢你的查阅,希望可以帮到你,祝你学习愉快!

我是和你一起学习的 易君

我的主页:https://blog.csdn.net/ERP_LXKUN_JAK

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值