您的位置:首页 > 移动开发 > Android开发

Android通过Path实现复杂效果(搜索按钮+时钟的实现 )

2016-11-08 21:40 507 查看
Path :

在Android中复杂的图形的绘制绝大多数是通过path来实现,比如绘制一条曲线,然后让一个物体随着这个曲线运动,比如搜索按钮,比如一个简单时钟的实现:

那么什么是path呢!

定义:path 就是路径,就是图形的路径的集合,它里边包含了路径里边的坐标点,等等的属性。我们可以获取到任意点的坐标,正切值。

那么要获取Path上边所有点的坐标还需要用到一个类,PathMeasure;

PathMesure:

PathMeasure是一个用来测量Path的类,主要有以下方法:


构造方法

方法名释义
PathMeasure()创建一个空的PathMeasure
PathMeasure(Path path, boolean forceClosed)创建 PathMeasure 并关联一个指定的Path(Path需要已经创建完成)。


公共方法

返回值方法名释义
voidsetPath(Path path, boolean forceClosed)关联一个Path
booleanisClosed()是否闭合
floatgetLength()获取Path的长度
booleannextContour()跳转到下一个轮廓
booleangetSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)截取片段
booleangetPosTan(float distance, float[] pos, float[] tan)获取指定长度的位置坐标及该点切线值
booleangetMatrix(float distance, Matrix matrix, int flags)获取指定长度的位置坐标及该点Matrix
可以看到,这个就等于是一个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]);

}

我们要的效果就是点击搜索按钮的时候开始从按钮变为旋转,然后搜索结束以后变为搜索按钮。

所以我们可以确定有四种状态:

[java] view plain copy

print?





public enum Seach_State{

START,END,NONE,SEARCHING

}

然后根据状态来进行动态绘制path,动态绘制path就要使用到PathMeasure测量当前path的坐标,然后进行绘制。

[java] view plain copy

print?





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。

下边是整个代码:

[java] view plain copy

print?





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,可以自己测试)

需要计算当前时针,分针,秒针的角度,然后进行绘制

整体代码:

[java] view plain copy

print?





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里边。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: