Canvas提供了一个drawBitmapMesh(bitmap, meshWidth, meshHeight, verts, vertOffset, colors, colorOffset, paint)方法,该方法可以 对bitmap进行扭曲。用好这个方法,开发者可以在Android应用上开发出“水波震荡”、“风吹旗帜”等各种扭曲效果。
假想在一张图片上有很多网格,如下图。
在这张图上,每个网格上的像素与图片上的像素是一一对应的,也就是网格怎么扭动,图像就会怎么动。比如把网格扭成下面这样,图像就跟着扭曲了。
但是如何把网格扭成这样呢?在Android中很简单,设置网格顶点所在位置就可以了。但是顶点之间的线不是扭曲的么?不需要自己计算弯曲的方式吗?
不需要。
在DrawBitmapMesh中,只需要定义好这个顶点将要扭曲到哪个坐标点上,然后将顶点扭曲后的坐标告诉DrawBitmapMesh,便会自动计算出周边的线条扭曲形式,并根据结果扭曲图像。
总结上面的,需要做的就是三步:
1、根据图片,生成原始的、四四方方的网格
2、根据上面生成的网格,算出将要扭曲的网格
3、将网格传入drawBitmapMesh
而生成网格的方式,就是定义网格中,每个顶点的X,Y坐标。
回到方法定义上来。Canvas的drawBitmapMesh定义如下:
public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint)
关键参数主要下面四个:
-bitmap: 就是将要扭曲的图像
-meshWidth: 需要的横向网格数目
-meshHeight: 需要的纵向网格数目
-verts: 网格顶点坐标数组。
-vertOffset: verts数组中开始跳过的(x,y)对的数目。
其中,verts是个一维数组,保存所有顶点坐标信息。偶数项保存x坐标,奇数项保存y坐标。比如有有meshWidth*meshHeight个网格,如果vertOffset为0,那么算上两端就有(meshWidth+1)*(meshHeight+1)个顶点,verts数组就应该至少长度为(meshWidth+1)*(meshHeight+1)。
看效果图:
代码:
1 package com.example.mymeshtest; 2 3 import android.app.Activity; 4 import android.content.Context; 5 import android.graphics.Bitmap; 6 import android.graphics.BitmapFactory; 7 import android.graphics.Canvas; 8 import android.graphics.Color; 9 import android.os.Bundle; 10 import android.view.MotionEvent; 11 import android.view.View; 12 13 public class MyMeshTest extends Activity 14 { 15 16 private Bitmap bitmap; 17 18 19 @Override 20 protected void onCreate(Bundle savedInstanceState) 21 { 22 super.onCreate(savedInstanceState); 23 setContentView(new MyView(this,R.drawable.test)); 24 } 25 26 private class MyView extends View 27 { 28 29 //定义两个常量,这两个常量指定该图片横向、纵向上被划分为40格 30 private final int WIDTH = 40; 31 private final int HEIGHT = 40; 32 33 //记录该图片上包含41*41个顶点 34 private final int COUNT = (WIDTH +1) * (HEIGHT + 1); 35 36 //定义一个数组,保存Bitmap上的41*41个点得坐标 37 private final float[] orig = new float[COUNT*2]; 38 39 //定义一个数组,记录Bitmap上的41*41个点经过扭曲后的坐标 40 //对 图片进行扭曲的关键就是修改该数组里的元素的值 41 private final float[] verts = new float[COUNT*2]; 42 43 public MyView(Context context,int drawable_id) 44 { 45 super(context); 46 // TODO Auto-generated constructor stub 47 setFocusable(true); 48 49 //根据指定资源加载图片 50 bitmap = BitmapFactory.decodeResource(getResources(), drawable_id); 51 52 //获取图片的宽和高 53 float bitmapWidth = bitmap.getWidth(); 54 float bitmapHeight = bitmap.getHeight(); 55 int index = 0; 56 for (int y = 0; y<= HEIGHT; y++) 57 { 58 float fy = bitmapHeight*y/HEIGHT; 59 for (int x = 0; x<= WIDTH; x++) 60 { 61 float fx = bitmapWidth*x/WIDTH; 62 /*初始化orig、verts数组。 63 初始化后,orig、verts两个数组均匀地保存了 64 41*41个点得x、y坐标*/ 65 orig[index*2+0] = verts[index*2+0] = fx; 66 orig[index*2+1] = verts[index*2+1] = fy; 67 index += 1; 68 } 69 } 70 71 //设置背景颜色 72 setBackgroundColor(Color.WHITE); 73 } 74 75 @Override 76 protected void onDraw(Canvas canvas) 77 { 78 /*对bitmap按verts数组进行扭曲 79 从第一个点(由第5个参数0开始)开始扭曲*/ 80 canvas.drawBitmapMesh(bitmap, WIDTH, HEIGHT, verts, 0, null, 0, null); 81 } 82 83 //工具方法,用于根据触摸事件的位置计算verts数组里各元素的值 84 private void warp(float cx, float cy) 85 { 86 for (int i = 0; i< COUNT * 2; i += 2) 87 { 88 float dx = cx - orig[i +0]; 89 float dy = cy - orig[i +1]; 90 float dd = dx*dx + dy*dy; 91 //计算每个坐标点与当前点(cx、cy)之间的距离 92 float d = (float)Math.sqrt(dd); 93 94 //计算扭曲度,距离当前点(cx,cy)越远,扭曲度越小 95 float pull = 80000 / ((float) (dd *d)); 96 97 //对verts数组(保存bitmap上41*41个点经过扭曲后的坐标)重新辅助 98 if(pull >= 1) 99 { 100 verts[i + 0] = cx; 101 verts[i + 1] = cy; 102 } 103 else 104 { 105 //控制各定点向触摸事件发生点偏移 106 verts[i +0] = orig [i +0] +dx*pull; 107 verts[i +1] = orig [i +1] +dy*pull; 108 } 109 } 110 //通知View组建重绘 111 invalidate(); 112 } 113 @Override 114 public boolean onTouchEvent(MotionEvent event) 115 { 116 //调用warp方法根据触摸屏事件的坐标点来扭曲verts数组 117 warp(event.getX(),event.getY()); 118 return true; 119 } 120 121 } 122 123 }