首页 > 代码库 > Android笔记三十.图形特效处理
Android笔记三十.图形特效处理
转载请表明出处: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)
>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对绘制的图像进行旋转、倾斜变换。
<span style="font-family:Times New Roman;font-size:18px;">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); } }</span>
效果演示:
源码分析:上面程序中,我们首先定义一个继承于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<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;
}
3.源码实战
功能:当用户"触摸"图片的指定点时,该图片会在这个点被用户"按"下去(即图片被触摸的位置发生了扭曲)。
<span style="font-family:Times New Roman;font-size:18px;">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<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; } invalidate(); //通知View组件重绘 } } /*4.触摸屏监听器 * 功能:触碰事件响应-调用warp方法根据触摸屏事件的坐标来扭曲verts数组*/ @Override public boolean onTouchEvent(MotionEvent event) { warp(event.getX(),event.getY()); return true; } } }</span>
效果演示:
源码分析:
为了实现这个效果,程序要在用户触摸图片的指定点时,动态地改变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
<span style="font-family:Times New Roman;font-size:18px;">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); } }</span>
源码分析:实例化一个Paint对象(画笔),并重写onDraw方法绘制一个矩形,该View子类将会作为主界面的一个来显示。
(2)ShaderTest.java
<span style="font-family:Times New Roman;font-size:18px;">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(); } }</span>
源码分析:实例化渲染效果Shader对象,并为5个按钮分别注册一个事件监听器,当按钮被按下时调用Paint的setShader()方法实现各种图形填充效果。
3.main.xml
<span style="font-family:Times New Roman;font-size:18px;"><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 5个Button按钮(略) --> <!-- 位图 --> <com.example.shader.MyView android:id="@+id/my_view" android:layout_width="match_parent" android:layout_height="251dp" /> </LinearLayout></span>
源码分析:在主Actvitity界面中,我们将View子类作为布局文件的一个组件并为其设置ID,通过获取该组件的ID来修改MyView子类中Paint的相关属性。
效果演示:
Android笔记三十.图形特效处理
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。