公共方法
可以看到,这个就等于是一个Path的一个工具类,方法很简单,那么就开始我们所要做的按钮跟时钟的开发吧
(1)搜索按钮,首先上图:
要实现这个功能首先要把他分解开来做;
创建搜索按钮的path路径,然后创建外圈旋转的path,
public void initPath(){mPath_search = new Path();mPath_circle = new Path();mMeasure = new PathMeasure();// 注意,不要到360度,否则内部会自动优化,测量不能取到需要的数值RectF oval1 = new RectF(-50, -50, 50, 50); // 放大镜圆环mPath_search.addArc(oval1, 45, 359.9f);RectF oval2 = new RectF(-100, -100, 100, 100); // 外部圆环mPath_circle.addArc(oval2, 45, -359.9f);float[] pos = new float[2];mMeasure.setPath(mPath_circle, false);// 放大镜把手的位置mMeasure.getPosTan(0, pos, null);mPath_search.lineTo(pos[0], pos[1]); // 放大镜把手Log.i("TAG", "pos=" + pos[0] + ":" + pos[1]);}我们要的效果就是点击搜索按钮的时候开始从按钮变为旋转,然后搜索结束以后变为搜索按钮。
publicenum Seach_State{START,END,NONE,SEARCHING}然后根据状态来进行动态绘制path,动态绘制path就要使用到PathMeasure测量当前path的坐标,然后进行绘制。
private void drawPath(Canvas c) {c.translate(mWidth / 2, mHeight / 2);switch (mState){case NONE:c.drawPath(mPath_search,mPaint);break;case START:mMeasure.setPath(mPath_search,true);Path path = new Path();mMeasure.getSegment(mMeasure.getLength() * curretnAnimationValue,mMeasure.getLength(),path, true);c.drawPath(path,mPaint);break;case SEARCHING:mMeasure.setPath(mPath_circle,true);Path path_search = new Path();mMeasure.getSegment(mMeasure.getLength()*curretnAnimationValue -30,mMeasure.getLength()*curretnAnimationValue,path_search,true);c.drawPath(path_search,mPaint);break;case END:mMeasure.setPath(mPath_search,true);Path path_view = new Path();mMeasure.getSegment(0,mMeasure.getLength()*curretnAnimationValue,path_view,true);c.drawPath(path_view,mPaint);break;}}然后就是需要通过使用属性动画来返回当前该绘制的百分百,通过这个值来进行计算要绘制的path。
package com.duoku.platform.demo.canvaslibrary.attract.view;import android.animation.Animator;import android.animation.ValueAnimator;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.graphics.PathMeasure;import android.graphics.RectF;import android.util.AttributeSet;import android.util.Log;import android.view.View;/** * Created by chenpengfei_d on 2016/9/7. */public class SearchView extends View {private Paint mPaint;private Context mContext;private Path mPath_circle;private Path mPath_search;private PathMeasure mMeasure;private ValueAnimator mValueAnimator_search;private long defaultduration=3000;private float curretnAnimationValue;private Seach_State mState = Seach_State.SEARCHING;public SearchView(Context context) {super(context);init(context);}public SearchView(Context context, AttributeSet attrs) {super(context, attrs);init(context);}public SearchView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context);}public void init(Context context){this.mContext = context;initPaint();initPath();initAnimation();}public void initPaint(){mPaint = new Paint();mPaint.setDither(true);mPaint.setStrokeCap(Paint.Cap.ROUND);//设置笔头效果mPaint.setAntiAlias(true);mPaint.setColor(Color.RED);mPaint.setStrokeWidth(3);mPaint.setStyle(Paint.Style.STROKE);}public void initPath(){mPath_search = new Path();mPath_circle = new Path();mMeasure = new PathMeasure();// 注意,不要到360度,否则内部会自动优化,测量不能取到需要的数值RectF oval1 = new RectF(-50, -50, 50, 50); // 放大镜圆环mPath_search.addArc(oval1, 45, 359.9f);RectF oval2 = new RectF(-100, -100, 100, 100); // 外部圆环mPath_circle.addArc(oval2, 45, -359.9f);float[] pos = new float[2];mMeasure.setPath(mPath_circle, false);// 放大镜把手的位置mMeasure.getPosTan(0, pos, null);mPath_search.lineTo(pos[0], pos[1]); // 放大镜把手Log.i("TAG", "pos=" + pos[0] + ":" + pos[1]);}public void initAnimation(){mValueAnimator_search = ValueAnimator.ofFloat(0f,1.0f).setDuration(defaultduration);mValueAnimator_search.addUpdateListener(updateListener);mValueAnimator_search.addListener(animationListener);}private ValueAnimator.AnimatorUpdateListener updateListener = new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {curretnAnimationValue = (float) animation.getAnimatedValue();invalidate();}};private Animator.AnimatorListener animationListener = new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animation) {}@Overridepublic void onAnimationEnd(Animator animation) {if(mState ==Seach_State.START){setState(Seach_State.SEARCHING);}}@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic void onAnimationRepeat(Animator animation) {}};@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);drawPath(canvas);}private int mWidth,mHeight;@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);mWidth = w;mHeight = h;}private void drawPath(Canvas c) {c.translate(mWidth / 2, mHeight / 2);switch (mState){case NONE:c.drawPath(mPath_search,mPaint);break;case START:mMeasure.setPath(mPath_search,true);Path path = new Path();mMeasure.getSegment(mMeasure.getLength() * curretnAnimationValue,mMeasure.getLength(),path, true);c.drawPath(path,mPaint);break;case SEARCHING:mMeasure.setPath(mPath_circle,true);Path path_search = new Path();mMeasure.getSegment(mMeasure.getLength()*curretnAnimationValue -30,mMeasure.getLength()*curretnAnimationValue,path_search,true);c.drawPath(path_search,mPaint);break;case END:mMeasure.setPath(mPath_search,true);Path path_view = new Path();mMeasure.getSegment(0,mMeasure.getLength()*curretnAnimationValue,path_view,true);c.drawPath(path_view,mPaint);break;}}public void setState(Seach_State state){this.mState = state;startSearch();}public void startSearch(){switch (mState){case START:mValueAnimator_search.setRepeatCount(0);break;case SEARCHING:mValueAnimator_search.setRepeatCount(ValueAnimator.INFINITE);mValueAnimator_search.setRepeatMode(ValueAnimator.REVERSE);break;case END:mValueAnimator_search.setRepeatCount(0);break;}mValueAnimator_search.start();}publicenum Seach_State{START,END,NONE,SEARCHING}}(学习的点:path可以组合,可以把不同的path放置到一个path里边,然后进行统一的绘制)
说一下时钟的思路啊,网上很多时钟都是通过Canvas绘制基本图形实现的,没有通过path来实现的,使用path实现是为了以后更加灵活的控制时钟的绘制效果,比如我们要让最外边的圆圈逆时针旋转,还比如在上边添加些小星星啥的,用path的话会更加灵活。
时钟的实现分部分:
1、创建外圈path路径
2、创建刻度path路径,要区分整点,绘制时间点
3、绘制指针,(这个使用的是canvas绘制的线段,也可以使用Path,可以自己测试)
需要计算当前时针,分针,秒针的角度,然后进行绘制
整体代码:
package com.duoku.platform.demo.canvaslibrary.attract.view;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.graphics.PathMeasure;import android.os.Handler;import android.util.AttributeSet;import android.view.View;import java.util.Calendar;/** * Created by chenpengfei_d on 2016/9/8. */public class TimeView extends View {private Paint mPaint,mPaint_time;private Paint mPaint_h,mPaint_m,mPaint_s;private Path mPath_Circle;private Path mPath_Circle_h;private Path mPath_Circle_m;private Path mPath_h,mPath_m,mPath_s;private Path mPath_duration;private PathMeasure mMeasure;private PathMeasure mMeasure_h;private PathMeasure mMeasure_m;private Handler mHandler = new Handler();private Runnable clockRunnable;private boolean isRunning;public TimeView(Context context) {super(context);init();}public TimeView(Context context, AttributeSet attrs) {super(context, attrs);init();}public TimeView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}int t = 3;public void init(){//初始化画笔mPaint = new Paint();mPaint.setDither(true);mPaint.setAntiAlias(true);mPaint.setStyle(Paint.Style.STROKE);mPaint.setStrokeWidth(2);mPaint.setStrokeCap(Paint.Cap.ROUND);mPaint.setStrokeJoin(Paint.Join.ROUND);mPaint.setColor(Color.RED);mPaint_time = new Paint();mPaint_time.setDither(true);mPaint_time.setAntiAlias(true);mPaint_time.setStyle(Paint.Style.STROKE);mPaint_time.setStrokeWidth(2);mPaint_time.setTextSize(15);mPaint_time.setStrokeCap(Paint.Cap.ROUND);mPaint_time.setStrokeJoin(Paint.Join.ROUND);mPaint_time.setColor(Color.RED);mPaint_h = new Paint();mPaint_h.setDither(true);mPaint_h.setAntiAlias(true);mPaint_h.setStyle(Paint.Style.STROKE);mPaint_h.setStrokeWidth(6);mPaint_h.setTextSize(15);mPaint_h.setStrokeCap(Paint.Cap.ROUND);mPaint_h.setStrokeJoin(Paint.Join.ROUND);mPaint_h.setColor(Color.RED);mPaint_m = new Paint();mPaint_m.setDither(true);mPaint_m.setAntiAlias(true);mPaint_m.setStyle(Paint.Style.STROKE);mPaint_m.setStrokeWidth(4);mPaint_m.setTextSize(15);mPaint_m.setStrokeCap(Paint.Cap.ROUND);mPaint_m.setStrokeJoin(Paint.Join.ROUND);mPaint_m.setColor(Color.RED);mPaint_s = new Paint();mPaint_s.setDither(true);mPaint_s.setAntiAlias(true);mPaint_s.setStyle(Paint.Style.STROKE);mPaint_s.setStrokeWidth(2);mPaint_s.setTextSize(15);mPaint_s.setStrokeCap(Paint.Cap.ROUND);mPaint_s.setStrokeJoin(Paint.Join.ROUND);mPaint_s.setColor(Color.RED);//初始化刻度mPath_Circle = new Path();mPath_Circle.addCircle(0,0,250, Path.Direction.CCW);mPath_Circle_h = new Path();mPath_Circle_h.addCircle(0,0,220, Path.Direction.CCW);mPath_Circle_m = new Path();mPath_Circle_m.addCircle(0,0,235, Path.Direction.CCW);//初始化PathMeasure测量path坐标,mMeasure = new PathMeasure();mMeasure.setPath(mPath_Circle,true);mMeasure_h = new PathMeasure();mMeasure_h.setPath(mPath_Circle_h,true);mMeasure_m = new PathMeasure();mMeasure_m.setPath(mPath_Circle_m,true);//获取刻度pathmPath_duration = new Path();for (int i = 60; i>0 ;i --){Path path = new Path();float pos [] = new float[2];float tan [] = new float[2];float pos2 [] = new float[2];float tan2 [] = new float[2];float pos3 [] = new float[2];float tan3 [] = new float[2];mMeasure.getPosTan(mMeasure.getLength()*i/60,pos,tan);mMeasure_h.getPosTan(mMeasure_h.getLength()*i/60,pos2,tan2);mMeasure_m.getPosTan(mMeasure_m.getLength()*i/60,pos3,tan3);float x = pos[0];float y = pos[1];float x2 = pos2[0];float y2 = pos2[1];float x3 = pos3[0];float y3 = pos3[1];path.moveTo(x , y);if(i% 5 ==0){path.lineTo(x2,y2);if(t>12){t = t-12;}String time = t++ +"";Path path_time = new Path();mMeasure_h.getPosTan(mMeasure_h.getLength()*(i-1)/60,pos2,tan2);mPaint.getTextPath(time,0,time.length(),(x2- (x2/15)),y2-(y2/15),path_time);path.close();path.addPath(path_time);}else{path.lineTo(x3,y3);}mPath_duration.addPath(path);clockRunnable = new Runnable() {//里面做的事情就是每隔一秒,刷新一次界面@Overridepublic void run() {//线程中刷新界面postInvalidate();mHandler.postDelayed(this, 1000);}};}mPath_h = new Path();mPath_h.rLineTo(50,30);mPath_m = new Path();mPath_m.rLineTo(80,80);mPath_s = new Path();mPath_s.rLineTo(130,50);}private int mWidth,mHeight;@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);mWidth = w;mHeight = h;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if(!isRunning){isRunning = true;mHandler.postDelayed(clockRunnable,1000);}else{canvas.translate(mWidth/2,mHeight/2);canvas.drawPath(mPath_Circle,mPaint);canvas.save();canvas.drawPath(mPath_duration,mPaint_time);canvas.drawPoint(0,0,mPaint_time);drawClockPoint(canvas);}}private Calendar cal;private int hour;private int min;private int second;private float hourAngle,minAngle,secAngle;/** * 绘制三个指针 * @param canvas */private void drawClockPoint(Canvas canvas) {cal = Calendar.getInstance();hour = cal.get(Calendar.HOUR);//Calendar.HOUR获取的是12小时制,Calendar.HOUR_OF_DAY获取的是24小时制min = cal.get(Calendar.MINUTE);second = cal.get(Calendar.SECOND);//计算时分秒指针各自需要偏移的角度hourAngle = (float)hour / 12 * 360 + (float)min / 60 * (360 / 12);//360/12是指每个数字之间的角度minAngle = (float)min / 60 * 360;secAngle = (float)second / 60 * 360;//下面将时、分、秒指针按照各自的偏移角度进行旋转,每次旋转前要先保存canvas的原始状态canvas.save();canvas.rotate(hourAngle,0, 0);canvas.drawLine(0, 0, mWidth/6, getHeight() / 6 - 65, mPaint_h);//时针长度设置为65canvas.restore();canvas.save();canvas.rotate(minAngle,0, 0);canvas.drawLine(0, 0, mWidth/6, getHeight() / 6 - 90 , mPaint_m);//分针长度设置为90canvas.restore();canvas.save();canvas.rotate(secAngle,0, 0);canvas.drawLine(0, 0, mWidth/6, getHeight() / 6 - 110 , mPaint_s);//秒针长度设置为110canvas.restore();}}这其实还不算特别复杂的动画,也许你有啥好的想法,可以自己通过Path + 属性动画来实现更好看的效果;