当前位置: 首页 > 工具软件 > Touch Gallery > 使用案例 >

14 Gallery-关于GLView

谢和同
2023-12-01

0. 原文拜读

1. GLView 介绍

在GLView.java开端的一段注释,它写明了GLView的作用。以往我们定义一个视图,所有视图控件都是从View.java继承来的,而这里,自定义了一个GLView.java实现View的功能。作为视图,它只实现了View的一小部分功能,从这里我们也可以学习到Android系统视图控件是如何实现的。先解释下上面这段注释:

GLView是可以接收触摸事件并通过GLCanvas进行渲染图画的UI组件。GLView可以拥有子控件从而组成一个树状的控件结构。渲染和触摸事件都通过这个树状结构进行传递。

一个GLView树在事件传递和渲染之前必须先附着到一个GLRoot对象中。GLView通过调用requestRender()和requestLayoutContentPane()要求GLRoot对GLView图层的重复渲染(包括画图和位置分配)。

render()方法需要在GLView所在线程之外的线程中调用。在调用dispatchTouchEvent()和layout()方法之前,GLRoot要求锁住线程以避免同时运行渲染线程。如果在主线程之中有其他到达GLView的入口(比如一个Handler),我们需要先调用lockRendering()锁住该线程避免该线程和主线程的同时运行。

package com.android.gallery3d.ui;

// GLView is a UI component. It can render to a GLCanvas and accept touch
// events. A GLView may have zero or more child GLView and they form a tree
// structure. The rendering and event handling will pass through the tree
// structure.
//
// A GLView tree should be attached to a GLRoot before event dispatching and
// rendering happens. GLView asks GLRoot to re-render or re-layout the
// GLView hierarchy using requestRender() and requestLayoutContentPane().
//
// The render() method is called in a separate thread. Before calling
// dispatchTouchEvent() and layout(), GLRoot acquires a lock to avoid the
// rendering thread running at the same time. If there are other entry points
// from main thread (like a Handler) in your GLView, you need to call
// lockRendering() if the rendering thread should not run at the same time.
//
public class GLView {

Gallery2的图库功能的UI实现没有采用Android通常的View控件系统, Gallery2实现了GLView、GLRootView、GLCanvas等。

  • GLRootView除了继承自GLSurfaceView之外还实现了GLRoot接口。是所有GLView的根控件。即整个GLView树是添加在它上面的,这通过GLRootView的setContentPane方法实现,有些类似于Activity的setContentView。GLView的渲染在GL线程中进行,事件处理在主线程中进行。这两个线程需要同步。在事件处理和布局的时候GLRootView会获取一个锁,从而避免渲染线程在这个时候执行。这个锁就是GLRootView中的mRenderLock。

  • GLView就相当于通常Android控件体系中的View,它是所有控件的父类。GLView可以接收触摸事件,并把自己画到GLCanvas上.接着说GLView,一个GLView可以有零个或多个子GLView,它们形成一个树结构。渲染和事件处理通过这个数结构传递。GLView必需加入到(Attach)GLRoot中才能完成渲染和接收事件。GLView可以通过调用GLRoot的requestRender()和requestLayoutContentPane()方法实现GLView树的渲染和布局。

  • GLCanvas是一个接口其实现有GLES11Canvas、GLES20Canvas。GLCanvas把自己绘制到GLSurfaceView中。

总结一下就是Gallery2不同于一般app之处是它的控件从GLView继承而不是View,GLView渲染在GLSurfaceView中。Gallery2实现了自己的一套UI控件系统。

接下来,我们从源码中分析,到底GLView是怎么实现注释中所描述的功能。而作为一个视图控件,又是怎么完成“视图”这一角色的。

2. 点击事件的声明

这也是GLView唯一拥有的事件(关于事件,回调,监听这些概念,需要了解回调事件,反转调用这些机制)。

public class GLView {

    public interface OnClickListener {
        void onClick(GLView v);
    }
}

3. GLView.attachToRoot 函数

建立和GLRootView的联系

    // This should only be called on the content pane (the topmost GLView).
    public void attachToRoot(GLRoot root) {
        //断言,这是一种调试方法
        Utils.assertTrue(mParent == null && mRoot == null);
        onAttachToRoot(root);
    }

    // This should only be called on the content pane (the topmost GLView).
    public void detachFromRoot() {
        Utils.assertTrue(mParent == null && mRoot != null);
        onDetachFromRoot();
    }
    
    protected void onAttachToRoot(GLRoot root) {
        mRoot = root;
        //通过递归对root(某个具体的调用attachToRoot()方法传进来的GLView作为父视图)和所有子视图形成树状结构。
        for (int i = 0, n = getComponentCount(); i < n; ++i) {
            getComponent(i).onAttachToRoot(root);
        }
    }

    protected void onDetachFromRoot() {
        for (int i = 0, n = getComponentCount(); i < n; ++i) {
            getComponent(i).onDetachFromRoot();
        }
        mRoot = null;
    }

上述的回调事件位于 GLRootView.setContentPane 事件中

package com.android.gallery3d.ui;

public class GLRootView extends GLSurfaceView

    @Override
    public void setContentPane(GLView content) {
        ...
        if (content != null) {
            content.attachToRoot(this);
            requestLayoutContentPane();
        }
    }

3. GLView.invalidate 函数

invalidate()在View.java中表示刷新View的意思,这里表明了GLView的刷新是通过requestRender()完成的。而GLRoot是一个接口,requestRender()还没有定义,需要在具体应用中具体实现如何刷新。这就是接口的反转调用,根据具体情况进行灵活的实现。这是可以学习的地方。

public class GLRootView extends GLSurfaceView

    // Request re-rendering of the view hierarchy.
    // This is used for animation or when the contents changed.
    public void invalidate() {
        GLRoot root = getGLRoot();
        if (root != null) root.requestRender();
    }

同理的是重新分配子视图位置的方法:

public class GLRootView extends GLSurfaceView

    // Request re-layout of the view hierarchy.
    public void requestLayout() {
        mViewFlags |= FLAG_LAYOUT_REQUESTED;
        mLastHeightSpec = -1;
        mLastWidthSpec = -1;
        if (mParent != null) {
            mParent.requestLayout();
        } else {
            // Is this a content pane ?
            GLRoot root = getGLRoot();
            // GLView可以通过调用GLRoot的requestRender()和requestLayoutContentPane()方法实现GLView树的渲染和布局。
            if (root != null) root.requestLayoutContentPane();
        }
    }

查看 GLRootView.requestLayoutContentPane

public class GLRootView extends GLSurfaceView

    // GLView的渲染在GL线程中进行,事件处理在主线程中进行。这两个线程需要同步。在事件处理和布局的时候GLRootView会获取一个锁,从而避免渲染线程在这个时候执行。这个锁就是GLRootView中的mRenderLock。
    @Override
    public void requestLayoutContentPane() {
        mRenderLock.lock();
        try {
            if (mContentView == null || (mFlags & FLAG_NEED_LAYOUT) != 0) return;

            // "View" system will invoke onLayout() for initialization(bug ?), we
            // have to ignore it since the GLThread is not ready yet.
            if ((mFlags & FLAG_INITIALIZED) == 0) return;

            mFlags |= FLAG_NEED_LAYOUT;
            
            /**
             * Request that the renderer render a frame.
             * This method is typically used when the render mode has been set to
             * {@link #RENDERMODE_WHEN_DIRTY}, so that frames are only rendered on demand.
             * May be called
             * from any thread. M
             ust not be called before a renderer has been set.
             */ 
            requestRender();
        } finally {
            mRenderLock.unlock();
        }
    }

4. 重点分析的render()函数

等同于View.java中的onDraw()函数。即该子视图显示什么样的内容由这里实现

public class GLView {


    protected void render(GLCanvas canvas) {
        boolean transitionActive = false;
        if (mTransition != null && mTransition.calculate(AnimationTime.get())) {
            invalidate();
            transitionActive = mTransition.isActive();
        }
        // 绘制动画服务
        renderBackground(canvas);
        canvas.save();
        if (transitionActive) {
            mTransition.applyContentTransform(this, canvas);
        }
        for (int i = 0, n = getComponentCount(); i < n; ++i) {
            // 重点
            renderChild(canvas, getComponent(i));
        }
        canvas.restore();
        if (transitionActive) {
            mTransition.applyOverlay(this, canvas);
        }
    }

4.1 查看 GLView.renderBackground

    protected void renderBackground(GLCanvas view) {
        if (mBackgroundColor != null) {
            view.clearBuffer(mBackgroundColor);
        }
        if (mTransition != null && mTransition.isActive()) {
            mTransition.applyBackground(this, view);
            return;
        }
    }

这里需要关注的是applyBackground()这个方法,这是定义在StateTransitionAnimation.java这一动画类中的方法

package com.android.gallery3d.anim;

public class StateTransitionAnimation extends Animation {

    public void applyBackground(GLView view, GLCanvas canvas) {
        if (mCurrentBackgroundAlpha > 0f) {
            applyOldTexture(view, canvas, mCurrentBackgroundAlpha, mCurrentBackgroundScale, true);
        }
    }
    
    
    private void applyOldTexture(GLView view, GLCanvas canvas, float alpha, float scale, boolean clear) {
        if (mOldScreenTexture == null)
            return;

        if (clear) canvas.clearBuffer(view.getBackgroundColor());
        canvas.save();
        canvas.setAlpha(alpha);
        int xOffset = view.getWidth() / 2;
        int yOffset = view.getHeight() / 2;
        canvas.translate(xOffset, yOffset);
        canvas.scale(scale, scale, 1);
        
        mOldScreenTexture.draw(canvas, -xOffset, -yOffset);
        
        canvas.restore();
    }

mOldScreenTexture在StateTransitionAnimation.java的构造参数中申明,因此我们终于搞明白了GLView首先渲染的是调用StateTransitionAnimation.java这一动画的视图时传进来的材质(材质的知识是OpenGL中关于材质渲染需要了解的),至于具体是什么材质,1可以从GLView.java中看对StateTransitionAnimation的调用是否为空,如果不为空,说明这是GLView的固有属性,所有子视图都会拥有这一属性。2为空则看某个子视图不为空的调用,则是该子视图的扩展属性。

    public void setIntroAnimation(StateTransitionAnimation intro) {
        mTransition = intro;
        if (mTransition != null) mTransition.start();
    }

这个方法是启动这个动画的地方,也是render()中首先渲染的材质传进来的地方。因此说明renderBackground()和该动画联系密切。可以说renderBackground()是专为有动画时服务的。

面的内容说明GLView拥有一个与StateTransitionAnimation动画相关的性质,这个性质的具体内容则要看在StateTransitonAnimation中如何实现的,此处不关注。回到render()函数,接下来重点关注的是 renderChild() 这一函数。

4.2 查看 GLView.renderChild

    protected void renderChild(GLCanvas canvas, GLView component) {
        if (component.getVisibility() != GLView.VISIBLE
                && component.mAnimation == null) return;

        int xoffset = component.mBounds.left - mScrollX;
        int yoffset = component.mBounds.top - mScrollY;

        canvas.translate(xoffset, yoffset);

        CanvasAnimation anim = component.mAnimation;
        if (anim != null) {
            canvas.save(anim.getCanvasSaveFlags());
            if (anim.calculate(AnimationTime.get())) {
                invalidate();
            } else {
                component.mAnimation = null;
            }
            anim.apply(canvas);
        }
        component.render(canvas);
        if (anim != null) canvas.restore();
        canvas.translate(-xoffset, -yoffset);
    }

上面这段代码中,对Canvas作了平移操作,然后调用component.render(canvas)渲染子视图,因此实现了递归渲染所有子视图到画布上。

这里的这个GLCanvas画布是个什么东西,由Canvas的调用堆栈这一节我们知道它是一个GLCanvasImpl对象,查看GLCanvasImpl构造函数知道这是一块缓冲区,没有进行任何绘制操作。因此GLView中的render()函数只是对画布进行了一些平移操作,没有进行任何绘制操作。而具体子视图则需要看子视图中是如何进行绘制的。

GLView.java的内容暂时分析到这里,具体的视图与图形外貌的联系还需要我们查看具体子视图代码中是如何进行绘制的,GLView只是所有子视图一个共有的属性和动作的集合,但是子视图也可能会更改这些属性和动作。

 类似资料: