Android自定义控件——3D画廊和图像矩阵

转载请注明出处:http://blog.csdn.net/allen315410/article/details/39932689

1.3D画廊的实现

我们知道android系统已经为我们提供好了一个展示图片的“容器”——Gallery,但是这个Gallery显示的效果是平面化的,动态效果不强。这里,我们动手做一个自定义的Gallery组件,实现图片的3D效果展示,想想应该不错吧,先看看效果图:

实现这个3D效果的Gallery该怎么做呢?首先,分析一下,

1,展示图片,系统自带Gallery组件,可以基于这个Gallery组件扩展我们所需要的效果。

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

3,展示的图片下方需要显示图片的倒影。

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

好了,问题列好了,我们一个个来解决吧!代码量不多,直接上代码好了。

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;

@SuppressWarnings("deprecation")
public class CustomGallery extends Gallery {

	/** Gallery的中心点 */
	private int galleryCenterPoint = 0;
	/** 摄像机对象 */
	private Camera camera;

	public CustomGallery(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) {
		// TODO Auto-generated method stub
		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;
	}
}

这个工具类就是获取整个图片的,包括实现图片的倒影和遮罩效果,看注释!这里需要讲解的是,如果避免OOM,这是一个较复杂的概念,不是一两句话就能讲清楚的,android下加载图片很容易就处理OOM,当然了,避免OOM的方式有很多,我在这是使用了内存缓存机制来避免了,即使用Java给我们提供好的“软引用”来解决。接下来,就是怎么引用这个画廊组件了。

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

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

</RelativeLayout>
package com.example.gallery;

import com.example.gallery.view.CustomGallery;
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.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Gallery.LayoutParams;
import android.widget.ImageView;

public class MainActivity extends Activity {

	/** 图片资源数组 */
	private int[] imageResIDs;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		imageResIDs = new int[]{//
		R.drawable.imgres_01, //
				R.drawable.imgres_02, //
				R.drawable.imgres_03, //
				R.drawable.imgres_04, //
				R.drawable.imgres_05, //
				R.drawable.imgres_06, //
				R.drawable.imgres_07, //
				R.drawable.imgres_08, //
				R.drawable.imgres_01, //
				R.drawable.imgres_02, //
				R.drawable.imgres_03, //
				R.drawable.imgres_04, //
				R.drawable.imgres_05, //
				R.drawable.imgres_06, //
				R.drawable.imgres_07, //
				R.drawable.imgres_08 //
		};
		CustomGallery customGallery = (CustomGallery) findViewById(R.id.customgallery);
		ImageAdapter adapter = new ImageAdapter();
		customGallery.setAdapter(adapter);
	}

	public class ImageAdapter extends BaseAdapter {

		@Override
		public int getCount() {
			// TODO Auto-generated method stub
			return imageResIDs.length;
		}

		@Override
		public Object getItem(int position) {
			// TODO Auto-generated method stub
			return imageResIDs[position];
		}

		@Override
		public long getItemId(int position) {
			// TODO Auto-generated method stub
			return position;
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			// TODO Auto-generated method stub
			ImageView imageView;
			if (convertView != null) {
				imageView = (ImageView) convertView;
			} else {
				imageView = new ImageView(MainActivity.this);
			}
			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;
		}
	}
}

===========================================华丽丽的分割线=============================================

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的一段代码,用于将图片处理成泛黄的效果:

 public static Bitmap testBitmap(Bitmap bitmap){
        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
                bitmap.getHeight(), Config.RGB_565);

        Canvas canvas = new Canvas(output);

        Paint paint = new Paint();
        ColorMatrix cm = new ColorMatrix();
        float[] array = {1,0,0,0,100,
                0,1,0,0,100,
                0,0,1,0,0,
                0,0,0,1,0};
        cm.set(array);
        paint.setColorFilter(new ColorMatrixColorFilter(cm));

        canvas.drawBitmap(bitmap, 0, 0, paint);
        return output;
    }

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的一段示例代码,用于将图片平移,也就是裁剪的效果,其他效果可以参照对应坐标变换矩阵修改即可:

 public static Bitmap test1Bitmap(Bitmap bitmap){
        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
                bitmap.getHeight(), Config.RGB_565);

        Canvas canvas = new Canvas(output);

        Paint paint = new Paint();
        Matrix cm = new Matrix();

        float[] array = {1,0,50,
                0,1,50,
                0,0,1};
        cm.setValues(array);
        canvas.drawBitmap(bitmap, cm, paint);
        return output;
    }

下面将介绍几种常用的变换矩阵:

1.旋转

绕原点逆时针旋转θ度角的变换公式是 x‘ = xcosθ ? ysinθ
与 y‘ = xsinθ + ycosθ

2. 缩放

变换后长宽分别放大x‘=scale*x;y‘=scale*y.

3.切变

4.反射

5.正投影

Android的图像矩阵绝对不止这些,这是一个很复杂的知识,涉及到大学相关数学的课程,能了解大学线性代数里的矩阵知识,对学习Android下的图像矩阵有很好的帮助,在这里限于篇幅,我只做了简单的基础讲解,基本可以理解,可以使用即可,如果想深入学习一下的话,请查看下方的资料链接,去下载我今天上传到CSDN资源库里面的资料。

Android图像矩阵基础与详解资料

源码请在这里下载

时间: 2024-10-11 21:28:38

Android自定义控件——3D画廊和图像矩阵的相关文章

android 自定义控件实现3D画廊效果

今天来实现一个3D画廊的效果,这个效果的难点在于计算旋转角度,当然里面会有好几个知识点要讲,针对Paint的2个方法,一个是setShader(),一个是setXfermode(),首先看下大概的效果, 大概是这种,这是我在网上随便找了一个类似的图片,因为我的效果还没写,没关系,这没啥影响,这个效果我准备分开写,然后后面合成起来,上面的效果可以分为如下几步 1:首先是怎么截取一张图片中的一部分 2:怎么把多张图片合成一张图片 3:怎么生成倒影效果, 4:怎么改变倒影中的图片透明度 5:最后一步是

Android ViewPager打造3D画廊

网上有很多关于使用Gallery来打造3D画廊的博客,但是在关于Gallery的官方说法中表明: This class was deprecated in API level 16.This widget is no longer supported. Other horizontally scrolling widgets include HorizontalScrollView and ViewPager from the support library.(来自:https://develo

android 自定义控件---圆形方向盘

在做Android平台开发的时候,经常会遇到安卓原生控件无法满足需求的情况,安卓允许开发者去继承已经存在的控件或者实现你自己的控件. 先来看一下效果图 采用直接集成View类,重写onDrow方法绘制. 下面附上主要代码. 1 新建一个类CircleView 继承自View 1 package com.lennon.view; 2 3 import android.content.Context; 4 import android.graphics.Canvas; 5 import androi

android学习---Gallery画廊视图

Gallery与Spinner有共同父类:AbsPinner,说明Gallery与Spinner都是一个列表框.它们之间的区别在于Spinner显示的是一个垂直的列表选择框,而Gallery显示的是一个水平的列表选择框.Spinner的作用是供用户选择,而Gallery则允许用户通过拖动查看上一个,下一个. Gallery用法与Spinner的用法形似,只要为它提供一个内容Adapter就可以了.Adapter的getView方法返回View作为Gallery列表的列表项.如果程序需要监控Gal

基于 Android 的 3D 视频样本代码

作者:Mark Liu 下载样本代码 简介 在Android 中,创建一个能够播放视频剪辑的应用非常简单:创建一个采用 3D 图形平面的游戏应用也非常简单.但是,创建一个能够在 3D 图形对象上播放视频的应用却不容易.本文介绍了我为应对该挑战创建的应用.该应用可在 3D 平面上渲染视频,并支持用户以交互的方式在视频平面上播放. 该应用需要解决三大实施问题: 如何构建代码以支持用户在播放视频时变更 3D 平面? 虽然 Android 中默认的 MediaPlayer 配备了全面的播放操作,但是难以

Android自定义控件并且使其可以在xml中自定义属性

为什么要自定义View android开发中自定义View的好处是显而易见的.比如说下面的这个顶部导航,它被设计出现在应用的每个界面,但每次的内容却不尽相同.我们不能在每个layout资源中都配置一组相同的View吧?如果使用<include layou="@layout/xxx"/>标签,虽然解决了布局文件的重用性,但是相关View的初始化设置还是没能够重用(集中),需要每次都采用view.findViewById(id)来初始化他们. 有了对"可重用性&quo

Android自定义控件一_Canvas分析

自定义控件分为两种一种是自定义ViewGroup控件,一种是自定义View控件:跟踪View的步伐其实能跟到Java实现的最下面我们能发现的也就只有Canvas了,再下去就是C++或C实现了:所以本文主要是站在设计的的角度讲解一下Canvas跟View的关系,再简单分析一下Canvas用法: View作为Android中一切显示视图的父类,我们可看到它的绘制方法draw(Canvas canvas)中,无非也是通过Canvas的绘制来达到各种View的显示,如此Android中各种控件如:Ima

利用ViewPager实现3D画廊效果及其图片加载优化

前言 对于ViewPager,相信大家都已经很熟悉了,在各种切换场景比如Fragment切换.选项卡的切换或者顶部轮播图片等都可以用ViewPager去实现.那么本篇文章带来ViewPager的一种实现效果:3D画廊.直接上图来看: 从上面的图我们可以看出,整个页面分成三个部分,中间的是大图,正中地显示给用户:而两边的是侧图,而这两幅图片又有着角度的旋转,与大图看起来不在同一平面上,这就形成了3D效果.接着拖动页面,侧面的图慢慢移到中间,这个过程也是有着动画的,包括了图片的旋转.缩放和平移.在欣

Android自定义控件之自定义组合控件(三)

前言: 前两篇介绍了自定义控件的基础原理Android自定义控件之基本原理(一).自定义属性Android自定义控件之自定义属性(二).今天重点介绍一下如何通过自定义组合控件来提高布局的复用,降低开发成本,以及维护成本. 使用自定义组合控件的好处? 我们在项目开发中经常会遇见很多相似或者相同的布局,比如APP的标题栏,我们从三种方式实现标题栏来对比自定义组件带来的好处,毕竟好的东西还是以提高开发效率,降低开发成本为导向的. 1.)第一种方式:直接在每个xml布局中写相同的标题栏布局代码 <?xm