Android 自定义 View _ 扭曲动效

(/ω\)。要做到这种姿势这种动作,噢,不,是动效,有点难。 但仔细观察,车厢好比是有人用手指,在车厢的左下和右下进行拉扯和复原所达到的效果, Canvas提供了一个方法:

drawBitmapMesh(Bitmap bitmap,

int meshWidth,

int meshHeight,

float[] verts,

int vertOffset,

int[] colors,int colorffset,Paint paint)

这个方法可以对bitmap进行扭曲,部分参数说明如下:

  • bitmap    需要扭曲的源位图
  • meshWidth   控制在横向上把该源位图划成成多少格
  • meshHeight   控制在纵向上把该源位图划成成多少格
  • verts     长度为(meshWidth + 1) * (meshHeight + 1) * 2的数组,它记录了扭曲后的位图各顶点位置
  • vertOffset 控制verts数组中从第几个数组元素开始才对bitmap进行扭曲

3. drawBitmapMesh简单示例

  • 根据参数的含义,我们先初始化这些参数:

//将图像分成多少格

private int carBodyWidthPart = 4;

private int carBodyHeightPart = 4;

//交点坐标的个数

private int carBodyPartCount = (carBodyWidthPart + 1) * (carBodyHeightPart + 1);

//用于保存COUNT的坐标

//x0, y0, x1, y1...

private float[] carBodyNewsPos = new float[carBodyPartCount * 2];

private float[] carBodyOrigPos = new float[carBodyPartCount * 2];

/**

  • 得到网格点集合

  • @param bitmap bitmap

  • @param heightPart 纵向分割的份数

  • @param widthPart 横向分割的份数

  • @param newPos 新点集合

  • @param origPos 原始点集合

    */

    protected void getBitmapMeshPoints(Bitmap bitmap, int heightPart, int widthPart, float[] newPos, float[] origPos) {

    //初始化网格点

    int index = 0;

    float bmWidth = bitmap.getWidth();

    float bmHeight = bitmap.getHeight();

    for (int i = 0; i < heightPart + 1; i++) {

    float fy = bmHeight * i / heightPart;

    for (int j = 0; j < widthPart + 1; j++) {

    float fx = bmWidth * j / widthPart;

    //X轴坐标 放在偶数位

    newPos[index * 2] = fx;

    origPos[index * 2] = newPos[index * 2];

    //Y轴坐标 放在奇数位

    newPos[index * 2 + 1] = fy;

    origPos[index * 2 + 1] = newPos[index * 2 + 1];

    index += 1;

    }

    }

    }

  • 这里我把初始化数组写成了一个函数,注意的是,数组按照第一个点的x1,y1,然后第二个点的x2,y2,这样的顺序存储。我们先把图给画出来看看效果,我们把这些点集也画出来:

/**

  • 绘制网格点

  • @param canvas canvas

  • @param pos 点集

  • @param paint 画笔
    /
    protected void drawPoint(Canvas canvas, float[] pos, Paint paint) {
    for (int i = 0; i < pos.length/2; i++) {
    int x = i
    2;

    int y = x + 1;

    canvas.drawPoint(pos[x], pos[y], paint);

    }

    }

  • 绘制方法:

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

canvas.setDrawFilter(paintFlagsDrawFilter);

//原始点

canvas.save();

canvas.translate(50,50);

canvas.drawBitmapMesh(bitmapCarBody, carBodyWidthPart, carBodyHeightPart, carBodyOrigPos, 0, null, 0, paint);

paint.setColor(Color.RED);

paint.setStrokeWidth(5);

drawPoint(canvas, carBodyOrigPos, paint);

canvas.restore();

//变化点

canvas.save();

canvas.translate(1000,50);

canvas.drawBitmapMesh(bitmapCarBody, carBodyWidthPart, carBodyHeightPart, carBodyNewsPos, 0, null, 0, paint);

paint.setColor(Color.RED);

paint.setStrokeWidth(5);

drawPoint(canvas, carBodyNewsPos, paint);

canvas.restore();

}

  • 左边的是原始点所画出来的,右边是变化后所画出来的,因为这里没有对点进行偏移所以左右是一样的。还有要注意的一点,绘制drawBitmapMesh是没有传入绘制的起始点,因为他默认是从原始点开始绘制,所以这里有把坐标移动的代码。

4. 尝试加点扭曲

既然要形变要扭曲,那么我们先对carBodyNewsPos这个点集进行肆意妄为地操作一下:

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

canvas.setDrawFilter(paintFlagsDrawFilter);

//原始点

canvas.save();

canvas.translate(50,50);

canvas.drawBitmapMesh(bitmapCarBody, carBodyWidthPart, carBodyHeightPart, carBodyOrigPos, 0, null, 0, paint);

paint.setColor(Color.RED);

paint.setStrokeWidth(5);

drawPoint(canvas, carBodyOrigPos, paint);

canvas.restore();

//遍历交点,修改

for (int i = 0; i < carBodyPartCount; i++) {

int x = i * 2;

int y = x + 1;

carBodyNewsPos[x] = carBodyOrigPos[x] * (changeFactor * 1.0f / 100 + 1);

}

//变化点

canvas.save();

canvas.translate(1000,50);

canvas.drawBitmapMesh(bitmapCarBody, carBodyWidthPart, carBodyHeightPart, carBodyNewsPos, 0, null, 0, paint);

paint.setColor(Color.RED);

paint.setStrokeWidth(5);

drawPoint(canvas, carBodyNewsPos, paint);

canvas.restore();

}

我们对每一个点应用了:carBodyNewsPos[x] = carBodyOrigPos[x] * (changeFactor * 1.0f / 100 + 1)

5. 车厢的扭曲实现

仔细观察车厢的动画,发现有几点细节:

  • 扭曲可以分解为x,y轴的扭曲,其叠加一起就是车厢的动画
  • 车厢和车顶的扭曲一样,不用的是扭曲度
  • x轴的扭曲,以车厢的中心轴为分界线,左边向左扭曲,右边向右扭曲
  • x轴方向的拉伸,离x中心轴线越近变化越小,离顶部(y=0)越近,变化也越小
  • y轴的扭曲,是以y=0为中心轴,离x中心轴线越近变化越大,离顶部(y=0)越近,变化也越小

因为我们是依次遍历点集,就是从i=0一直到i=posCount,那么我们来实现一下上面的变化:

  • 设定两个因子:xFactor和yFactor,控制x,y轴方向的最大扭曲度。
  • 设定变化因子changeFactor,带动扭曲度随着时间而变化
  • 当点的x在x中心轴的左边,其x坐标应该是原始坐标减去扭曲度,反之亦然

大致得到X方向的扭曲度:changeFactor*Math.abs((widthPart+1)/2-i%(widthPart+1))*(origPos[y]/bitmap.getHeight()) * xFactor;

  • xFactor是控制整个扭曲度的大小
  • 顶部(y=0)越近,变化也越小,而因为是从y=0开始绘制,所以,bitmap.getHeight()就是y轴的最大值,那么,origPos[y]/bitmap.getHeight() 就是当前点所占y轴的百分比,也就是,离y=0越近,origPos[y]/bitmap.getHeight() 的值越小。
  • 离x周轴线越近变化越小,Math.abs((widthPart+1)/2-i%(widthPart+1))

如上图,红色为x的中心轴,而我们遍历的i(点的个数)值是从0-N,而,i=0,i=4的时候最大,i=1,i=3次小,如此类推。 (widthPart+1)/2是算出中心轴的坐标相对位置,例如图中,算出应该是2,那么中心轴的相对位置则为2,而i%(widthPart+1),在图中就是,0-4的取值,跟中心轴的相对位置2相减取绝对值,就是一个离中心轴的变化量的大小了。

Y方向的扭曲度,也是一样的。这里就不说了。。。最后得出的扭曲函数如下:

/**

  • 改变车厢的Points
  • @param bitmap bitmap
  • @param posCount 点的个数
  • @param widthPart 横向分割的份数
  • @param heightPart 纵向分割的份数
  • @param newPos 新点集合
  • @param origPos 原始点集合
  • @param xFactor 变化因子
  • @param yFactor 变化因子
    /
    protected void changeCarBodyPoint(Bitmap bitmap, int posCount, int widthPart, int heightPart, float[] newPos, float[] origPos, float xFactor, float yFactor) {
    //遍历交点,修改
    for (int i = 0; i < posCount; i++) {
    int x = i
    2;
    int y = x + 1;
    //x方向的拉伸,离x周轴线越近变化越小,离顶部(y=0)越近,变化也越小
    if (newPos[x]<centralAxisX) {
    newPos[x] = origPos[x] - changeFactorMath.abs((widthPart+1)/2-i%(widthPart+1)) (origPos[y]/bitmap.getHeight()) * xFactor;
    }
    else if (newPos[x]>centralAxisX) {
    newPos[x] = origPos[x] + changeFactorMath.abs((widthPart+1)/2-i%(widthPart+1)) (origPos[y]/bitmap.getHeight()) * xFactor;
    Pos[y]/bitmap.getHeight()) * xFactor;
    }
    else if (newPos[x]>centralAxisX) {
    newPos[x] = origPos[x] + changeFactorMath.abs((widthPart+1)/2-i%(widthPart+1))(origPos[y]/bitmap.getHeight()) * xFactor;
相关推荐
阿巴斯甜15 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker16 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952717 小时前
Andorid Google 登录接入文档
android
黄林晴18 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android