原创文章,转载注明
先看一下效果图吧
这里说一下关键的思路,所有的细节都说到太麻烦了。
1、沙漏的绘制
上下两边的绘制,用二阶贝塞尔曲线,先确定左端点,即可获得对称的右端点,上边中间控制点为(屏幕x/2,y>左右端点y),下边中间控制点为(屏幕X/2,y<左右端点)
左右两边的绘制,用二阶贝塞尔曲线,上下两边的端点已知,另一端点为屏幕中间点加或减一定的阈值,这样才可以留出沙子下落的通道。控制点 为两端点间的高度再比较靠上的位置。
2、下沙堆的绘制
下沙堆比较容易画出,一样用二阶贝塞尔曲线,端点分别为沙漏下端两端点,控制点为(屏幕x/2,y>沙漏底边控制点y),随着时间的减少,只需要将控制点的y坐标逐渐加大到屏幕y/2即可。
3、上沙堆的绘制
上沙堆的绘制比较难,原因在于android只提供了贝尔塞尔曲线的绘制,没有提供获取贝塞尔曲线任意点的坐标,比如我想在沙漏上部分1/2处找到左右两边上对应的点坐标就没办法做到了。由于贝尔塞尔曲线是用插值方程作为轨迹方程的。因此我们可以通过该方程获得点坐标。而对于插值,它的范围为0-1,这里将插值设为1/2即可获得中点坐标。到此为止,端点以及获得了,但是此处不能用贝塞尔曲线了,因为控制点无法保证图像能填充慢整个沙堆到中心点。因此这里需要用三角形,也就是说我们之前获得的两个端点为三角形底边的端点。另外一点为屏幕中点。为了弥补沙堆边缘不能填充慢整个沙漏的问题,插值不能设置的过小,也就是说沙堆不能太高。最后,上面沙堆的顶部弧形,这个就可以用二阶贝塞尔曲线了,端点已知,控制点为(屏幕x/2.y>端点y)。
4、落下的沙粒动画
这个就不细说了,想怎么实现怎么实现
5、时间和沙堆变化的关联
我们知道下沙堆变高是通过改变控制点,我们只需要把时间和控制点的最高,最低y坐标的差值建立关系即可。
对于上沙堆,它的变化是通过左右两端点的下滑实现的,然而左右两端点的变化在二阶贝塞尔曲线的轨迹方程上,然而,贝塞尔曲线的轨迹方程为插值方程,那么我们只需要改变相应的插值即可。因此,我们只需要把时间和沙漏上部左右两边的贝塞尔方程的插值建立关系即可。
最后上代码
贝尔塞尔曲线轨迹方程
package com.xf.ztime.base.util;
/**
*
* 提供平面坐标的一系列计算
*
* @author 吴林峰
*
*/
public class Point {
public int x;
public int y;
/**
*
* 通过直线插值方程计算任意点
*
* @param t
* @param p0
* @param p1
* @return
*/
public static Point interpolationLine(float t,Point p0,Point p1){
if(t<0 || t>1)
return null;
Point point=new Point();
point.setX((int) ((1-t)*p0.x+t*p1.x));
point.setY((int) ((1-t)*p0.y+t*p1.y));
return point;
}
/**
*
* 通过二阶贝塞尔曲线方程计算任意点
*
* @param t
* @param p0
* @param p1
* @param p2
* @return
*/
public static Point bezier(float t,Point p0, Point p1, Point p2){
if(t<0 || t>1)
return null;
Point point=new Point();
point.setX((int) ((1-t)*(1-t)*p0.x+2*t*(1-t)*p1.x+t*t*p2.x));
point.setY((int) ((1-t)*(1-t)*p0.y+2*t*(1-t)*p1.y+t*t*p2.y));
return point;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
沙漏计时器的实现
package com.xf.ztime.countDown.view;
import com.example.ztime.R;
import com.xf.ztime.base.util.Point;
import com.xf.ztime.base.util.timeFactory.SystemTimeFactory;
import com.xf.ztime.base.util.timeFactory.TimeFactory;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android