首页 > 代码库 > 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)
(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笔记三十.图形特效处理