前言

上一篇Path特效功臣----PathMesure我们讲了PathMesure中api的详细方法和测试。本文就用我们学到的PathMeasure实现一个动态效果的SearchView,先瞄一下好不好看

searchView图片来源于网上

思路(由简到繁)

绘制静态的路径(放大镜和外圆)

1、onSizeChang()中得到组件大小

2、填充Path的放大镜和外圆

为静态图添加动态效果

1、采用ValueAnimtor提供实时变量

2、采用PathMeasure根据实时变量去绘制

3、动态效果分为四种状态(初始化,放大镜动画,外圆动画,结束动画)

绘制静态的路径

绘制的路径全部由Path去填充,放大镜可以拆分为一个圆和一条斜线,需要注意的是这里addArc的起始角度和终点角度

红色为Path的起点45度

class MySearchView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {

constructor(context: Context?) : this(context, null)

private val mPaint = Paint()//画笔

private var offestFactoty = 0.9f //偏移因子画布是组件的0.9

private var mWidth: Float = 0.0f //组件宽度

private var mHeight: Float = 0.0f //组件高度

private lateinit var searchRecf: RectF

private lateinit var cicleRecf: RectF

private val searchPath: Path = Path() //放大镜的path

private val ciclePath: Path = Path() //外圆的path

init {

initPaint()

}

private fun initPaint() {

mPaint.color = Color.BLUE

mPaint.isAntiAlias = true

mPaint.style = Paint.Style.STROKE

mPaint.strokeCap = Paint.Cap.ROUND

mPaint.strokeWidth = 8f

}

//1、onSizeChang()中得到组件大小

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {

super.onSizeChanged(w, h, oldw, oldh)

mWidth = w.toFloat()

mHeight = h.toFloat()

initPath()

}

//2、填充Path的放大镜和外圆

private fun initPath() {

cicleRecf = RectF(-mWidth / 2 * offestFactoty, -mHeight / 2 * offestFactoty, mWidth / 2 * offestFactoty, mHeight / 2 * offestFactoty)

searchRecf = RectF(-mWidth / 5, -mHeight / 5, mWidth / 5, mHeight / 5)

searchPath.addArc(searchRecf, 45f, 359.9f) //填充内圆

ciclePath.addArc(cicleRecf, 45f, 359.9f) //填充外圆

val pathMeasure = PathMeasure(ciclePath, false)

val floatArray = FloatArray(2)

val posTan = pathMeasure.getPosTan(0f, floatArray, null)//拿到手柄的终点

searchPath.lineTo(floatArray[0], floatArray[1])//为内圆添加手柄路径形成放大镜

}

override fun onDraw(canvas: Canvas?) {

canvas?.translate(mWidth / 2, mHeight / 2)//移动坐标到组件中心

canvas?.drawPath(searchPath, mPaint)

canvas?.drawPath(ciclePath,mPaint)

}

}

静态图

为静态图添加动态效果

package com.hzb.myutils.view

import android.animation.Animator

import android.animation.ValueAnimator

import android.content.Context

import android.graphics.*

import android.util.AttributeSet

import android.view.View

import android.view.animation.LinearInterpolator

import com.hzb.utilsbox.utils.LogUtil

class MySearchView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {

constructor(context: Context?) : this(context, null)

private val mPaint = Paint()//画笔

private var offestFactoty = 0.9f //偏移因子画布是组件的0.9

private var mWidth: Float = 0.0f //组件宽度

private var mHeight: Float = 0.0f //组件高度

private lateinit var searchRecf: RectF

private lateinit var cicleRecf: RectF

private val searchPath: Path = Path() //放大镜的path

private val ciclePath: Path = Path() //外圆的path

private var viewStatus = AnimStatus.NONE

private var isTopAnim:Boolean=false

private var animatorValue: Float = 0.0f //动画变量,0-1

//属性动画

private lateinit var startAnim: ValueAnimator

private lateinit var searingAnim: ValueAnimator

private lateinit var endAnim: ValueAnimator

private enum class AnimStatus { //标志动画状态

NONE, START, SEARING, END//初始状态,开始搜索,搜索中,结束搜索

}

init {

initPaint()

initAnimator()

initEvent()

}

private fun initAnimator() {

//AnimStatus.START状态的动画

startAnim = ValueAnimator.ofFloat(0f, 1f)

startAnim.duration = 1000

startAnim.addUpdateListener { animation ->

animatorValue = animation.animatedValue as Float

invalidate()

}

//AniStatus.SEARING状态动画

searingAnim = ValueAnimator.ofFloat(0f, 1f)

searingAnim.interpolator = LinearInterpolator()

searingAnim.duration=1500

searingAnim.repeatCount=ValueAnimator.INFINITE

searingAnim.repeatMode=ValueAnimator.RESTART

searingAnim.addUpdateListener { animation ->

if (viewStatus==AnimStatus.SEARING) { //这里必须添加,不然放大镜会有一刹那全部显示

animatorValue = animation.animatedValue as Float

invalidate()

}

}

//AniStatus.END状态动画

endAnim = ValueAnimator.ofFloat(1f, 0f)

endAnim.duration=1000

endAnim.addUpdateListener { animation ->

animatorValue = animation.animatedValue as Float

invalidate()

}

}

private fun initPaint() {

mPaint.color = Color.BLUE

mPaint.isAntiAlias = true

mPaint.style = Paint.Style.STROKE

mPaint.strokeCap = Paint.Cap.ROUND

mPaint.strokeWidth = 8f

}

1、onSizeChang()中得到组件大小

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {

super.onSizeChanged(w, h, oldw, oldh)

mWidth = w.toFloat()

mHeight = h.toFloat()

initPath()

}

//2、填充Path的放大镜和外圆

private fun initPath() {

cicleRecf = RectF(-mWidth / 2 * offestFactoty, -mHeight / 2 * offestFactoty, mWidth / 2 * offestFactoty, mHeight / 2 * offestFactoty)

searchRecf = RectF(-mWidth / 5, -mHeight / 5, mWidth / 5, mHeight / 5)

searchPath.addArc(searchRecf, 45f, 359.9f)

ciclePath.addArc(cicleRecf, 45f, 359.9f)

val pathMeasure = PathMeasure(ciclePath, false)

val floatArray = FloatArray(2)

val posTan = pathMeasure.getPosTan(0f, floatArray, null)

searchPath.lineTo(floatArray[0], floatArray[1])//放大镜的手柄

}

override fun onDraw(canvas: Canvas?) {

canvas?.translate(mWidth / 2, mHeight / 2)//移动坐标到组件中心

when (viewStatus) {

AnimStatus.NONE -> { //初识转态

canvas?.drawPath(searchPath, mPaint)

}

AnimStatus.START -> { //开始搜索

val pathMeasure = PathMeasure(searchPath, false)

val dst = Path()

pathMeasure.getSegment(pathMeasure.length * animatorValue, pathMeasure.length, dst, true)

canvas?.drawPath(dst, mPaint)

}

AnimStatus.SEARING -> { //搜索中

val pathMeasure = PathMeasure(ciclePath, false)

val dst = Path()

val stop = pathMeasure.length * animatorValue

val start = (stop - (0.5 - Math.abs(animatorValue - 0.5)) * pathMeasure.length/2).toFloat()

pathMeasure.getSegment(start,stop, dst, true)

canvas?.drawPath(dst, mPaint)

}

AnimStatus.END -> { //搜索结束

val pathMeasure = PathMeasure(searchPath, false)

val dst = Path()

pathMeasure.getSegment(pathMeasure.length*animatorValue , pathMeasure.length, dst, true)

canvas?.drawPath(dst, mPaint)

}

}

}

/**

* 开始动画

*/

fun startAnim() {

viewStatus=AnimStatus.START

startAnim.start()

this.isClickable = false

}

/**

* 结束动画

*/

fun stopAnim(){

isTopAnim=true

}

/**

* 监听动画结束

*/

private fun initEvent() {

val listener: Animator.AnimatorListener = object : Animator.AnimatorListener {

override fun onAnimationRepeat(animation: Animator?) {

if (isTopAnim) {

animation?.cancel()

viewStatus=AnimStatus.END

endAnim.start()

}

}

override fun onAnimationEnd(animation: Animator?) {

val name = Thread.currentThread().name

LogUtil.i(name)

when (viewStatus) {

AnimStatus.START -> {

searingAnim.start()

viewStatus=AnimStatus.SEARING

}

AnimStatus.END -> {

viewStatus = AnimStatus.NONE

this@MySearchView.isClickable = true

isTopAnim=false

}

}

}

override fun onAnimationCancel(animation: Animator?) {

}

override fun onAnimationStart(animation: Animator?) {

}

}

startAnim.addListener(listener)

searingAnim.addListener(listener)

endAnim.addListener(listener)

}

}

运行效果

效果gif

android 自定义searchview,android自定义view--SearchView相关推荐

  1. android炫酷的自定义view,Android自定义View实现炫酷进度条

    本文实例为大家分享了Android实现炫酷进度条的具体代码,供大家参考,具体内容如下 下面我们来实现如下效果: 第一步:创建attrs文件夹,自定义属性: 第二步:自定义View: /** * Cre ...

  2. android组件什么时候加载到r文件,Android自定义加载loading view动画组件

    我写写使用步骤 自定义view(CircleProgress )的代码 package com.hysmarthotel.view; import com.hysmarthotel.roomcontr ...

  3. android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu[转]

    http://blog.csdn.net/jj120522/article/details/8095852 示意图就不展示了,和上一节的一样,滑动菜单SlidingMenu效果如何大家都比较熟悉,在这 ...

  4. android sqlite自定义函数,Android中自定义一个View的方法详解

    本文实例讲述了Android中自定义一个View的方法.分享给大家供大家参考,具体如下: Android中自定义View的实现比较简单,无非就是继承父类,然后重载方法,即便如此,在实际编码中难免会遇到 ...

  5. Android Paint应用之自定义View实现进度条控件

    在上一篇文章<Android神笔之Paint>学习了Paint的基本用法,但是具体的应用我们还没有实践过.从标题中可知,本文是带领读者使用Paint,自定义一个进度条控件. 上图就是本文要 ...

  6. 【Android 应用开发】自定义View 和 ViewGroup

    一. 自定义View介绍 自定义View时, 继承View基类, 并实现其中的一些方法. (1) ~ (2) 方法与构造相关 (3) ~ (5) 方法与组件大小位置相关 (6) ~ (9) 方法与触摸 ...

  7. Android开发-将自定义View布局到Layout中并调用

    写程序的时候,关于布局方面遇到并解决的问题 1.自定义View及其layout属性. 自定义View: [java] view plaincopy public class DrawView exte ...

  8. Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)

    转载请注明地址:http://blog.csdn.net/xiaanming/article/details/10298163 很多的时候,系统自带的View满足不了我们功能的需求,那么我们就需要自己 ...

  9. Android 自定义 圆环,Android自定义view实现圆环效果实例代码

    先上效果图,如果大家感觉不错,请参考实现代码. 重要的是如何实现自定义的view效果 (1)创建类,继承view,重写onDraw和onMesure方法 public class CirclePerc ...

最新文章

  1. 基于二代和三代测序技术的柚子基因组混合拼装
  2. 工业组态领头羊--组态王开始涉足.net程序开发(与林伟先生一次近距离接触)...
  3. window.postMessage跨文档通信
  4. 【数字信号处理】傅里叶变换性质 ( 傅里叶变换频移性质示例 | PCM 音频信号处理 | 使用 matlab 进行频移操作 )
  5. 2013豆瓣校园招聘研发类笔试题
  6. arp攻击源代码(收集)
  7. Audio Offload
  8. “Transaction rolled back because it has been marked as rollback-only”
  9. C#设计模式:迭代器模式(Iterator Pattern)
  10. html 禁用自动跳转,javascript 实现页面跳转,禁止返回上一页【转】
  11. 怎么查看raid0或者raid5_海康监控磁盘阵列怎么配置?一文了解清楚
  12. 平方根的计算(二分逼近、牛顿拉普生法)
  13. Linux Shell 中 ()、(())、[]、[[]]、{} 的作用
  14. Linux驱动设置log打印开关
  15. php 写博客教程,最近写了一个博客程序: QuickBlog PHP 开源的一文多发系统
  16. python arp 网关_python arp欺骗伪造网关代码
  17. labwindows制作特殊图形面板
  18. python进阶day13
  19. php汉字转拼音百家姓版,百家姓全文查询,百家姓全文带拼音
  20. 他人——2015实习招聘经验

热门文章

  1. 郭金东旗下利德东方举行2019年综合应急救援演练
  2. 进销存软件解决采购审批流程复杂的四大法宝
  3. Mediocre String Problem (2018南京M,回文+LCP 3×3=9种做法 %%%千年好题 感谢Grunt大佬的细心讲解)...
  4. css图片自适应裁剪
  5. PRUNING-卷积神经网络剪枝
  6. MLK | 机器学习常见算法优缺点了解一下
  7. Newcoder和LeetCode七月刷题笔记
  8. CoreData数据库
  9. css 块级元素与内联元素
  10. SynchroTrap:基于相似度的异常检测算法