Android自定义View绘制贝塞尔曲线实现流程

目录
  • 前言
  • 二阶贝塞尔曲线
  • 三阶贝塞尔曲线

前言

对于Android开发,实现贝塞尔曲线还是比较方便的,有对应的API供你调用。由于一阶贝塞尔曲线就是一条直线,实际没啥多大用处,因此,下面主要讲解二阶和三阶。

二阶贝塞尔曲线

在Android中,使用quadTo来实现二阶贝塞尔

        path.reset()
        path.moveTo(startX, startY)
        path.quadTo(currentX, currentY, endX, endY)
        canvas.drawPath(path, curvePaint)

startX和startY,endX和endY为两个固定点,currentX和currentY就是控制点,通过改变控制点的位置来改变二阶贝塞尔曲线的形状。

a点和b点就是固定点,c点是控制点,我们可以改变c点的位置来改变曲线的形状。

    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
                currentX = event.x
                currentY = event.y
                postInvalidate()
            }
        }
        return true
    }

三阶贝塞尔曲线

在Android中,使用cubicTo来实现三阶贝塞尔

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        path.reset()
        path.moveTo(startX, startY)
        path.cubicTo(fixedX1, fixedY1, fixedX2, fixedY2, endX, endY)
        canvas.drawPath(path, curvePaint)
        //绘制辅助线
        drawHelpLine(canvas)
    }
    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
                //divideLine区分触摸点是左边还是右边
                if (event.x < divideLine) {
                    fixedX1 = event.x
                    fixedY1 = event.y
                } else {
                    fixedX2 = event.x
                    fixedY2 = event.y
                }
                postInvalidate()
            }
        }
        return true
    }

其中,startX和startY,endX和endY为两个固定点,fixedX1和fixedY1,fixedX2和fixedY2分别为两个控制点,通过改变控制点的位置来改变三阶贝塞尔曲线的形状。

a点和b点就是固定点,c点和d点是控制点,我们可以改变c点或d点的位置来改变曲线的形状。

OK,贝塞尔曲线的基础到此就讲完了,下面来个实战,体验一下贝塞尔曲线的丝滑吧!

关于贝塞尔曲线,最典型的应用就是波浪球了,那咱们也来整一个,先上图

首先裁剪一下画布,变为圆形

        val circlePath = Path()
        circlePath.addCircle(width / 2f, height / 2f, width / 2f, Path.Direction.CW)
        canvas.clipPath(circlePath)

Path.Direction.CW:沿顺时针方向绘制,Path.Direction.CCW:沿逆时针方向绘制

以View为中心,画圆

canvas.drawCircle(width / 2f, height / 2f, width / 2f, circularPaint)

利用二阶贝塞尔,绘制波浪,起点为屏幕外,circleLen为曲线1/4周期长度

private val startPoint = Point(-4 * circleLen, 0)

根据进度改变起点坐标的y值,控制点为曲线的顶部和底部,循环绘制,然后构建曲线之下的封闭区域,填充

        //根据进度改变起点坐标的y值
        startPoint.y = ((1 - (progress / 100.0)) * height).toInt()
        //移动到起点
        wavePath.moveTo(startPoint.x.toFloat(), startPoint.y.toFloat())
        var j = 1
        //循环绘制曲线
        for (i in 1..8) {
            val controlX = (startPoint.x + circleLen * j).toFloat()
            //波顶和波底
            val controlY =
                if (i % 2 == 0) (startPoint.y + waveHeight).toFloat() else (startPoint.y - waveHeight).toFloat()
            //二阶贝塞尔
            wavePath.quadTo(
                controlX,
                controlY,
                (startPoint.x + circleLen * 2 * i).toFloat(),
                startPoint.y.toFloat()
            )
            j += 2
        }
        //绘制封闭的区域
        wavePath.lineTo(width.toFloat(), height.toFloat())
        wavePath.lineTo(startPoint.x.toFloat(), height.toFloat())
        wavePath.lineTo(startPoint.x.toFloat(), startPoint.y.toFloat())
        wavePath.close()
        canvas.drawPath(wavePath, wavePaint)
        wavePath.reset()
        //走完一周回到原点
        startPoint.x =
            if (startPoint.x + translateX >= 0) -circleLen * 4 else startPoint.x + translateX

这里是设置每隔100ms,进度加一

        progress = if (progress >= 100) 0 else progress + 1
        postInvalidateDelayed(100)

全部代码如下

class ProgressBallView : View {
    //曲线1/4周期的长度
    private val circleLen = DensityUtils.dp2px(context, 53)
    //曲线高度
    private val waveHeight = DensityUtils.dp2px(context, 27)
    //默认的长宽值
    private val defaultSize = DensityUtils.dp2px(context, 300)
    //进度
    private var progress = 0
    //平移的长度
    private val translateX = circleLen / 4
    //圆形Paint
    private val circularPaint = Paint()
    //波浪Paint
    private val wavePaint = Paint()
    //波浪的路径
    private val wavePath = Path()
    //曲线的起始坐标
    private val startPoint = Point(-4 * circleLen, 0)
    constructor(context: Context) : super(context)
    constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
        initPaint()
    }
    private fun initPaint() {
        with(circularPaint) {
            isAntiAlias = true
            color = Color.GRAY
        }
        with(wavePaint) {
            isAntiAlias = true
            color = Color.RED
        }
    }
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        var viewWidth = measureView(widthMeasureSpec)
        var viewHeight = measureView(heightMeasureSpec)
        //取最小的,作为长宽
        viewWidth = min(viewWidth, viewHeight)
        viewHeight = viewWidth
        setMeasuredDimension(viewWidth, viewHeight)
    }
    private fun measureView(measureSpec: Int): Int {
        val mode = MeasureSpec.getMode(measureSpec)
        return if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) {
            MeasureSpec.getSize(measureSpec)
        } else {
            defaultSize
        }
    }
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        //裁剪画布为圆形
        cutCanvas(canvas)
        //绘制圆形
        drawRound(canvas)
        //绘制波浪
        drawWave(canvas)
        //自动增长进度
        autoGrow()
    }
    //进度从0-100,自动增长
    private fun autoGrow() {
        progress = if (progress >= 100) 0 else progress + 1
        postInvalidateDelayed(100)
    }
    //裁剪画布为圆形
    private fun cutCanvas(canvas: Canvas) {
        val circlePath = Path()
        circlePath.addCircle(width / 2f, height / 2f, width / 2f, Path.Direction.CW)
        canvas.clipPath(circlePath)
    }
    //绘制圆形
    private fun drawRound(canvas: Canvas) {
        canvas.drawCircle(width / 2f, height / 2f, width / 2f, circularPaint)
    }
    //绘制波浪
    private fun drawWave(canvas: Canvas) {
        //根据进度改变起点坐标的y值
        startPoint.y = ((1 - (progress / 100.0)) * height).toInt()
        //移动到起点
        wavePath.moveTo(startPoint.x.toFloat(), startPoint.y.toFloat())
        var j = 1
        //循环绘制曲线
        for (i in 1..8) {
            val controlX = (startPoint.x + circleLen * j).toFloat()
            //波顶和波底
            val controlY =
                if (i % 2 == 0) (startPoint.y + waveHeight).toFloat() else (startPoint.y - waveHeight).toFloat()
            //二阶贝塞尔
            wavePath.quadTo(
                controlX,
                controlY,
                (startPoint.x + circleLen * 2 * i).toFloat(),
                startPoint.y.toFloat()
            )
            j += 2
        }
        //绘制封闭的区域
        wavePath.lineTo(width.toFloat(), height.toFloat())
        wavePath.lineTo(startPoint.x.toFloat(), height.toFloat())
        wavePath.lineTo(startPoint.x.toFloat(), startPoint.y.toFloat())
        wavePath.close()
        canvas.drawPath(wavePath, wavePaint)
        wavePath.reset()
        //走完一周回到原点
        startPoint.x =
            if (startPoint.x + translateX >= 0) -circleLen * 4 else startPoint.x + translateX
    }
}

到此这篇关于Android自定义View绘制贝塞尔曲线实现流程的文章就介绍到这了,更多相关Android贝塞尔曲线内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android自定义View实现时钟功能

    最近在练习自定义view, 想起之前面试的时候笔试有道题是写出自定义一个时钟的关键代码. 今天就来实现一下. 步骤依然是先分析, 再上代码. 实现效果 View分析 时钟主要分为五个部分: 1.中心点: 圆心位置2.圆盘: 以中心点为圆心,drawCircle画个圆3.刻度: paint有个aip, setPathEffect可以根据path画特效, 那么刻度就可以根据圆的path画一个矩形path的特效, 并且这个api只会画特效, 不会画出圆. /** * shape: 特效的path, 这

  • Android自定义view实现滚动选择控件详解

    目录 前言 需求 编写代码 主要问题 前言 上篇文章通过一个有header和footer的滚动控件(Viewgroup)学了下MeasureSpec.onMeasure以及onLayout,接下来就用一个滚动选择的控件(View)来学一下onDraw的使用,并且了解下在XML自定义控件参数. 需求 这里就是一个滚动选择文字的控件,还是挺常见的,之前用别人的,现在选择手撕一个,核心思想如下: 1.有三层不同大小及透明度的选项,选中项放在中间 2.接受一个列表的数据,静态时显示三个值,滚动时显示四个

  • Android自定义View实现跟随手指移动

    对View的移动,实现的方法有好几种,原理是通过改变View的位置来移动View,下面来实现这样的效果 动画的方法 通过改变View的tranlationX和tranlationY的值来实现移动,首先来写一个自定义View类,重写onTouchEvent方法,实现构造方法 public class MyView extends View {     public MyView(Context context) {         super(context);     }     public

  • Android小工具自定义view课表

    本文实例为大家分享了Android自定义view课表的具体代码,供大家参考,具体内容如下 这里是模拟课表,数据写死了的,不过也可以通过抓包获取教务系统课表 1.xml文件 <?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:app="

  • Android自定义view实现侧滑栏详解

    目录 前言 需求 效果图 编写代码 主要问题 前言 上一篇文章学了下自定义View的onDraw函数及自定义属性,做出来的滚动选择控件还算不错,就是逻辑复杂了一些.这篇文章打算利用自定义view的知识,直接手撕一个安卓侧滑栏,涉及到自定义LayoutParams.带padding和margin的measure和layout.利用requestLayout实现动画效果等,有一定难度,但能重新学到很多知识! 需求 这里类似旧版QQ(我特别喜欢之前的侧滑栏),有两层页面,滑动不是最左侧才触发的,而是从

  • Android自定义View实现水波纹扩散效果

    目录 1.创建RippleView.class, 继承与View 1.1特殊属性解释 1.2新建attrs.xml文件(res/values) 1.3初始化画笔 2.开始绘制onDraw() 效果:水波纹扩散 场景:雷达.按钮点击效果.搜索等 实现:先上效果图,之前记得支付宝有一个咻一咻,当时就是水波纹效果,实现起来一共两步,第一画内圆,第二画多个外圆,不同时创建有间隔创建然后缓慢增大外圆半径,到达最远距离时移除掉,扩散时把透明度从255-1不断赋值即可.复杂在第二步,开工. 开工 1.创建Ri

  • Android自定义View实现天气预报折线图

    本文实例为大家分享了Android自定义View画天气预报折线图的具体代码,供大家参考,具体内容如下 效果图如下: 刚开始尝试用第三方画曲线的框架来画效果图,后来发现曲线间的阴影当有负数的度数的时候画不出来,而且不需要点击放大.点点可点的效果,用框架显得很臃肿,所以最后用自定义View来画的折线图.自定义画折线图的大致思路:这个图是有多个四边形组成的(4个点连接起来就是一个四边形),两边延伸:添加四个多余的点,将左右的边距设置成负数即可. 代码如下: public class WeatherCh

  • Android自定义View绘制贝塞尔曲线实现流程

    目录 前言 二阶贝塞尔曲线 三阶贝塞尔曲线 前言 对于Android开发,实现贝塞尔曲线还是比较方便的,有对应的API供你调用.由于一阶贝塞尔曲线就是一条直线,实际没啥多大用处,因此,下面主要讲解二阶和三阶. 二阶贝塞尔曲线 在Android中,使用quadTo来实现二阶贝塞尔 path.reset() path.moveTo(startX, startY) path.quadTo(currentX, currentY, endX, endY) canvas.drawPath(path, cur

  • Android自定义View绘制贝塞尔曲线的方法

    本文实例为大家分享了Android自定义View绘制贝塞尔曲线的具体代码,供大家参考,具体内容如下 在平面内任选 3 个不共线的点,依次用线段连接. 在第一条线段上任选一个点 D.计算该点到线段起点的距离 AD,与该线段总长 AB 的比例. 根据上一步得到的比例,从第二条线段上找出对应的点 E,使得 AD:AB = BE:BC. 连接这两点 DE. 从新的线段 DE 上再次找出相同比例的点 F,使得 DF:DE = AD:AB = BE:BC. 到这里,我们就确定了贝塞尔曲线上的一个点 F.接下

  • Android自定义View绘制贝塞尔曲线中小红点的方法

    目录 前言 需求 效果图 代码 主要问题 简单画法 使用贝塞尔曲线 前言 上一篇文章用扇形图练习了一下安卓的多点触控,实现了单指旋转.二指放大.三指移动,四指以上同时按下进行复位的功能.今天这篇文章用很多应用常见的小红点,来练习一下贝塞尔曲线的使用. 需求 这里想法来自QQ的拖动小红点取消显示聊天条数功能,不过好像是记忆里的了,现在看了下好像效果变了.总而言之,就是一个小圆点,拖动的时候变成水滴状,超过一定范围后触发消失回调,核心思想如下: 1.一个正方形view,中间是小红点,小红点距离边框有

  • Android自定义View绘制彩色圆弧

    本文实例为大家分享了Android自定义View绘制彩色圆弧的具体代码,供大家参考,具体内容如下 效果如下: 自定义View代码如下: package com.example.yan; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; i

  • Android自定义view绘制表格的方法

    本文实例为大家分享了Android自定义view绘制表格的具体代码,供大家参考,具体内容如下 先上效果图 平时很少有这样的表格需求,不过第一想法就是自定义view绘制表格,事实上我确实是用的canvas来绘制的,整个过程看似复杂,实为简单,计算好各个点的坐标后事情就完成一半了.不废话show code import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; imp

  • Android自定义View绘制居中文本

    本文实例为大家分享了Android自定义View绘制居中文本的具体代码,供大家参考,具体内容如下 自定义view的步骤: 1.自定义View的属性2.在View的构造方法中获得我们自定义的属性3.重写onMesure(非必须)4.重写onDraw 1.自定义View的属性,首先在res/values/ 下建立一个attrs.xml , 在里面定义我们的属性,只定义三个,有文本.颜色和字体大小: <!--CustomTextView-->     <declare-styleable na

  • Android自定义View绘制随机生成图片验证码

    本篇文章讲的是Android自定义View之随机生成图片验证码,开发中我们会经常需要随机生成图片验证码,但是这个是其次,主要还是想总结一些自定义View的开发过程以及一些需要注意的地方. 按照惯例先看看效果图: 一.先总结下自定义View的步骤: 1.自定义View的属性 2.在View的构造方法中获得我们自定义的属性 3.重写onMesure 4.重写onDraw 其中onMesure方法不一定要重写,但大部分情况下还是需要重写的 二.View 的几个构造函数 1.public CustomV

  • Android自定义View绘制的方法及过程(二)

    上一篇<Android 自定义View(一) Paint.Rect.Canvas介绍>讲了最基础的如何自定义一个View,以及View用到的一些工具类.下面讲下View绘制的方法及过程 public class MyView extends View { private String TAG = "--------MyView"; private int width, height; public MyView(Context context, AttributeSet a

  • 自定义滑动按钮为例图文剖析Android自定义View绘制

    自定义View一直是横在Android开发者面前的一道坎. 一.View和ViewGroup的关系 从View和ViewGroup的关系来看,ViewGroup继承View. View的子类,多是功能型的控件,提供绘制的样式,比如imageView,TextView等,而ViewGroup的子类,多用于管理控件的大小,位置,如LinearLayout,RelativeLayout等,从下图可以看出 从实际应用中看,他们又是组合关系,我们在布局中,常常是一个ViewGroup嵌套多个ViewGro

  • Android自定义View绘制四位数随机码

    现在有这样一个需求,实现显示随机随机数可能在代码中直接很简单的就实现了,但是现在我们直接自定义View来实现这个效果,那么我们来分析一波吧,我们允许开发者自己设置这个textview的大小,颜色,和初始四位随机数的文字,那么我们需要提供自定义属性,好吧,首先把自定义属性的简单使用介绍一下吧: 首先在res/values文件夹下建利attrs.xml文件,由于这次我们功能决定我们要提供三个自定义属性,分别是textTitle String类型的,textColor是color类型的,textSiz

随机推荐