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;
相关推荐
子非衣1 小时前
MySQL修改JSON格式数据示例
android·mysql·json
openinstall全渠道统计4 小时前
免填邀请码工具:赋能六大核心场景,重构App增长新模型
android·ios·harmonyos
双鱼大猫4 小时前
一句话说透Android里面的ServiceManager的注册服务
android
双鱼大猫4 小时前
一句话说透Android里面的查找服务
android
双鱼大猫4 小时前
一句话说透Android里面的SystemServer进程的作用
android
双鱼大猫4 小时前
一句话说透Android里面的View的绘制流程和实现原理
android
双鱼大猫5 小时前
一句话说透Android里面的Window的内部机制
android
双鱼大猫6 小时前
一句话说透Android里面的为什么要设计Window?
android
双鱼大猫6 小时前
一句话说透Android里面的主线程创建时机,frameworks层面分析
android
苏金标6 小时前
android 快速定位当前页面
android