转载请表明出处:http://blog.csdn.net/u012637501(嵌入式_小J的天空)
一、图形变换特效
1.图形变换理论
图形变换,一般是指图形的平移、旋转、缩放、倾斜等效果。Maxtrix是Android提供的一个矩形工具类,不仅可用于控制图形的平移、旋转、缩放、倾斜变换,也可控制View组件进行平移、旋转和缩放等。有一点需要注意的是,Maxtrix本身并不能对图像或组件进行变换,而是通过与其他API结合来控制图形、组件的变换。
2.使用Matrix控制变换步骤
(1)获取Matrix对象,该Matrix对象既可新创建,也可直接获取其他对象内封装的Matrix(比如Transformation对象内部就封装了Matrix);
(2)调用Matrix的方法进行平移、旋转、缩放、倾斜等;
(3)将程序对Matrix所做的变换应用到指定图像或组件。
总之,一旦对Matrix进行了变换,下一步便是应用该Matrix对图形进行控制(变换)。比如Canvas提供了一个drawBitmap(Bitmap bitmap,Matrix matrix,Paint paint)方法,调用该方法就可以在绘制bitmap时应用Matrix上的变换。
3.关键代码
Matrix matrix = new Matrix();
matrix.reset(); //重置Matrix
matrix.setSkew(kx, ky); // 控制Matrix进行缩放
matrix.setScale(sx, sy) ; // 控制Matrix进行缩放
Bitmap bitmap2 = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true); // 根据原始位图和Matrix创建新图片
canvas.drawBitmap(bitmap2, matrix, null); // View的canvas绘制新位图
4.Matrix
(1)功能:Maxtrix通过与其他API结合来实现图形、组件的平移、旋转、缩放、倾斜变换。
(2)构造方法
>Matrix()
>Matrix(Matrix src)
(3)常用方法
>setTranslate(float dx,float dy):控制Matrix进行平移
>setSkew(float kx,float ky,float px,float py):控制Matrix以px、py为轴心进行倾斜,其中kx、ky为X、Y方向上的倾斜距离
>setSkew(float kx,float ky):控制Matrix进行倾斜,kx、ky为X、Y方向上的倾斜距离
>setRotate(float degrees):控制Matrix进行旋转,degrees控制旋转的角度
>setRotate(float degrees,float px,float py):设置以px、py为轴心进行旋转,degrees控制旋转的角度
>setScale(float sx,float sy):设置Matrix进行缩放,sx,sy控制X、Y方向上的缩放比例
>setScale(float sx,float sy,float px,float py):设置Matrix以px、py为轴心缩放,sx,sy控制X、Y方向上的缩放比例
5.源码实战
(1)控制图形变换
功能:开发一个自定义View,该自定义View可以检测到用户的键盘事件,当用户单击手机的方向键时,该自定义View会用Matrix对绘制的图像进行旋转、倾斜变换。
package com.example.matrix;
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; // 初始化图片资源
private int width, height; // 位图高、宽
private Matrix matrix = new Matrix(); // Matrix实例
private float sx = 0.0f; // 设置倾斜度
private float scale = 1.0f; // 设置缩放比例
private boolean isScale = false; //判断是缩放还是旋转
/*
* 1.构造方法 功能:获得当前位图资源并且使得当前视图获得焦点
*/
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
bitmap = ((BitmapDrawable) context.getResources().getDrawable(R.drawable.a)).getBitmap();
// 获得当前位图资源
width = bitmap.getWidth(); // 返回位图的宽度
height = bitmap.getHeight(); // 返回位图的高度
this.setFocusable(true); // 使当前视图获得焦点
}
/*
* 2.onDraw方法 功能:选择Matrix控制功能,创建新位图并绘制到View视图
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// a.设置Matrix,选择Matrix控制功能
matrix.reset(); // 重置Matrix
if (!isScale) // 设置Matrix为旋转或者缩放位图
{
matrix.setSkew(sx, 0); // 控制Matrix进行旋转
} else {
matrix.setScale(scale, scale);// 控制Matrix进行缩放
}
// b.根据原始位图和Matrix创建新图片
Bitmap bitmap2 = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
// c.View的canvas绘制新位图
canvas.drawBitmap(bitmap2, matrix, null);
}
/* 3.触屏事件 */
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
//向左倾斜
case KeyEvent.KEYCODE_A:
isScale = false;
sx +=0.1;
postInvalidate(); //重绘View,即调用onDraw方法
break;
//向右倾斜
case KeyEvent.KEYCODE_D:
isScale = false;
sx -=0.1;
postInvalidate(); //重绘View,即调用onDraw方法
break;
//放大
case KeyEvent.KEYCODE_W:
isScale = true;
if(scale<2.0)
scale +=0.1;
postInvalidate(); //重绘View,即调用onDraw方法
break;
//缩小
case KeyEvent.KEYCODE_S:
isScale = true;
if(scale>0.5)
scale -=0.1;
postInvalidate(); //重绘View,即调用onDraw方法
break;
}
return super.onKeyDown(keyCode, event);
}
}
效果演示:
源码分析:上面程序中,我们首先定义一个继承于View的子类,完成以下功能:
(1)获取当前位图资源及其宽度、高度;
(2)重写onDraw方法重置Matrix并对Matrix进行了变换,然后调用Bitmap的createBitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter) 方法根据原始图片和变化的Matrix创建新位图bitmap,最后调用Canvas(属于View)的drawBitmap(Bitmap bitmap,Matrix matrix,Paint paint)方法绘制新的位图。
(3)重写onKeyDown方法用于检测按下键并更改相应的Matrix方法的参数,即当用户单击手机的方向键时,事件处理器负责修改程序中sx(控制水平倾斜度)和scale(控制缩放比)两个参数,再调用postInvalidate()方法重绘View(即再次调用onDraw方法)。
需要注意的是,当我们定义好View的子类后,需在程序的主界面布局文件中(main.xml),为视图View添加一个<com.example.matrix.MyView......./>元素。
二、图像扭曲特效
1.图形扭曲理论
Android提供了Canvas的drawBitmapMesh()方法实现对位图进行扭曲效果,通过该方法我们可以开发出"水波荡漾"、"风吹旗帜"等各种扭曲效果。.Canvas的drawBitmapMesh()方法如下:
drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts
, int vertOffset, int[] colors, int colorOffset, Paint paint)
参数说明:
bitmap:指定需要扭曲的位图;
meshWidth:该参数控制在横向上把该源位图划分成多少格;
meshHeight:该参数控制在纵向上把该源位图划分成多少格;
verts:该参数是一个长度为(meshWidth+1)*(meshHeight+1)*2的数组,它记录了扭曲后的位图各”顶点“位置。需要注意的是,虽然它是一个一维数组,但实际上它记录的数据是形如(x0,y0)、(x1,y1)、(x2、y2)...(xN,yN)格式的数据,这些数组元素控制对bitmap位图的扭曲效果。
vertOffset:控制verts数组中从第几个数组元素开始才对bitmap进行扭曲(即忽略vertOffset之前数据的扭曲效果)
总之,drawBitmapMesh方法对源位图扭曲时最关键的参数是meshWidth、meshHeight、verts这三个参数对扭曲的控制。当程序希望调用drawBitmapMesh方法对位图进行扭曲时,关键是计算verts数据的值---该数组的值记录了扭曲后的位图上各"顶点"。
2.关键代码
(1)初始化verts数据,其中HEIGHT、WIDTH为位图划分格数
int index = 0;
for(int y = 0; y <= HEIGHT; y++)
{
float fy = bitmapHeight * y / HEIGHT;
for(int x = 0 ; x <= WIDTH; x++)
{
loat 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;
}
}
(2) 根据触摸事件的位置坐标,计算并修改verts数组里的元素值。其中,cx、cy为当前触点坐标值。
for(int i=0;i
{
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;
}
3.源码实战
功能:当用户"触摸"图片的指定点时,该图片会在这个点被用户"按"下去(即图片被触摸的位置发生了扭曲)。
package com.example.drawbitmapmesh;
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 WrapTest extends Activity {
private Bitmap bitmap;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MyView(this,R.drawable.photo));
}
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];
/*1.构造方法*/
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); //设置背景颜色
}
/*2.绘图方法
* 功能:绘制图形。对bitmap按verts数组进行扭曲
* 从第一个点(由第5个参数0控制)开始扭曲*/
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmapMesh(bitmap, WIDTH, HEIGHT, verts, 0, null, 0, null);
}
/*3.工具方法
* 功能:用于根据触摸事件的位置计算verts数组里各元素的值*/
private void warp(float cx,float cy)
{
for(int i=0;i=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;
}
invalidate(); //通知View组件重绘
}
}
/*4.触摸屏监听器
* 功能:触碰事件响应-调用warp方法根据触摸屏事件的坐标来扭曲verts数组*/
@Override
public boolean onTouchEvent(MotionEvent event)
{
warp(event.getX(),event.getY());
return true;
}
}
}
效果演示:
源码分析:
为了实现这个效果,程序要在用户触摸图片的指定点时,动态地改变verts数组里每个元素的位置(也就是控制扭曲后每个顶点的坐标)。即使程序计算图片上每个顶点与触摸点的距离,顶点与触摸点的距离越小,该顶点向触摸点移动的距离越大。在上面的Avtivity代码中,我们实现一个继承于View的子类,完成以下功能:
(1)调用BitmapFactory的decodeResource((Resources res, int id)方法根据指定资源加载图片(返回一个Bitmap对象)并获取当前图片的实际高度和宽度;
(2)计算verts数组初始值
a.假设该图片在横向、纵向均被划分成20格
WIDHT=20
HEIGHT=20
b.计算该图片的实际宽度、高度
bitmapWidth
bitmapHeight
c.计算verts数组各元素的初始值
fy = bitmapHeight * y / HEIGHT
fx = bitmapWidth * x / WIDTH
(3)计算触摸事件发生后,获取当前触点坐标并对verts数据元素值作出改变
a.假设触目点的坐标为(cx,cy),计算每个坐标点(21*21个)与当前(cx,cy)之间的距离d
dx = cx - orig[i+0]
dy = cy - orig[i+1]
dd = dx*dx+dy*dy
d = (float)Math.sqrt(dd)
b.计算扭曲度,距离当前点(cx,cy)越远,扭曲度越小
pull = 80000 / ((float)(dd*d))
c.对verts数组各元素重写赋值
(4)调用invalidaye()方法通知View组件重绘位图
三、使用Shader填充图形
1.图形填充理论
在上节中我们在学习Paint类时,其提供了一个setShader(Shader s)方法来控制画笔的"渲染"效果。实际上,Android不仅可以使用颜色来填充图形,也可以使用Shader对象来指定渲染效果来填充图形。Shader本身是一个抽象类,它提高了以下几个子类来实现填充效果:
>BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY) :使用位图平铺的渲染效果,一般tileX、tileY设置为TileMode.REPEAT, TileMode.MIRROR;
>LinearGradient.LinearGradient(float x0, float y0, float x1, float y1, int[] colors, float[] positions, TileMode tile) :使用线性渐变来填充图形,其中(x0,y0)、(x1,y1)为渐变线的起始端和末端,tileMode一般为TileMode.REPEAT
>RadialGradient(float centerX, float centerY, float radius, int[] colors, float[] stops, TileMode tileMode) :使用圆形渐变来填充图形,其中(X,Y)为圆中心/radius为半径、 tileMode一般为TileMode.REPEAT
>SweepGradient(float cx, float cy, int[] colors, float[] positions) :使用角度渐变来填充图形
>ComposeShader(Shader shaderA, Shader shaderB, Mode mode) :使用组合渲染效果来填充图形
2.源码实战
功能:创建一个View子类并实现绘制一个矩形到View组件,当用户单击不同按钮时系统将会设置Paint使用不同的Shader来实现矩形框中不同的填充效果。
(1)MyView.java
package com.example.shader;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
public class MyView extends View {
Paint paint= new Paint() ;
public MyView(Context context, AttributeSet attrs)
{
super(context, attrs);
paint= new Paint();
paint.setAntiAlias(true); // 去锯齿
paint.setStyle(Paint.Style.FILL); // 设置画笔的填充风格
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawRect(30, 30, 200,200, paint);
}
}
源码分析:实例化一个Paint对象(画笔),并重写onDraw方法绘制一个矩形,该View子类将会作为主界面的一个来显示。
(2)ShaderTest.java
package com.example.shader;
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.Shader.TileMode;
import android.graphics.SweepGradient;
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; //自定义视图类
/*1.初始化*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
myView = (MyView)findViewById(R.id.my_view);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.water); //获取Bitmap实例
colors = new int[] {Color.RED,Color.GREEN,Color.BLUE}; //设置渐变的颜色组
shaders[0] = new BitmapShader(bitmap, TileMode.REPEAT, TileMode.MIRROR);//实例话BitmapShader,x坐标方法重复图形,y坐标方向镜像图形
shaders[1] =new LinearGradient(0, 0, 100, 100, colors, null, TileMode.REPEAT); //实例化LinearGradient
shaders[2]=new RadialGradient(100, 100, 80, colors, null, TileMode.REPEAT); //实例化RadialGradient
shaders[3]=new SweepGradient(160, 160, colors, null); //实例化SweepGradient
shaders[4]=new ComposeShader(shaders[1], shaders[2], PorterDuff.Mode.DARKEN);//实例化ComposeShader
//获取界面按钮并为其注册事件监听器
Button btn1 = (Button)findViewById(R.id.bn1);
Button btn2 = (Button)findViewById(R.id.bn2);
Button btn3 = (Button)findViewById(R.id.bn3);
Button btn4 = (Button)findViewById(R.id.bn4);
Button btn5 = (Button)findViewById(R.id.bn5);
btn1.setOnClickListener(this);
btn2.setOnClickListener(this);
btn3.setOnClickListener(this);
btn4.setOnClickListener(this);
btn5.setOnClickListener(this);
}
@Override
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();
}
}
源码分析:实例化渲染效果Shader对象,并为5个按钮分别注册一个事件监听器,当按钮被按下时调用Paint的setShader()方法实现各种图形填充效果。
3.main.xml
源码分析:在主Actvitity界面中,我们将View子类作为布局文件的一个组件并为其设置ID,通过获取该组件的ID来修改MyView子类中Paint的相关属性。
效果演示: