首页 > 代码库 > 自定义Gallery控件实现简单3D图片浏览器

自定义Gallery控件实现简单3D图片浏览器

本篇文章主要介绍如何使用自定义的Gallery控件,实现3D效果的图片浏览器的效果。

话不多说,先看效果。


上面是一个自定义的Gallery控件,实现倒影和仿3D的效果,下面是一个图片查看器,点击上面的小图片,可以在下面查看大图片。

下面重点说一下,实现图片查看器的思路。

1.手机中图片路径的获取

首先,先不管图片如何展示,如果我们想实现图片查看器的功能,我们首先需要做的是获取到所有的图片的路径信息,只有这样,我们才能实现对图片的查看。

我们可以使用下面的代码实现

private List<String> getImagesFromSD() {
		List<String> imageList = new ArrayList<String>();
		File f = Environment.getExternalStorageDirectory();
		if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
			f = new File(Environment.getExternalStorageDirectory().toString());
		} else {
			Toast.makeText(MainActivity.this, R.string.sdcarderror, Toast.LENGTH_LONG).show();
			return imageList;
		}

		File[] files = f.listFiles();
		if (files == null || files.length == 0)
			return imageList;

		for (int i = 0; i < files.length; i++) {
			File file = files[i];
			if (isImageFile(file.getPath()))
				imageList.add(file.getPath());
		}
		return imageList;
	}

上面这个方法的作用,就是获取SD卡中,所有文件的后缀名满足图片后缀名的文件的路径,然后放到List容器中返回。

isImageFile方法是这样实现的

private boolean isImageFile(String fName) {
		String end = fName.substring(fName.lastIndexOf(".") + 1, fName.length()).toLowerCase();
		if (end.equals("jpg") || end.equals("gif") || end.equals("png") || end.equals("jpeg") || end.equals("bmp")) {
			return true;
		}
		return false;

	}

2.上方小图片3D效果展示的实现

在完成了图片路径的获取之后,我们下面要做的就是将图片展示在上面的有3D效果的自定义Gallery控件上面。现在版本中Gallery控件已经不再推荐使用,取而代之的ViewPager和HorizonalScrollView控件。
下面介绍具有自定义Gallery控件的实现


/**
 * 3D效果Gallery实现
 * 
 * @time 2014年6月26日 下午9:10:47
 */
@SuppressWarnings("deprecation")
public class GalleryFlow extends Gallery {

	private Camera mCamera = new Camera();
	// 两侧图片最大旋转角度
	private int mMaxRotationAngle = 60;
	private int mMaxZoom = -120;
	private int mCoveflowCenter;

	public GalleryFlow(Context context) {
		super(context);
		this.setStaticTransformationsEnabled(true);
	}

	public GalleryFlow(Context context, AttributeSet attrs) {
		super(context, attrs);
		this.setStaticTransformationsEnabled(true);
	}

	public GalleryFlow(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		this.setStaticTransformationsEnabled(true);
	}

	// 设置最大旋转角
	public void setMaxRotationAngle(int maxRotationAngle) {
		mMaxRotationAngle = maxRotationAngle;
	}

	// 获取当前控件中心点x轴位置
	private int getCenterOfCoverflow() {
		return (getWidth() - getPaddingLeft() - getPaddingRight()) / 2 + getPaddingLeft();
	}

	// 获取view控件的x轴位置
	private static int getCenterOfView(View view) {
		return view.getLeft() + view.getWidth() / 2;
	}

	// 默认返回值是false,若设置城true,则每次gallery生成子控件的时候,都会调用这个方法,所以我们可以将返回值设置为true,然后完成child的旋转等变形操作
	protected boolean getChildStaticTransformation(View child, Transformation t) {

		final int childCenter = getCenterOfView(child);
		final int childWidth = child.getWidth();
		int rotationAngle = 0;
		t.clear();
		t.setTransformationType(Transformation.TYPE_MATRIX);

		// 如果child控件在中心位置,则不旋转
		if (childCenter == mCoveflowCenter) {
			transformImageBitmap((ImageView) child, t, 0);

		} else {
			// 否则,将当前child控件旋转一定角度
			rotationAngle = (int) (((float) (mCoveflowCenter - childCenter) / childWidth) * mMaxRotationAngle);
			if (Math.abs(rotationAngle) > mMaxRotationAngle) {
				rotationAngle = (rotationAngle < 0) ? -mMaxRotationAngle : mMaxRotationAngle;
			}
			transformImageBitmap((ImageView) child, t, rotationAngle);
		}

		return true;
	}

	//重新计算控件的x轴位置
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		mCoveflowCenter = getCenterOfCoverflow();
		super.onSizeChanged(w, h, oldw, oldh);
	}

	// 将child控件旋转rotationAngle方法的实现
	private void transformImageBitmap(ImageView child, Transformation t, int rotationAngle) {

		mCamera.save();
		final Matrix imageMatrix = t.getMatrix();
		final int imageHeight = child.getLayoutParams().height;
		final int imageWidth = child.getLayoutParams().width;
		final int rotation = Math.abs(rotationAngle);

		// 在Z轴上正向移动camera的视角,实际效果为放大图片。 如果在Y轴上移动,则图片上下移动;X轴上对应图片左右移动。
		mCamera.translate(0.0f, 0.0f, 100.0f);
		if (rotation < mMaxRotationAngle) {
			float zoomAmount = (float) (mMaxZoom + (rotation * 1.5));
			mCamera.translate(0.0f, 0.0f, zoomAmount);
		}

		// 在Y轴上旋转,对应图片竖向向里翻转。如果在X轴上旋转,则对应图片横向向里翻转。
		mCamera.rotateY(rotationAngle);
		mCamera.getMatrix(imageMatrix);
		imageMatrix.preTranslate(-(imageWidth / 2), -(imageHeight / 2));
		imageMatrix.postTranslate((imageWidth / 2), (imageHeight / 2));

		// 恢复相机原状态
		mCamera.restore();
	}
}
自定义gallery控件,现在我们已经实现了当滑动Gallery里面的图片时,两侧的图片会发生一定角度的旋转,也就是完成了3D效果的第一部,下一步,就需要我们在Gallery的Adapter里面,对getView方法,进行改造,从而完成预览小图片的倒影效果

3.实现Adapter,完成倒影效果

要完成倒映效果,我们需要在getView方法中,对穿进来的图片进行处理,具体代码如下
@SuppressWarnings({ "deprecation", "unused" })
public class ImageAdapter extends BaseAdapter {

	private Context mContext;
	//用于存放图片的路径
	private List<String> imageFileList;
	//原始图片
	private Bitmap originalImage;
	//反射的倒影图片,高度为原始图片一半
	private Bitmap reflectionImage;
	//用于存放处理后的整个图片,高度为原始图片的1.5倍
	private Bitmap bitmapWithReflection;
	//图片的宽高
	private int width;
	private int height;
	//矩阵
	private Matrix matrix;
	//画布
	private Canvas canvas;
	//原始图像与反射的倒影图像之间的间隔高度
	final int reflectionGap = 4;
	//用于getView返回
	private ImageView imageView;
	//倒影的阴影模糊效果
	private LinearGradient shader;
	
	public ImageAdapter(Context c, List<String> _imageFileList) { 
		mContext = c;
		imageFileList = _imageFileList;
		matrix = new Matrix();
		//设置为x轴翻转
		matrix.preScale(1, -1);
		
	}
	
	@Override
	public int getCount() {
		return imageFileList.size();
	}

	@Override
	public Object getItem(int position) {
		return position;
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	//返回经过处理的ImageView对象
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		return createReflectedImages(imageFileList.get(position));
	}
	
	//这是最主要的方法,完成了对图片的倒映效果处理
	public ImageView createReflectedImages(String filePath) {

		//获取原始图片
		originalImage = BitmapFactory.decodeFile(filePath);
		
		width = originalImage.getWidth();
		height = originalImage.getHeight();
		
		//创建倒影图像,高度是原始图像的一半,并且使用矩阵进行了x轴反转,也就是倒影效果
		reflectionImage = Bitmap.createBitmap(originalImage, 0, height / 2, width, height / 2, matrix, false);
		//初始化Bitmap对象,用于存放处理后的图片,高度为原始图片的1.5倍
		bitmapWithReflection = Bitmap.createBitmap(width, (height + height / 2), Config.ARGB_8888);
		//根据bitmapWithReflection对象,创建一个画布
		canvas = new Canvas(bitmapWithReflection);
		//先把原始图像画上
		canvas.drawBitmap(originalImage, 0, 0, null);
		
		Paint paint = new Paint();
		//画出原始图像与反射图像之间的小空隙,高度为reflectionGap
		canvas.drawRect(0, height, width, height + reflectionGap, paint);
		
		//画出倒影
		canvas.drawBitmap(reflectionImage, 0, height + reflectionGap, null);
		//设置画笔的阴影效果
		shader = new LinearGradient(0, originalImage.getHeight(), 0, bitmapWithReflection.getHeight() + reflectionGap, 0x70ffffff, 0x00ffffff,
				TileMode.CLAMP);
		paint.setShader(shader);
		paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));

		//在倒影图上用带阴影的画笔绘制矩形
		canvas.drawRect(0, height, width, bitmapWithReflection.getHeight() + reflectionGap, paint);
		//初始化一个ImageView对象
		imageView = new ImageView(mContext);
		//将处理后的图像设置为图片资源
		imageView.setImageBitmap(bitmapWithReflection);
		imageView.setLayoutParams(new Gallery.LayoutParams(120, 160));
		imageView.setScaleType(ScaleType.CENTER_INSIDE);
		return imageView;
	}

	
}

最主要的还是理解如何实现的倒影效果。注释应该是很详细了,不过多解释。

4.如何使用自定义的Gallery控件实现最终的图片查看器

下面,我们看一下,如何使用这个控件,实现我们最终的效果。
布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#F9F900"
    android:gravity="center_horizontal"
    android:orientation="vertical" >

    <com.examole.gallery.GalleryFlow
        android:id="@+id/mygallery"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_marginTop="20dp"
        android:gravity="center_vertical" />

    <ImageSwitcher
        android:id="@+id/switcher"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="center_horizontal" />

</LinearLayout>
我们在这里使用了一个很陌生的类,那就是ImageSwicher,我们看一下,在Activity如何使用
public class MainActivity extends Activity implements AdapterView.OnItemSelectedListener, ViewSwitcher.ViewFactory {

	private List<String> ImageList;
	private ImageSwitcher mSwitcher;
	private Gallery gallery;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		// 获取图片路径
		ImageList = getImagesFromSD();
		mSwitcher = (ImageSwitcher) findViewById(R.id.switcher);
		gallery = (Gallery) findViewById(R.id.mygallery);
		// ImageSwitcher控件必须实现ViewSwitcher.ViewFactory接口,然后在makeView方法中,返回我们需要显示的控件即可
		mSwitcher.setFactory(this);
		// 设置图片的进入和离开的动画效果
		mSwitcher.setInAnimation(AnimationUtils.loadAnimation(this, android.R.anim.slide_in_left));
		mSwitcher.setOutAnimation(AnimationUtils.loadAnimation(this, android.R.anim.slide_out_right));
		// 给gallery设置适配器
		gallery.setAdapter(new ImageAdapter(this, ImageList));
		gallery.setOnItemSelectedListener(this);

	}

	private List<String> getImagesFromSD() {
		List<String> imageList = new ArrayList<String>();
		File f = Environment.getExternalStorageDirectory();
		if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
			f = new File(Environment.getExternalStorageDirectory().toString());
		} else {
			Toast.makeText(MainActivity.this, R.string.sdcarderror, Toast.LENGTH_LONG).show();
			return imageList;
		}

		File[] files = f.listFiles();
		if (files == null || files.length == 0)
			return imageList;

		for (int i = 0; i < files.length; i++) {
			File file = files[i];
			if (isImageFile(file.getPath()))
				imageList.add(file.getPath());
		}
		return imageList;
	}

	@SuppressLint("DefaultLocale")
	private boolean isImageFile(String fName) {
		String end = fName.substring(fName.lastIndexOf(".") + 1, fName.length()).toLowerCase();
		if (end.equals("jpg") || end.equals("gif") || end.equals("png") || end.equals("jpeg") || end.equals("bmp")) {
			return true;
		}
		return false;

	}

	@Override
	public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
		// 当点击上面的小图片的时候,获取图片的绝对路径,然后设置给mSwitcher
		String photoURL = ImageList.get(position);
		mSwitcher.setImageURI(Uri.parse(photoURL));
	}

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

	}

	@Override
	public View makeView() {
		ImageView i = new ImageView(this);
		i.setBackgroundColor(0x00000000);
		i.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
		i.setLayoutParams(new ImageSwitcher.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
		return i;
	}
}

除了ImageSwitcher这个控件,其他的应该都很熟悉了,经过这几个步骤,我们终于实现了一个简单的仿3D效果的图片查看器。