Gallery3D模块介绍
1. Gallery3D模块简介
1.1Gallery3D的几个状态1. Gallery3D模块简介
Gallery3D没有用 android 的 UI 系统,而是用 opengl 画出来的,即界面是在同一个activity 的,如主界面,缩略图界面,单张图片查看界面,以及按时间分类界面。这些界面等都属于同一个 activity。各个界面之间的切换是通过状态机表示的。GridLayer中定义了STATE_MEDIA_SETS,STATE_GRID_VIEW,STATE_FULL_SCREEN,STATE_TIMELINE几个状态。这些界面分别如下图所示。当用户触发触屏或者按键事件,可能会改变这些状态,执行GridLayer的setState()方法,并触发HudLayer的onGridStateChanged方法,重新绘制界面。
这部分代码主要实现。
定义4个状态(GridLayer.java):
publicstaticfinalintSTATE_MEDIA_SETS = 0;
publicstaticfinalintSTATE_GRID_VIEW = 1;
publicstaticfinalintSTATE_FULL_SCREEN = 2;
publicstaticfinalintSTATE_TIMELINE = 3;
设置状态(GridLayer.java)):
publicvoidsetState(int state) {
...........
//根据state,调整不同的状态
switch (state) {
case STATE_GRID_VIEW:
....state.......
break;
case STATE_TIMELINE:
..state.....
break;
case STATE_FULL_SCREEN:
.state.......
break;
case STATE_MEDIA_SETS:
.state....
break;
}
mState = state;
//触发HudLayer的onGridStateChanged
mHud.onGridStateChanged();
if (performLayout &&mFeedAboutToChange ==false) {
onLayout(Shared.INVALID, Shared.INVALID, oldLayout);
}
if (state != STATE_FULL_SCREEN) {
mCamera.moveYTo(0);
mCamera.moveZTo(0);
}
}
onGridStateChanged(HudLayer.java)
publicvoidonGridStateChanged() {
//重新绘制界面
updateViews();
}
STATE_MEDIA_SETS 主界面,初始进入文件夹状态
STATE_GRID_VIEW 缩略图界面。进入某个文件夹,图片会以网格缩略图模式显示
STATE_FULL_SCREEN 单张图片查看界面一般是单张图片大图浏览界面,或者是裁剪、设壁纸状态
STATE_TIMELINE 时间分类界面,以时间序列分类某个文件夹内的图片模式
1.2Gallery3D 执行过程
1. Gallery3D主要的activity只有一个, 即Gallery类。在Gallery的onCreate方法中,通过setCotentView将视图设置为mRenderView,mRenderView其中一个GLSurfaceView的一个子类,所有的openGL操作都绘制在画在这个view中。
然后用checkStorage方法加载SD卡中的图片。onCreate的主要代码:
@Override
publicvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mRenderView = newRenderView(this);//新建RenderView,为GLSurfaceView子类
mGridLayer = newGridLayer(this, (int)(96.0f * App.PIXEL_DENSITY), (int)(72.0f * App.PIXEL_DENSITY),newGridLayoutInterface(4),//新建GridLayer,在下面会详细讲
mRenderView);
mRenderView.setRootLayer(mGridLayer);
setContentView(mRenderView);
sendInitialMessage();//发送初始化信息,将会从SD卡中加载图片。
}//Gallery.java
2. sendInitialMessage函数发送初始化消息,将调用initializeDataSource函数,设置数据源,(数据源包括本地数据源与picasa的数据源,为方便起见,这里只考虑本地的数据源)接着启动图片监视进程。
private voidinitializeDataSource() {
//新建数据源,数据源包括本地数据源与picasa的数据源,为方便起见,这里只考虑本地的数据源
final LocalDataSourcelocalDataSource = new LocalDataSource(Gallery.this,LocalDataSource.URI_ALL_MEDIA, false);
//设置数据源
if (hasStorage) {
mGridLayer.setDataSource(localDataSource);
}
}//Gallery.java
public void setDataSource(DataSourcedataSource) {
MediaFeed feed = mMediaFeed;/主要通过MediaFeed来加载数据源
mMediaFeed = new MediaFeed(mContext,dataSource, this);
... ... ...
mMediaFeed.start(); //启动初始化线程,在此函数中将启动监视进程
}//GridLayer.java
3.其中MediaFeed初始化线程的工作是:调用MediaFeed的loadMediaSets加载相册,它又调用了下层LocalDataSource中的refresh方法(查询数据库是否有相册变化,新增或修改feed中相应的MediaSet相册的名字)和loadMediaSets方法(调用下层CacheService.loadMediaSets方法,这个方法下面会重点展开)加载所有相册和相册中的所有相片信息。
4.MediaFeed监听线程MediaFeed.run()的工作是:根据“内容变化监听器“返回的媒体变动消息(增删改),持续不断的更新MediaFeed中的相册和相片变量。具体机制是这样的,如果全局的刷新请求列表中有内容,则调用LocalDataSource.refresh进行相册信息的更新(其中LocalDataSource.refresh调用了CacheService的computeDirtySets),然后run遍历每个相册并调用dataSource.loadItemsForSet()方法为相册加载相片记录。
本部分主要涉及的类:
GridLayer 根图层
Gallery 主要的Activity
MediaFeed 不断更新数据
DataSource 数据源,子类有LocalDataSource与PicasaDataSource分别加载本地数据与网络数据ConcatenatedDataSource为这两个的并集
CacheService. 缓存服务
DiskCache 缓存
RenderView GLSurfaceView子类,下一部分有详细讲解
Reord 记录
3.绘图机制
3.1 RenderView绘制
Gallery3D中的绘图都是通过openGL绘制在mRenderView上的mRenderView是GLSurfaceView的一个子类,并且实现了GLSurfaceView.Renderer接口。GLSurfaceView.Renderer中有三个方法需要重写:
1. onSurfaceCreated():该方法在渲染开始前调用,OpenGL ES 的绘制上下文被重建时也会被调用。当 activity 暂停时绘制上下文会丢失,当 activity 继续时,绘制上下文会被重建。另外,创建长期存在的 OpenGL 资源(如 texture)往往也在这里进行。
2.onSurfaceChanged():当 surface 的尺寸发生改变时该方法被调用。往往在这里设置 viewport。若你的 camera 是固定的,也可以在这里设置 camera。
3. onDrawFrame():每帧都通过该方法进行绘制。绘制时通常先调用 glClear 函数来清空framebuffer,然后在调用 OpenGL ES 的起它的接口进行绘制。 在Gallery3D中,几乎所有的界面绘制都是在onDrawFrame中进行的。OnDrawFrame的主要源码如下:
publicvoidonDrawFrame(GL10 gl1) {
//是否启用FPS测试
if (ENABLE_FPS_TEST) {
long now = System.nanoTime();
if (mFrameCountingStart == 0) {
mFrameCountingStart = now;
} elseif((now - mFrameCountingStart) > 1000000000) {
Log.v(TAG, "fps:" + (double)mFrameCount
* 1000000000 / (now -mFrameCountingStart));
mFrameCountingStart = now;
mFrameCount = 0;
}
++mFrameCount;
}
GL11 gl = (GL11) gl1;
if (!mFirstDraw) {
Log.i(TAG, "First Draw");
}
mFirstDraw = true;
// Rebuild the display lists if the render tree haschanged.
if (mListsDirty) {
updateLists();
}
boolean wasLoadingExpensiveTextures =isLoadingExpensiveTextures();
boolean loadingExpensiveTextures =false;
int numTextureThreads =sTextureLoadThreads.length;
for (int i =2; i < numTextureThreads; ++i) {
if(sTextureLoadThreads[i].mIsLoading) {
loadingExpensiveTextures =true;
break;
}
}
if (loadingExpensiveTextures !=wasLoadingExpensiveTextures) {
mLoadingExpensiveTexturesStartTime = loadingExpensiveTextures ?SystemClock.uptimeMillis() : 0;
}
// Upload new textures.
processTextures(false);
// Update the current time and frame time interval.
long now =SystemClock.uptimeMillis();
finalfloat dt =0.001f * Math.min(50, now - mFrameTime);
mFrameInterval = dt;
mFrameTime = now;
// Dispatch the current touch event.
processCurrentEvent();
processTouchEvent();
// Run the update pass.
final Lists lists = sLists;
synchronized (lists) {
final ArrayList<Layer>updateList = lists.updateList;
boolean isDirty =false;
for (int i =0, size = updateList.size(); i != size; ++i) {
booleanretVal = updateList.get(i).update(this,mFrameInterval);
isDirty |= retVal;
}
if (isDirty) {
requestRender();
}
// Clear the depth buffer.
gl.glEnable(GL11.GL_SCISSOR_TEST);
gl.glScissor(0, 0, getWidth(), getHeight());
gl.glClear(GL11.GL_DEPTH_BUFFER_BIT);
// Run the opaque pass.
gl.glDisable(GL11.GL_BLEND);
final ArrayList<Layer>opaqueList = lists.opaqueList;
for (int i =opaqueList.size() - 1; i >= 0; --i) {
finalLayer layer = opaqueList.get(i);
if(!layer.mHidden) {
layer.renderOpaque(this,gl);
}
}
// Run the blended pass.
gl.glEnable(GL11.GL_BLEND);
final ArrayList<Layer>blendedList = lists.blendedList;
for (int i =0, size = blendedList.size(); i != size; ++i) {
finalLayer layer = blendedList.get(i);
if(!layer.mHidden) {
layer.renderBlended(this,gl);
}
}
gl.glDisable(GL11.GL_BLEND);
}
}//RenderView.java
关于持续型渲染模式与通知型渲染模式 :
大多数 3D 应用,如游戏、模拟等都是持续型渲染的动画,还有些 3D应用是反应式的(reactive),它们往往先被动等待,当用户有了动作再做出反应。对于这种应用,持续渲染屏幕是浪费时间。若开发反应式的应用,可以调用GLSurfaceView的requestRender方法 。Gallery3D默认是反应式渲染模式,当启用FPS测试时(ENABLE_FPS_TEST = ture),也可以设置为持续性渲染。这部分代码,在onSurfaceCreated中可以看到。
publicvoidonSurfaceCreated(GL10 gl1, EGLConfig config) {
….
if (ENABLE_FPS_TEST) {
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);//持续渲染模式
} else {
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);//反应式渲染模式
} ….
}//RenderVew.java
OnDrawFrame的主要过程为,首先进行初始化加载所有需要的Texture等资源,接着分别测试lists.updateList中的layer是否需要重新渲染,如果需要,执行requestRender();接着遍历lists.opaqueList中的layer,分别执行layer.renderOpaque(this, gl)操作,来绘制不透明的部分。最后遍历lists.blendedList中的layer,分别执行layer.renderBlended(this, gl),绘制半透明的部分。
3.2 Gallery3D中的layer
以上可以看出,OnDrawFrame主要是加载了lists中的的layer进行绘制。Lists中包含的layer如下图所示。
updateList:包含需要测试是否渲染的layer
opaqueList:包含需要绘制不透明部分的layer
blendedList:包含需要绘制半透明部分的layer
systemList: 加入这个list中的layer会调用onSurfaceCreated方法,但是没有具体的实现代码。可能是备用的。
hitTestList: 包含需要测试是否被点击的layer
这些layer都继承了抽象类Layer,以下是包含各个layer的类图:
Layer的主要代码为:
publicabstractclassLayer {
publicabstractvoidgenerate(RenderView view, RenderView.Lists lists);
//Returns true if something is animating.
publicbooleanupdate(RenderView view,floatframeInterval) {
returnfalse;
}
publicvoidrenderOpaque(RenderView view, GL11 gl) {
}
publicvoidrenderBlended(RenderView view, GL11 gl) {
}
publicboolean onTouchEvent(MotionEventevent) {
returnfalse;
}
}
update 用来检测是否需要渲染,若返回true,则会执行GLSurfaceView的requestRender方法。renderOpaque绘制不透明的部分,renderBlended绘制半透明的部分。onTouchEvent用来处理触屏事件。Generate会将这个layer会在哪个lists中出现表示出来。例如:GridLayer的generate:
publicvoidgenerate(RenderView view, RenderView.Lists lists) {
lists.updateList.add(this);
lists.opaqueList.add(this);
mBackground.generate(view, lists);
lists.blendedList.add(this);
lists.hitTestList.add(this);
mHud.generate(view, lists);
}
会将这个layer加入updateList,opaqueList,blendedList,hitTestList中。
3.3 GridLayer的绘制
GridLayer与其他layer一样,也是提供了renderOpaque与renderBlended方法供RenderView.onDrawFrame调用,不过由于GridLayer很复杂,所以大部分的绘图操作都是在GridDrawManager中进行的。GridDrawManager的主要方法有:
//drawBlendedComponents ,画半透组键
publicvoiddrawBlendedComponents(RenderView view, GL11 gl,floatalpha,int state,inthudMode,float stackMixRatio, floatgridMixRatio, MediaBucketList selectedBucketList, MediaBucketListmarkedBucketList,booleanisFeedLoading) {…}
// drawFocusItemsImportant 画单张浏览大图
publicvoiddrawFocusItems(RenderView view, GL11 gl,floatzoomValue,boolean slideshowMode,floattimeElapsedSinceView) {…}
//drawDisplayItem 底层函数,画四边形并且绑定纹理,供上层调用
privatevoiddrawDisplayItem(RenderView view, GL11 gl, DisplayItem displayItem, Texturetexture, int pass,Texture previousTexture,floatmixRatio) {…}
// drawThumbnails 画缩略图
publicvoiddrawThumbnails(RenderView view, GL11 gl,intstate) {…}
仔细看代码可以发现,这些函数都是调用DisplayItem中方法,Gallery3D将每个图片文件夹或者图片都封装在DisplayItem中,通过调用DisplayItem的代理,可以实现许多绘图功能。:
1. drawDisplayItem(view, gl, displayItem, texture, PASS_THUMBNAIL_CONTENT, placeholder,displayItem.mAnimatedPlaceholderFade); 画缩略图的,注掉此句,前两屏只显示框,第三屏OK
2. drawDisplayItem(view, gl, displayItem, texture, PASS_FOCUS_CONTENT, null, 0.0f);画单张图片的,注掉,第三屏黑屏
3. drawDisplayItem(view, gl, itemDrawn, textureToUse, PASS_FRAME, previousTexture, ratio);画边框的,注掉,前两屏明显没有边框,巨齿明显
4. drawDisplayItem(view, gl, displayItem, textureString, PASS_TEXT_LABEL, null, 0);画文本标签的
5. drawDisplayItem(view, gl, displayItem, textureToUse, PASS_SELECTION_LABEL, null, 0);画选中标记的
6. drawDisplayItem(view, gl, displayItem, videoTexture, PASS_VIDEO_LABEL, null, 0);画视频标记的
7. drawDisplayItem(view, gl, displayItem, locationTexture, PASS_LOCATION_LABEL, null, 0);画位置标记的
8. drawDisplayItem(view, gl, displayItem, locationTexture, PASS_MEDIASET_SOURCE_LABEL,transparentTexture, 0.85f);画源来源图标的(相机或一般文件夹)
3.3这一部分主要涉及的类:
Layer:
GridLayer : 网格缩略图显示和单个图片显示
BackgroundLayer : 背景层
HudLayer : 相册显示
ImageButton : 图片按钮(主要指进入Gallery后右上角的那个控件)
TimeBar : 进入Gallery后下方可拖动的悬浮控件
MenuBar : 点击图片时弹出的菜单按钮
PopupMenu : 点击菜单按钮后弹出来的菜单项
PathBarLayer : Gallery左上方显示图片路径的空间
openGL相关:
RenderView: GLSurfceView子类,提供主要绘图操作
GridDrawManager: GridLayer的绘制
DisplayItem 各个图片记忆图片文件夹的绘图元素
DisplayList DisplayItem的集合
GridDrawables 可绘制图形
4.事件处理
事件处理主要包括触屏与按键处理,对于按键,Gallery类将接收到的事件委托给RenderView的onKeyDown或者onKeyUp处理,然后它们调用processKeyEvent处理按键事件。RenderView最终又会委托给GridLayer的相关方法处理。触屏事件的处理也类似,RenderView检测的onTouchEvent会将event加入mTouchEventQueue这个队列中。接着调用processTouchEvent对队列中的事件处理。检测触屏区域的layer,然后调用相关layer的onTouchEvent。
publicbooleanonTouchEvent(MotionEvent event) {
// Wait for the render thread to process this event.
if (mTouchEventQueue.size() >8 && event.getAction() == MotionEvent.ACTION_MOVE)
returntrue;
synchronized (mTouchEventQueue) {
MotionEvent eventCopy = MotionEvent.obtain(event);
mTouchEventQueue.addLast(eventCopy);
requestRender();
}
returntrue;
}
privatevoidprocessTouchEvent() {
MotionEvent event = null;
int numEvents =mTouchEventQueue.size();
int i = 0;
do {
// We look at the touch event queue and process one eventat a time
synchronized (mTouchEventQueue) {
event =mTouchEventQueue.pollFirst();
}
if (event ==null)
return;
// Detect the hit layer.
finalintaction = event.getAction();
Layer target;
if (action ==MotionEvent.ACTION_DOWN) {
//检测是哪个layer被点击到了
target = hitTest(event.getX(),event.getY());
mTouchEventTarget = target;
} else {
target = mTouchEventTarget;
}
// Dispatch event to the hit layer.
if (target !=null) {
target.onTouchEvent(event);
}
// Clear the hit layer.
if (action ==MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
mTouchEventTarget =null;
}
event.recycle();
++i;
} while (event !=null&& i < numEvents);
synchronized (this) {
this.notify();
}
}//RenderView.java
从代码可以看出,通过hitTest得到了点击的layer,然后调用layer的onTouchEvent方法。不过由于GridLayer的触屏比较复杂,它又专门用了一个类GridInputProcessor来处理触屏事件。GridInputProcessor实现了GestureDetector的OnGestureListener 接口处理单击事件,实现了GestureDetector的OnDoubleTapListener接口处理双击事件,还有另一个Gallery3D自己的ScaleGestureDetector.OnScaleGestureListener接口实现多点触摸的事件,所以这部分比较复杂。此外,还有一些sensorchanged等事件也是有GridInputProcessor处理的。
GridInputProcessor的onTouchEvent:
publicbooleanonTouchEvent(MotionEvent event) {
……….
switch (mActionCode) {//首先对做一些界面处理
case MotionEvent.ACTION_UP:
touchEnded(mTouchPosX, mTouchPosY, timeElapsed);
break;
case MotionEvent.ACTION_DOWN:
mPrevTouchTime = timestamp;
touchBegan(mTouchPosX, mTouchPosY);
break;
case MotionEvent.ACTION_MOVE:
touchMoved(mTouchPosX, mTouchPosY, timeElapsed);
setPrevFocus(event);
break;
}
if (!mZoomGesture)
/*调用GestureDetector的onTouchEvent*/
mGestureDetector.onTouchEvent(event);
/*调用ScaleGestureDetector的onTouchEvent*/
mScaleGestureDetector.onTouchEvent(event);
returntrue;
}
4.4本部分主要涉及的类:
GridInputProcessor 输入处理
GestureDetector 手势识别,GridInputProcessor类实现了GestureDetector .OnGestureListener的onSingleTapup方法,实现了OnDoubleTapListener的onDoubleTap方法
ScaleGestureDetector 多点触摸识别,包含OnScaleGestureListener接口。有onScale,onScaleBegin ,onScaleEnd等方法,由GridInputProcessor 实现。
7.参考资料
Gallery_3D_源码汇总.docx(这个比较全面)
MSM7627_Multimedia_004_A_gallery3d.pdf
Layer相关:
http://blog.sina.com.cn/s/blog_9de189c60101455k.html
http://hi.baidu.com/wuhenaiyanyan/blog/item/0569db0d8303a2d67acbe138.html
数据源相关:
http://www.eoeandroid.com/archiver/tid-41920.html
OpenGL相关:
Android开发OpenGL图书.pdf
Android_3D_OpenGLES 基础教程.doc