Android系统提供了Matrix对象控制图形进行平移、旋转、缩放、倾斜等操作,对View组件也可以进行平移、旋转、缩放等。
import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.drawable.BitmapDrawable; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.View; public class MyView extends View { // 初始的图片资源 private Bitmap bitmap; // Matrix 实例 private Matrix matrix = new Matrix(); // 设置倾斜度 private float sx = 0.0f; // 位图宽和高 private int width, height; // 缩放比例 private float scale = 1.0f; // 判断缩放还是旋转 private boolean isScale = false; public MyView(Context context , AttributeSet set) { super(context , set); // 获得位图 bitmap = ((BitmapDrawable) context.getResources().getDrawable(R.drawable.a)).getBitmap(); // 获得位图宽 width = bitmap.getWidth(); // 获得位图高 height = bitmap.getHeight(); // 使当前视图获得焦点 this.setFocusable(true); } protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 重置Matrix matrix.reset(); if (!isScale) { // 旋转Matrix matrix.setSkew(sx, 0); } else { // 缩放Matrix matrix.setScale(scale, scale); } // 根据原始位图和Matrix创建新图片 Bitmap bitmap2 = Bitmap.createBitmap(bitmap, 0, 0, width, height,matrix, true); // 绘制新位图 canvas.drawBitmap(bitmap2, matrix, null); } public boolean onKeyDown(int keyCode, KeyEvent event) { switch(keyCode) { // 按键A向左倾斜 case KeyEvent.KEYCODE_A: isScale = false; sx += 0.1; postInvalidate(); break; // 按键D向右倾斜 case KeyEvent.KEYCODE_D: isScale = false; sx -= 0.1; postInvalidate(); break; // 按键W放大 case KeyEvent.KEYCODE_W: isScale = true; if (scale < 2.0) scale += 0.1; postInvalidate(); break; // 按键S缩小 case KeyEvent.KEYCODE_S: isScale = true; if (scale > 0.5) scale -= 0.1; postInvalidate(); break; } return super.onKeyDown(keyCode, event); } }
Canvas提供了一个drawBitmapMesh方法对Bitmap对象进行扭曲,其效果相当于PS里的滤镜,可以做出水波荡漾、红旗飘飘等扭曲效果。
drawBitmapMesh(Bitmap bitmap, int meshWidth,int meshHeight,float[] verts,int vertOffset,int[] colors,int colorOffset,Paint paint)
其中bitmap为需要扭曲的图源,meshWidth和meshHeight分别是横向和纵向上把bitmap分成多少格,
verts是一个长度为(meshWidth+1)*(meshHeight+1)
vertOffset控制verts数组从第几个数组元素开始才对bitmap进行扭曲
import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; public class WarpTest extends Activity { private Bitmap bitmap; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new MyView(this, R.drawable.jinta)); } private class MyView extends View { // 定义两个常量,这两个常量指定该图片横向、纵向上都被划分为20格。 private final int WIDTH = 20; private final int HEIGHT = 20; // 记录该图片上包含441个顶点 private final int COUNT = (WIDTH + 1) * (HEIGHT + 1); // 定义一个数组,保存Bitmap上的21 * 21个点的座标 private final float[] verts = new float[COUNT * 2]; // 定义一个数组,记录Bitmap上的21 * 21个点经过扭曲后的座标 // 对图片进行扭曲的关键就是修改该数组里元素的值。 private final float[] orig = new float[COUNT * 2]; public MyView(Context context, int drawableId) { super(context); setFocusable(true); // 根据指定资源加载图片 bitmap = BitmapFactory.decodeResource(getResources(), drawableId); // 获取图片宽度、高度 float bitmapWidth = bitmap.getWidth(); float bitmapHeight = bitmap.getHeight(); int index = 0; for (int y = 0; y <= HEIGHT; y++) { float fy = bitmapHeight * y / HEIGHT; for (int x = 0; x <= WIDTH; x++) { float fx = bitmapWidth * x / WIDTH; // 初始化orig、verts数组。 初始化后,orig、verts // 两个数组均匀地保存了21 * 21个点的x,y座标 orig[index * 2 + 0] = verts[index * 2 + 0] = fx; orig[index * 2 + 1] = verts[index * 2 + 1] = fy; index += 1; } } // 设置背景色 setBackgroundColor(Color.WHITE); } protected void onDraw(Canvas canvas) { //对bitmap按verts数组进行扭曲 //从第一个点(由第5个参数0控制)开始扭曲 canvas.drawBitmapMesh(bitmap, WIDTH, HEIGHT, verts , 0, null, 0,null); } // 工具方法,用于根据触摸事件的位置计算verts数组里各元素的值 private void warp(float cx, float cy) { for (int i = 0; i < COUNT * 2; i += 2) { float dx = cx - orig[i + 0]; float dy = cy - orig[i + 1]; float dd = dx * dx + dy * dy; // 计算每个座标点与当前点(cx、cy)之间的距离 float d = (float) Math.sqrt(dd); // 计算扭曲度,距离当前点(cx、cy)越远,扭曲度越小 float pull = 80000 / ((float) (dd * d)); // 对verts数组(保存bitmap上21 * 21个点经过扭曲后的座标)重新赋值 if (pull >= 1) { verts[i + 0] = cx; verts[i + 1] = cy; } else { // 控制各顶点向触摸事件发生点偏移 verts[i + 0] = orig[i + 0] + dx * pull; verts[i + 1] = orig[i + 1] + dy * pull; } } // 通知View组件重绘 invalidate(); } public boolean onTouchEvent(MotionEvent event) { // 调用warp方法根据触摸屏事件的座标点来扭曲verts数组 warp(event.getX(), event.getY()); return true; } } }
使用Shader填充图形
Shader本身是一个抽象类,它提供了如下实现类
BitmapShader 使用位图平铺的渲染效果
LinearGradient 使用线性渐变来填充图形
RadialGradient 使用圆形渐变来填充图形
WseepGradient 使用角度渐变来填充图形
ComposeShader 使用组合渲染效果来填充图形
import android.app.Activity; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapShader; import android.graphics.Color; import android.graphics.ComposeShader; import android.graphics.LinearGradient; import android.graphics.PorterDuff; import android.graphics.RadialGradient; import android.graphics.Shader; import android.graphics.SweepGradient; import android.graphics.Shader.TileMode; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class ShaderTest extends Activity implements OnClickListener { // 声明位图渲染对象 private Shader[] shaders = new Shader[5]; // 声明颜色数组 private int[] colors; MyView myView; // 自定义视图类 public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); myView = (MyView)findViewById(R.id.my_view); // 获得Bitmap实例 Bitmap bm = BitmapFactory.decodeResource(getResources() , R.drawable.water); // 设置渐变的颜色组,也就是按红、绿、蓝的方式渐变 colors = new int[] { Color.RED, Color.GREEN, Color.BLUE }; // 实例化BitmapShader,x坐标方向重复图形,y坐标方向镜像图形 shaders[0] = new BitmapShader(bm, TileMode.REPEAT, TileMode.MIRROR); // 实例化LinearGradient shaders[1] = new LinearGradient(0, 0, 100, 100 , colors, null, TileMode.REPEAT); // 实例化RadialGradient shaders[2] = new RadialGradient(100, 100, 80, colors, null, TileMode.REPEAT); // 实例化SweepGradient shaders[3] = new SweepGradient(160, 160, colors, null); // 实例化ComposeShader shaders[4] = new ComposeShader(shaders[1], shaders[2], PorterDuff.Mode.DARKEN); Button bn1 = (Button)findViewById(R.id.bn1); Button bn2 = (Button)findViewById(R.id.bn2); Button bn3 = (Button)findViewById(R.id.bn3); Button bn4 = (Button)findViewById(R.id.bn4); Button bn5 = (Button)findViewById(R.id.bn5); bn1.setOnClickListener(this); bn2.setOnClickListener(this); bn3.setOnClickListener(this); bn4.setOnClickListener(this); bn5.setOnClickListener(this); } public void onClick(View source) { switch(source.getId()) { case R.id.bn1: myView.paint.setShader(shaders[0]); break; case R.id.bn2: myView.paint.setShader(shaders[1]); break; case R.id.bn3: myView.paint.setShader(shaders[2]); break; case R.id.bn4: myView.paint.setShader(shaders[3]); break; case R.id.bn5: myView.paint.setShader(shaders[4]); break; } // 重绘界面 myView.invalidate(); } }