| page.title= Responding to Touch Events |
| parent.title=Displaying Graphics with OpenGL ES |
| parent.link=index.html |
| |
| trainingnavtop=true |
| previous.title=Adding Motion |
| previous.link=motion.html |
| |
| @jd:body |
| |
| <div id="tb-wrapper"> |
| <div id="tb"> |
| |
| <h2>This lesson teaches you to</h2> |
| <ol> |
| <li><a href="#listener">Setup a Touch Listener</a></li> |
| <li><a href="#angle">Expose the Rotation Angle</a></li> |
| <li><a href="#rotate">Apply Rotation</a></li> |
| </ol> |
| |
| <h2>You should also read</h2> |
| <ul> |
| <li><a href="{@docRoot}guide/topics/graphics/opengl.html">OpenGL</a></li> |
| </ul> |
| |
| <div class="download-box"> |
| <a href="http://developer.android.com/shareables/training/OpenGLES.zip" |
| class="button">Download the sample</a> |
| <p class="filename">OpenGLES.zip</p> |
| </div> |
| |
| </div> |
| </div> |
| |
| <p>Making objects move according to a preset program like the rotating triangle is useful for |
| getting some attention, but what if you want to have users interact with your OpenGL ES graphics? |
| The key to making your OpenGL ES application touch interactive is expanding your implementation of |
| {@link android.opengl.GLSurfaceView} to override the {@link |
| android.opengl.GLSurfaceView#onTouchEvent onTouchEvent()} to listen for touch events.</p> |
| |
| <p>This lesson shows you how to listen for touch events to let users rotate an OpenGL ES object.</p> |
| |
| |
| <h2 id="listener">Setup a Touch Listener</h2> |
| |
| <p>In order to make your OpenGL ES application respond to touch events, you must implement the |
| {@link android.opengl.GLSurfaceView#onTouchEvent onTouchEvent()} method in your |
| {@link android.opengl.GLSurfaceView} class. The example implementation below shows how to listen for |
| {@link android.view.MotionEvent#ACTION_MOVE MotionEvent.ACTION_MOVE} events and translate them to |
| an angle of rotation for a shape.</p> |
| |
| <pre> |
| private final float TOUCH_SCALE_FACTOR = 180.0f / 320; |
| private float mPreviousX; |
| private float mPreviousY; |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent e) { |
| // MotionEvent reports input details from the touch screen |
| // and other input controls. In this case, you are only |
| // interested in events where the touch position changed. |
| |
| float x = e.getX(); |
| float y = e.getY(); |
| |
| switch (e.getAction()) { |
| case MotionEvent.ACTION_MOVE: |
| |
| float dx = x - mPreviousX; |
| float dy = y - mPreviousY; |
| |
| // reverse direction of rotation above the mid-line |
| if (y > getHeight() / 2) { |
| dx = dx * -1 ; |
| } |
| |
| // reverse direction of rotation to left of the mid-line |
| if (x < getWidth() / 2) { |
| dy = dy * -1 ; |
| } |
| |
| mRenderer.setAngle( |
| mRenderer.getAngle() + |
| ((dx + dy) * TOUCH_SCALE_FACTOR)); |
| requestRender(); |
| } |
| |
| mPreviousX = x; |
| mPreviousY = y; |
| return true; |
| } |
| </pre> |
| |
| <p>Notice that after calculating the rotation angle, this method calls {@link |
| android.opengl.GLSurfaceView#requestRender requestRender()} to tell the |
| renderer that it is time to render the frame. This approach is the most efficient in this example |
| because the frame does not need to be redrawn unless there is a change in the rotation. However, it |
| does not have any impact on efficiency unless you also request that the renderer only redraw when |
| the data changes using the {@link android.opengl.GLSurfaceView#setRenderMode setRenderMode()} |
| method, so make sure this line is uncommented in the renderer:</p> |
| |
| <pre> |
| public MyGLSurfaceView(Context context) { |
| ... |
| // Render the view only when there is a change in the drawing data |
| <strong>setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);</strong> |
| } |
| </pre> |
| |
| <h2 id="angle">Expose the Rotation Angle</h2> |
| |
| <p>The example code above requires that you expose the rotation angle through your renderer by |
| adding a public member. Since the renderer code is running on a separate thread from the main user |
| interface thread of your application, you must declare this public variable as {@code volatile}. |
| Here is the code to declare the variable and expose the getter and setter pair:</p> |
| |
| <pre> |
| public class MyGLRenderer implements GLSurfaceView.Renderer { |
| ... |
| |
| public volatile float mAngle; |
| |
| public float getAngle() { |
| return mAngle; |
| } |
| |
| public void setAngle(float angle) { |
| mAngle = angle; |
| } |
| } |
| </pre> |
| |
| |
| <h2 id="rotate">Apply Rotation</h2> |
| |
| <p>To apply the rotation generated by touch input, comment out the code that generates an angle and |
| add {@code mAngle}, which contains the touch input generated angle:</p> |
| |
| <pre> |
| public void onDrawFrame(GL10 gl) { |
| ... |
| float[] scratch = new float[16]; |
| |
| // Create a rotation for the triangle |
| // long time = SystemClock.uptimeMillis() % 4000L; |
| // float angle = 0.090f * ((int) time); |
| <strong>Matrix.setRotateM(mRotationMatrix, 0, mAngle, 0, 0, -1.0f);</strong> |
| |
| // Combine the rotation matrix with the projection and camera view |
| // Note that the mMVPMatrix factor *must be first* in order |
| // for the matrix multiplication product to be correct. |
| Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0); |
| |
| // Draw triangle |
| mTriangle.draw(scratch); |
| } |
| </pre> |
| |
| <p>When you have completed the steps described above, run the program and drag your finger over the |
| screen to rotate the triangle:</p> |
| |
| <img src="{@docRoot}images/opengl/ogl-triangle-touch.png"> |
| <p class="img-caption"> |
| <strong>Figure 1.</strong> Triangle being rotated with touch input (circle shows touch |
| location).</p> |