Android中的3D相册
2011年11月28日
最近学习,工作走很忙,空闲之余,我还学习了一下Android编程和Android上的一些好玩的东西。
这几天,我做了一个Android的3D相册,简单但是听好玩的,算是学习吧。也借鉴了一些别人的东西。这个相册的开发过程中,我用到了OpenGL ES中的图形绘制技术,包括纹理映射技术、物体旋转技术glRotatef()、物体平移技术glTranslatef(),并且用到了屏幕触控技术。
下图是3D相册的效果图,通过滑动手机屏幕可以改变照片呈现的角度。
通过单击操作来查看相册中的某张图片,按返回键会返回相册显示。
3D相册的具体代码已上传到日志,感兴趣的可以查看,有什么不足和待改进的地方请大神们指教。
界面实现类MySufaceView中的代码如下:
package com.bn.ex11f;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import android.opengl.GLSurfaceView;
import android.opengl.GLUtils;
import android.view.KeyEvent;
import android.view.MotionEvent;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import static com.bn.ex11f.Constant.*;
class MySurfaceView extends GLSurfaceView
{
public float screenWidth;//屏幕宽带
public float screenHeight;//屏幕高度
public float ratio;//屏幕宽高比
private final float TOUCH_SCALE_FACTOR = 180.0f/320;//角度缩放比例
private SceneRenderer mRenderer;//场景渲染器
float mPreviousX;//上次按下位置x坐标
float mPreviousY;//上次按下位置y坐标
long previousTime;//上次按下的时间
boolean isCheck=false;//是否点击查看图片
boolean isMove=false;
int[] textureIds=new int[PHOTO_COUNT];//n张照片纹理id数组
float yAngle=0;//总场景旋转角度
int currIndex=0;//当前选中的索引
float yAngleV=0;//总场景角度变化速度
public MySurfaceView(Context context) {
super(context);
mRenderer = new SceneRenderer(); //创建场景渲染器
setRenderer(mRenderer); //设置渲染器
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);//设置渲染模式为主动渲染
//启动一个线程根据当前的角速度旋转场景
threadWork=true;
new Thread()
{
public void run()
{
while(threadWork)
{
if(Float.isNaN(yAngle)||Float.isNaN(yAngleV))
{
throw new RuntimeException("yangle "+yAngle+"yAngleV="+yAngleV);
}
//根据角速度计算新的场景旋转角度
yAngle+=yAngleV;
if(Float.isNaN(yAngle))
{
throw new RuntimeException("yangle"+yAngle);
}
//将角度规格化到0~360之间
yAngle=(yAngle+360)%360;
if(Float.isNaN(yAngle)||Float.isNaN(yAngleV))
{
throw new RuntimeException("yangle "+yAngle+"yAngleV="+yAngleV);
}
//若当前手指已经抬起则角速度衰减
if(!isMove)
{
yAngleV=yAngleV*0.7f;
}
//若 角速度小于阈值则归0
if(Math.abs(yAngleV)5||Math.abs(dy)>5)
{//触控位移大于阈值则进入移动状态
isMove=true;
}
if(isMove)
{//若在移动状态则计算角度变化速度
if(timeSpan!=0)
{
yAngleV=dx * TOUCH_SCALE_FACTOR/timeSpan;
}
}
break;
case MotionEvent.ACTION_UP:
//若在非移动状态且角度速度为0则看选中的是哪幅照片来显示
if(!isMove&&yAngleV==0)
{
//折算出触控点在NEAR面上的位置
float nearX=(x-screenWidth/2)*ratio/(screenWidth/2);
float nearY=(screenHeight/2-y)/(screenHeight/2);
//先判断点下去的是左边还是右边
if(x>screenWidth/2)
{//右边
ArrayList al=new ArrayList();
for(int i=0;i270&&tempAngle al=new ArrayList();
for(int i=0;i180&&tempAngle纹理矩形
[email protected]
public void onDrawFrame(GL10 gl)
{
//清除颜色缓存于深度缓存
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
//设置当前矩阵为模式矩阵
gl.glMatrixMode(GL10.GL_MODELVIEW);
//设置当前矩阵为单位矩阵
gl.glLoadIdentity();
if(isCheck)
{
//显示某一幅照片,detail状态
gl.glPushMatrix();
gl.glTranslatef(0, 0f, -52f);
gl.glTranslatef(-Board.length-Board.width*0.5f, 0, 0);
tp.drawSelf(gl,textureIds[currIndex]);
gl.glPopMatrix();
}
else
{
//显示照片组,可触控旋转选择
gl.glPushMatrix();
gl.glTranslatef(0, 0f, CENTER_Z);
yAngle=yAngle%360;
gl.glRotatef(yAngle, 0, 1, 0);
for(int i=0;i纹理数组,显示每幅照片
gl.glPushMatrix();
gl.glRotatef(i*PHOTO_ANGLE_SPAN, 0, 1, 0);
tp.drawSelf(gl,textureIds);
gl.glPopMatrix();
}
gl.glPopMatrix();
}
}
[email protected]
public void onSurfaceChanged(GL10 gl, int width, int height) {
//设置视窗大小及位置
gl.glViewport(0, 0, width, height);
//设置当前矩阵为投影矩阵
gl.glMatrixMode(GL10.GL_PROJECTION);
//设置当前矩阵为单位矩阵
gl.glLoadIdentity();
//调用此方法计算产生透视投影矩阵
gl.glFrustumf(-ratio, ratio, -1, 1, NEAR, 100);
//设置为关闭背面剪裁
gl.glDisable(GL10.GL_CULL_FACE);
}
[email protected]
public void onSurfaceCreated(GL10 gl, EGLConfig config)
{
//关闭抗抖动
gl.glDisable(GL10.GL_DITHER);
//设置特定Hint项目的模式,这里为设置为使用快速模式
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,GL10.GL_FASTEST);
//设置屏幕背景色黑色RGBA
gl.glClearColor(0,0,0,0);
//启用深度测试
gl.glEnable(GL10.GL_DEPTH_TEST);
//加载n幅纹理图
textureIds[0]=initTexture(gl,R.drawable.fj1);//图片1
textureIds[1]=initTexture(gl,R.drawable.fj2);//图片2
textureIds[2]=initTexture(gl,R.drawable.fj3);//图片3
textureIds[3]=initTexture(gl,R.drawable.fj4);//图片4
textureIds[4]=initTexture(gl,R.drawable.fj5);//图片5
textureIds[5]=initTexture(gl,R.drawable.fj6);//图片6
textureIds[6]=initTexture(gl,R.drawable.fj7);//图片7
textureIds[7]=initTexture(gl,R.drawable.fj8);//图片8
textureIds[8]=initTexture(gl,R.drawable.fj9);//图片9
textureIds[9]=initTexture(gl,R.drawable.fj10);//图片10
textureIds[10]=initTexture(gl,R.drawable.fj11);//图片11
textureIds[11]=initTexture(gl,R.drawable.fj12);//图片12
textureIds[12]=initTexture(gl,R.drawable.fj13);//图片13
textureIds[13]=initTexture(gl,R.drawable.fj14);//图片14
}
}
//初始化纹理
public int initTexture(GL10 gl,int drawableId)//textureId
{
//生成纹理ID
int[] textures = new int[1];
gl.glGenTextures(1, textures, 0);
int currTextureId=textures[0];
gl.glBindTexture(GL10.GL_TEXTURE_2D, currTextureId);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,GL10.GL_LINEAR);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,GL10.GL_CLAMP_TO_EDGE);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,GL10.GL_CLAMP_TO_EDGE);
InputStream is = this.getResources().openRawResource(drawableId);
Bitmap bitmapTmp;
try
{
bitmapTmp = BitmapFactory.decodeStream(is);
}
finally
{
try
{
is.close();
}
catch(IOException e)
{
e.printStackTrace();
}
}
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmapTmp, 0);
bitmapTmp.recycle();
return currTextureId;
}
}
其中,public MySurfaceView (Context context) { }是界面类的构造部分,设置渲染器对象,根据当前的角度旋转场景的线程。