android*小米时钟(使用Camera和Matrix实现3D效果)
发布时间 - 2026-01-10 22:44:41 点击率:次继续练习自定义View。。毕竟熟才能生巧。一直觉得小米的时钟很精美,那这次就搞它~这次除了练习自定义View,还涉及到使用Camera和Matrix实现3D效果。
一个这样的效果,在绘制的时候最好选择一个方向一步一步的绘制,这里我选择由外到内、由深到浅的方向来绘制,代码步骤如下:
1、首先老一套~新建attrs.xml文件,编写自定义属性如时钟背景色、亮色(用于分针、秒针、渐变终止色)、暗色(圆弧、刻度线、时针、渐变起始色),新建MiClockView继承View,重写构造方法,获取自定义属性值,初始化Paint、Path以及画圆、弧需要的RectF等东东,重写onMeasure计算宽高,这里不再啰嗦~刚开始学自定义View的同学建议从我的前几篇博客看起
2、由于onSizeChanged方法在构造方法、onMeasure之后,又在onDraw之前,此时已经完*局变量初始化,也得到了控件的宽高,所以可以在这个方法中确定一些与宽高有关的数值,比如这个View的半径啊、padding值等,方便绘制的时候计算大小和位置:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//宽和高分别去掉padding值,取min的一半即表盘的半径
mRadius = Math.min(w - getPaddingLeft() - getPaddingRight(),
h - getPaddingTop() - getPaddingBottom()) / 2;
//加一个默认的padding值,为了防止用camera旋转时钟时造成四周超出view大小
mDefaultPadding = 0.12f * mRadius;//根据比例确定默认padding大小
//为了适配控件大小match_parent、wrap_content、精确数值以及padding属性
mPaddingLeft = mDefaultPadding + w / 2 - mRadius + getPaddingLeft();
mPaddingTop = mDefaultPadding + h / 2 - mRadius + getPaddingTop();
mPaddingRight = mPaddingLeft;
mPaddingBottom = mPaddingTop;
mScaleLength = 0.12f * mRadius;//根据比例确定刻度线长度
mScaleArcPaint.setStrokeWidth(mScaleLength);//刻度盘的弧宽
mScaleLinePaint.setStrokeWidth(0.012f * mRadius);//刻度线的宽度
//梯度扫描渐变,以(w/2,h/2)为中心点,两种起止颜色梯度渐变
//float数组表示,[0,0.75)为起始颜色所占比例,[0.75,1}为起止颜色渐变所占比例
mSweepGradient = new SweepGradient(w / 2, h / 2,
new int[]{mDarkColor, mLightColor}, new float[]{0.75f, 1});
}
3、准备工作做的差不多了,那就开始绘制,根据方向我先确定最外层的小时时间文本的位置及其旁边的四个弧:
注意两位数字的宽度和一位数的宽度是不一样的,在计算的时候一定要注意
String timeText = "12";
mTextPaint.getTextBounds(timeText, 0, timeText.length(), mTextRect);
int textLargeWidth = mTextRect.width();//两位数字的宽
mCanvas.drawText("12", getWidth() / 2 - textLargeWidth / 2, mPaddingTop + mTextRect.height(), mTextPaint);
timeText = "3";
mTextPaint.getTextBounds(timeText, 0, timeText.length(), mTextRect);
int textSmallWidth = mTextRect.width();//一位数字的宽
mCanvas.drawText("3", getWidth() - mPaddingRight - mTextRect.height() / 2 - textSmallWidth / 2,
getHeight() / 2 + mTextRect.height() / 2, mTextPaint);
mCanvas.drawText("6", getWidth() / 2 - textSmallWidth / 2, getHeight() - mPaddingBottom, mTextPaint);
mCanvas.drawText("9", mPaddingLeft + mTextRect.height() / 2 - textSmallWidth / 2,
getHeight() / 2 + mTextRect.height() / 2, mTextPaint);
我计算文本的宽高一般采用的方法是,new一个Rect,然后再绘制时调用
mTextPaint.getTextBounds(timeText, 0, timeText.length(), mTextRect);
将这个文本的范围赋值给这个mTextRect,此时mTextRect.width()就是这段文本的宽,mTextRect.height()就是这段文本的高。
画文本旁边的四个弧:
mCircleRectF.set(mPaddingLeft + mTextRect.height() / 2 + mCircleStrokeWidth / 2,
mPaddingTop + mTextRect.height() / 2 + mCircleStrokeWidth / 2,
getWidth() - mPaddingRight - mTextRect.height() / 2 + mCircleStrokeWidth / 2,
getHeight() - mPaddingBottom - mTextRect.height() / 2 + mCircleStrokeWidth / 2);
for (int i = 0; i < 4; i++) {
mCanvas.drawArc(mCircleRectF, 5 + 90 * i, 80, false, mCirclePaint);
}
计算圆弧外接矩形的范围别忘了加上圆弧线宽的一半
4、再往里是刻度盘,画这个刻度盘的思路是现在底层画一个mScaleLength宽度的圆,并设置SweepGradient渐变,上面再画一圈背景色的刻度线。获得SweepGradient的Matrix对象,通过不断旋转mGradientMatrix的角度实现刻度盘的旋转效果:
/**
* 画一圈梯度渲染的亮暗色渐变圆弧,重绘时不断旋转,上面盖一圈背景色的刻度线
*/
private void drawScaleLine() {
mScaleArcRectF.set(mPaddingLeft + 1.5f * mScaleLength + mTextRect.height() / 2,
mPaddingTop + 1.5f * mScaleLength + mTextRect.height() / 2,
getWidth() - mPaddingRight - mTextRect.height() / 2 - 1.5f * mScaleLength,
getHeight() - mPaddingBottom - mTextRect.height() / 2 - 1.5f * mScaleLength);
//matrix默认会在三点钟方向开始颜色的渐变,为了吻合
//钟表十二点钟顺时针旋转的方向,把秒针旋转的角度减去90度
mGradientMatrix.setRotate(mSecondDegree - 90, getWidth() / 2, getHeight() / 2);
mSweepGradient.setLocalMatrix(mGradientMatrix);
mScaleArcPaint.setShader(mSweepGradient);
mCanvas.drawArc(mScaleArcRectF, 0, 360, false, mScaleArcPaint);
//画背景色刻度线
mCanvas.save();
for (int i = 0; i < 200; i++) {
mCanvas.drawLine(getWidth() / 2, mPaddingTop + mScaleLength + mTextRect.height() / 2,
getWidth() / 2, mPaddingTop + 2 * mScaleLength + mTextRect.height() / 2, mScaleLinePaint);
mCanvas.rotate(1.8f, getWidth() / 2, getHeight() / 2);
}
mCanvas.restore();
}
这里有一个全局变量mSecondDegree,即秒针旋转的角度,需要根据当前时间动态获取:
/**
* 获取当前 时分秒 所对应的角度
* 为了不让秒针走得像老式挂钟一样僵硬,需要精确到毫秒
*/
private void getTimeDegree() {
Calendar calendar = Calendar.getInstance();
float milliSecond = calendar.get(Calendar.MILLISECOND);
float second = calendar.get(Calendar.SECOND) + milliSecond / 1000;
float minute = calendar.get(Calendar.MINUTE) + second / 60;
float hour = calendar.get(Calendar.HOUR) + minute / 60;
mSecondDegree = second / 60 * 360;
mMinuteDegree = minute / 60 * 360;
mHourDegree = hour / 12 * 360;
}
5、然后就是画秒针,用Path绘制一个指向12点钟的三角形,通过不断旋转画布实现秒针的旋转:
/**
* 画秒针,根据不断变化的秒针角度旋转画布
*/
private void drawSecondHand() {
mCanvas.save();
mCanvas.rotate(mSecondDegree, getWidth() / 2, getHeight() / 2);
mSecondHandPath.reset();
float offset = mPaddingTop + mTextRect.height() / 2;
mSecondHandPath.moveTo(getWidth() / 2, offset + 0.27f * mRadius);
mSecondHandPath.lineTo(getWidth() / 2 - 0.05f * mRadius, offset + 0.35f * mRadius);
mSecondHandPath.lineTo(getWidth() / 2 + 0.05f * mRadius, offset + 0.35f * mRadius);
mSecondHandPath.close();
mSecondHandPaint.setColor(mLightColor);
mCanvas.drawPath(mSecondHandPath, mSecondHandPaint);
mCanvas.restore();
}
6、看实现图,时针在分针之下并且比分针颜色浅,那我就先画时针,仍然是Path,并且针头为圆弧状,那么就用二阶贝赛尔曲线,路径为moveTo( A),lineTo(B),quadTo(C,D),lineTo(E),close.
/**
* 画时针,根据不断变化的时针角度旋转画布
* 针头为圆弧状,使用二阶贝塞尔曲线
*/
private void drawHourHand() {
mCanvas.save();
mCanvas.rotate(mHourDegree, getWidth() / 2, getHeight() / 2);
mHourHandPath.reset();
float offset = mPaddingTop + mTextRect.height() / 2;
mHourHandPath.moveTo(getWidth() / 2 - 0.02f * mRadius, getHeight() / 2);
mHourHandPath.lineTo(getWidth() / 2 - 0.01f * mRadius, offset + 0.5f * mRadius);
mHourHandPath.quadTo(getWidth() / 2, offset + 0.48f * mRadius,
getWidth() / 2 + 0.01f * mRadius, offset + 0.5f * mRadius);
mHourHandPath.lineTo(getWidth() / 2 + 0.02f * mRadius, getHeight() / 2);
mHourHandPath.close();
mCanvas.drawPath(mHourHandPath, mHourHandPaint);
mCanvas.restore();
}
7、然后是分针,按照时针的思路:
/**
* 画分针,根据不断变化的分针角度旋转画布
*/
private void drawMinuteHand() {
mCanvas.save();
mCanvas.rotate(mMinuteDegree, getWidth() / 2, getHeight() / 2);
mMinuteHandPath.reset();
float offset = mPaddingTop + mTextRect.height() / 2;
mMinuteHandPath.moveTo(getWidth() / 2 - 0.01f * mRadius, getHeight() / 2);
mMinuteHandPath.lineTo(getWidth() / 2 - 0.008f * mRadius, offset + 0.38f * mRadius);
mMinuteHandPath.quadTo(getWidth() / 2, offset + 0.36f * mRadius,
getWidth() / 2 + 0.008f * mRadius, offset + 0.38f * mRadius);
mMinuteHandPath.lineTo(getWidth() / 2 + 0.01f * mRadius, getHeight() / 2);
mMinuteHandPath.close();
mCanvas.drawPath(mMinuteHandPath, mMinuteHandPaint);
mCanvas.restore();
}
8、最后由于path是close的,所以干脆画两个圆盖在上面:
/**
* 画指针的连接圆圈,盖住指针path在圆心的连接线
*/
private void drawCoverCircle() {
mCanvas.drawCircle(getWidth() / 2, getHeight() / 2, 0.05f * mRadius, mSecondHandPaint);
mSecondHandPaint.setColor(mBackgroundColor);
mCanvas.drawCircle(getWidth() / 2, getHeight() / 2, 0.025f * mRadius, mSecondHandPaint);
}
9、终于画完了,onDraw部分就是这样
@Override
protected void onDraw(Canvas canvas) {
mCanvas = canvas;
getTimeDegree();
drawTimeText();
drawScaleLine();
drawSecondHand();
drawHourHand();
drawMinuteHand();
drawCoverCircle();
invalidate();
}
绘制的时候,尤其是像这样圆形view,灵活运用
canvas.save(); canvas.rotate(mDegree, mCenterX, mCenterY); <!-- draw something --> canvas.restore();
这一套组合拳可以减少不少三角函数、角度弧度相关的计算。
10、辣么接下来就是如何实现触摸使钟表3D旋转
借助Camera类和Matrix类,在构造方法中:
Matrix mCameraMatrix = new Matrix(); Camera mCamera = new Camera();
/**
* 设置3D时钟效果,触摸矩阵的相关设置、照相机的旋转大小
* 应用在绘制图形之前,否则无效
*
* @param rotateX 绕X轴旋转的大小
* @param rotateY 绕Y轴旋转的大小
*/
private void setCameraRotate(float rotateX, float rotateY) {
mCameraMatrix.reset();
mCamera.save();
mCamera.rotateX(mCameraRotateX);//绕x轴旋转角度
mCamera.rotateY(mCameraRotateY);//绕y轴旋转角度
mCamera.getMatrix(mCameraMatrix);//相关属性设置到matrix中
mCamera.restore();
//camera在view左上角那个点,故旋转默认是以左上角为中心旋转
//故在动作之前pre将matrix向左移动getWidth()/2长度,向上移动getHeight()/2长度
mCameraMatrix.preTranslate(-getWidth() / 2, -getHeight() / 2);
//在动作之后post再回到原位
mCameraMatrix.postTranslate(getWidth() / 2, getHeight() / 2);
mCanvas.concat(mCameraMatrix);//matrix与canvas相关联
}
这段代码除了camera的旋转、平移、缩放之类的操作之外,剩下的代码一般是固定的
全局变量mCameraRotateX和mCameraRotateY应该与此时手指触摸坐标相关联动态获取:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
getCameraRotate(event);
break;
case MotionEvent.ACTION_MOVE:
//根据手指坐标计算camera应该旋转的大小
getCameraRotate(event);
break;
}
return true;
}
Camera的坐标系和View的坐标系是不一样的
View坐标系是二维的,原点在屏幕左上角,右为x轴正方向,下为y轴正方向;而Camera坐标系是三维的,原点在屏幕左上角,右为x轴正方向,上为y轴正方向,屏幕向里为z轴正方向
/**
* 获取camera旋转的大小
* 注意view坐标与camera坐标方向的转换
*/
private void getCameraRotate(MotionEvent event) {
float rotateX = -(event.getY() - getHeight() / 2);
float rotateY = (event.getX() - getWidth() / 2);
//求出此时旋转的大小与半径之比
float percentX = rotateX / mRadius;
float percentY = rotateY / mRadius;
if (percentX > 1) {
percentX = 1;
} else if (percentX < -1) {
percentX = -1;
}
if (percentY > 1) {
percentY = 1;
} else if (percentY < -1) {
percentY = -1;
}
//最终旋转的大小按比例匀称改变
mCameraRotateX = percentX * mMaxCameraRotate;
mCameraRotateY = percentY * mMaxCameraRotate;
}
11、最后在onTouchEvent中松开手指时加一个复原并晃动的动画
case MotionEvent.ACTION_UP:
//松开手指,时钟复原并伴随晃动动画
ValueAnimator animX = getShakeAnim(mCameraRotateX, 0);
animX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mCameraRotateX = (float) valueAnimator.getAnimatedValue();
}
});
ValueAnimator animY = getShakeAnim(mCameraRotateY, 0);
animY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mCameraRotateY = (float) valueAnimator.getAnimatedValue();
}
});
break;
/**
* 使用OvershootInterpolator完成时钟晃动动画
*/
private ValueAnimator getShakeAnim(float start, float end) {
ValueAnimator anim = ValueAnimator.ofFloat(start, end);
anim.setInterpolator(new OvershootInterpolator(10));
anim.setDuration(500);
anim.start();
return anim;
}
终于写完了,这个MiClockView适配也做的差不多了,时间也是同步的手机时间,一般可以拿来就用了~
demo下载地址:http://xiazai./201701/yuanma/MiClockView_jb51.rar
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# android
# 小米时钟
# camera
# matrix时钟
# 仿小米时钟
# Android获取设备CPU核数、时钟频率以及内存大小的方法
# Android多功能时钟开发案例(实战篇)
# android实现widget时钟示例分享
# Android 仿日历翻页、仿htc时钟翻页、数字翻页切换效果
# Android多功能时钟开发案例(基础篇)
# Android实现简单时钟View的方法
# Android自定义动态壁纸开发(时钟)
# Android编程基于自定义控件实现时钟功能的方法
# Android仿小米时钟效果
# Android自定义View实现时钟功能
# 自定义
# 这段
# 背景色
# 画一
# 两位
# 相关联
# 重写
# 求出
# 所占
# 角形
# 全局变量
# 这一
# 在这个
# 尤其是
# 那就
# 中心点
# 下载地址
# 差不多了
# 两种
# 会在
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
Windows驱动无法加载错误解决方法_驱动签名验证失败处理步骤
Windows10如何删除恢复分区_Win10 Diskpart命令强制删除分区
阿里云高弹*务器配置方案|支持分布式架构与多节点部署
Laravel如何配置任务调度?(Cron Job示例)
html5audio标签播放结束怎么触发事件_onended回调方法【教程】
Laravel怎么防止CSRF攻击_Laravel CSRF保护中间件原理与实践
Laravel如何构建RESTful API_Laravel标准化API接口开发指南
如何选择PHP开源工具快速搭建网站?
Laravel如何生成和使用数据填充?(Seeder和Factory示例)
Laravel事件和监听器如何实现_Laravel Events & Listeners解耦应用的实战教程
Laravel怎么写单元测试_PHPUnit在Laravel项目中的基础测试入门
网站制作软件有哪些,制图软件有哪些?
做企业网站制作流程,企业网站制作基本流程有哪些?
开心动漫网站制作软件下载,十分开心动画为何停播?
Laravel如何创建自定义中间件?(Middleware代码示例)
Laravel如何处理和验证JSON类型的数据库字段
LinuxShell函数封装方法_脚本复用设计思路【教程】
logo在线制作免费网站在线制作好吗,DW网页制作时,如何在网页标题前加上logo?
如何自定义建站之星模板颜色并下载新样式?
网站广告牌制作方法,街上的广告牌,横幅,用PS还是其他软件做的?
网站制作怎么样才能赚钱,用自己的电脑做服务器架设网站有什么利弊,能赚钱吗?
Laravel怎么集成Log日志记录_Laravel单文件与每日日志配置及自定义通道【详解】
Win11怎么关闭资讯和兴趣_Windows11任务栏设置隐藏小组件
如何在万网主机上快速搭建网站?
在Oracle关闭情况下如何修改spfile的参数
php后缀怎么变mp4格式错误_修改扩展名提示格式不对怎么办【技巧】
如何在云主机上快速搭建多站点网站?
Laravel如何保护应用免受CSRF攻击?(原理和示例)
什么是JavaScript解构赋值_解构赋值有哪些实用技巧
HTML透明颜色代码怎么让下拉菜单透明_下拉菜单透明背景指南【技巧】
Laravel如何处理CORS跨域请求?(配置示例)
Laravel如何配置.env文件管理环境变量_Laravel环境变量使用与安全管理
如何在新浪SAE免费搭建个人博客?
简单实现jsp分页
齐河建站公司:营销型网站建设与SEO优化双核驱动策略
简历没回改:利用AI润色让你的文字更专业
Android自定义listview布局实现上拉加载下拉刷新功能
javascript读取文本节点方法小结
Internet Explorer官网直接进入 IE浏览器在线体验版网址
Laravel怎么实现观察者模式Observer_Laravel模型事件监听与解耦开发【指南】
Zeus浏览器网页版官网入口 宙斯浏览器官网在线通道
Win11怎样安装网易有道词典_Win11安装词典教程【步骤】
Laravel如何与Vue.js集成_Laravel + Vue前后端分离项目搭建指南
laravel怎么用DB facade执行原生SQL查询_laravel DB facade原生SQL执行方法
html5怎么画眼睛_HT5用Canvas或SVG画眼球瞳孔加JS控制动态【绘制】
JS弹性运动实现方法分析
Laravel项目如何进行性能优化_Laravel应用性能分析与优化技巧大全
Python高阶函数应用_函数作为参数说明【指导】
如何在Windows虚拟主机上快速搭建网站?
html5如何设置样式_HTML5样式设置方法与CSS应用技巧【教程】

