Android通过Path实现搜索按钮和时钟复杂效果
在android中复杂的图形的绘制绝大多数是通过path来实现,比如绘制一条曲线,然后让一个物体随着这个曲线运动,比如搜索按钮,比如一个简单时钟的实现:
那么什么是path呢!
定义:path 就是路径,就是图形的路径的集合,它里边包含了路径里边的坐标点,等等的属性。我们可以获取到任意点的坐标,正切值。
那么要获取path上边所有点的坐标还需要用到一个类,pathmeasure;
pathmesure:
pathmeasure是一个用来测量path的类,主要有以下方法:
构造方法
公共方法
可以看到,这个就等于是一个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]); }
我们要的效果就是点击搜索按钮的时候开始从按钮变为旋转,然后搜索结束以后变为搜索按钮。
所以我们可以确定有四种状态:
public enum 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() { @override public void onanimationupdate(valueanimator animation) { curretnanimationvalue = (float) animation.getanimatedvalue(); invalidate(); } }; private animator.animatorlistener animationlistener = new animator.animatorlistener() { @override public void onanimationstart(animator animation) { } @override public void onanimationend(animator animation) { if(mstate ==seach_state.start){ setstate(seach_state.searching); } } @override public void onanimationcancel(animator animation) { } @override public void onanimationrepeat(animator animation) { } }; @override protected void ondraw(canvas canvas) { super.ondraw(canvas); drawpath(canvas); } private int mwidth,mheight; @override protected 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(); } public enum seach_state{ start,end,none,searching } }
(学习的点:path可以组合,可以把不同的path放置到一个path里边,然后进行统一的绘制)
(2)时钟效果:
说一下时钟的思路啊,网上很多时钟都是通过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); //获取刻度path mpath_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() {//里面做的事情就是每隔一秒,刷新一次界面 @override public 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; @override protected void onsizechanged(int w, int h, int oldw, int oldh) { super.onsizechanged(w, h, oldw, oldh); mwidth = w; mheight = h; } @override protected 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);//时针长度设置为65 canvas.restore(); canvas.save(); canvas.rotate(minangle,0, 0); canvas.drawline(0, 0, mwidth/6, getheight() / 6 - 90 , mpaint_m);//分针长度设置为90 canvas.restore(); canvas.save(); canvas.rotate(secangle,0, 0); canvas.drawline(0, 0, mwidth/6, getheight() / 6 - 110 , mpaint_s);//秒针长度设置为110 canvas.restore(); } }
这其实还不算特别复杂的动画,也许你有啥好的想法,可以自己通过path + 属性动画来实现更好看的效果;
比如星空的效果,比如动态绘制文字 + 路径实现类似ppt中播放的一些特效,比如电子书的自动翻页。
(3)下边再介绍一个知识,就是svg:
svg是什么东西呢?
他的学名叫做可缩放矢量图形,是基于可扩展标记语言(标准通用标记语言的子集),用于描述二维矢量图形的一种图形格式。
这种格式的图形式可以加载到android的path里边。
既然可以加载到path里边,那么是不是就可以实现更复杂的效果呢,下边看图:(明天再写了)
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 初步学习Java中线程的实现与生命周期