前言:好想义无反顾地追逐梦想
html
相关文章:
《Android自定义控件三部曲文章索引》: http://blog.csdn.net/harvic880925/article/details/50995268
从这篇开始,我将延续androidGraphics系列文章把图片相关的知识给你们讲完,这一篇先稍微进阶一下,给你们把《android Graphics(二):路径及文字》略去的quadTo(二阶贝塞尔)函数,给你们补充一下。
本篇最终将以两个例子给你们演示贝塞尔曲线的强大用途:
一、手势轨迹
java
利用贝塞尔曲线,咱们能实现平滑的手势轨迹效果
二、水波纹效果
android
电池充电时,有些手机会显示水波纹效果,就是这样作出来的。
废话很少说,开整吧
算法
在《android Graphics(二):路径及文字》中咱们略去了有关全部贝赛尔曲线的知识,在Path中有四个函数与贝赛尔曲线有关:
canvas
//二阶贝赛尔 public void quadTo(float x1, float y1, float x2, float y2) public void rQuadTo(float dx1, float dy1, float dx2, float dy2) //三阶贝赛尔 public void cubicTo(float x1, float y1, float x2, float y2,float x3, float y3) public void rCubicTo(float x1, float y1, float x2, float y2,float x3, float y3)这里的四个函数的具体意义咱们后面会具体详细讲解,咱们这篇也就是利用这四个函数来实现咱们的贝赛尔曲线相关的效果的。
在数学的数值分析领域中,贝赛尔曲线(Bézier曲线)是电脑图形学中至关重要的参数曲线。更高维度的普遍化贝塞尔曲线就称做贝塞尔曲面,其中贝塞尔三角是一种特殊的实例。
贝塞尔曲线于1962年,由法国工程师皮埃尔·贝塞尔(Pierre Bézier)所普遍发表,他运用贝塞尔曲线来为汽车的主体进行设计。贝塞尔曲线最初由Paul de Casteljau于1959年运用de Casteljau算法开发,以稳定数值的方法求出贝塞尔曲线。
ide
这部分是颇有难度的,你们作好准备了哦
函数
其公式可归纳为:
工具
对应动画演示为:
布局
P0为起点、P1为终点,t表示当前时间,B(t)表示公式的结果值。
注意,曲线的意义就是公式结果B(t)随时间的变化,其取值所造成的轨迹。在动画中,黑色点表示在当前时间t下公式B(t)的取值。而红色的那条线就不在各个时间点下不一样取值的B(t)所造成的轨迹。
总而言之:对于一阶贝赛尔曲线,你们能够理解为在起始点和终点造成的这条直线上,匀速移动的点。
post
一样,先来看看二阶贝赛尔曲线的公式(虽然看不懂,呵呵)
你们也不用研究这个公式了,没必定数学功底也研究不出来了啥,咱仍是看动画吧
在这里P0是起始点,P2是终点,P1是控制点
假设将时间定在t=0.25的时刻,此时的状态以下图所示:
首先,P0点和P1点造成了一条贝赛尔曲线,还记得咱们上面对一阶贝赛尔曲线的总结么:就是一个点在这条直线上作匀速运动;因此P0-P1这条直线上的移动的点就是Q0;
一样,P1,P2造成了一条一阶贝赛尔曲线,在这条一阶贝赛尔曲线上,它们的随时间移动的点是Q1
最后,动态点Q0和Q1又造成了一条一阶贝赛尔曲线,在它们这条一阶贝赛尔曲线动态移动的点是B
而B的移动轨迹就是这个二阶贝赛尔曲线的最终形态。从上面的讲解你们也能够知道,之因此叫它二阶贝赛尔曲线是由于,B的移动轨迹是创建在两个一阶贝赛尔曲线的中间点Q0,Q1的基础上的。
在理解了二阶贝赛尔曲线的造成原理之后,咱们就不难理解三阶贝赛尔曲线了
一样,先列下基本看不懂的公式
这玩意估计也看不懂,讲了也没什么意义,仍是结合动画来吧
一样,咱们取其中一点来说解轨迹的造成原理,当t=0.25时,此时状态以下:
一样,P0是起始点,P3是终点;P1是第一个控制点,P2是第二个控制点;
首先,这里有三条一阶贝赛尔曲线,分别是P0-P1,P1-P2,P2-P3;
他们随时间变化的点分别为Q0,Q1,Q2
而后是由Q0,Q1,Q2这三个点,再次链接,造成了两条一阶贝赛尔曲线,分别是Q0—Q1,Q1—Q2;他们随时间变化的点为R0,R1
一样,R0和R1一样能够链接造成一条一阶贝赛尔曲线,在R0—R1这条贝赛尔曲线上随时间移动的点是B
而B的移动轨迹就是这个三阶贝赛尔曲线的最终形状。
从上面的解析你们能够看出,所谓几阶贝赛尔曲线,所有是由一条条一阶贝赛尔曲线搭起来的;
在上图中,造成一阶贝赛尔曲线的直线是灰色的,造成二阶贝赛尔曲线线是绿色的,造成三阶贝赛尔曲线的线是蓝色的。
在理解了上面的二阶和三阶贝赛尔曲线之后,咱们再来看几个贝赛尔曲线的动态图
这里就再也不一一讲解造成原理了,你们理解了二阶和三阶贝赛尔曲线之后,这两条的看看就行了,想必你们也是能本身推出四阶贝赛尔曲线的造成原理的。
若是有些同窗不懂PhotoShop,这篇文章可能就会有些难度了,本篇文章主要是利用PhotoShop的钢笔工具来得出具体贝塞尔图像的
这么屌的贝赛尔曲线,在专业绘图工具PhotoShop中固然会有它的踪迹,它就是钢笔工具,钢笔工具所使用的路径弯曲效果就是二阶贝赛尔曲线。
我来给你们演示一下钢笔工具的用法:
咱们拿最终成形的图形来看一下为何钢笔工具是二阶贝赛尔曲线:
右图演示的假设某一点t=0.25时,动态点B的位置图
一样,这里P0是起始点,P2是终点,P1是控制点;
P0-P一、P1-P2造成了第一层的一阶贝赛尔曲线。它们随时间的动态点分别是Q0,Q1
动态点Q0,Q1又造成了第二层的一阶贝赛尔曲线,它们的动态点是B.而B的轨迹跟钢笔工具的形状是彻底同样的。因此钢笔工具的拉伸效果是使用的二阶贝赛尔曲线!
这个图与上面二阶贝赛尔曲线t=0.25时的曲线差很少,你们理解起来难度也不大。
这里须要注意的是,咱们在使用钢笔工具时,拖动的是P5点。其实二阶贝赛尔曲线的控制点是其对面的P1点,钢笔工具这样设计是固然是由于操做起来比较方便。
好了,对贝赛尔曲线的知识讲了那么多,下面开始实战了,看在代码中,贝赛尔曲线是怎么来作的。
在开篇中,咱们已经提到,在Path类中有四个方法与贝赛尔曲线相关,分别是:
//二阶贝赛尔 public void quadTo(float x1, float y1, float x2, float y2) public void rQuadTo(float dx1, float dy1, float dx2, float dy2) //三阶贝赛尔 public void cubicTo(float x1, float y1, float x2, float y2,float x3, float y3) public void rCubicTo(float x1, float y1, float x2, float y2,float x3, float y3)在这四个函数中quadTo、rQuadTo是二阶贝赛尔曲线,cubicTo、rCubicTo是三阶贝赛尔曲线;咱们这篇文章以二阶贝赛尔曲线的quadTo、rQuadTo为主,三阶贝赛尔曲线cubicTo、rCubicTo用的使用方法与二阶贝赛尔曲线相似,用处也比较少,这篇就再也不细讲了。
这部分咱们先来看看quadTo函数的用法,其定义以下:
public void quadTo(float x1, float y1, float x2, float y2)参数中(x1,y1)是控制点坐标,(x2,y2)是终点坐标
最关键的是如何来肯定控制点的位置!前面讲过,PhotoShop中的钢笔工具是二阶贝赛尔曲线,因此咱们能够利用钢笔工具来模拟画出这条波浪线来辅助肯定控制点的位置
下面咱们来看看这个路径轨迹中,控制点分别在哪一个位置
咱们先看P0-P2这条轨迹,P0是起点,假设位置坐标是(100,300),P2是终点,假充位置坐标是(300,300);在以P0为起始点,P2为终点这条二阶贝赛尔曲线上,P1是控制点,很明显P1大概在P0,P2中间的位置,因此它的X坐标应该是200,关于Y坐标,咱们没法肯定,但很明显的是P1在P0,P2点的上方,也就是它的Y值比它们的小,因此根据钢笔工具上面的位置,咱们让P1的比P0,P2的小100;因此P1的坐标是(200,200)
同理,不难求出在P2,P4这条二阶贝赛尔曲线上,它们的控制点P3的坐标位置应该是(400,400);P3的X坐标是400是,由于P3点是P2,P4的中间点;与P3与P1距离P0-P2-P4这条直线的距离应该是相等的。P1距离P0-P2的值为100;P3距离P2-P4的距离也应该是100,这样不难算出P3的坐标应该是(400,400);
下面开始是代码部分了。
咱们知道在动画绘图时,会调用onDraw(Canvas canvas)函数,咱们若是重写了onDraw(Canvas canvas)函数,那么咱们利用canvas在上面画了什么,就会显示什么。因此咱们自定义一个View
public class MyView extends View { public MyView(Context context) { super(context); } public MyView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint paint = new Paint(); paint.setStyle(Paint.Style.STROKE); paint.setColor(Color.GREEN); Path path = new Path(); path.moveTo(100,300); path.quadTo(200,200,300,300); path.quadTo(400,400,500,300); canvas.drawPath(path,paint); } }这里最重要的就是在onDraw(Canvas canvas)中建立Path的过程,咱们在上面已经提到,第一个起始点是须要调用path.moveTo(100,300)来指定的,以后后一个path.quadTo的起始点是之前一个path.quadTo的终点为起始点的。有关控制点的位置如何查找,咱们上面已经利用钢笔工具给你们讲解了,这里就再也不细讲。
在自定义控件之后,而后直接把它引入到主布局文件中便可(main.xml)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <com.harvic.BlogBerzMovePath.MyView android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout>因为直接作为控件显示,因此MainActivity不须要额外的代码便可显示,MainActivity代码以下:
public class MyActivity extends Activity { /** * Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } }源码在文章底部给出
要实现手指轨迹实际上是很是简单的,咱们只须要在自定义中拦截OnTouchEvent,而后根据手指的移动轨迹来绘制Path便可。
要实现把手指的移动轨迹链接起来,最简单的方法就是直接使用Path.lineTo()就能实现把各个点链接起来。
咱们先来看看效果图:
首先,咱们自定义一个View,完整代码以下:
public class MyView extends View { private Path mPath = new Path(); public MyView(Context context) { super(context); } public MyView(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: { mPath.moveTo(event.getX(), event.getY()); return true; } case MotionEvent.ACTION_MOVE: mPath.lineTo(event.getX(), event.getY()); postInvalidate(); break; default: break; } return super.onTouchEvent(event); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint paint = new Paint(); paint.setColor(Color.GREEN); paint.setStyle(Paint.Style.STROKE); canvas.drawPath(mPath,paint); } public void reset(){ mPath.reset(); invalidate(); } }最重要的位置就是在重写onTouchEvent的位置:
public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: { mPath.moveTo(event.getX(), event.getY()); return true; } case MotionEvent.ACTION_MOVE: mPath.lineTo(event.getX(), event.getY()); postInvalidate(); break; default: break; } return super.onTouchEvent(event); }当用户点击屏幕的时候,咱们调用mPath.moveTo(event.getX(), event.getY());而后在用户移动手指时使用mPath.lineTo(event.getX(), event.getY());将各个点串起来。而后调用postInvalidate()重绘;
protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint paint = new Paint(); paint.setColor(Color.GREEN); paint.setStyle(Paint.Style.STROKE); canvas.drawPath(mPath,paint); }最后,我还额外写了一个重置函数:
public void reset(){ mPath.reset(); invalidate(); }
而后看看布局文件(mian.xml)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <Button android:id="@+id/reset" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="reset"/> <com.harvic.BlogMovePath.MyView android:id="@+id/myview" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout>没什么难度,就是把自定义控件添加到布局中
而后看MyActivity的操做:
public class MyActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); final MyView myView = (MyView)findViewById(R.id.myview); findViewById(R.id.reset).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { myView.reset(); } }); } }这里实现的就是当点击按钮时,调用 myView.reset()来重置画布;
上面咱们虽然实现了,画出手指的移动轨迹,但咱们仔细来看看画出来的图:
咱们把S放大,明显看出,在两个点链接处有明显的转折,并且在S顶部位置横纵坐标变化比较快的位置,看起来跟图片这大后的马赛克同样;利用Path绘图,是不可能出现马赛克的,由于除了Bitmap之外的任何canvas绘图所有都是矢量图,也就是利用数学公式来做出来的图,不管放在多大屏幕上,都不可能会出现马赛克!这里利用Path绘图,在S顶部之因此看起来像是马赛克是由于这个S是由各个不一样点之间连线写出来的,而之间并无平滑过渡,因此当坐标变化比较剧烈时,线与线之间的转折就显得特别明显了。
因此要想优化这种效果,就得实现线与线之间的平滑过渡,很显然,二阶贝赛尔曲线就是干这个事的。下面咱们就利用咱们新学的Path.quadTo函数来从新实现下移动轨迹效果。
咱们上面讲了,使用Path.lineTo()的最大问题就是线段转折处不够平滑。Path.quadTo()能够实现平滑过渡,但使用Path.quadTo()的最大问题是,如何找到起始点和结束点。
下图中,有用绿点表示的三个点,连成的两条直线,很明显他们转折处是有明显折痕的
下面咱们在PhotoShop中利用钢笔工具,看如何才能实现这两条线之间的转折
从这两个线段中能够看出,咱们使用Path.lineTo()的时候,是直接把手指触点A,B,C给连起来。
而钢笔工具要实现这三个点间的流畅过渡,就只能将这两个线段的中间点作为起始点和结束点,而将手指的倒数第二个触点B作为控制点。
你们可能会以为,那这样,在结束的时候,A到P0和P1到C1的这段距离岂不是没画进去?是的,若是Path最终没有close的话,这两段距离是被抛弃掉的。由于手指间滑动时,每两个点间的距离很小,因此P1到C之间的距离能够忽略不计。
下面咱们就利用这种方法在photoshop中求证,在链接多个线段时,是否能行?
在这个图形中,有不少点连成了弯弯曲曲的线段,咱们利用上面咱们讲的,将两个线段的中间作为二阶贝尔赛曲线的起始点和终点,把上一个手指的位置作为控制点,来看看是否真的能组成平滑的连线
整个链接过程如动画所示:
在最终的路径中看来,各个点间的连线是很是平滑的。从这里也能够看出,在为了实现平滑效果,咱们只能把开头的线段一半和结束的线段的一半抛弃掉。
在讲了原理以后,下面就来看看在代码中如何来实现吧。
先贴出完整代码而后再细讲:
public class MyView extends View { private Path mPath = new Path(); private float mPreX,mPreY; public MyView(Context context) { super(context); } public MyView(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN:{ mPath.moveTo(event.getX(),event.getY()); mPreX = event.getX(); mPreY = event.getY(); return true; } case MotionEvent.ACTION_MOVE:{ float endX = (mPreX+event.getX())/2; float endY = (mPreY+event.getY())/2; mPath.quadTo(mPreX,mPreY,endX,endY); mPreX = event.getX(); mPreY =event.getY(); invalidate(); } break; default: break; } return super.onTouchEvent(event); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint paint = new Paint(); paint.setStyle(Paint.Style.STROKE); paint.setColor(Color.GREEN); paint.setStrokeWidth(2); canvas.drawPath(mPath,paint); } public void reset(){ mPath.reset(); postInvalidate(); } }最难的部分依然是onTouchEvent函数这里:
public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN:{ mPath.moveTo(event.getX(),event.getY()); mPreX = event.getX(); mPreY = event.getY(); return true; } ………… } return super.onTouchEvent(event); }在ACTION_DOWN的时候,利用 mPath.moveTo(event.getX(),event.getY())将Path的初始位置设置到手指的触点处,若是不调用mPath.moveTo的话,会默认是从(0,0)开始的。而后咱们定义两个变量mPreX,mPreY来表示手指的前一个点。咱们经过上面的分析知道,这个点是用来作控制点的。最后return true让ACTION_MOVE,ACTION_UP事件继续向这个控件传递。
case MotionEvent.ACTION_MOVE:{ float endX = (mPreX+event.getX())/2; float endY = (mPreY+event.getY())/2; mPath.quadTo(mPreX,mPreY,endX,endY); mPreX = event.getX(); mPreY =event.getY(); invalidate(); }咱们先找到结束点,咱们说告终束点是这个线段的中间位置,因此很容易求出它的坐标endX,endY;控制点是上一个手指位置即mPreX,mPreY;那有些同窗可能会问了,那起始点是哪啊。在开篇讲quadTo()函数时,就已经说过,第一个起始点是Path.moveTo(x,y)定义的,其它部分,一个quadTo的终点,是下一个quadTo的起始点。
从效果图中能够明显能够看出,经过quadTo实现的曲线更顺滑
源码在文章底部给出
Ok啦,quadeTo的用法,到这里就结束了,下部分再来说讲rQuadTo的用法及波浪动画效果
该函数声明以下
public void rQuadTo(float dx1, float dy1, float dx2, float dy2)其中:
这四个参数都是传递的都是相对值,相对上一个终点的位移值。
好比,咱们上一个终点坐标是(300,400)那么利用rQuadTo(100,-100,200,100);
获得的控制点坐标是(300+100,400-100)即(500,300)
一样,获得的终点坐标是(300+200,400+100)即(500,500)
因此下面这两段代码是等价的:
利用quadTo定义绝对坐标
path.moveTo(300,400); path.quadTo(500,300,500,500);与利用rQuadTo定义相对坐标
path.moveTo(300,400); path.rQuadTo(100,-100,200,100)
在上篇中,咱们使用quadTo实现了一个简单的波浪线:
各个点具体计算过程,在上篇已经计算过了,下面是上篇中onDraw的代码:
protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint paint = new Paint(); paint.setStyle(Paint.Style.STROKE); paint.setColor(Color.GREEN); Path path = new Path(); path.moveTo(100,300); path.quadTo(200,200,300,300); path.quadTo(400,400,500,300); canvas.drawPath(path,paint); }下面咱们将它转化为rQuadTo来从新实现下:
protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint paint = new Paint(); paint.setStyle(Paint.Style.STROKE); paint.setColor(Color.GREEN); Path path = new Path(); path.moveTo(100,300); path.rQuadTo(100,-100,200,0); path.rQuadTo(100,100,200,0); canvas.drawPath(path,paint); }简单来说,就是将原来的:
path.moveTo(100,300); path.quadTo(200,200,300,300); path.quadTo(400,400,500,300);转化为:
path.moveTo(100,300); path.rQuadTo(100,-100,200,0); path.rQuadTo(100,100,200,0);第一句:path.rQuadTo(100,-100,200,0);是创建在(100,300)这个点基础上来计算相对坐标的。
本节完成以后,将实现文章开头的波浪效果,以下。
上面咱们已经可以实现一个波形,只要咱们再多实现几个波形,就能够覆盖整个屏幕了。
对应代码以下:
public class MyView extends View { private Paint mPaint; private Path mPath; private int mItemWaveLength = 400; public MyView(Context context, AttributeSet attrs) { super(context, attrs); mPath = new Path(); mPaint = new Paint(); mPaint.setColor(Color.GREEN); mPaint.setStyle(Paint.Style.STROKE); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPath.reset(); int originY = 300; int halfWaveLen = mItemWaveLength/2; mPath.moveTo(-mItemWaveLength,originY); for (int i = -mItemWaveLength;i<=getWidth()+mItemWaveLength;i+=mItemWaveLength){ mPath.rQuadTo(halfWaveLen/2,-50,halfWaveLen,0); mPath.rQuadTo(halfWaveLen/2,50,halfWaveLen,0); } canvas.drawPath(mPath,mPaint); } }最难的部分依然是在onDraw函数中:
protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPath.reset(); int originY = 300; int halfWaveLen = mItemWaveLength/2; mPath.moveTo(-mItemWaveLength,originY); for (int i = -mItemWaveLength;i<=getWidth()+mItemWaveLength;i+=mItemWaveLength){ mPath.rQuadTo(halfWaveLen/2,-100,halfWaveLen,0); mPath.rQuadTo(halfWaveLen/2,100,halfWaveLen,0); } canvas.drawPath(mPath,mPaint); }咱们将mPath的起始位置向左移一个波长:
mPath.moveTo(-mItemWaveLength,originY);而后利用for循环画出当前屏幕中可能容得下的全部波:
for (int i = -mItemWaveLength;i<=getWidth()+mItemWaveLength;i+=mItemWaveLength){ mPath.rQuadTo(halfWaveLen/2,-100,halfWaveLen,0); mPath.rQuadTo(halfWaveLen/2,100,halfWaveLen,0); }mPath.rQuadTo(halfWaveLen/2,-100,halfWaveLen,0);画的是一个波长中的前半个波,mPath.rQuadTo(halfWaveLen/2,100,halfWaveLen,0);画的是一个波长中的后半个波。你们在这里能够看到,屏幕左右都多画了一个波长的图形。这是为了波形移动作准备的。
其中,图中红色区域是我标出来利用lineTo闭合的区域
public class MyView extends View { private Paint mPaint; private Path mPath; private int mItemWaveLength = 400; public MyView(Context context, AttributeSet attrs) { super(context, attrs); mPath = new Path(); mPaint = new Paint(); mPaint.setColor(Color.GREEN); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPath.reset(); int originY = 300; int halfWaveLen = mItemWaveLength/2; mPath.moveTo(-mItemWaveLength+dx,originY); for (int i = -mItemWaveLength;i<=getWidth()+mItemWaveLength;i+=mItemWaveLength){ mPath.rQuadTo(halfWaveLen/2,-100,halfWaveLen,0); mPath.rQuadTo(halfWaveLen/2,100,halfWaveLen,0); } mPath.lineTo(getWidth(),getHeight()); mPath.lineTo(0,getHeight()); mPath.close(); canvas.drawPath(mPath,mPaint); } }这段代码相比上面的代码,增长了两部份内容:
mPath.moveTo(-mItemWaveLength+dx,originY); for (int i = -mItemWaveLength;i<=getWidth()+mItemWaveLength;i+=mItemWaveLength){ mPath.rQuadTo(halfWaveLen/2,-100,halfWaveLen,0); mPath.rQuadTo(halfWaveLen/2,100,halfWaveLen,0); } mPath.lineTo(getWidth(),getHeight()); mPath.lineTo(0,getHeight()); mPath.close();
让波纹动起来其实挺简单,利用调用在path.moveTo的时候,将起始点向右移动便可实现移动,并且只要咱们移动一个波长的长度,波纹就会重合,就能够实现无限循环了。
为此咱们定义一个动画:
public void startAnim(){ ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength); animator.setDuration(2000); animator.setRepeatCount(ValueAnimator.INFINITE); animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { dx = (int)animation.getAnimatedValue(); postInvalidate(); } }); animator.start(); }动画的长度为一个波长,将当前值保存在类的成员变量dx中;
protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPath.reset(); int originY = 300; int halfWaveLen = mItemWaveLength/2; mPath.moveTo(-mItemWaveLength+dx,originY); for (int i = -mItemWaveLength;i<=getWidth()+mItemWaveLength;i+=mItemWaveLength){ mPath.rQuadTo(halfWaveLen/2,-100,halfWaveLen,0); mPath.rQuadTo(halfWaveLen/2,100,halfWaveLen,0); } mPath.lineTo(getWidth(),getHeight()); mPath.lineTo(0,getHeight()); mPath.close(); canvas.drawPath(mPath,mPaint); }完整的MyView代码以下:
public class MyView extends View { private Paint mPaint; private Path mPath; private int mItemWaveLength = 400; private int dx; public MyView(Context context, AttributeSet attrs) { super(context, attrs); mPath = new Path(); mPaint = new Paint(); mPaint.setColor(Color.GREEN); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPath.reset(); int originY = 300; int halfWaveLen = mItemWaveLength/2; mPath.moveTo(-mItemWaveLength+dx,originY); for (int i = -mItemWaveLength;i<=getWidth()+mItemWaveLength;i+=mItemWaveLength){ mPath.rQuadTo(halfWaveLen/2,-100,halfWaveLen,0); mPath.rQuadTo(halfWaveLen/2,100,halfWaveLen,0); } mPath.lineTo(getWidth(),getHeight()); mPath.lineTo(0,getHeight()); mPath.close(); canvas.drawPath(mPath,mPaint); } public void startAnim(){ ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength); animator.setDuration(2000); animator.setRepeatCount(ValueAnimator.INFINITE); animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { dx = (int)animation.getAnimatedValue(); postInvalidate(); } }); animator.start(); } }而后在MyActivity中开始动画:
public class MyActivity extends Activity { /** * Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); final MyView myView = (MyView)findViewById(R.id.myview); myView.startAnim(); } }这样就实现了动画:
若是把波长设置为1000,就能够实现本段开篇的动画了。
若是想让波纹像开篇时那要同时向下移动,你们只须要在path.moveTo(x,y)的时候,经过动画同时移动y坐标就能够了,代码比较简单,并且本文实在是太长了,具体实现就再也不讲了,你们能够在源码中加以尝试。
源码在文章底部给出
好了,本篇文章到这里就结束了
若是本文有帮到你,记得加关注哦
源码下载地址:http://download.csdn.net/detail/harvic880925/9476153
请你们尊重原创者版权,转载请标明出处:http://blog.csdn.net/harvic880925/article/details/50995587 谢谢