发现每次都是要下岗前写博客,真不吉利啊,平时都没有空写。言归正传,上次的讲到RenderView和RootLayer,看起来必须把OpenGL的相关类弄清楚才能理解。
我们知道要使用的OpenGL 是画图的一堆接口(当然包括3D 纹理 渲染很多东西),在用这些的同时,我们的Activity本身还有响应触摸按键事件,重现绘制界面,这两者必须同时良好的运行,怎么做到这些?不用着急,android写好了一个类:android.opengl.GLSurfaceView
这个类有什么用,应该怎么使用呢?
这个类可以调用OpenGL API的接口,并添加自己的渲染器,如果要实现触摸监听等事件,就需要扩展这个类实现触摸监听。
public final class RenderView extends GLSurfaceView implements GLSurfaceView.Renderer, SensorEventListener {
}
看下Gallery3d是如何做的
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if (mRenderView != null)
{
return mRenderView.onKeyDown(keyCode, event)
|| super.onKeyDown(keyCode, event);
} else
{
return super.onKeyDown(keyCode, event);
}
}
不知道大家明白没有,这样的代码意味着,如果mRenderView在的话,只要在mRenderView里面就可以处理这个Activity的按键消息。
为什么?因为Activity的按键消息(onKeyDown的消息) 都被转到mRenderView.onKeyDown中了。
同样的原理在
onPause
onResume
也有类似的代码,意味着这个View和Activity是同步动作的。
但是没有看到触摸的处理,触摸是在哪里处理的呢?
那么需要我们仔细看下我们继承后生成的RenderView里面的实现。
public void setRootLayer(RootLayer layer) {
if (mRootLayer != layer) {
mRootLayer = layer;
mListsDirty = true;
if (layer != null) {
mRootLayer.setSize(mViewWidth, mViewHeight);
}
}
}
这里实现了mGridLayer变成了mRootLayer,实现了二者的关联。
@Override
public void onResume() {
super.onResume();
Sensor sensorAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if (sensorAccelerometer != null) {
mSensorManager.registerListener(this, sensorAccelerometer, SensorManager.SENSOR_DELAY_UI);
}
if (mRootLayer != null) {
mRootLayer.onResume();
}
}
@Override
public void onPause() {
super.onPause();
Log.i(TAG, "OnPause RenderView " + this);
mSensorManager.unregisterListener(this);
if (mRootLayer != null) {
mRootLayer.onPause();
}
}
在这里面,通过上面这些代码,又将Root层的动作和View层同步。
RenderView要负责刷新和用户输入,而且要处理渲染,他是要新开线程的,看他的构造函数。
public RenderView(final Context context) {
super(context);
setBackgroundDrawable(null);
setFocusable(true);
setRenderer(this);
mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
if (sCachedTextureLoadThread == null) {
for (int i = 0; i != NUM_TEXTURE_LOAD_THREADS; ++i) {
TextureLoadThread thread = new TextureLoadThread();
if (i == 0) {
sCachedTextureLoadThread = thread;
}
if (i == 1) {
sVideoTextureLoadThread = thread;
}
sTextureLoadThreads[i] = thread;
thread.start();
}
}
}
这里是循环,NUM_TEXTURE_LOAD_THREADS 值为4,而看到 在0和1时,分别是sCacheTextureLoadThread和sVideoTextureLoadThread,并且又将四个线程放在了sTextureLoadTheads数组里面。
就是用线程刷纹理,什么是纹理,就是一张图片,贴到物体的表面,比如一个木头的纹理图片,贴到一个正方形上,看起来就感觉这个正方形是个木头了。
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Deque<Texture> inputQueue = (sVideoTextureLoadThread == this) ? sLoadInputQueueVideo
: ((sCachedTextureLoadThread == this) ? sLoadInputQueueCached : sLoadInputQueue);
Deque<Texture> outputQueue = sLoadOutputQueue;
try {
for (;;) {
// Pop the next texture from the input queue.
Texture texture = null;
synchronized (inputQueue) {
while ((texture = inputQueue.pollFirst()) == null) {
inputQueue.wait();
}
}
if (sCachedTextureLoadThread != this)
mIsLoading = true;
// Load the texture bitmap.
load(texture);
mIsLoading = false;
// Push the texture onto the output queue.
synchronized (outputQueue) {
outputQueue.addLast(texture);
}
}
} catch (InterruptedException e) {
// Terminate the thread.
}
}
加载函数
private void load(Texture texture) {
// Generate the texture bitmap.
RenderView view = RenderView.this;
view.loadTextureAsync(texture);
view.requestRender();
}
可以看到,纹理的输入队列只有三种情况
1 sLoadInputQueueVideo
2 sLoadInputQueueCached
3 sLoadInputQueue
private void queueLoad(final Texture texture, boolean highPriority) {
// Allow the texture to defer queuing.
if (!texture.shouldQueue()) {
return;
}
// Change the texture state to loading.
texture.mState = Texture.STATE_LOADING;
// Push the texture onto the load input queue.
Deque<Texture> inputQueue = (texture.isUncachedVideo()) ? sLoadInputQueueVideo
: (texture.isCached()) ? sLoadInputQueueCached : sLoadInputQueue;
;
synchronized (inputQueue) {
if (highPriority) {
inputQueue.addFirst(texture);
// Enforce the maximum loading count by removing something from the end of
// the loading queue, if necessary.
if (mLoadingCount >= MAX_LOADING_COUNT) {
Texture unloadTexture = inputQueue.pollLast();
unloadTexture.mState = Texture.STATE_UNLOADED;
--mLoadingCount;
}
} else {
inputQueue.addLast(texture);
}
inputQueue.notify();
}
++mLoadingCount;
}
onDrawFrame():
每帧都通过该方法进行绘制。绘制时通常先调用 glClear函数来清空 framebuffer,然后在调用 OpenGL ES 的接口进行绘制。
// @Override
public void onDrawFrame(GL10 gl1) {
GL11 gl = (GL11) gl1;
if (!mFirstDraw) {
Log.i(TAG, "First Draw");
}
mFirstDraw = true;
// setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
// Rebuild the display lists if the render tree has changed.
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();
final float 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) {
boolean retVal = updateList.get(i).update(this, mFrameInterval);
isDirty |= retVal;
}
if (isDirty) {
requestRender();
}
// Clear the depth buffer.
gl.glClear(GL11.GL_DEPTH_BUFFER_BIT);
gl.glEnable(GL11.GL_SCISSOR_TEST);
gl.glScissor(0, 0, getWidth(), getHeight());
// Run the opaque pass.
gl.glDisable(GL11.GL_BLEND);
final ArrayList<Layer> opaqueList = lists.opaqueList;
for (int i = opaqueList.size() - 1; i >= 0; --i) {
final Layer 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) {
final Layer layer = blendedList.get(i);
if (!layer.mHidden) {
layer.renderBlended(this, gl);
}
}
gl.glDisable(GL11.GL_BLEND);
}
}
这段代码有点长,我们分析下,首先是列表如果有改动,会要更新层列表 update opaque blended,为什么只刷这三个层啊?因为只有在这三个列表的层才和刷新相关,更新层,半透明层和不透明层。
然后是处理纹理,将纹理上传到GPU(如果这里不清楚,看OpenGL的概念)
终于我们在这里找到了触摸的处理。
// Dispatch the current touch event.
processCurrentEvent();
processTouchEvent();
大家应该还记得,上次我们已经把按键存起来了,触摸的事件保存在一个队列中了,现在可以处理了。
private void processKeyEvent() {
// Get the event.
final KeyEvent event = mCurrentKeyEvent;
boolean result = false;
mCurrentKeyEvent = null;
// Dispatch the event to the root layer.
if (mRootLayer != null) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
result = mRootLayer.onKeyDown(event.getKeyCode(), event);
} else {
result = mRootLayer.onKeyUp(event.getKeyCode(), event);
}
}
mCurrentKeyEventResult = result;
}
以上代码说明了一个问题,所有的按键事件都是在Root Layer层中处理的。
再来看触摸的处理
private void processTouchEvent() {
MotionEvent event = null;
int numEvents = mTouchEventQueue.size();
int i = 0;
do {
// We look at the touch event queue and process one event at a time
synchronized (mTouchEventQueue) {
event = mTouchEventQueue.pollFirst();
}
if (event == null)
return;
// Detect the hit layer.
final int action = event.getAction();
Layer target;
if (action == MotionEvent.ACTION_DOWN) {
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();
}
}
private Layer hitTest(float x, float y) {
final ArrayList<Layer> hitTestList = sLists.hitTestList;
for (int i = hitTestList.size() - 1; i >= 0; --i) {
final Layer layer = hitTestList.get(i);
if (layer != null && !layer.mHidden) {
final float layerX = layer.mX;
final float layerY = layer.mY;
if (x >= layerX && y >= layerY && x < layerX + layer.mWidth && y < layerY + layer.mHeight
&& layer.containsPoint(x, y)) {
return layer;
}
}
}
return null;
}
这两段代码看完,我们会发现触摸的处理方法。
但是,这个触摸的处理只是针对
hitTestList
这个层的LIST。但这个层的LIST在什么地方关联的?在什么地方添加的其他的层?呵呵,下次讲解。