Android通过Path实现搜索按钮和时钟复杂效果_Android

在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里边,那么是不是就可以实现更复杂的效果呢,下边看图:(明天再写了)

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索android
, path
, 时钟
搜索按钮
二叉搜索树时间复杂度、深度优先搜索复杂度、二分搜索时间复杂度、lc搜索时间复杂度、二叉搜索树 复杂度,以便于您获取更多的相关知识。

时间: 2024-10-23 19:31:27

Android通过Path实现搜索按钮和时钟复杂效果_Android的相关文章

Android通过Path实现搜索按钮和时钟复杂效果

在Android中复杂的图形的绘制绝大多数是通过path来实现,比如绘制一条曲线,然后让一个物体随着这个曲线运动,比如搜索按钮,比如一个简单时钟的实现: 那么什么是path呢! 定义:path  就是路径,就是图形的路径的集合,它里边包含了路径里边的坐标点,等等的属性.我们可以获取到任意点的坐标,正切值. 那么要获取Path上边所有点的坐标还需要用到一个类,PathMeasure; PathMesure: PathMeasure是一个用来测量Path的类,主要有以下方法: 构造方法 公共方法 可

如何在Android中实现渐显按钮的左右滑动效果_Android

先看下运行效果:    程序结构: MainActivity文件中代码: 复制代码 代码如下: package com.android.buttonpageflipper;import android.app.Activity;import android.graphics.PixelFormat;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.view.Gra

如何在Android中实现渐显按钮的左右滑动效果

先看下运行效果: 程序结构: MainActivity文件中代码: 复制代码 代码如下: package com.android.buttonpageflipper; import android.app.Activity; import android.graphics.PixelFormat; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.

android搜索框上下滑动变色效果_Android

搜索框上下滑动变透明度是现在APP中很常见的效果,先看看效果: 首先来看下布局骨架: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height

Android利用悬浮按钮实现翻页效果_Android

今天给大家分享下自己用悬浮按钮点击实现翻页效果的例子. 首先,一个按钮要实现悬浮,就要用到系统顶级窗口相关的WindowManager,WindowManager.LayoutParams.那么在AndroidManifest.xml中添加权限: <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> 然后,我们要对WindowManager,WindowManager.Layout

Android实现底部弹出PopupWindow背景逐渐变暗效果_Android

在Android开发中,经常需要通过点击某个按钮弹出对话框或者选择框,通过Dialog或者PopupMenu.PopupWindow都能实现. 这里主要介绍后两者:PopupMenu.PopupWindow的实现. 先看两个效果图上边PopupMenu,下边PopupWindow: PopupMenu PopupWindow 一.PopupMenu实现: PopupMenu实现起来比较简单,主要用来实现根据按钮附近弹出的对话框. 首先定义一个menu文件\res\menu\headmenu.xm

Android 中 TabHost与ViewPager结合实现首页导航效果_Android

今天发的是TabHost结合ViewPager实现首页底部导航的效果,虽然说网上有很多这样的Demo,不过呢,我还是要把自己练习写的发出来,没错!就是这么任性: 先上效果图,如下: 代码里面有注释,就不过多解释了,说几点需要注意的问题 1:TabHost .TabWidget.FrameLayout一定添加id这个属性,否则会报错 android:id="@android:id/tabhost" android:id="@android:id/tabcontent"

Android实现手势滑动多点触摸缩放平移图片效果_Android

现在app中,图片预览功能肯定是少不了的,用户基本已经形成条件反射,看到小图,点击看大图,看到大图两个手指开始进行放大,放大后,开始移动到指定部位.一.概述想要做到图片支持多点触控,自由的进行缩放.平移,需要了解几个知识点:Matrix , GestureDetector , ScaleGestureDetector 以及事件分发机制,ps:不会咋办,不会你懂的.1.Matrix 矩阵,看深入了都是3维矩阵的乘啊什么的,怪麻烦的~~ 其实这么了解下就行了: Matrix数据结构:3维矩阵:内部存

Android实现手势滑动多点触摸放大缩小图片效果_Android

网上文章虽多,但是这种效果少之又少,我真诚的献上以供大家参考 实现原理:自定义ImageView对此控件进行相应的layout(动态布局). 这里你要明白几个方法执行的流程: 首先ImageView是继承自View的子类.onLayout方法:是一个回调方法.该方法会在在View中的layout方法中执行,在执行layout方法前面会首先执行setFrame方法.setFrame方法:判断我们的View是否发生变化,如果发生变化,那么将最新的l,t,r,b传递给View,然后刷新进行动态更新UI