1. 程式人生 > >淺談android中圖片處理之圖形變換特效Matrix(四)

淺談android中圖片處理之圖形變換特效Matrix(四)

今天,我們就來談下android中圖片的變形的特效,在上講部落格中我們談到android中圖片中的色彩特效來實現的。改變它的顏色主要通過ColorMatrix類來實現。
現在今天所講的圖片變形的特效主要就是通過Matrix類來實現,我們通過上篇部落格知道,改變色彩特效,主要是通過ColorMatrxi矩陣的係數,以及每個畫素點上所對應的顏色偏移量。而今天的圖形變換與那個也是非常的類似。它是一個3*3矩陣,而顏色矩陣則是一個4*5的矩陣。在這個3*3矩陣中則表述出了每個畫素點的XY座標資訊。然後通過修改這個矩陣,就可達到修改圖片中的每個畫素點的XY座標,即改變每個畫素點的位置資訊,通過對特定的矩陣元素值的修改就可以達到實現圖片的中的
圖形變換特效如:平移變換特效,旋轉變換特效,縮放變換特效,錯切變換特效。那麼接下來我們就通過從原理的角度來一一分析下每個特效對應的矩陣是怎麼樣的。
預設的圖形變換的初始矩陣是:
                        1  0  0 
                        0  1  0
                        0  0  1
  第一、平移變換特效:
                 我們很容易知道這個,在一個平面中,將一個畫素點從位置A(x0,y0)移到另一個位置B(x,y)
很容易得到如下的公式:
                   x=x0+X方向的偏移量(xt);
                   y=y0+Y方向的偏移量(yt);
依據上面的等式我們很容易得到如下矩陣:


      |x|     |1 0 xt|  |x0|
                   |y| =  |0 1 yt| ×    |y0|
                   |1|     |0 0  1|       | 1 |
然後我們通過如上的矩陣乘法計算得到等式(與我們得出的結論一致,所以也就是我們通過改變初始矩陣中那個矩陣元素的值就可以實現圖片在X,Y方向上的平移):
                    x=x0+X方向的偏移量(xt);
                    y=y0+Y方向的偏移量(yt);
 第二、旋轉變換特效:


                   所謂旋轉變換就相當於一個畫素點圍繞某個中心點O旋轉到一個新的點,在高中學過三角函式的都知道,通過從初始點A(x0,y0)旋轉到B點(x,y)
初始點與X軸正方向夾角為a,旋轉過的角度為t,通過三角函式的計算得出如下公式:
                  設:旋轉軸長為:r
                   x0=r*cosa;  y0=r*sina;
                   x=r*cos(a+t)=r*cosa*cost-rsina*sint=x0*cost-y0*sint;
                   y=r*sin(a+t)=r*sina*cost+r*cosa*sint=y0*cost+x0*sint;
從而可以得出如下矩陣:
                    |x|    |cost -sint 0| |x0|
                    |y| = |sint  cost 0| ×|y0|
                    |1|    |0      0     1|    | 1 |
然後我們通過如上的矩陣乘法計算得到等式(與我們得出的結論一致,所以也就是我們通過改變初始矩陣中那個矩陣元素的值就可以實現圖片旋轉):


                   x=x0*cost-y0*sint;
                                                                  y=y0*cost+x0*sint;
注意:前面所講的旋轉都是以座標的原點為旋轉中心的,我們還可以以任意點O為旋轉中心來進行旋轉的變換但是通常需要如下三個步驟:
首先第一需要將座標的原點平移到任意指定的點O,然後再使用上述我們的所講的旋轉方法來進行旋轉,最後就需要將我們的原點還原回去。
第三、縮放變換特效:
                 所謂畫素點的縮放,實際上並不會對畫素點縮放,因為畫素點已經夠小了,不存在什麼縮放的概念,那我們這裡所說的縮放是怎麼樣的呢?
我們是通過將每個畫素點所在的XY座標按一定的比例縮放,然後使得圖片整體看起來的有一個縮放的效果。
                    x=K1*x0
                                    y=K2*y0 
通過以上公式反映到我們的變換矩陣中的形式:
                              |x|    |K1 0 0| |x0|
                      |y| = |0 K2 0| ×|y0|
                      |1|    |0 0   1|   | 1 |
通過矩陣乘法驗證得到等式(與我們得出的結論一致,所以也就是我們通過改變初始矩陣中那兩個矩陣元素的值就可以實現圖片在X,Y方向上的縮放)
第四、錯切變換特效:
                所謂錯切變換的效果很類似數學上的Shear mapping.錯切主要分兩種形式:
第一水平錯切變換:就是在正常圖片的基礎上,讓每個畫素點的Y軸座標保持不變,而讓他們的X座標按一定比例的縮放,第二就是垂直錯切變換:就是在原圖的基礎上,讓讓每個畫素點的X座標保持不變,讓Y座標按一定比例的縮放。從而可以得到如下的變換公式:
                 x=x0+K1*y0;y=x0+K2*y0;
通過以上的公式反應到我們的變換矩陣中的形式:
                 
      |x|    |1    K1  0| |x0|
                   |y| =  |K2  1   0| ×|y0|
                   |1|    |0     0    1|  | 1 |
通過矩陣乘法驗證得到等式(與我們得出的結論一致,所以也就是我們通過改變初始矩陣中那兩個矩陣元素的值就可以實現圖片在X,Y方向上的錯切)
綜合上述:將得出我們最後的矩陣變換公式:
                    |Scale_X Skew_X  Trans_X|
                    |Skew_Y  Scale_Y Trans_Y|
                    | 0               0             1        |
也就是我只需要改變矩陣中對應的元素的值,我們就可以實現各種變換的特效。
如果用A,B,C,D,E,F來一次標示那些區的話,A和E決定縮放變換,B和D決定了錯切變換,C和F決定了平移變換

下面我們就通過一個Demo來驗證一下我們的觀點。 

package com.mikyou.matrix;

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;


public class MainActivity extends Activity {
	private ImageView iv;
	private Canvas canvas;
	private Paint paint;
	private Bitmap baseBitmap;
	private Bitmap copyBitmap;
	private Matrix matrix;
	private EditText e1,e2,e3,e4,e5,e6,e7,e8,e9;
	private float t1,t2,t3,t4,t5,t6,t7,t8,t9;
	private List<Float> valueList;
	public void ok(View view){
		valueList=new ArrayList<Float>();
		valueList.clear();
		iv.setImageBitmap(null);
		t1=Float.valueOf(e1.getText().toString());
		valueList.add(t1);
		t2=Float.valueOf(e2.getText().toString());
		valueList.add(t2);
		t3=Float.valueOf(e3.getText().toString());
		valueList.add(t3);
		t4=Float.valueOf(e4.getText().toString());
		valueList.add(t4);
		t5=Float.valueOf(e5.getText().toString());
		valueList.add(t5);
		t6=Float.valueOf(e6.getText().toString());
		valueList.add(t6);
		t7=Float.valueOf(e7.getText().toString());
		valueList.add(t7);
		t8=Float.valueOf(e8.getText().toString());
		valueList.add(t8);
		t9=Float.valueOf(e9.getText().toString());
		valueList.add(t9);
		float[] imageMatrix=new float[9];
		for (int i = 0; i <valueList.size(); i++) {
			imageMatrix[i]=valueList.get(i);
		}
		matrix=new Matrix();
		matrix.setValues(imageMatrix);//類似ColorMatrix中的setColorFilter方法
		canvas.drawBitmap(baseBitmap, matrix, paint);
		iv.setImageBitmap(copyBitmap);
	}
	public void reset(View view){
		iv.setImageBitmap(null);
		e1.setText("1");
		e2.setText("0");
		e3.setText("0");
		e4.setText("0");
		e5.setText("1");
		e6.setText("0");
		e7.setText("0");
		e8.setText("0");
		e9.setText("1");
	}
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		initView();
		initData();
	}
	private void initView() {
		registerAllViewId();
		registerAllViewEvent();
	}
	private void registerAllViewEvent() {

	}
	private void registerAllViewId() {
		iv=(ImageView) findViewById(R.id.iv);
		e1=(EditText) findViewById(R.id.e1);
		e2=(EditText) findViewById(R.id.e2);
		e3=(EditText) findViewById(R.id.e3);
		e4=(EditText) findViewById(R.id.e4);
		e5=(EditText) findViewById(R.id.e5);
		e6=(EditText) findViewById(R.id.e6);
		e7=(EditText) findViewById(R.id.e7);
		e8=(EditText) findViewById(R.id.e8);
		e9=(EditText) findViewById(R.id.e9);
	}
	private void initData() {
		baseBitmap=BitmapFactory.decodeResource(getResources(), R.drawable.pre);
		copyBitmap=Bitmap.createBitmap(baseBitmap.getWidth(), baseBitmap.getHeight(), baseBitmap.getConfig());
		canvas=new Canvas(copyBitmap);
		paint=new Paint();

	}

}

執行結果:




實際上,除了我們通過底層的方法,直接修改他們的變換矩陣的值來達到某種變換的效果,實際上android已經封裝一些變換的API介面
例如:
旋轉變換:matrix.setRotate() 平移變換 matrix.setTranslate() 縮放變換 matrix.setScale() 錯切變換 matrix.setSkew()
不過android中還提供兩個非常重要的方法一個是pre(),一個是post() 提供矩陣的前乘和後乘運算。
注意: 以上的幾個set方法都會重新清空重置矩陣中的值,而有時候我需要實現疊加的效果,就單單用set方法是無法完成,因為第二次疊加的變換,會把上一次的
第一次變換的矩陣值給清空,只會儲存最近一次的矩陣中的值。所以android 就給我們提供pre和post方法,這兩個方法可實現矩陣混合效果,從而實現圖形變換的
疊加。例如:
先移動到點(400,400),在旋轉45度,最後平移到(200,200)
用以上例子來說明pre(先乘)和post(後乘)運算的區別,因為在矩陣乘法中不支援交換律,所以我們需要用這兩個方法來區別實現
pre運算實現:
                                  matrix.setTranslate(200,200) matrix.preRotate(45)
post運算實現:
          matrix.setRotate(45) matrix.postTranslate(200,200)
最後我們通過一個案例來實現一個組合疊加變換的效果

package com.mikyou.dealImage;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;

public class MainActivity extends Activity {
/**
 *@author zhongqihong
 *圖片的繪製的個人總結:
 *如何去仿照一張原圖去重新繪製一張圖片呢??
 *需要如下材料:
 *1、需要參考的原圖
 *2、畫紙(實際上就是一張沒有任何內容的圖片,空白圖片)
 *3、畫布(就是用於固定畫紙的)
 *4、畫筆(就是用於繪製圖片的工具)
 *其實一般分為如下幾步:
 *1、首先必須拿到原圖構造出一張空畫紙(即空白圖片),知道原圖的大小,解析度等資訊,從而就可以確定我的畫紙的大小,及所確定的解析度(預設使用原圖的解析度)。
 *		baseBitmap= BitmapFactory.decodeResource(getResources(),R.drawable.img_small_1);//拿到原圖物件
 *	   copyBitmap=Bitmap.createBitmap(baseBitmap.getWidth(),baseBitmap.getHeight(), baseBitmap.getConfig());//根據原圖物件的相關資訊,得到同大小的畫紙,包括原圖解析度
 *2、然後,確定了一個空白的畫紙後,就需要將我們的畫紙固定在我們的畫布上
 *	Canvas canvas=new Canvas(copyBitmap);
 *3、接著就是構造一個畫筆物件
 * Paint paint=new Paint();
 * 4、接著對圖片進行一系列的操作,諸如:縮放、平移、旋轉等操作
 * Matrix matrix =new Matrix();
 * 縮放:(縮放中心點:預設是畫紙左上角點的座標)
 * matrix.setScale(1.5f,1.5f);//第一個引數為X軸上的縮放比例為1.5(>1表示放大,<1表示縮小)此處就表示在XY軸上分別放大為原圖1.5倍
 * matrix.setScale(-1f,1f);//表示在X軸反向縮放,Y軸不變,即相當於把原圖往X軸負方向反向翻轉了一下,即相當於處理後的圖片與原圖位置(即現在的畫紙位置)關於Y負半軸對稱
 * matrix.setScale(1f,-1f);//表示在X軸不變,Y軸的,即相當於把原圖往Y軸負方向反向翻轉了一下,即相當於處理後的圖片與原圖位置(即現在的畫紙位置)關於X正半軸對稱
 * 5、處理完後,就需要把我們處理好的圖片繪製在畫布上形成最後的圖片
 * canvas.drawBitmap(baseBitmap,matrix,paint);//第一個引數表示原圖的Bitmap物件,表示按照原圖的樣式內容在原來相同規格空白的紙上畫出圖片內容
		iv.setImageBitmap(copyBitmap);//最後將圖片的顯示在ImageView控制元件上
 * 個人理解:感覺整個過程很像PS中置入一張圖片到一張空白畫紙上,首先我們
 * 需要去設定一張空白的畫紙,然後將我們的原圖置入,在置入之前我們可以做些對圖片的操作
 * 包括縮放,平移,旋轉,確定一些操作後,點選確定就相當於呼叫繪製方法,從而就形成一張處理後的圖片
 * 
 * */

	private ImageView iv;
	private Bitmap baseBitmap;//原圖
	private Bitmap copyBitmap;//畫紙
	private Canvas canvas;//畫布
	private Paint paint;//畫筆
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		iv= (ImageView) findViewById(R.id.iv2);
	}
	public void btn(View view){
		//拿到原圖:得到原圖大小
		baseBitmap= BitmapFactory.decodeResource(getResources(),R.drawable.img_small_1);
		//1、拿到一張與原圖一樣大小的紙,並沒有內容
		copyBitmap=Bitmap.createBitmap(1200,1300, baseBitmap.getConfig());//拿到原圖的解析度和原圖的Config
		//2、將這張紙固定在畫布上
		Canvas canvas=new Canvas(copyBitmap);
		//3、畫筆
		paint=new Paint();
		paint.setAlpha(100);
		//4、加入一些處理的規則
		Matrix matrix=new Matrix();
		//1、縮放規則
		//matrix.setScale(1.5f,1.5f);
		//2、位移,
		//   matrix.setTranslate(50f,50f);//分別在x軸和Y軸上位移50,針對圖片的左上角為原點來移動
		//3、旋轉
		//matrix.setRotate(45f);//代表順時針旋轉45度,預設以左上角為原點
		// matrix.setRotate(45, baseBitmap.getWidth()/2, baseBitmap.getHeight()/2);//第二個引數和第三個引數表示旋轉中心的點的X,Y座標
		//4、翻轉鏡面效果
		// matrix.setScale(-1f, 1f);
		// matrix.setTranslate(baseBitmap.getWidth(), 0);
		//  matrix.postTranslate(baseBitmap.getWidth(), 0);//如果要對圖片進行多次操作,就要用post的方法來操作
		//5、倒影效果
		matrix.setScale(1f, -1f);//先把圖片在y軸方向反向縮放
		matrix.postTranslate(0, baseBitmap.getHeight());//然後再把反向縮放後的圖片移到canvas上顯示即可 
		matrix.postTranslate(baseBitmap.getWidth()+160, 0);
		matrix.postSkew(-1, 0);
		//5、將處理過的圖片畫出來
		canvas.drawBitmap(baseBitmap,matrix,paint);//第一個引數表示原圖的Bitmap物件,表示按照原圖的樣式內容在原來相同規格空白的紙上畫出圖片內容
		iv.setImageBitmap(copyBitmap);//最後將圖片的顯示在ImageView控制元件上

	}

}

執行效果:


與ColorMatrix顏色矩陣類似,在圖形變換中也有一個針對每一個畫素塊做處理,而在顏色變換是針對每個畫素點。針對畫素快的處理我們使用
drawBitmapMesh()方法來處理。
drawBitmapMesh()和處理每畫素點類似,只不是將影象分成一個一個的小塊然後針對每個小塊來改變整個影象
drawBitmapMesh()方法很是重要利用可以實現很多的圖片的特效。下面我們就重點來介紹一下這個方法:
drawBitmapMesh(bitmap, meshWidth, meshHeight, verts, vertOffset, colors, colorOffset, paint)
官方原始碼是這樣介紹這個方法的:
    /**
     * Draw the bitmap through the mesh, where mesh vertices are evenly
     * distributed across the bitmap. There are meshWidth+1 vertices across, and
     * meshHeight+1 vertices down. The verts array is accessed in row-major
     * order, so that the first meshWidth+1 vertices are distributed across the
     * top of the bitmap from left to right. A more general version of this
     * method is drawVertices().
     *
     * @param bitmap The bitmap to draw using the mesh
     * @param meshWidth The number of columns in the mesh. Nothing is drawn if
     *                  this is 0
     * @param meshHeight The number of rows in the mesh. Nothing is drawn if
     *                   this is 0
     * @param verts Array of x,y pairs, specifying where the mesh should be
     *              drawn. There must be at least
     *              (meshWidth+1) * (meshHeight+1) * 2 + vertOffset values
     *              in the array
     * @param vertOffset Number of verts elements to skip before drawing
     * @param colors May be null. Specifies a color at each vertex, which is
     *               interpolated across the cell, and whose values are
     *               multiplied by the corresponding bitmap colors. If not null,
     *               there must be at least (meshWidth+1) * (meshHeight+1) +
     *               colorOffset values in the array.
     * @param colorOffset Number of color elements to skip before drawing
     * @param paint  May be null. The paint used to draw the bitmap
     */
它大概的意思的是這樣的,通過網格來繪製圖片,那麼這張圖片將會被meshWidth畫出橫向格子,meshHeight畫出縱向的格子
那麼在橫向上將會產生meshWidth+1個交叉點,在縱向上會產生meshHeight+1個交叉點,最後總的交叉點個數為(meshWidth+1)*(meshHeight+1)
bitmap: 需要繪製在網格上的影象。
meshWidth: 網格的寬度方向的數目(列數),為0時不繪製圖像。
meshHeight:網格的高度方向的數目(含數),為0時不繪製圖像。
verts: (x,y)對的陣列,表示網格頂點的座標,至少需要有(meshWidth+1) * (meshHeight+1) * 2 + meshOffset 個(x,y)座標。
vertOffset: verts陣列中開始跳過的(x,y)對的數目。
Colors: 可以為空,不為空為沒個頂點定義對應的顏色值,至少需要有(meshWidth+1) * (meshHeight+1) * 2 + meshOffset 個(x,y)座標。
colorOffset: colors陣列中開始跳過的(x,y)對的數目。
paint: 可以為空。
下面將通過一個自定義View方法來實現,旗幟飄揚的圖片控制元件。並且新增自定義屬性,下次使用可以方便的在XMl中更換圖片
修改劃分方格數,振幅大小,頻率大小
實現整體思路如下:
針對畫素塊來實現圖形扭曲的原理:
它的原理就是通過修改劃分後的小方格產生交叉點的座標,來實現圖片的扭曲
本案例實現一個旗幟飄動形狀的圖片
大致的思路如下:
實現思路整體分兩部分,第一部分取得所有扭曲前圖片的所有交叉點的座標
並把這些交叉點座標儲存在orig陣列中;第二部分修改原圖中方格每個交叉點的座標,遍歷這個陣列,然後通過某種演算法
使得這些交叉點的座標,呈某種規律函式曲線變化。這裡就以三角函式中的正弦函式
來改變這些交叉點座標,從而產生每個交叉點新的座標,然後再將這些新的座標儲存在verts陣列中,最後通過drawBitmapMesh(bitmap, meshWidth, meshHeight, verts, vertOffset, colors, colorOffset, paint)
實現圖片的繪製。

自定義屬性:attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <attr name="Src" format="reference"></attr>
    <attr name="Amplitude" format="integer"></attr>
    <attr name="RowNum" format="integer"></attr>
    <attr name="ColumnNum" format="integer"></attr>
    <attr name="Frequency" format="float"></attr>

    <declare-styleable name="MikyouBannerView">
        <attr name="Src"></attr>
        <attr name="Amplitude"></attr>
        <attr name="Frequency"></attr>
        <attr name="RowNum"></attr>
        <attr name="ColumnNum"></attr>
    </declare-styleable>

</resources>

自定義View的MyBannerImageView類:
package com.mikyou.myview;

import com.mikyou.piexkuai.R;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.Window;
import android.widget.ImageView;
/**
 * @author mikyou
 * 針對畫素塊來實現圖形扭曲的原理:
 * 它的原理就是通過修改劃分後的小方格產生交叉點的座標,來實現圖片的扭曲
 * 本案例實現一個旗幟飄動形狀的圖片
 * 大致的思路如下:
 * 實現思路整體分兩部分,第一部分取得所有扭曲前圖片的所有交叉點的座標
 * 並把這些交叉點座標儲存在orig陣列中;第二部分修改原圖中方格每個交叉點的座標,遍歷這個陣列,然後通過某種演算法
 * 使得這些交叉點的座標,呈某種規律函式曲線變化。這裡就以三角函式中的正弦函式
 * 來改變這些交叉點座標,從而產生每個交叉點新的座標,然後再將這些新的座標儲存在
 * verts陣列中,最後通過drawBitmapMesh(bitmap, meshWidth, meshHeight, verts, vertOffset, colors, colorOffset, paint)
 * 實現圖片的繪製。
 * */
public class MyBannerImageView extends View{
	//定義兩個常量表示需要將這張圖片劃分成20*20=400個小方格,//定義兩個常量,這兩個常量指定該圖片橫向,縱向上都被劃分為20格  
	private  int WIDTH=40;//橫向劃分的方格數目
	private  int HEIGHT=40;//縱向劃分的方格數目
	private float FREQUENCY=0.1f;//三角函式的頻率大小
	private int AMPLITUDE=60;//三角函式的振幅大小
	//那麼將會產生21*21=421個交叉點
	private  int POINT_COUNT=(WIDTH+1)*(HEIGHT+1);
	//由於,我要儲存一個座標資訊,一個座標包括x,y兩個值的資訊,相鄰2個值儲存為一個座標點
	//其實大家應該都認為這樣不好吧,還不如直接寫一個類來直接儲存一個點的資訊,但是沒辦法
	// 但是在drawBitmapMesh方法中傳入的是一個verts陣列,該陣列就是儲存所有點的x,y座標全都放在一起
	//所以,我就只能這樣去控制定義orig和verts陣列了,
	private Bitmap baseBitmap;
	private float[] orig=new float[POINT_COUNT*2];//乘以2是因為x,y值是一對的。
	private float[] verts=new float[POINT_COUNT*2];
	private float k;
	public MyBannerImageView(Context context, AttributeSet attrs,
			int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		//接收自定義屬性值
		TypedArray array=context.obtainStyledAttributes(attrs, R.styleable.MikyouBannerView);
		for (int i = 0; i < array.getIndexCount(); i++) {
			int attr=array.getIndex(i);
			switch (attr) {
			case R.styleable.MikyouBannerView_Src:
				baseBitmap=BitmapFactory.decodeResource(getResources(), array.getResourceId(attr, R.drawable.ic_launcher));
				break;
			case R.styleable.MikyouBannerView_ColumnNum:
				HEIGHT=array.getInt(attr, 40);
				break;
			case R.styleable.MikyouBannerView_RowNum:
				WIDTH=array.getInt(attr, 40);
				break;
			case R.styleable.MikyouBannerView_Amplitude:
				AMPLITUDE=array.getInt(attr, 60);
				break;
			case R.styleable.MikyouBannerView_Frequency:
				FREQUENCY=array.getFloat(attr, 0.1f);
				break;
			default:
				break;
			}
		}
		array.recycle();
		initData();
	}
	public MyBannerImageView(Context context, AttributeSet attrs) {
		this(context, attrs,0);
	}
	public MyBannerImageView(Context context) {
		this(context,null);
	}
	
	//set,gfanfg
	public int getWIDTH() {
		return WIDTH;
	}
	public void setWIDTH(int wIDTH) {
		WIDTH = wIDTH;
	}
	public int getHEIGHT() {
		return HEIGHT;
	}
	public void setHEIGHT(int hEIGHT) {
		HEIGHT = hEIGHT;
	}
	public float getFREQUENCY() {
		return FREQUENCY;
	}
	public void setFREQUENCY(float fREQUENCY) {
		FREQUENCY = fREQUENCY;
	}
	public int getAMPLITUDE() {
		return AMPLITUDE;
	}
	public void setAMPLITUDE(int aMPLITUDE) {
		AMPLITUDE = aMPLITUDE;
	}
	public Bitmap getBaseBitmap() {
		return baseBitmap;
	}
	public void setBaseBitmap(Bitmap baseBitmap) {
		this.baseBitmap = baseBitmap;
	}
	@Override
	protected void onDraw(Canvas canvas) {
		flagWave();
		k+=FREQUENCY;
		canvas.drawBitmapMesh(baseBitmap, WIDTH, HEIGHT, verts, 0, null, 0, null);
		invalidate();
	}
	private void initData() {
		float baseBitmapWidth=baseBitmap.getWidth();
		float baseBitmapHeight=baseBitmap.getHeight();
		int index=0;
		//通過遍歷所有的劃分後得到的畫素塊,得到原圖中每個交叉點的座標,並把它們儲存在orig陣列中
		for (int i = 0; i <= HEIGHT; i++) {//因為這個陣列是採取行優先原則儲存點的座標,所以最外層為縱向的格子數,然後一行一行的遍歷
			float fy=baseBitmapHeight*i/HEIGHT;//得到每行中每個交叉點的y座標,同一行的y座標一樣
			for (int j = 0; j <= WIDTH; j++) {
				float fx=baseBitmapHeight*j/WIDTH;//得到每行中的每個交叉點的x座標,同一列的x座標一樣
				orig[index*2+0]=verts[index*2+0]=fx;//儲存每行中每個交叉點的x座標,為什麼是index*2+0作為陣列的序號呢??
				//因為我們之前也說過這個陣列既儲存x座標也儲存y座標,所以每個點就佔有2個單位陣列空間
				orig[index*2+1]=verts[index*2+1]=fy+200;//儲存每行中每個交叉點的y座標.為什麼需要+1呢?正好取x座標相鄰的下標的元素的值
				//+200是為了避免等下在正弦函式扭曲下,會把上部分給擋住所以下移200
				index++;
			}
		}	
	}
	/**
	 * @author mikyou
	 * 加入三角函式正弦函式Sinx的演算法,來修改原圖陣列儲存的交叉點的座標
	 * 從而得到旗幟飄揚的效果,這裡我們只修改y座標,x座標保持不變
	 * */
	public void flagWave(){
		for (int i = 0; i <=HEIGHT ; i++) {
			for (int j = 0; j <WIDTH; j++) {
				verts[(i*(WIDTH+1)+j)*2+0]+=0;
				float offSetY=(float)Math.sin((float)j/WIDTH*2*Math.PI+Math.PI*k);
				verts[(i*(WIDTH+1)+j)*2+1]=orig[(i*(WIDTH+1)+j)*2+1]+offSetY*AMPLITUDE;
			}
		}
	}
}

activity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:mikyou="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <com.mikyou.myview.MyBannerImageView
        android:id="@+id/iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        mikyou:Amplitude="100"
        mikyou:ColumnNum="20"
        mikyou:Frequency="0.1"
        mikyou:RowNum="20"
        mikyou:Src="@drawable/pre" />

</LinearLayout>
執行的結果;

注意:不好意思,因為沒有錄製GIF所以看不出動態效果,大家可以下載Demo看看.