1. 程式人生 > >Android 3D畫廊採用Gallery實現無限迴圈、自動輪播

Android 3D畫廊採用Gallery實現無限迴圈、自動輪播

公司最近有一個需求,是打算做一個輪播圖的展示介面,不過和傳統意義上不同,並非是在手機app的頂部展示幾張定時切換的固定大小寬高的圖片,而是中間長方形,兩邊向裡傾斜,形成對稱感的特殊介面,如下圖:


需要實現功能:無限迴圈,自動跳轉,倒影效果。

(原本的企劃是動畫輪播的時候,下面會呈現一條Listview,裡面會因為展示的不同介面而呈現不同的內容,但是後面放棄了。)

下面開始上程式碼:

MainActivity:

package com.example.gallery;

import com.example.gallery.view.MyGallery;
import com.example.gallery.view.ImageUtil;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Gallery.LayoutParams;
import android.widget.ImageView;
import android.widget.Toast;

import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends Activity {

    /**
     * 圖片資源陣列
     */
    private int[] imageResIDs;
    private MyGallery gallery;
    private int index = 0;
    private final int AUTOPLAY = 2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        imageResIDs = new int[]{R.drawable.a00, R.drawable.a01, R.drawable.a02, R.drawable.a03,
                R.drawable.a04, R.drawable.a05,};

        gallery = (MyGallery) findViewById(R.id.mygallery);
        ImageAdapter adapter = new ImageAdapter();
        gallery.setAdapter(adapter);
        gallery.setSpacing(50); //圖片之間的間距
        gallery.setSelection((Integer.MAX_VALUE / 2) - (Integer.MAX_VALUE / 2) % imageResIDs.length);

        gallery.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {

            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {
            }
        });

        // 設定點選事件監聽
        gallery.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Toast.makeText(MainActivity.this, "當前位置position:"+position+"的圖片被選中了", Toast.LENGTH_SHORT).show();
            }
        });

        Timer timer = new Timer();
        timer.schedule(task, 3000, 3000);

    }

    /**
     * 定時器,實現自動播放
     */
    private TimerTask task = new TimerTask() {
        @Override
        public void run() {
            Message message = new Message();
            message.what = AUTOPLAY;
            index = gallery.getSelectedItemPosition();
            index++;
            handler.sendMessage(message);
        }
    };

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case AUTOPLAY:
                    gallery.setSelection(index);
                    break;
                default:
                    break;
            }
        }
    };

    public class ImageAdapter extends BaseAdapter {

        @Override
        public int getCount() {
            return Integer.MAX_VALUE;//用於迴圈滾動
        }

        @Override
        public Object getItem(int position) {
            if (position >= imageResIDs.length) {
                position = position % imageResIDs.length;
            }

            return position;
        }

        @Override
        public long getItemId(int position) {
            if (position >= imageResIDs.length) {
                position = position % imageResIDs.length;
            }

            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            ImageView imageView;
            if (convertView != null) {
                imageView = (ImageView) convertView;
            } else {
                imageView = new ImageView(MainActivity.this);
            }

            if (position >= imageResIDs.length) {
                position = position % imageResIDs.length;
            }

            Bitmap bitmap = ImageUtil.getImageBitmap(getResources(),
                    imageResIDs[position]);
            BitmapDrawable drawable = new BitmapDrawable(bitmap);
            drawable.setAntiAlias(true); // 消除鋸齒
            imageView.setImageDrawable(drawable);
            LayoutParams params = new LayoutParams(240, 320);
            imageView.setLayoutParams(params);
            return imageView;
        }
    }
}
備註:自己根據介面適當設定圖片間距,這樣會呈現不同的展示效果。

自定義Gallery:

package com.example.gallery.view;

import android.content.Context;
import android.graphics.Camera;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Transformation;
import android.widget.Gallery;
import android.widget.ImageView;

public class MyGallery extends Gallery {

	/** Gallery的中心點 */
	private int galleryCenterPoint = 0;
	/** 攝像機物件 */
	private Camera camera;

	public MyGallery(Context context, AttributeSet attrs) {
		super(context, attrs);
		// 啟動getChildStaticTransformation
		setStaticTransformationsEnabled(true);
		camera = new Camera();
	}

	/**
	 * 當Gallery的寬和高改變時回撥此方法,第一次計算gallery的寬和高時,也會呼叫此方法
	 */
	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);

		galleryCenterPoint = getGalleryCenterPoint();

	}

	/**
	 * 返回gallery的item的子圖形的變換效果
	 * 
	 * @param t
	 *            指定當前item的變換效果
	 */
	@Override
	protected boolean getChildStaticTransformation(View child, Transformation t) {
		int viewCenterPoint = getViewCenterPoint(child); // view的中心點
		int rotateAngle = 0; // 旋轉角度,預設為0

		// 如果view的中心點不等於gallery中心,兩邊圖片需要計算旋轉的角度
		if (viewCenterPoint != galleryCenterPoint) {
			// gallery中心點 - view中心點 = 差值
			int diff = galleryCenterPoint - viewCenterPoint;
			// 差值 / 圖片的寬度 = 比值
			float scale = (float) diff / (float) child.getWidth();
			// 比值 * 最大旋轉角度 = 最終view的旋轉角度(最大旋轉角度定為50度)
			rotateAngle = (int) (scale * 50);

			if (Math.abs(rotateAngle) > 50) {// 當最終旋轉角度 》 最大旋轉角度,要改成50或-50
				rotateAngle = rotateAngle > 0 ? 50 : -50;
		}
		}

		// 設定變換效果前,需要把Transformation中的上一個item的變換效果清除
		t.clear();
		t.setTransformationType(Transformation.TYPE_MATRIX); // 設定變換效果的型別為矩陣型別
		startTransformationItem((ImageView) child, rotateAngle, t);
		return true;
	}

	/**
	 * 設定變換的效果
	 * 
	 * @param iv
	 *            gallery的item
	 * @param rotateAngle
	 *            旋轉的角度
	 * @param t
	 *            變換的物件
	 */
	private void startTransformationItem(ImageView iv, int rotateAngle,
			Transformation t) {
		camera.save(); // 儲存狀態
		int absRotateAngle = Math.abs(rotateAngle);

		// 1.放大效果(中間的圖片要比兩邊的圖片大)
		camera.translate(0, 0, 100f); // 給攝像機定位
		int zoom = -250 + (absRotateAngle * 2);
		camera.translate(0, 0, zoom);

		// 2.透明度(中間的圖片完全顯示,兩邊有一定的透明度)
		int alpha = (int) (255 - (absRotateAngle * 2.5));
		iv.setAlpha(alpha);

		// 3.旋轉(中間的圖片沒有旋轉角度,只要不在中間的圖片都有旋轉角度)
		camera.rotateY(rotateAngle);

		Matrix matrix = t.getMatrix(); // 變換的矩陣,將變換效果新增到矩陣中
		camera.getMatrix(matrix); // 把matrix矩陣給camera物件,camera物件會把上面新增的效果轉換成矩陣新增到matrix物件中
		matrix.preTranslate(-iv.getWidth() / 2, -iv.getHeight() / 2); // 矩陣前乘
		matrix.postTranslate(iv.getWidth() / 2, iv.getHeight() / 2); // 矩陣後乘

		camera.restore(); // 恢復之前儲存的狀態
	}

	/**
	 * 獲取Gallery的中心點
	 * 
	 * @return
	 */
	private int getGalleryCenterPoint() {
		return this.getWidth() / 2;
	}

	/**
	 * 獲取item上view的中心點
	 * 
	 * @param v
	 * @return
	 */
	private int getViewCenterPoint(View v) {
		return v.getWidth() / 2 + v.getLeft(); // 圖片寬度的一半+圖片距離螢幕左邊距
	}

}
對圖片進行處理:
package com.example.gallery.view;

import java.lang.ref.SoftReference;
import java.util.Hashtable;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Shader.TileMode;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.util.Log;

public class ImageUtil {

	private static final String TAG = "ImageUtil";
	/** 快取集合 */
	private static Hashtable<Integer, SoftReference<Bitmap>> mImageCache = new Hashtable<Integer, SoftReference<Bitmap>>();

	/**
	 * 根據id返回一個處理後的圖片
	 * 
	 * @param res
	 * @param resID
	 * @return
	 */
	public static Bitmap getImageBitmap(Resources res, int resID) {
		// 先去集合中取當前resID是否已經拿過圖片,如果集合中有,說明已經拿過,直接使用集合中的圖片返回
		SoftReference<Bitmap> reference = mImageCache.get(resID);
		if (reference != null) {
			Bitmap bitmap = reference.get();
			if (bitmap != null) {// 從記憶體中取
				Log.i(TAG, "從記憶體中取");
				return bitmap;
			}
		}
		// 如果集合中沒有,就呼叫getInvertImage得到一個圖片,需要向集合中保留一張,最後返回當前圖片
		Log.i(TAG, "重新載入");
		Bitmap invertBitmap = getInvertBitmap(res, resID);
		// 在集合中儲存一份,便於下次獲取時直接在集合中獲取
		mImageCache.put(resID, new SoftReference<Bitmap>(invertBitmap));
		return invertBitmap;
	}

	/**
	 * 根據圖片的id,獲取到處理之後的圖片
	 * 
	 * @param resID
	 * @return
	 */
	public static Bitmap getInvertBitmap(Resources res, int resID) {
		// 1.獲取原圖
		Bitmap sourceBitmap = BitmapFactory.decodeResource(res, resID);

		// 2.生成倒影圖片
		Matrix m = new Matrix(); // 圖片矩陣
		m.setScale(1.0f, -1.0f); // 讓圖片按照矩陣進行反轉
		Bitmap invertBitmap = Bitmap.createBitmap(sourceBitmap, 0,
				sourceBitmap.getHeight() / 2, sourceBitmap.getWidth(),
				sourceBitmap.getHeight() / 2, m, false);

		// 3.兩張圖片合成一張圖片
		Bitmap resultBitmap = Bitmap.createBitmap(sourceBitmap.getWidth(),
				(int) (sourceBitmap.getHeight() * 1.5 + 5), Config.ARGB_8888);
		Canvas canvas = new Canvas(resultBitmap); // 為合成圖片指定一個畫板
		canvas.drawBitmap(sourceBitmap, 0f, 0f, null); // 將原圖片畫在畫布的上方
		canvas.drawBitmap(invertBitmap, 0f, sourceBitmap.getHeight() + 5, null); // 將倒影圖片畫在畫布的下方

		// 4.新增遮罩效果
		Paint paint = new Paint();
		// 設定遮罩的顏色,這裡使用的是線性梯度
		LinearGradient shader = new LinearGradient(0,
				sourceBitmap.getHeight() + 5, 0, resultBitmap.getHeight(),
				0x70ffffff, 0x00ffffff, TileMode.CLAMP);
		paint.setShader(shader);
		// 設定模式為:遮罩,取交集
		paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
		canvas.drawRect(0, sourceBitmap.getHeight() + 5,
				sourceBitmap.getWidth(), resultBitmap.getHeight(), paint);

		return resultBitmap;
	}
}

佈局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@android:color/black">

    <com.example.gallery.view.MyGallery
        android:id="@+id/mygallery"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </com.example.gallery.view.MyGallery>

</RelativeLayout>

~Demo下載連結~