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;
相关推荐
张风捷特烈2 小时前
Flutter 伪3D绘制#03 | 轴测投影原理分析
android·flutter·canvas
omegayy5 小时前
Unity 2022.3.x部分Android设备播放视频黑屏问题
android·unity·视频播放·黑屏
mingqian_chu5 小时前
ubuntu中使用安卓模拟器
android·linux·ubuntu
自动花钱机5 小时前
Kotlin问题汇总
android·开发语言·kotlin
行墨8 小时前
Kotlin 主构造函数
android
前行的小黑炭8 小时前
Android从传统的XML转到Compose的变化:mutableStateOf、MutableStateFlow;有的使用by有的使用by remember
android·kotlin
_一条咸鱼_8 小时前
Android Compose 框架尺寸与密度深入剖析(五十五)
android
在狂风暴雨中奔跑8 小时前
使用AI开发Android界面
android·人工智能
行墨8 小时前
Kotlin 定义类与field关键
android
信徒_9 小时前
Mysql 在什么样的情况下会产生死锁?
android·数据库·mysql