android——沙漏计时器 快来打我* 2022-07-12 03:28 416阅读 0赞 原创文章,转载注明 先看一下效果图吧 ![Center][] 这里说一下关键的思路,所有的细节都说到太麻烦了。 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.view.View; /** * * 沙漏动画的实现,可以自己指定大小 * * @author 吴林峰 * */ public class HourglassView extends View{ private static final String TAG="HourglassView"; public static final String STATE_RUN="run"; public static final String STATE_START="start"; public static final String STATE_PAUSE="pause"; public static final String STATE_END="end"; private String state=STATE_RUN; private Context context; private Thread refreshThread;//刷新时间线程 private float refresh_time = 1000;//刷新的时间 private int count=0; private int maxSendCount=20; //移动的沙子最大数量 private int currentSend=0; //当前移动的沙子数量 private int sendMaxHeight; //移动沙子的最大高度 private boolean isSizeParamgramable; //是否可以通过编程设定宽高 private int marginLR; //向左右边缘点横向margin,左上角点的x private int marginR; //右边缘点的x private int topOffset; //最高点,顶点y的偏移量 private int marginLRY; //顶部左右边缘点纵向margin,左上角点,右上角点的y private int topY; //最高点,顶点的y private int bottom; //底部左右边缘纵向margin,左下角点,右下角点的y private int centerX; //中心点x private int centerY; //中心点y private int controlLX; //左控制点X private int controlTY; //上控制点y private int controlRX; //右控制点x private int controlBY; //下控制点y private Point pointTL=new Point(); //左上角端点 private Point pointTR=new Point(); //右上角端点 private Point pointControlLT=new Point(); //左上控制点 private Point pointControlRT=new Point(); //右上控制点 private Point pointCenter=new Point(); //中心点 private Point pointCenterLOffset=new Point(); //中心左偏移点 private Point pointCenterROffset=new Point(); //中心右偏移点 private float mWidth = 1000;//当宽为wrap_content时,默认的宽度 private float mHeight = 1200;//当高为wrap_content时,默认的高度 private float scaleT; //上部沙堆和时间的比例 private float scaleControlY; //底部沙堆控制点y轴坐标与时间的比例 private String time; private TimeFactory timeFactory; public HourglassView(Context context) { this(context, null, 0); } public HourglassView(Context context, AttributeSet attrs) { this(context, attrs, 0); this.context=context; } public HourglassView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.clock); refresh_time = ta.getFloat(R.styleable.clock_refresh_time, 1000); ta.recycle(); } private void init() { refreshThread = new Thread(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //添加了适应wrap_content的界面计算 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension((int) mWidth, (int) mHeight); } else if (widthSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension((int) mWidth, heightSpecSize); } else if (heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpecSize, (int) mHeight); } } @SuppressLint("DrawAllocation") @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); initParam(); Log.d(TAG, "state:"+state); if(state.equals(STATE_START)){ initScale(); state=STATE_RUN; } float t=calculateT(); float y=calculateBottomSendControlHeight(); drawTopSend(canvas,t); drawBottomSend(canvas,y); drawDropSend(canvas); drawTime(canvas); drawTop(canvas); drawSide(canvas); drawBottom(canvas); } private void initParam(){ if(timeFactory!=null) time=timeFactory.createTimeGetter().getTime(); if(state.equals(STATE_END)) time="0:0:0"; if(isSizeParamgramable){ setMeasuredDimension((int) mWidth, (int) mHeight); return; } this.mWidth = Math.min(getWidth(), getHeight()); this.mHeight = Math.max(getWidth(), getHeight()); //向左右边缘点横向margin,左上角点的x marginLR=(int) (this.mWidth/10); //右边缘点的x marginR=(int) mWidth-marginLR; //最高点,顶点y的偏移量 topOffset=(int) (this.mHeight/50); //底部左右边缘纵向margin,左下角点,右下角点的y bottom=(int) (mHeight-topOffset); //顶部左右边缘点纵向margin,左上角点,右上角点的y marginLRY=topOffset*2; //最高点,顶点的y topY=marginLRY-30; //中心点x centerX=(int) (mWidth/2); //中心点y centerY=(int) (mHeight/2); //左控制点X controlLX=marginLR+10; //上控制点y controlTY=(int) (mHeight/4); //右控制点x controlRX=(int) (mWidth-marginLR-10); //下控制点y controlBY=(int) (mHeight*3/4); //左上角端点 pointTL.setX(marginLR); pointTL.setY(marginLRY); //右上角端点 pointTR.setX(marginR); pointTR.setY(marginLRY); //左上控制点 pointControlLT.setX(controlLX); pointControlLT.setY(controlTY); //右上控制点 pointControlRT.setX(controlRX); pointControlRT.setY(controlTY); //中心点 pointCenter.setX(centerX); pointCenter.setY(centerY); //中心左偏移点 pointCenterLOffset.setX((int) (centerX-10)); pointCenterLOffset.setY(centerY); //中心右偏移点 pointCenterROffset.setX(centerX+10); pointCenterROffset.setY(centerY); } /** * * 画上部沙堆 * * @param canvas */ private void drawTopSend(Canvas canvas,float t){ Log.d(TAG,"t:"+t); if(t==1) return; //顶部沙堆左侧点 Point sendPointTL=Point.bezier(t, pointTL, pointControlLT, pointCenterLOffset); //底部沙堆右侧点 Point sendPointTR=Point.bezier(t, pointTR, pointControlRT, pointCenterROffset); //初始化上部沙堆 //为了解决填充问题,上部分是贝塞尔曲线。下部分贝塞尔曲线可能不会经过控制点,所以下部分为封闭三角形,因此t不能太小 Path sendTopPath1=new Path(); Path sendTopPath2=new Path(); Paint sendTopPaint1=new Paint(); Paint sendTopPaint2=new Paint(); sendTopPaint1.setColor(context.getResources().getColor(R.color.send)); sendTopPaint1.setStyle(Paint.Style.FILL); sendTopPaint2.setColor(context.getResources().getColor(R.color.send)); sendTopPaint2.setStyle(Paint.Style.FILL); sendTopPath1.moveTo(sendPointTL.x, sendPointTL.y); sendTopPath1.quadTo(centerX, mHeight*2/10, sendPointTR.x,sendPointTR.y); //sendTopPath1.moveTo((centerX-marginLR)/2+16, mHeight*3/10); //sendTopPath1.quadTo(centerX, mHeight*2/10, centerX+(centerX+marginLR)/2-16, mHeight*3/10); canvas.drawPath(sendTopPath1, sendTopPaint1); sendTopPath2.moveTo(sendPointTL.x, sendPointTL.y); //sendTopPath2.moveTo((centerX-marginLR)/2+16, mHeight*3/10); sendTopPath2.lineTo(centerX, centerY); sendTopPath2.lineTo(sendPointTR.x,sendPointTR.y); //sendTopPath2.lineTo(centerX+(centerX+marginLR)/2-16, mHeight*3/10); sendTopPath2.close(); canvas.drawPath(sendTopPath2, sendTopPaint2); } /** * * 画下部沙堆 * * @param canvas */ private void drawBottomSend(Canvas canvas,float y){ Log.d(TAG,"y:"+y); if(y==0) y=mHeight/10; Path sendPath=new Path(); Paint sendPaint=new Paint(); sendPaint.setColor(context.getResources().getColor(R.color.send)); sendPaint.setStyle(Paint.Style.FILL); sendPath.moveTo(marginLR, bottom); sendPath.quadTo(centerX, y, marginR, bottom); canvas.drawPath(sendPath, sendPaint); } /** * * 画落下的沙子 * * @param canvas */ private void drawDropSend(Canvas canvas){ if(state.equals(STATE_PAUSE) || state.equals(STATE_END)) return; if(timeFactory!=null){ Paint pointPaint=new Paint(); pointPaint.setStrokeWidth(10f); pointPaint.setColor(context.getResources().getColor(R.color.send)); pointPaint.setStyle(Paint.Style.FILL); if(currentSend<maxSendCount){ if(currentSend==0) sendMaxHeight=(int) (centerY); if(count%2==0){ canvas.drawPoint(centerX, centerY, pointPaint); currentSend++; } count++; } if(currentSend>0){ if(sendMaxHeight<mHeight*8/10){ for(int i=0;i<currentSend;i++){ canvas.drawPoint(centerX, sendMaxHeight-i*20+30, pointPaint); } sendMaxHeight=sendMaxHeight+20; } else{ count=0; currentSend=0; sendMaxHeight=(int) (centerY); } } } } /** * * 画时间 * * @param canvas */ private void drawTime(Canvas canvas){ Paint painTime = new Paint(); painTime.setTextSize(80); if(time!=null) canvas.drawText(time, centerX-painTime.measureText(time)/2, mHeight/6, painTime); } /** * * 画顶部 * * @param canvas */ private void drawTop(Canvas canvas){ Paint p = new Paint(); p.setStrokeWidth(20f); p.setColor(Color.BLACK); p.setStyle(Paint.Style.STROKE); Path path=new Path(); path.moveTo(marginLR, marginLRY); path.quadTo(centerX, topY, marginR, marginLRY); canvas.drawPath(path, p); } /** * * 画侧面 * * @param canvas * @param p */ private void drawSide(Canvas canvas){ Paint p = new Paint(); p.setStrokeWidth(20f); p.setColor(Color.BLACK); p.setStyle(Paint.Style.STROKE); Path path=new Path(); //沙漏左上侧面 path.moveTo(marginLR, marginLRY); path.quadTo(controlLX, controlTY, centerX-10, centerY); canvas.drawPath(path, p); //沙漏右上侧面 path.moveTo(marginR, marginLRY); path.quadTo(controlRX, controlTY, centerX+10, centerY); canvas.drawPath(path, p); //沙漏左下侧面 path.moveTo(centerX-10, centerY); path.quadTo(controlLX, controlBY, marginLR, bottom); canvas.drawPath(path, p); //沙漏右下侧面 path.moveTo(centerX+10, centerY); path.quadTo(controlRX, controlBY, marginR, bottom); canvas.drawPath(path, p); } /** * * 画底部 * * @param canvas */ private void drawBottom(Canvas canvas){ Paint bottomBordPaint=new Paint(); Paint bottomPaint=new Paint(); Path bottomBordPath=new Path(); Path bottomPath=new Path(); bottomBordPaint.setStrokeWidth(8f); bottomBordPaint.setColor(Color.BLACK); bottomBordPaint.setStyle(Paint.Style.STROKE); bottomPaint.setColor(context.getResources().getColor(R.color.send)); bottomPaint.setStyle(Paint.Style.FILL); bottomPath.moveTo(marginLR, bottom); bottomPath.quadTo(centerX, bottom+30, marginR, bottom); canvas.drawPath(bottomPath, bottomPaint); bottomBordPath.moveTo(marginLR, bottom); bottomBordPath.quadTo(centerX, bottom+30, marginR, bottom); canvas.drawPath(bottomBordPath, bottomBordPaint); } /** * * 计算时间和上部沙堆,下部沙堆的比例关系 * * @return */ private void initScale(){ Log.d(TAG,"initScale is called"); int second=calculateSencod(time); //t的范围为0.6-1 scaleT=(float) (0.4/second); //y的范围为mHeight-mHeight/10 scaleControlY=(mHeight-mHeight/10)/second; } /** * * 根据时间计算贝塞尔曲线的t值 * */ private float calculateT(){ int second=calculateSencod(time); return 1-second*scaleT; } /** * * 根据时间计算底部沙堆贝塞尔曲线控制点的y坐标 * * @return */ private int calculateBottomSendControlHeight(){ int second=calculateSencod(time); return (int) (second*scaleControlY); } /** * * 根据传进来的时间字符串,获得秒数 * * @param time * @return */ private int calculateSencod(String time){ int second; try{ String[] ts=time.split(":"); second=Integer.valueOf(ts[0])*60*60+Integer.valueOf(ts[1])*60+Integer.valueOf(ts[2]); }catch(Exception e){ second=0; } return second; } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); //在添加到Activity的时候启动线程 refreshThread = new Thread(new Runnable() { @Override public void run() { while (true) { //设置更新界面的刷新时间 SystemClock.sleep((long) refresh_time); postInvalidate(); } } }); refreshThread.start(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); //停止刷新线程 refreshThread.interrupt(); } public TimeFactory getTimeFactory() { return timeFactory; } public void setTimeFactory(TimeFactory timeFactory) { this.timeFactory = timeFactory; } public float getmWidth() { return mWidth; } public void setmWidth(float mWidth) { this.mWidth = mWidth; } public float getmHeight() { return mHeight; } public void setmHeight(float mHeight) { this.mHeight = mHeight; } public String getState() { return state; } public void setState(String state) { this.state = state; } public boolean getIsSizeParamgramable() { return isSizeParamgramable; } public void setIsSizeParamgramable(boolean isSizeParamgramable) { this.isSizeParamgramable = isSizeParamgramable; } } [Center]: /images/20220711/e95165692923486a9074eaff8016129e.png
相关 打印沙漏 本题要求你写个程序把给定的符号打印成沙漏的形状。例如给定17个“\”,要求按下列格式打印 所谓“沙漏形状”,是指每 Myth丶恋晨/ 2023年07月24日 05:49/ 0 赞/ 34 阅读
相关 打印沙漏 本题要求你写个程序把给定的符号打印成沙漏的形状。例如给定17个“\”,要求按下列格式打印 所谓“沙漏形状”,是指每 末蓝、/ 2022年06月15日 08:27/ 0 赞/ 227 阅读
相关 打印沙漏 https://www.patest.cn/contests/gplt 天梯赛-练习赛 本题要求你写个程序把给定的符号打印成沙漏的形状。例如给定17个“\”,要求按下列格式打 素颜马尾好姑娘i/ 2022年05月31日 14:52/ 0 赞/ 205 阅读
相关 1027. 打印沙漏(20) 本题要求你写个程序把给定的符号打印成沙漏的形状。例如给定17个“\”,要求按下列格式打印 所谓“沙漏形状”,是指每 Bertha 。/ 2022年05月26日 05:47/ 0 赞/ 182 阅读
相关 作业1:打印沙漏 7-1 打印沙漏 (20 分) 本题要求你写个程序把给定的符号打印成沙漏的形状。例如给定17个“”,要求按下列格式打印 \\\\ \\\ --------- 不念不忘少年蓝@/ 2021年12月24日 15:43/ 0 赞/ 271 阅读
相关 PTA:打印沙漏 打印沙漏 (20 分) -------------------- 文章目录 打印沙漏 (20 分) 1. 题目描述 2. 输入格式 柔光的暖阳◎/ 2021年09月26日 15:26/ 0 赞/ 483 阅读
相关 java 打印沙漏 打印沙漏, 打印出如下形状 \\\\\\\ \\\\\ \\\ \ \\\ \\\\\ \\\\\\\ /第一种方法 墨蓝/ 2021年09月19日 22:18/ 0 赞/ 363 阅读
相关 1027.打印沙漏 本题要求你写个程序把给定的符号打印成沙漏的形状。例如给定17个“\”,要求按下列格式打印 所谓“沙漏形状”,是指每 梦里梦外;/ 2021年09月12日 07:56/ 0 赞/ 368 阅读
还没有评论,来说两句吧...