// 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. //
上面这一段为定义在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()锁住该线程避免该线程和主线程的同时运行。
接下来,我们从源码中分析,到底GLView是怎么实现注释中所描述的功能。而作为一个视图控件,又是怎么完成“视图”这一角色的。
public interface OnClickListener {
void onClick(GLView v);
}
申明点击事件,这也是GLView唯一拥有的事件(关于事件,回调,监听这些概念,需要了解回调事件,反转调用这些机制)。
// 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;
for (int i = 0, n = getComponentCount(); i < n; ++i) {//通过递归对root(某个具体的调用attachToRoot()方法传进来的GLView作为父视图)和所有子视图形成树状结构。
getComponent(i).onAttachToRoot(root);
}
}
protected void onDetachFromRoot() {
for (int i = 0, n = getComponentCount(); i < n; ++i) {
getComponent(i).onDetachFromRoot();
}
mRoot = null;
}
我们关注一下下面的这个方法:
public void invalidate() {
GLRoot root = getGLRoot();
if (root != null) root.requestRender();
}
invalidate()在View.java中表示刷新View的意思,这里表明了GLView的刷新是通过requestRender()完成的。而GLRoot是一个接口,requestRender()还没有定义,需要在具体应用中具体实现如何刷新。这就是接口的反转调用,根据具体情况进行灵活的实现。这是可以学习的地方。
同理的是重新分配子视图位置的方法:
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();
if (root != null) root.requestLayoutContentPane();
}
}
接下来是重点分析的render()函数,它等同于View.java中的onDraw()函数。即该子视图显示什么样的内容由这里实现。
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);
}
}
protected void renderBackground(GLCanvas view) {
if (mBackgroundColor != null) {
view.clearBuffer(mBackgroundColor);
}
if (mTransition != null && mTransition.isActive()) {
mTransition.applyBackground(this, view);
return;
}
}
显然这里需要关注的是applyBackground()这个方法,这是定义在StateTransitionAnimation.java这一动画类中的方法。
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为空则看某个子视图不为空的调用,则是该子视图的扩展属性。
在GLView.java中,mTransition的值为:
public void setIntroAnimation(StateTransitionAnimation intro) {
mTransition = intro;
if (mTransition != null) mTransition.start();
}
这个方法是启动这个动画的地方,也是render()中首先渲染的材质传进来的地方。因此说明renderBackground()和该动画联系密切。可以说renderBackground()是专为有动画时服务的。
上面的内容说明GLView拥有一个与StateTransitionAnimation动画相关的性质,这个性质的具体内容则要看在StateTransitonAnimation中如何实现的,此处不关注。回到render()函数,接下来重点关注的是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);
}
这里的这个GLCanvas画布是个什么东西,由Canvas的调用堆栈这一节我们知道它是一个GLCanvasImpl对象,查看GLCanvasImpl构造函数知道这是一块缓冲区,没有进行任何绘制操作。因此GLView中的render()函数只是对画布进行了一些平移操作,没有进行任何绘制操作。而具体子视图则需要看子视图中是如何进行绘制的。
GLView.java的内容暂时分析到这里,具体的视图与图形外貌的联系还需要我们查看具体子视图代码中是如何进行绘制的,GLView只是所有子视图一个共有的属性和动作的集合,但是子视图也可能会更改这些属性和动作。