Android的Gallery3D模块介绍

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;

//触发HudLayeronGridStateChanged

       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初始化线程的工作是:调用MediaFeedloadMediaSets加载相册,它又调用了下层LocalDataSource中的refresh方法(查询数据库是否有相册变化,新增或修改feed中相应的MediaSet相册的名字)和loadMediaSets方法(调用下层CacheService.loadMediaSets方法,这个方法下面会重点展开)加载所有相册和相册中的所有相片信息。

4.MediaFeed
监听线程MediaFeed.run()的工作是:根据内容变化监听器返回的媒体变动消息(增删改),持续不断的更新MediaFeed中的相册和相片变量。具体机制是这样的,如果全局的刷新请求列表中有内容,则调用LocalDataSource.refresh进行相册信息的更新(其中LocalDataSource.refresh调用了CacheServicecomputeDirtySets),然后run遍历每个相册并调用dataSource.loadItemsForSet()方法为相册加载相片记录。

本部分主要涉及的类:

GridLayer  根图层

Gallery    主要的Activity

MediaFeed  不断更新数据

DataSource 数据源,子类有LocalDataSource与PicasaDataSource分别加载本地数据与网络数据ConcatenatedDataSource为这两个的并集

CacheService. 缓存服务

DiskCache   缓存

RenderView  GLSurfaceView子类,下一部分有详细讲解

Reord 记录

 

3.绘图机制

  3.1 RenderView绘制

         Gallery3D中的绘图都是通过openGL绘制在mRenderView上的mRenderViewGLSurfaceView的一个子类,并且实现了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),它们往往先被动等待,当用户有了动作再做出反应。对于这种应用,持续渲染屏幕是浪费时间。若开发反应式的应用,可以调用GLSurfaceViewrequestRender方法 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,则会执行GLSurfaceViewrequestRender方法。renderOpaque绘制不透明的部分,renderBlended绘制半透明的部分。onTouchEvent用来处理触屏事件。Generate会将这个layer会在哪个lists中出现表示出来。例如:GridLayergenerate

       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加入updateListopaqueListblendedListhitTestList中。

3.3 GridLayer的绘制

GridLayer与其他layer一样,也是提供了renderOpaquerenderBlended方法供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, null0.0f);画单张图片的,注掉,第三屏黑屏  

3.  drawDisplayItem(view, gl, itemDrawn, textureToUse, PASS_FRAME, previousTexture, ratio);画边框的,注掉,前两屏明显没有边框,巨齿明显  

4.  drawDisplayItem(view, gl, displayItem, textureString, PASS_TEXT_LABEL, null0);画文本标签的  

5.  drawDisplayItem(view, gl, displayItem, textureToUse, PASS_SELECTION_LABEL, null0);画选中标记的  

6.  drawDisplayItem(view, gl, displayItem, videoTexture, PASS_VIDEO_LABEL, null0);画视频标记的  

7.  drawDisplayItem(view, gl, displayItem, locationTexture, PASS_LOCATION_LABEL, null0);画位置标记的  

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,然后调用layeronTouchEvent方法。不过由于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)

         /*调用GestureDetectoronTouchEvent*/

           mGestureDetector.onTouchEvent(event);

        /*调用ScaleGestureDetectoronTouchEvent*/

       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

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值