Android自定義控件——3D畫廊和圖像矩陣

Android自定義控件——3D畫廊和圖像矩陣

標籤: 3DGallery矩陣變換軟引用OOM
 1880人閱讀 評論(1) 收藏 舉報
 分類:
 

目錄(?)[+]

   轉載請註明出處:http://blog.csdn.net/allen315410/article/details/39932689

1.3D畫廊的實現

       我們知道android系統已經爲我們提供好了一個展示圖片的“容器”——Gallery,但是這個Gallery顯示的效果是平面化的,動態效果不強。這裏,我們動手做一個自定義的Gallery組件,實現圖片的3D效果展示,想想應該不錯吧,先看看效果圖:


        實現這個3D效果的Gallery該怎麼做呢?首先,分析一下,

1,展示圖片,系統自帶Gallery組件,可以基於這個Gallery組件擴展我們所需要的效果。

2,展示效果需要進行3D成像。

3,展示的圖片下方需要顯示圖片的倒影。

4,展示圖片的倒影需要加上“遮罩”效果。

     好了,問題列好了,我們一個個來解決吧!代碼量不多,直接上代碼好了。

[java] view plain copy
 print?
  1. package com.example.gallery.view;  
  2.   
  3. import android.content.Context;  
  4. import android.graphics.Camera;  
  5. import android.graphics.Matrix;  
  6. import android.util.AttributeSet;  
  7. import android.view.View;  
  8. import android.view.animation.Transformation;  
  9. import android.widget.Gallery;  
  10. import android.widget.ImageView;  
  11.   
  12. @SuppressWarnings("deprecation")  
  13. public class CustomGallery extends Gallery {  
  14.   
  15.     /** Gallery的中心點 */  
  16.     private int galleryCenterPoint = 0;  
  17.     /** 攝像機對象 */  
  18.     private Camera camera;  
  19.   
  20.     public CustomGallery(Context context, AttributeSet attrs) {  
  21.         super(context, attrs);  
  22.         // 啓動getChildStaticTransformation  
  23.         setStaticTransformationsEnabled(true);  
  24.         camera = new Camera();  
  25.     }  
  26.   
  27.     /** 
  28.      * 當Gallery的寬和高改變時回調此方法,第一次計算gallery的寬和高時,也會調用此方法 
  29.      */  
  30.     @Override  
  31.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  32.         // TODO Auto-generated method stub  
  33.         super.onSizeChanged(w, h, oldw, oldh);  
  34.   
  35.         galleryCenterPoint = getGalleryCenterPoint();  
  36.   
  37.     }  
  38.   
  39.     /** 
  40.      * 返回gallery的item的子圖形的變換效果 
  41.      *  
  42.      * @param t 
  43.      *            指定當前item的變換效果 
  44.      */  
  45.     @Override  
  46.     protected boolean getChildStaticTransformation(View child, Transformation t) {  
  47.         int viewCenterPoint = getViewCenterPoint(child); // view的中心點  
  48.         int rotateAngle = 0// 旋轉角度,默認爲0  
  49.   
  50.         // 如果view的中心點不等於gallery中心,兩邊圖片需要計算旋轉的角度  
  51.         if (viewCenterPoint != galleryCenterPoint) {  
  52.             // gallery中心點 - view中心點 = 差值  
  53.             int diff = galleryCenterPoint - viewCenterPoint;  
  54.             // 差值 / 圖片的寬度 = 比值  
  55.             float scale = (float) diff / (float) child.getWidth();  
  56.             // 比值 * 最大旋轉角度 = 最終view的旋轉角度(最大旋轉角度定爲50度)  
  57.             rotateAngle = (int) (scale * 50);  
  58.   
  59.             if (Math.abs(rotateAngle) > 50) {// 當最終旋轉角度 》 最大旋轉角度,要改成50或-50  
  60.                 rotateAngle = rotateAngle > 0 ? 50 : -50;  
  61.             }  
  62.         }  
  63.   
  64.         // 設置變換效果前,需要把Transformation中的上一個item的變換效果清除  
  65.         t.clear();  
  66.         t.setTransformationType(Transformation.TYPE_MATRIX); // 設置變換效果的類型爲矩陣類型  
  67.         startTransformationItem((ImageView) child, rotateAngle, t);  
  68.         return true;  
  69.     }  
  70.   
  71.     /** 
  72.      * 設置變換的效果 
  73.      *  
  74.      * @param iv 
  75.      *            gallery的item 
  76.      * @param rotateAngle 
  77.      *            旋轉的角度 
  78.      * @param t 
  79.      *            變換的對象 
  80.      */  
  81.     private void startTransformationItem(ImageView iv, int rotateAngle,  
  82.             Transformation t) {  
  83.         camera.save(); // 保存狀態  
  84.         int absRotateAngle = Math.abs(rotateAngle);  
  85.   
  86.         // 1.放大效果(中間的圖片要比兩邊的圖片大)  
  87.         camera.translate(00, 100f); // 給攝像機定位  
  88.         int zoom = -250 + (absRotateAngle * 2);  
  89.         camera.translate(00, zoom);  
  90.   
  91.         // 2.透明度(中間的圖片完全顯示,兩邊有一定的透明度)  
  92.         int alpha = (int) (255 - (absRotateAngle * 2.5));  
  93.         iv.setAlpha(alpha);  
  94.   
  95.         // 3.旋轉(中間的圖片沒有旋轉角度,只要不在中間的圖片都有旋轉角度)  
  96.         camera.rotateY(rotateAngle);  
  97.   
  98.         Matrix matrix = t.getMatrix(); // 變換的矩陣,將變換效果添加到矩陣中  
  99.         camera.getMatrix(matrix); // 把matrix矩陣給camera對象,camera對象會把上面添加的效果轉換成矩陣添加到matrix對象中  
  100.         matrix.preTranslate(-iv.getWidth() / 2, -iv.getHeight() / 2); // 矩陣前乘  
  101.         matrix.postTranslate(iv.getWidth() / 2, iv.getHeight() / 2); // 矩陣後乘  
  102.   
  103.         camera.restore(); // 恢復之前保存的狀態  
  104.     }  
  105.   
  106.     /** 
  107.      * 獲取Gallery的中心點 
  108.      *  
  109.      * @return 
  110.      */  
  111.     private int getGalleryCenterPoint() {  
  112.         return this.getWidth() / 2;  
  113.     }  
  114.   
  115.     /** 
  116.      * 獲取item上view的中心點 
  117.      *  
  118.      * @param v 
  119.      * @return 
  120.      */  
  121.     private int getViewCenterPoint(View v) {  
  122.         return v.getWidth() / 2 + v.getLeft(); // 圖片寬度的一半+圖片距離屏幕左邊距  
  123.     }  
  124.   
  125. }  
       代碼中有註釋,大家可以看着註釋理解代碼,我在這裏要是說怎麼考慮的,顯得特別麻煩!這裏還有一個很重要的概念——矩陣,這個我留在下面去講解,往下看吧。

獲取圖片的工具類:

[java] view plain copy
 print?
  1. package com.example.gallery.view;  
  2.   
  3. import java.lang.ref.SoftReference;  
  4. import java.util.Hashtable;  
  5.   
  6. import android.content.res.Resources;  
  7. import android.graphics.Bitmap;  
  8. import android.graphics.Bitmap.Config;  
  9. import android.graphics.PorterDuff.Mode;  
  10. import android.graphics.PorterDuffXfermode;  
  11. import android.graphics.Shader.TileMode;  
  12. import android.graphics.BitmapFactory;  
  13. import android.graphics.Canvas;  
  14. import android.graphics.LinearGradient;  
  15. import android.graphics.Matrix;  
  16. import android.graphics.Paint;  
  17. import android.util.Log;  
  18.   
  19. public class ImageUtil {  
  20.   
  21.     private static final String TAG = "ImageUtil";  
  22.     /** 緩存集合 */  
  23.     private static Hashtable<Integer, SoftReference<Bitmap>> mImageCache //  
  24.     = new Hashtable<Integer, SoftReference<Bitmap>>();  
  25.   
  26.     /** 
  27.      * 根據id返回一個處理後的圖片 
  28.      *  
  29.      * @param res 
  30.      * @param resID 
  31.      * @return 
  32.      */  
  33.     public static Bitmap getImageBitmap(Resources res, int resID) {  
  34.         // 先去集合中取當前resID是否已經拿過圖片,如果集合中有,說明已經拿過,直接使用集合中的圖片返回  
  35.         SoftReference<Bitmap> reference = mImageCache.get(resID);  
  36.         if (reference != null) {  
  37.             Bitmap bitmap = reference.get();  
  38.             if (bitmap != null) {// 從內存中取  
  39.                 Log.i(TAG, "從內存中取");  
  40.                 return bitmap;  
  41.             }  
  42.         }  
  43.         // 如果集合中沒有,就調用getInvertImage得到一個圖片,需要向集合中保留一張,最後返回當前圖片  
  44.         Log.i(TAG, "重新加載");  
  45.         Bitmap invertBitmap = getInvertBitmap(res, resID);  
  46.         // 在集合中保存一份,便於下次獲取時直接在集合中獲取  
  47.         mImageCache.put(resID, new SoftReference<Bitmap>(invertBitmap));  
  48.         return invertBitmap;  
  49.     }  
  50.   
  51.     /** 
  52.      * 根據圖片的id,獲取到處理之後的圖片 
  53.      *  
  54.      * @param resID 
  55.      * @return 
  56.      */  
  57.     public static Bitmap getInvertBitmap(Resources res, int resID) {  
  58.         // 1.獲取原圖  
  59.         Bitmap sourceBitmap = BitmapFactory.decodeResource(res, resID);  
  60.   
  61.         // 2.生成倒影圖片  
  62.         Matrix m = new Matrix(); // 圖片矩陣  
  63.         m.setScale(1.0f, -1.0f); // 讓圖片按照矩陣進行反轉  
  64.         Bitmap invertBitmap = Bitmap.createBitmap(sourceBitmap, 0,  
  65.                 sourceBitmap.getHeight() / 2, sourceBitmap.getWidth(),  
  66.                 sourceBitmap.getHeight() / 2, m, false);  
  67.   
  68.         // 3.兩張圖片合成一張圖片  
  69.         Bitmap resultBitmap = Bitmap.createBitmap(sourceBitmap.getWidth(),  
  70.                 (int) (sourceBitmap.getHeight() * 1.5 + 5), Config.ARGB_8888);  
  71.         Canvas canvas = new Canvas(resultBitmap); // 爲合成圖片指定一個畫板  
  72.         canvas.drawBitmap(sourceBitmap, 0f, 0f, null); // 將原圖片畫在畫布的上方  
  73.         canvas.drawBitmap(invertBitmap, 0f, sourceBitmap.getHeight() + 5null); // 將倒影圖片畫在畫布的下方  
  74.   
  75.         // 4.添加遮罩效果  
  76.         Paint paint = new Paint();  
  77.         // 設置遮罩的顏色,這裏使用的是線性梯度  
  78.         LinearGradient shader = new LinearGradient(0,  
  79.                 sourceBitmap.getHeight() + 50, resultBitmap.getHeight(),  
  80.                 0x70ffffff0x00ffffff, TileMode.CLAMP);  
  81.         paint.setShader(shader);  
  82.         // 設置模式爲:遮罩,取交集  
  83.         paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));  
  84.         canvas.drawRect(0, sourceBitmap.getHeight() + 5,  
  85.                 sourceBitmap.getWidth(), resultBitmap.getHeight(), paint);  
  86.   
  87.         return resultBitmap;  
  88.     }  
  89. }  
      這個工具類就是獲取整個圖片的,包括實現圖片的倒影和遮罩效果,看註釋!這裏需要講解的是,如果避免OOM,這是一個較複雜的概念,不是一兩句話就能講清楚的,android下加載圖片很容易就處理OOM,當然了,避免OOM的方式有很多,我在這是使用了內存緩存機制來避免了,即使用Java給我們提供好的“軟引用”來解決。接下來,就是怎麼引用這個畫廊組件了。

[html] view plain copy
 print?
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:background="@android:color/black" >  
  6.   
  7.     <com.example.gallery.view.CustomGallery  
  8.         android:id="@+id/customgallery"  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="match_parent" >  
  11.     </com.example.gallery.view.CustomGallery>  
  12.   
  13. </RelativeLayout>  
[java] view plain copy
 print?
  1. package com.example.gallery;  
  2.   
  3. import com.example.gallery.view.CustomGallery;  
  4. import com.example.gallery.view.ImageUtil;  
  5.   
  6. import android.app.Activity;  
  7. import android.graphics.Bitmap;  
  8. import android.graphics.drawable.BitmapDrawable;  
  9. import android.os.Bundle;  
  10. import android.view.View;  
  11. import android.view.ViewGroup;  
  12. import android.widget.BaseAdapter;  
  13. import android.widget.Gallery.LayoutParams;  
  14. import android.widget.ImageView;  
  15.   
  16. public class MainActivity extends Activity {  
  17.   
  18.     /** 圖片資源數組 */  
  19.     private int[] imageResIDs;  
  20.   
  21.     @Override  
  22.     protected void onCreate(Bundle savedInstanceState) {  
  23.         super.onCreate(savedInstanceState);  
  24.         setContentView(R.layout.activity_main);  
  25.         imageResIDs = new int[]{//  
  26.         R.drawable.imgres_01, //  
  27.                 R.drawable.imgres_02, //  
  28.                 R.drawable.imgres_03, //  
  29.                 R.drawable.imgres_04, //  
  30.                 R.drawable.imgres_05, //  
  31.                 R.drawable.imgres_06, //  
  32.                 R.drawable.imgres_07, //  
  33.                 R.drawable.imgres_08, //  
  34.                 R.drawable.imgres_01, //  
  35.                 R.drawable.imgres_02, //  
  36.                 R.drawable.imgres_03, //  
  37.                 R.drawable.imgres_04, //  
  38.                 R.drawable.imgres_05, //  
  39.                 R.drawable.imgres_06, //  
  40.                 R.drawable.imgres_07, //  
  41.                 R.drawable.imgres_08 //  
  42.         };  
  43.         CustomGallery customGallery = (CustomGallery) findViewById(R.id.customgallery);  
  44.         ImageAdapter adapter = new ImageAdapter();  
  45.         customGallery.setAdapter(adapter);  
  46.     }  
  47.   
  48.     public class ImageAdapter extends BaseAdapter {  
  49.   
  50.         @Override  
  51.         public int getCount() {  
  52.             // TODO Auto-generated method stub  
  53.             return imageResIDs.length;  
  54.         }  
  55.   
  56.         @Override  
  57.         public Object getItem(int position) {  
  58.             // TODO Auto-generated method stub  
  59.             return imageResIDs[position];  
  60.         }  
  61.   
  62.         @Override  
  63.         public long getItemId(int position) {  
  64.             // TODO Auto-generated method stub  
  65.             return position;  
  66.         }  
  67.   
  68.         @Override  
  69.         public View getView(int position, View convertView, ViewGroup parent) {  
  70.             // TODO Auto-generated method stub  
  71.             ImageView imageView;  
  72.             if (convertView != null) {  
  73.                 imageView = (ImageView) convertView;  
  74.             } else {  
  75.                 imageView = new ImageView(MainActivity.this);  
  76.             }  
  77.             Bitmap bitmap = ImageUtil.getImageBitmap(getResources(),  
  78.                     imageResIDs[position]);  
  79.             BitmapDrawable drawable = new BitmapDrawable(bitmap);  
  80.             drawable.setAntiAlias(true); // 消除鋸齒  
  81.             imageView.setImageDrawable(drawable);  
  82.             LayoutParams params = new LayoutParams(240320);  
  83.             imageView.setLayoutParams(params);  
  84.             return imageView;  
  85.         }  
  86.     }  
  87. }  


===========================================華麗麗的分割線=============================================


2.Android的矩陣基礎

UI開發過程中,我們經常需要對圖片進行處理,常見的如貼圖,複雜一些的還有位置變換、旋轉、濾鏡特效等,下面簡單介紹一下關於圖片處理的一些基本知識和原理。

1 基本概念
        對於圖片的處理,最常使用到的數據結構是Bitmap,它包含了一張圖片所有的數據,這些數據數據包括那些內容呢?簡單說來就是由點陣和顏色值組成的,所謂點陣就是一個在概念上是Width * Height的矩陣,每一個元素對應着圖片的一個像素,也就是說,點陣保存着圖片的空間位置信息;而顏色值即ARGB,分別對應透明度、紅、綠、藍這四個通道分量,每個通道用8比特定義,所以一個顏色值就是一個int整型,可以表示256*256*256種顏色值。

       Android中我們常用到這麼幾個常量:ARGB_8888、ARGB_4444、RGB_565。這幾個常量其實就是告訴系統如何對圖片的顏色值進行處理,例如ARGB_8888是告訴系統透明度、R、G、B在顏色值中分別用8bit表示,這時顏色值爲32bit,這樣的定義能夠表示最多的顏色值,圖片質量也是最好的;ARGB_4444則是每個通道用4bit表示,這樣顏色值只用16bit,節省了空間,但是卻只能表示16*16*16種顏色,也就是說圖片很失去很多彩色信息;RGB_565類型的顏色值同樣是16bit,但是它丟棄了透明度信息,可以表示32*64*32種顏色值。

2 顏色矩陣
顏色矩陣是一個5*4的矩陣,用來對圖片顏色值進行處理。定義顏色矩陣和顏色值如下如下:


進行如下矩陣運算:


結果R爲4*1的矩陣,這個矩陣就是新的顏色值,R中每個通道的值分別如下:
R’ = a*R + b*G + c*B + d*A + e;
G’ = f*R + g*G + h*B + i*A + j;
B’ = k*R + l*G + m*B + n*A + o;
A’ = p*R + q*G + r*B + s*A + t;

這樣看起來或許很抽象,很難理解顏色矩陣和結果R直接的關係,我們假設顏色矩陣值如下所示:


那麼結果爲:
R’ = R;
G’ = G;
B’ = B;
A’ = A;
也就是說,新的顏色值跟原先的一樣!再看一個例子,顏色矩陣取值爲:


結果爲:
R’ = R + 100;
G’ = G + 100;
B’ = B;
A’ = A;
新的顏色值中,紅色通道值和綠色通道值分別增加了100,此時圖片會泛黃(因爲R + G = Yellow)。

從上面的幾個例子我們很容易就能明白顏色矩陣中的每個分量(每一列)的意義:
第一行決定紅色,
第二行決定綠色,
第三行決定藍色,
第四行決定了透明度,
第五列是顏色的偏移量。
至此我們應該能理解如何通過顏色矩陣來改變顏色值的各個分量了。

下面是用於Android的一段代碼,用於將圖片處理成泛黃的效果:

[java] view plain copy
 print?
  1. public static Bitmap testBitmap(Bitmap bitmap){  
  2.        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),  
  3.                bitmap.getHeight(), Config.RGB_565);  
  4.   
  5.        Canvas canvas = new Canvas(output);  
  6.   
  7.        Paint paint = new Paint();          
  8.        ColorMatrix cm = new ColorMatrix();  
  9.        float[] array = {1,0,0,0,100,  
  10.                0,1,0,0,100,  
  11.                0,0,1,0,0,  
  12.                0,0,0,1,0};  
  13.        cm.set(array);  
  14.        paint.setColorFilter(new ColorMatrixColorFilter(cm));  
  15.   
  16.        canvas.drawBitmap(bitmap, 00, paint);  
  17.        return output;  
  18.    }  
3 座標變換矩陣
對圖片的操作除了顏色值的處理外,最常用的就是空間座標的變換了,常見的效果有平移、旋轉、拉伸等,這其實也是通過一個矩陣來完成的。座標變換矩陣是一個3*3的矩陣,通過與一個類似(X,Y,1)的座標值的矩陣乘法運算,能夠將這個座標值轉換成一個新的座標值,計算過程如下:

結果爲:
x’=a*x+b*y+c
y’=d*x+e*y+f
同顏色矩陣一樣,如果座標變換矩陣如下,則新的座標值X、Y增加50,也就是說圖片的每一點都平移了(50,50)的距離,即圖片整體平移到了(50,50)座標處。


如果座標變換矩陣如下,則所有的X、Y座標都增大兩倍,也就是說圖片被放大了兩倍,其他縮放效果原理類似。


更復雜一點的還有旋轉效果,一個旋轉變換矩陣如下:

結果爲x’ = xcosθ – ysinθ 與 y’ = xsinθ + ycosθ,這個結果的效果是繞原點逆時針旋轉θ度角。

下面是用於Android的一段示例代碼,用於將圖片平移,也就是裁剪的效果,其他效果可以參照對應座標變換矩陣修改即可:

[java] view plain copy
 print?
  1. public static Bitmap test1Bitmap(Bitmap bitmap){  
  2.        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),  
  3.                bitmap.getHeight(), Config.RGB_565);  
  4.   
  5.        Canvas canvas = new Canvas(output);  
  6.   
  7.        Paint paint = new Paint();          
  8.        Matrix cm = new Matrix();  
  9.   
  10.        float[] array = {1,0,50,  
  11.                0,1,50,  
  12.                0,0,1};  
  13.        cm.setValues(array);  
  14.        canvas.drawBitmap(bitmap, cm, paint);  
  15.        return output;  
  16.    }  

下面將介紹幾種常用的變換矩陣:
1.旋轉


繞原點逆時針旋轉θ度角的變換公式是 x' = xcosθ − ysinθ 與 y' = xsinθ + ycosθ
2. 縮放

變換後長寬分別放大x'=scale*x;y'=scale*y.
3.切變

4.反射

5.正投影

        Android的圖像矩陣絕對不止這些,這是一個很複雜的知識,涉及到大學相關數學的課程,能瞭解大學線性代數裏的矩陣知識,對學習Android下的圖像矩陣有很好的幫助,在這裏限於篇幅,我只做了簡單的基礎講解,基本可以理解,可以使用即可,如果想深入學習一下的話,請查看下方的資料鏈接,去下載我今天上傳到CSDN資源庫裏面的資料。


Android圖像矩陣基礎與詳解資料

源碼請在這裏下載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章