android高级UI之PathMeasure<三>--Path测量实战(笑脸loading效果实现、划船效果实现)

本文主要是介绍android高级UI之PathMeasure<三>--Path测量实战(笑脸loading效果实现、划船效果实现),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

接着上一次android高级UI之PathMeasure<二>--Path测量实战(各种Loading效果)的PathMeasure学习继续,这里将对PathMeasure的学习进行收尾。

笑脸loading效果实现:

效果:

具体实现: 

1、新建View:

2、画左、右边眼睛:

由于左右眼睛就是两个实心圆,绘制比较简单:

package com.cexo.pathmeasurestudy;import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;/*** Loading效果四:笑脸*/
public class LoadingView4 extends View {//constants/*** 左眼距离左边的距离(控件宽度*EYE_PERCENT_W),* 右眼距离右边的距离(控件宽度*EYE_PERCENT_W)*/private static final float EYE_PERCENT_W = 0.35F;/*** 眼睛距离top的距离(控件的高度*EYE_PERCENT_H)*/private static final float EYE_PERCENT_H = 0.38F;//variablesprivate Paint paint;private float eyesH = EYE_PERCENT_H;private float radius;public LoadingView4(Context context) {this(context, null);}public LoadingView4(Context context, AttributeSet attrs) {this(context, attrs, -1);}public LoadingView4(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}private void init() {paint = new Paint();paint.setColor(Color.GRAY);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);drawFace(canvas);}private void drawFace(Canvas canvas) {paint.setStyle(Paint.Style.FILL);//画左边的眼睛canvas.drawCircle(getWidth() * EYE_PERCENT_W, getHeight() * eyesH - radius, radius, paint);//画右边的眼睛canvas.drawCircle(getWidth() * (1 - EYE_PERCENT_W), getHeight() * eyesH - radius, radius, paint);}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);radius = getWidth() / 7F / 2;}
}

运行:

3、画嘴巴:

对于嘴巴是一条曲线,很明显需要使用到贝塞尔线来进行绘制,首先将path需要移动到眼睛的下面:

其中涉及到几个变量:

然后利用贝塞尔曲线来绘制一条曲线【关于这块如不熟,可以参考android高级UI之贝塞尔曲线<上>---基本概念、德卡斯特里奥算法】:

其中又涉及到变量:

此时运行看一下效果:

嗯,嘴巴有了,不过感觉线条太细了,加粗一点:

再运行:

4、画大脑的轮廓:

接下来则来画大脑的轮廓了,这块就是绘制一个椭圆的路径,所以先来定义path:

其中构建椭圆的路径用的是addRoundRect api,对于第一个参数比较好理解,是一个path的左上右下的位置,而第二个和第三个参数:

也就是用这俩参数来控制圆角的大小的,下面运行看一下:

5、眼睛跟嘴巴动效实现:

要实现让嘴和眼睛来进行上下动,其实就是需要控制这三个值:

而如何来控制呢?由于是无限循环进行变动,所以这里用一个动画进行控制是最合适的,如下:

这种动画的用法有啥用呢?下面看一下日志打印就知道了:

等于是从0~1之间进行数值的变化的,那么,就可以用这个百分比来控制上下滚动的幅度啦,如下:

此时再运行你会发现有个bug:

原因是需要加这么一句话:

再运行就如最初看到的效果一样啦,但是你会发现貌似木有用到PathMeasure这个东东对吧,目前这效果确实没用上,下面划船的就用到啦。 

划船效果实现:

效果:

其实它已经在github上进行开源了,GitHub - webor2006/UI2018: 安卓高级UI代码,配合博客详细讲解知识点,

这个开源项目里面有挺多UI效果的,想学学UI相关的可以瞅瞅它,上面则跟着源码走一遍流程,既使是抄一遍其收获也是有的~~

具体实现: 

1、新建View:

然后搭建主框架:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><com.cexo.pathmeasurestudy.BoatWaveViewandroid:id="@+id/boat_wave_view"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1" /><TextViewstyle="@style/textview_button"android:onClick="start"android:text="开始" /><TextViewstyle="@style/textview_button"android:onClick="stop"android:text="停止" /></LinearLayout>

其中按钮的样式:

  <style name="textview_button"><item name="android:layout_width">match_parent</item><item name="android:layout_height">45dp</item><item name="android:background">@drawable/selector_blue_round_5dp</item><item name="android:gravity">center</item><item name="android:textColor">@android:color/white</item><item name="android:textSize">14sp</item><item name="android:layout_margin">5dp</item></style><style name="textview_title"><item name="android:layout_width">match_parent</item><item name="android:layout_height">30dp</item><item name="android:textColor">#35a8ee</item><item name="android:textSize">14sp</item><item name="android:gravity">center</item></style>

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:state_pressed="true"><shape><corners android:radius="5dp" /><solid android:color="#35a8ee" /></shape></item><item android:state_focused="true"><shape><corners android:radius="5dp" /><solid android:color="#35a8ee" /></shape></item><item android:state_selected="true"><shape><corners android:radius="5dp" /><solid android:color="#35a8ee" /></shape></item><item><shape><corners android:radius="5dp" /><solid android:color="#1296db" /></shape></item></selector>

2、绘制坐标辅助线:

为了能看到绘制的坐标位置,首先来绘制一下背景的辅助线,也就是效果如下:

1、封装base:

对于坐标辅助线,可能其它View也可以使用,所以将其抽到Base当中来:

2、初始化paint:

很明显坐标网络是由三个样式组成:

所以先来初始化这三个paint:

package com.cexo.pathmeasurestudy;import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;import androidx.annotation.Nullable;public abstract class BaseView extends View {// 坐标画笔private Paint coordinatePaint;// 网格画笔private Paint gridPaint;// 写字画笔private Paint textPaint;// 坐标颜色private int coordinateColor;private int gridColor;// 坐标线宽度private final float coordinateLineWidth = 2.5f;// 网格宽度private final float gridLineWidth = 1f;// 字体大小private float textSize;public BaseView(Context context) {super(context, null);}public BaseView(Context context, @Nullable AttributeSet attrs) {super(context, attrs, -1);}public BaseView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initCoordinate(context);init(context);}private void initCoordinate(Context context) {coordinateColor = Color.BLACK;gridColor = Color.LTGRAY;textSize = spToPx(10);coordinatePaint = new Paint();coordinatePaint.setAntiAlias(true);coordinatePaint.setColor(coordinateColor);coordinatePaint.setStrokeWidth(coordinateLineWidth);gridPaint = new Paint();gridPaint.setAntiAlias(true);gridPaint.setColor(gridColor);gridPaint.setStrokeWidth(gridLineWidth);textPaint = new Paint();textPaint.setAntiAlias(true);textPaint.setColor(coordinateColor);textPaint.setTextAlign(Paint.Align.CENTER);textPaint.setTextSize(textSize);}/*** 转换 sp 至 px*/protected int spToPx(float spValue) {final float fontScale = Resources.getSystem().getDisplayMetrics().scaledDensity;return (int) (spValue * fontScale + 0.5f);}protected abstract void init(Context context);
}

3、画坐标和网格

由于它是一个通用的行为,很明显这个绘制应该是放在base当中,但是又并不是每个View都需要它,所以这里将绘制的逻辑只提取到base当中,而具体要不要用由子类来决定,如下:

那如何绘制呢?

1、画网格:

其中网络就是由横竖线组成的,具体绘制也不难,先将画布移到屏幕中心:

先来横着画竖线:

运行发现报错了:

空指针的原因是:

此时的效果是:

看着像竖着画对吧,其实是横着画指定高度的竖线,这里为了明白这点,可以只画一次循环,你会看到如下:

明白了吧,把代码还原,接下来再来竖着画横线:

此时的效果为:

4、画 x,y 轴:

此时运行,你会发现有问题:

这个其实原因也很简单,因为我们在绘网络时已经将画布的坐标点移到屏幕中心了:

此时再绘制坐标线时,应该将画布的中心坐标给还原,所以处理如下:

5、画刻度:

接下来则需要在x,y轴上进行刻度的绘制,跟绘制网络思路差不多,如下:

比较简单,直接看效果:

接下来则需要上面进行文字标注:

至此,坐标系效果就已经绘制完了。

3、绘制划船效果:

1、实现小船图片滑动效果:

接下来实现最核心的划船效果了,这里进行一个拆解,先来将小船图片绘制出来,然后再让它可以开始荡漾,如下:

1、首先将小船给绘制到屏幕上:

2、绘制小船行走的路径:

接下来则需要让小船进行水波荡漾的效果,此时是不是就需要改用这个api来进行绘制了?

这块如还不太清楚的可以参考android高级UI之PathMeasure<二>--Path测量实战(各种Loading效果) - cexo - 博客园,接下来则需要定义小船行走的轨迹,先来定义path:

接下来则就来构建一条浪的path,此时肯定得用到二阶贝塞尔曲线了,而二阶贝塞尔曲线在Android中已经有专门的API可供调用了,回忆一下:

下面先来用死的值构建一条曲线:

运行效果:

其中看出有辅助坐标系的作用了么?

有了坐标系,直接通过肉眼就可以知道你想要实现的效果,而关于贝塞尔曲线还有另一个API:

那它跟quadTo()有啥区别呢?先来看一下官网的解释:

关于它,我一直木有能理解透,好在搜到这么一篇大佬的文章自定义控件三部曲之绘图篇(六)——Path之贝赛尔曲线和手势轨迹、水波纹效果_启舰-CSDN博客才搞明白:

先来用它实现上面quadTo同样的效果:

其中可以算出:

控制点x坐标=上一个终点x坐标+控制点x位移=getWidth()/2-100+50 =getWidth()/2-50;

控制点y坐标=上一个终点y坐标+控制点y位移=getHeight()/2-50;

是不是就是图中的这个位置了?

而了解rQuadTo()这个API的原因是接下来绘制小船轨迹时就会用它来进行曲线的构建了,当一个扩展巩固,先来横向绘制满几个波浪:

运行效果:

对于上面这段代码是不是有点晕,这里就不详细说明了,说一下其绘制的思路,先将整个波浪的长度定为屏幕的1/3,也就是:

然后每次循环绘制一个浪,这里加一些日志你就明白其绘制的思路了:

运行日志输出:

2022-02-21 06:33:17.425 21064-21064/com.cexo.pathmeasurestudy E/cexo: x:-360;y:901;width:1080;height:1802
2022-02-21 06:33:17.425 21064-21064/com.cexo.pathmeasurestudy E/cexo: i----:-360;x:-360;y:901
2022-02-21 06:33:17.425 21064-21064/com.cexo.pathmeasurestudy E/cexo: control1:(-270,881)
2022-02-21 06:33:17.425 21064-21064/com.cexo.pathmeasurestudy E/cexo: end1:(-180,901)
2022-02-21 06:33:17.425 21064-21064/com.cexo.pathmeasurestudy E/cexo: control2:(-90,921)
2022-02-21 06:33:17.425 21064-21064/com.cexo.pathmeasurestudy E/cexo: end1:(0,901)
2022-02-21 06:33:17.425 21064-21064/com.cexo.pathmeasurestudy E/cexo: i----:0;x:0;y:901
2022-02-21 06:33:17.425 21064-21064/com.cexo.pathmeasurestudy E/cexo: control1:(90,881)
2022-02-21 06:33:17.425 21064-21064/com.cexo.pathmeasurestudy E/cexo: end1:(180,901)
2022-02-21 06:33:17.425 21064-21064/com.cexo.pathmeasurestudy E/cexo: control2:(270,921)
2022-02-21 06:33:17.425 21064-21064/com.cexo.pathmeasurestudy E/cexo: end1:(360,901)
2022-02-21 06:33:17.425 21064-21064/com.cexo.pathmeasurestudy E/cexo: i----:360;x:360;y:901
2022-02-21 06:33:17.425 21064-21064/com.cexo.pathmeasurestudy E/cexo: control1:(450,881)
2022-02-21 06:33:17.425 21064-21064/com.cexo.pathmeasurestudy E/cexo: end1:(540,901)
2022-02-21 06:33:17.425 21064-21064/com.cexo.pathmeasurestudy E/cexo: control2:(630,921)
2022-02-21 06:33:17.425 21064-21064/com.cexo.pathmeasurestudy E/cexo: end1:(720,901)
2022-02-21 06:33:17.426 21064-21064/com.cexo.pathmeasurestudy E/cexo: i----:720;x:720;y:901
2022-02-21 06:33:17.426 21064-21064/com.cexo.pathmeasurestudy E/cexo: control1:(810,881)
2022-02-21 06:33:17.426 21064-21064/com.cexo.pathmeasurestudy E/cexo: end1:(900,901)
2022-02-21 06:33:17.426 21064-21064/com.cexo.pathmeasurestudy E/cexo: control2:(990,921)
2022-02-21 06:33:17.426 21064-21064/com.cexo.pathmeasurestudy E/cexo: end1:(1080,901)
2022-02-21 06:33:17.426 21064-21064/com.cexo.pathmeasurestudy E/cexo: i----:1080;x:1080;y:901
2022-02-21 06:33:17.426 21064-21064/com.cexo.pathmeasurestudy E/cexo: control1:(1170,881)
2022-02-21 06:33:17.426 21064-21064/com.cexo.pathmeasurestudy E/cexo: end1:(1260,901)
2022-02-21 06:33:17.426 21064-21064/com.cexo.pathmeasurestudy E/cexo: control2:(1350,921)
2022-02-21 06:33:17.426 21064-21064/com.cexo.pathmeasurestudy E/cexo: end1:(1440,901)

可以看到,起始的坐标点都是已经超出屏幕了:

理解这个程序的核心一定是要知道此时画布的坐标点是在(0,0)这个位置,而非屏幕的中心哦,因为咱们在绘制完坐标系时已经将画布的平移给还原了:

也就是此时的绘制过程是从左到右进行的:

不过目前咱们这代码性能不太好,因为在onDraw()中会频繁的创建path:

所以这里将其放到onMeasure中只初始化一次:

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);if (!isInit) {isInit = true;width = getMeasuredWidth();height = getMeasuredHeight();waveLength = width / 3;//构建小船的路径boatPath = new Path();int x = -waveLength;int y = height / 2;Log.e("cexo", "x:" + x + ";y:" + y + ";width:" + width + ";height:" + height);boatPath.moveTo(x, y);int count = 0;for (int i = -waveLength; i < width * 1 + waveLength; i += waveLength) {Log.e("cexo", "i----:" + i + ";x:" + x + ";y:" + y);// rQuadTo 和 quadTo 区别在于// rQuadTo 是相对上一个点 而 quadTo是相对于画布int dx1 = waveLength / 4;int dy1 = -BOAT_WAVE_HEIGHT;int dx2 = waveLength / 2;int dy2 = 0;Log.e("cexo", "control1:(" + (x + dx1) + "," + (y + dy1) + ")");Log.e("cexo", "end1:(" + (x + dx2) + "," + (y + dy2) + ")");boatPath.rQuadTo(dx1, dy1, dx2, dy2);x = x + dx2;y = y + dy2;int dx11 = waveLength / 4;int dy11 = BOAT_WAVE_HEIGHT;int dx21 = waveLength / 2;int dy21 = 0;Log.e("cexo", "control2:(" + (x + dx11) + "," + (y + dy11) + ")");Log.e("cexo", "end1:(" + (x + dx21) + "," + (y + dy21) + ")");boatPath.rQuadTo(dx11, dy11, dx21, dy21);x = x + dx21;y = y + dy21;}}}

此时onDraw()中就只绘制path了:

好,有了path之后,接下来要想让小船图片随着这个path进行运行,此时PathMeasure就派上用场啦,需要对path进行测量如下:

接下来则就是绘制了,如下:

其中matrix.preTranslate()有啥作用呢? 可以参考对Matrix中preTranslate()和postTranslate()的理解_ProgramChangesWorld的专栏-CSDN博客_posttranslate,接下来运行看一下效果:

其中你会发现小船跟波浪方向是一致的,只是船体并不是完全沿着波浪线来的,这也符合物理视觉。

3、让小船动起来:

接下来让小船进行动起来就比较简单了,我们只需要来控制这个值既可:

这里还是利用ValueAnimator来实现,如下:

运行:

2、绘制小船的浪:

现在船已经动起来了,不过貌似这波浪是空心的,没有大海的感觉,应该像这样才行:

下面则来实现这样的效果:

1、 将其变成实心的浪:

这里就需要先来构建一个新的path了,目前咱们绘制的path是小船的路径:

而这个path的构建其实跟小船的构建逻辑是一样的,也就是:

然后path的构建几乎一模一样:

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);if (!isInit) {isInit = true;width = getMeasuredWidth();height = getMeasuredHeight();waveLength = width / 3;//构建小船的路径boatPath = new Path();int x = -waveLength;int y = height / 2;Log.e("cexo", "x:" + x + ";y:" + y + ";width:" + width + ";height:" + height);boatPath.moveTo(x, y);for (int i = -waveLength; i < width * 1 + waveLength; i += waveLength) {Log.e("cexo", "i----:" + i + ";x:" + x + ";y:" + y);// rQuadTo 和 quadTo 区别在于// rQuadTo 是相对上一个点 而 quadTo是相对于画布int dx1 = waveLength / 4;int dy1 = -BOAT_WAVE_HEIGHT;int dx2 = waveLength / 2;int dy2 = 0;Log.e("cexo", "control1:(" + (x + dx1) + "," + (y + dy1) + ")");Log.e("cexo", "end1:(" + (x + dx2) + "," + (y + dy2) + ")");boatPath.rQuadTo(dx1, dy1, dx2, dy2);x = x + dx2;y = y + dy2;int dx11 = waveLength / 4;int dy11 = BOAT_WAVE_HEIGHT;int dx21 = waveLength / 2;int dy21 = 0;Log.e("cexo", "control2:(" + (x + dx11) + "," + (y + dy11) + ")");Log.e("cexo", "end1:(" + (x + dx21) + "," + (y + dy21) + ")");boatPath.rQuadTo(dx11, dy11, dx21, dy21);x = x + dx21;y = y + dy21;}//构建小船底下的浪的路径boatWavePath = new Path();x = -waveLength;y = height / 2;boatWavePath.moveTo(x, y);for (int i = -waveLength; i < width * 1 + waveLength; i += waveLength) {Log.e("cexo", "i----:" + i + ";x:" + x + ";y:" + y);// rQuadTo 和 quadTo 区别在于// rQuadTo 是相对上一个点 而 quadTo是相对于画布int dx1 = waveLength / 4;int dy1 = -BOAT_WAVE_HEIGHT;int dx2 = waveLength / 2;int dy2 = 0;Log.e("cexo", "control1:(" + (x + dx1) + "," + (y + dy1) + ")");Log.e("cexo", "end1:(" + (x + dx2) + "," + (y + dy2) + ")");boatWavePath.rQuadTo(dx1, dy1, dx2, dy2);x = x + dx2;y = y + dy2;int dx11 = waveLength / 4;int dy11 = BOAT_WAVE_HEIGHT;int dx21 = waveLength / 2;int dy21 = 0;Log.e("cexo", "control2:(" + (x + dx11) + "," + (y + dy11) + ")");Log.e("cexo", "end1:(" + (x + dx21) + "," + (y + dy21) + ")");boatWavePath.rQuadTo(dx11, dy11, dx21, dy21);x = x + dx21;y = y + dy21;}// 让 PathMeasure 与 Path 关联boatPathMeasure.setPath(boatPath, false);}}

然后绘制改一下path:

此时,运行,你会发现还是跟之前一样,不是实心的,其实是咱们的paint设置没改:

此时再运行看一下,还是不如预期:

而原因就得对path.close()有一定的了解了,这块的基础知识可以参考android高级UI之PathMeasure<一>--Path测量基础(nextContour、getPosTan、getMatrix、getSegment),这里直接给出代码了:

而对于path它有lineTo和rLineTo两个类似的api,此时就需要对这俩进行一个区别的了解,可以参考lineTo和rLineTo的区别_wzping435的博客-CSDN博客,也就是构建这么一个区域可以达到闭环:

如果看不太懂,可以debug把值给打印,然后把坐标点算出来,就容易明白了,这里稍加过一下:

其实是定位在左下角的位置:

3、让浪动起来:

好,接下来浪扭动起来,咋扭动呢?这里需要用到画布平移的api了:

用一个具体代码例子来理解:

            canvas.save();//锁画布(为了保存之前的画布状态)canvas.translate(10, 10);//把当前画布的原点移到(10,10),后面的操作都以(10,10)作为参照点,默认原点为(0,0)drawScene(canvas);canvas.restore();//把当前画布返回(调整)到上一个save()状态之前 

所以,要想让波浪动起来,咱们只需要来让画布进行移动既可,具体如下:

然后开始移动:

此时运行你就会看到运动的效果了:

呃,新问题来了,露底了。。问题在原因在于对于小船这个浪不够“长”,所以解决起来也比较简单,将长度加大就成了,如下:

此时再运行,就木有这种露底的现象了,这里就不演示了。

4、代码抽取:

好,现在的问题就暴露了,小船的轨迹和小船的浪轨迹这俩的生成规则几乎一模一样,那。。是不是有必要封装一下?是的,所以在继续往下实现之前先来干下这事,这里细节就不过多解释了,比较简单:

3、绘制海浪:

最后,再加一个海浪,目前只有一个显得有点单调,有了上面的抽取之后,再加浪就非常简单了,如下:

其中浪的颜色值为:

<color name="color_wave_blue">#503bff</color>

运行,你会发现有bug:

海浪断层了,其实这块也很容易解决,需要这样处理:

到此,整个效果完成,完整代码如下:

package com.cexo.pathmeasurestudy;import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;/*** 划船效果*/
public class BoatWaveView extends BaseView {// 小船浪花的高度private static final int BOAT_WAVE_HEIGHT = 20;// 浪花每次的偏移量private final static int WAVE_OFFSET = 5;// 波浪高度private static final int WAVE_HEIGHT = 35;// 小船的图片private Bitmap boatBitmap;// 用于变换小船的private Matrix matrix;// 小船的路径public Path boatPath;// 小船的浪路径public Path boatWavePath;// 海浪的路径public Path wavePath;public Paint wavePaint;// 小船的浪色值private int boatBlue;// 浪花的色值private int waveBlue;private int width;private int height;// 浪花的宽度private int waveLength;private boolean isInit = false;private PathMeasure boatPathMeasure;private ValueAnimator animator;// 当前小船在path路径上的百分比位置float currentPosition;// 小船的浪花偏移量private int boatWaveOffset = 0;// 浪花当前的偏移量private int curWaveOffset = 0;public BoatWaveView(Context context) {super(context);}public BoatWaveView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);}public BoatWaveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}public void startAnim() {if (animator != null)animator.start();}public void stopAnim() {if (animator != null)animator.cancel();}@Overrideprotected void init(Context context) {BitmapFactory.Options options = new BitmapFactory.Options();options.inSampleSize = 1;boatBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.boat, options);matrix = new Matrix();boatBlue = ContextCompat.getColor(context, R.color.color_boat_blue);waveBlue = ContextCompat.getColor(context, R.color.color_wave_blue);wavePaint = new Paint();wavePaint.setAntiAlias(true);wavePaint.setColor(boatBlue);
//        wavePaint.setStrokeWidth(4);
//        wavePaint.setStyle(Paint.Style.STROKE);boatPath = new Path();boatWavePath = new Path();wavePath = new Path();boatPathMeasure = new PathMeasure();animator = ValueAnimator.ofFloat(0, 1f);animator.setDuration(4000);animator.setRepeatCount(ValueAnimator.INFINITE);animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {currentPosition = (float) animation.getAnimatedValue();boatWaveOffset = (boatWaveOffset + WAVE_OFFSET / 2) % width;//小船的浪走得慢一点curWaveOffset = (curWaveOffset + WAVE_OFFSET) % width;//浪走得快一点postInvalidate();}});}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);if (!isInit) {isInit = true;width = getMeasuredWidth();height = getMeasuredHeight();waveLength = width / 3;//构建小船的路径initPath(boatPath, waveLength, BOAT_WAVE_HEIGHT, false, 1);//构建小船底下的浪的路径initPath(boatWavePath, waveLength, BOAT_WAVE_HEIGHT, true, 2);// 初始化 浪的路径initPath(wavePath, waveLength, WAVE_HEIGHT, true, 2);// 让 PathMeasure 与 Path 关联boatPathMeasure.setPath(boatPath, false);}}/*** @param path       路径* @param length     浪花的宽度* @param waveHeight 浪花的高度* @param isClose    是否要闭合* @param lengthTime 浪花长的倍数*/private void initPath(Path path, int length, int waveHeight, boolean isClose, float lengthTime) {// 初始化 小船的路径path.moveTo(-length, height / 2);for (int i = -length; i < width * lengthTime + length; i += length) {// rQuadTo 和 quadTo 区别在于// rQuadTo 是相对上一个点 而 quadTo是相对于画布path.rQuadTo(length / 4,-waveHeight,length / 2,0);path.rQuadTo(length / 4,waveHeight,length / 2,0);}if (isClose) {path.rLineTo(0, height / 2);path.rLineTo(-(width * 2 + 2 * length), 0);path.close();}}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);drawCoordinate(canvas);float length = boatPathMeasure.getLength();boatPathMeasure.getMatrix(length * currentPosition,matrix,PathMeasure.POSITION_MATRIX_FLAG | PathMeasure.TANGENT_MATRIX_FLAG);matrix.preTranslate(-boatBitmap.getWidth() / 2, -boatBitmap.getHeight() * 5 / 6);//根据轨迹来绘制小船canvas.drawBitmap(boatBitmap, matrix, null);// 画船的浪花canvas.save();canvas.translate(-boatWaveOffset, 0);wavePaint.setColor(boatBlue);canvas.drawPath(boatWavePath, wavePaint);canvas.restore();// 画浪花canvas.save();canvas.translate(-curWaveOffset, 0);wavePaint.setColor(waveBlue);canvas.drawPath(wavePath, wavePaint);canvas.restore();}
}

总结:

真是不容易,年后到现在就憋出了这么一篇,堕落啦【居然整个二月0篇】,另外有一个原因其实是由于年后公司组织架构调整了,到了一个全新的项目组,然后。。为了生存不得已需要耗用全部精力来熟悉新项目,所以学习计划就被搁置了,不过这也是给自己找借口,接下来还是得按照自己的学习计划前行,今年还是把去年落的计划一步一个脚印给补上,另外就是加强服务端java后端的学习【Java后台到全栈这门】,因为,年后被老大批了,说我们做app端的不思进取,把自己固守在自己的领域都不愿往后台搞一搞,好吧,算是逼自己换学习计划了,也挺好,接下来有时间就学它~~

这篇关于android高级UI之PathMeasure<三>--Path测量实战(笑脸loading效果实现、划船效果实现)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/742690

相关文章

通俗易懂的Java常见限流算法具体实现

《通俗易懂的Java常见限流算法具体实现》:本文主要介绍Java常见限流算法具体实现的相关资料,包括漏桶算法、令牌桶算法、Nginx限流和Redis+Lua限流的实现原理和具体步骤,并比较了它们的... 目录一、漏桶算法1.漏桶算法的思想和原理2.具体实现二、令牌桶算法1.令牌桶算法流程:2.具体实现2.1

MySQL8.0设置redo缓存大小的实现

《MySQL8.0设置redo缓存大小的实现》本文主要在MySQL8.0.30及之后版本中使用innodb_redo_log_capacity参数在线更改redo缓存文件大小,下面就来介绍一下,具有一... mysql 8.0.30及之后版本可以使用innodb_redo_log_capacity参数来更改

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

如何通过Python实现一个消息队列

《如何通过Python实现一个消息队列》这篇文章主要为大家详细介绍了如何通过Python实现一个简单的消息队列,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录如何通过 python 实现消息队列如何把 http 请求放在队列中执行1. 使用 queue.Queue 和 reque