Develop -- Training(十六) -- 显示绘图和OpenGL ES

Android framework提供了许多标准的工具,来创建有吸引力的、功能丰富的用户图形界面。但是,如果你想要更多的控制权,比如在应用程序的屏幕上绘图,或者冒险进入三维图形,你需要使用不同的工具。通过Android framework提供的OpenGL ES的API提供了一套显示高端的工具,动画图像超出你的想象,许多Android设备的图像处理单元得到了加速(GPUs)。

这节课主要开发一个OpenGL应用程序、包括设置、画对象、移动对象元素、响应触摸输入事件。

这节课的示例代码使用的是OpenGL ES 2.0 API,这是推荐使用的API版本和当前Android设备的版本。关于OpenGL ES的更多信息,请看OpenGL开发文档。

注意:要小心,不要混合使用OpenGL ES 2.0和OpenGL ES 1.x中API的方法,这两个API是不能互换的,试图同时使用它们是会出错的。

构建OpenGL ES环境

为了在应用程序中用OpenGL ES绘制图形,必须要为它们创建一个试图容器。一个比较直接的方式是实现GLSurfaceView 和 GLSurfaceView.Renderer。GLSurfaceView 是用于渲染绘制OpenGL和GLSurfaceView.Renderer图形视图的容器的控制器。有关这个类的更多信息,参见OpenGL ES开发指南。

GLSurfaceView仅仅只是一个将OpenGL ES图形合并到应用程序中的一种方式。对于一个全屏或者在全屏上绘制的视图,它是一个合理的选择。开发者如果想要合并一小部分OpenGL ES到布局中,应该要看看TextureView。真正地,做自己的开发者,也可能使用SurfaceView来构建一个OpenGL ES视图,但是也要写一些额外的代码。

这节课介绍了怎样完成一个实现了GLSurfaceView 和 GLSurfaceView.Renderer在一个应用程序的Activity的例子。

1.在清单文件中申明OpenGL ES的使用

为了应用程序能够使用OpenGL ES 2.0的API,你必须要在manifest中添加以下申明:

<uses-feature android:glEsVersion="0x00020000" android:required="true" />

如果你的应用程序使用纹理压缩,你必须要申明你的app所支持的压缩格式,因此,它只安装在兼容的设备上。

<supports-gl-texture android:name="GL_OES_compressed_ETC1_RGB8_texture" />
<supports-gl-texture android:name="GL_OES_compressed_paletted_texture" />

关于更多的纹理压缩格式的信息,请查看OpenGL 开发者指南。

2.创建Activity使用OpenGL ES图形

应用程序使用OpenGL ES的activity,就像其他应用程序一样有用户界面,主要和其他应用程序不同的是,你放置在布局中的活动。当更多的应用程序使用的是TextView,Button和ListView,在一个app中使用OpenGL ES,你要添加一个GLSurfaceView。

下面的代码示例显示了一个最小的实现,一个activity使用GLSurfaceView作为一个初级的视图:

public class OpenGLES20Activity extends Activity {

    private GLSurfaceView mGLView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Create a GLSurfaceView instance and set it
        // as the ContentView for this Activity.
        mGLView = new MyGLSurfaceView(this);
        setContentView(mGLView);
    }
}

注意:OpenGL ES要求Android 2.2(API 8)或者更高,所以要确保你的工程高于这个API。

3.构建GLSurfaceView对象
一个GLSurfaceView是专门的视图,用来绘制OpenGL ES图形。它本身不做太多的东西。实际上绘制这个对象是由GLSurfaceView.Renderer来控制的,你可以设置这个视图。事实上,这个对象的代码是很少的,你可以跳过扩展,仅仅只创建一个未更改的GLSurfaceView的实例,但不要这样做。你需要继承这个类,捕获触摸事件,覆盖响应它的触摸事件。

下面的代码是最小的、快速实现GLSurfaceView,在activity创建一个内部类使用它:

class MyGLSurfaceView extends GLSurfaceView {

    private final MyGLRenderer mRenderer;

    public MyGLSurfaceView(Context context){
        super(context);

        // Create an OpenGL ES 2.0 context
        setEGLContextClientVersion(2);

        mRenderer = new MyGLRenderer();

        // Set the Renderer for drawing on the GLSurfaceView
        setRenderer(mRenderer);
    }
}

另外一个可选的除了GLSurfaceView实现是设置渲染模式的,当你的绘图数据改变也只能绘制这个视图,使用的是GLSurfaceView.RENDERMODE_WHEN_DIRTY设置。

// Render the view only when there is a change in the drawing data
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);

这个设置可防止GLSurfaceView框架被重绘,直到调用requestRender() ,使这个简单的app更有效率。

4.构建渲染器类

在启用GLSurfaceView.Renderer类,或者渲染器,在使用OpenGL ES应用程序就使得事情开始变得有趣。这个类控制着在GLSurfaceView获取绘制是有关联的。渲染器有三个方法在android系统中会被回调,计算在GLSurfaceView怎样绘制。

onSurfceCreated() – 调用一次,OpenGL ES环境被创建的时候

onDrawFrame() – 视图被重绘就会回掉此方法

onSurfaceChanged() – 视图形状被改变,例如屏幕方向的改变就会回掉此方法

下面的代码实现了OpenGL ES渲染器,不做任何事情只绘制一个黑色的背景在GLSurfaceView上:

public class MyGLRenderer implements GLSurfaceView.Renderer {

    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        // Set the background frame color
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    }

    public void onDrawFrame(GL10 unused) {
        // Redraw background color
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
    }

    public void onSurfaceChanged(GL10 unused, int width, int height) {
        GLES20.glViewport(0, 0, width, height);
    }
}

这就是所有的一切!这个例子代码创建了一个简单的应用程序,使用OpenGL显示一个黑色的屏幕。这个代码不做任何有意思的事情,通过创建这些类,奠定了你需要开始用OpenGL绘制图形元素的基础。

注意:你可能想知道为什么这些方法都有一个GL10的参数,当你使用的是OpenGL ES 2.0API。这些方法的签名只是重复使用了2.0的API,以保持安卓框架代码的简单。

如果你想属性OpenGL ES API,你现在应该能够建立一个OpenGL ES环境的应用程序,并开始绘制图形。但是,如果你需要多一点了解如何开始使用OpenGL,开始学习下一节课。

定义形状

能够定义形状并在OpenGL ES视图上画出来,是你创建高端图像大作的第一步。用OpenGL ES来绘图,在不知道一些基本事情的情况下,想要绘制预期的图形是有一点困难的。

这节课将介绍OpenGL ES的相对于安卓设备屏幕的坐标系统,定义形状,形状的面孔,以及定义一个三角形和四边形的基础知识。

1.定义一个三角形

OpenGL ES允许你定义绘制对象使用三维坐标空间。所以,你在画一个三角形之前,你必须要定义它的坐标。在OpenGL中,一个通常的做法是定义一个以float类型的数为坐标的顶点数组。为了获得更高的效率,你要把这些坐标放到ByteBuffer中,然后传给OpenGL ES流来处理。

public class Triangle {

    private FloatBuffer vertexBuffer;

    // number of coordinates per vertex in this array
    static final int COORDS_PER_VERTEX = 3;
    static float triangleCoords[] = {   // in counterclockwise order:
             0.0f,  0.622008459f, 0.0f, // top
            -0.5f, -0.311004243f, 0.0f, // bottom left
             0.5f, -0.311004243f, 0.0f  // bottom right
    };

    // Set color with red, green, blue and alpha (opacity) values
    float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };

    public Triangle() {
        // initialize vertex byte buffer for shape coordinates
        ByteBuffer bb = ByteBuffer.allocateDirect(
                // (number of coordinate values * 4 bytes per float)
                triangleCoords.length * 4);
        // use the device hardware's native byte order
        bb.order(ByteOrder.nativeOrder());

        // create a floating point buffer from the ByteBuffer
        vertexBuffer = bb.asFloatBuffer();
        // add the coordinates to the FloatBuffer
        vertexBuffer.put(triangleCoords);
        // set the buffer to read the first coordinate
        vertexBuffer.position(0);
    }
}

默认情况下,OpenGL ES假定一个坐标系统,指定 [0,0,0] (X,Y,Z) 在GLSurfaceView的中间,[1,1,0]在右上角,[-1,-1,0] 在左下角。对于这个坐标系统的说明,请参阅 OpenGL ES的开发者指南。

请注意,这个形状的坐标是以逆时针顺序定义的。绘图的顺序是很重要的,因为它定义了哪一个边的外形,你通常想要得到它的反面,你也可以不使用OpenGL ES的面部挑选特征。关于更多的面部挑选,请参阅 OpenGL ES的开发者指南。

2.定义一个正方形

用OpenGL定义三角形是很容易的,但是如果你想得到一个更加复杂的图形呢?比如,一个正方形?有许多方法可以做到,但通常的做法是使用OpenGL ES画2个在一起的三角形。

这里写图片描述

最后,你应该定义一个逆时针顺序的顶点,2个相同的三角形代表这种形状,要把它的值放到ByteBuffer中。为了避免定义2个坐标被每个三角形共享2次,使用一个绘图列表告诉OpenGL ES图形流怎样画这些顶点。下面是正方形的代码:

public class Square {

    private FloatBuffer vertexBuffer;
    private ShortBuffer drawListBuffer;

    // number of coordinates per vertex in this array
    static final int COORDS_PER_VERTEX = 3;
    static float squareCoords[] = {
            -0.5f,  0.5f, 0.0f,   // top left
            -0.5f, -0.5f, 0.0f,   // bottom left
             0.5f, -0.5f, 0.0f,   // bottom right
             0.5f,  0.5f, 0.0f }; // top right

    private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices

    public Square() {
        // initialize vertex byte buffer for shape coordinates
        ByteBuffer bb = ByteBuffer.allocateDirect(
        // (# of coordinate values * 4 bytes per float)
                squareCoords.length * 4);
        bb.order(ByteOrder.nativeOrder());
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(squareCoords);
        vertexBuffer.position(0);

        // initialize byte buffer for the draw list
        ByteBuffer dlb = ByteBuffer.allocateDirect(
        // (# of coordinate values * 2 bytes per short)
                drawOrder.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        drawListBuffer = dlb.asShortBuffer();
        drawListBuffer.put(drawOrder);
        drawListBuffer.position(0);
    }
}

这个例子告诉你使用OpenGL创建一个复杂的形状。一般来说,你要使用三角形集合来画对象。在下节课中,你将学习如何在屏幕上绘制这些形状。

绘制形状

在你定义形状之后,你可能就想绘制它们了。画这些形状要使用OpenGL ES 2.0,要写大量的代码了,因为该API提供了大量的图形渲染流的控制。

这节课介绍怎样绘制你在上一节课定义的形状,使用OpenGL ES 2.0 API。

1.初始化图形

在你做任何绘图之前,你必须初始化并加载你想要画的图形。除非在程序的执行过程中,图形的结构(原始坐标)被改变,你应该在onSurfaceCreated() 方法中初始化它们,这样渲染器的内存和处理更加高效。

public class MyGLRenderer implements GLSurfaceView.Renderer {

    ...
    private Triangle mTriangle;
    private Square   mSquare;

    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        ...

        // initialize a triangle
        mTriangle = new Triangle();
        // initialize a square
        mSquare = new Square();
    }
    ...
}

2.画一个图形

使用OpenGL ES 2.0画一个定义的图形,需要大量的代码,因为你必须要提供大量详细的图形渲染流程。特别指出,你要根据下面的几点来做:

顶点着色器 – 渲染图形的顶点。

碎片着色器 – 渲染图形的面部颜色或者纹理。

程序 – 一个OpenGL ES对象包含着你想要画出一个或者更多图形的着色器。

你至少需要一个顶点着色器来画形状和一个片段着色器来画颜色。这些着色器必须被编译,然后添加到一个OpenGL ES程序中去,然后用来绘制形状。下面的代码定义了基本的着色器来绘制一个三角形类:

public class Triangle {

    private final String vertexShaderCode =
        "attribute vec4 vPosition;" +
        "void main() {" +
        "  gl_Position = vPosition;" +
        "}";

    private final String fragmentShaderCode =
        "precision mediump float;" +
        "uniform vec4 vColor;" +
        "void main() {" +
        "  gl_FragColor = vColor;" +
        "}";

    ...
}

着色器包含OpenGL着色语言(GLSL)代码必须被编译使用它,在OpenGL ES环境之前。编译下面代码,在你的着色器类创建一个实用的方法。

public static int loadShader(int type, String shaderCode){

    // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
    // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
    int shader = GLES20.glCreateShader(type);

    // add the source code to the shader and compile it
    GLES20.glShaderSource(shader, shaderCode);
    GLES20.glCompileShader(shader);

    return shader;
}

为了画出图形,你必须编译着色器代码,将其添加到OpenGL ES的程序对象中,然后链接程序。在此绘制对象的构造器,所以它只运行一次。

注意:编译OpenGL ES着色器和链接程序在CPU处理周期和处理时间,是要付出很昂贵的代价的,所以你应该避免多次调用。如果你不知道着色器在运行期间的内容,你应该建立你的代码,使它们只创建一次,然后缓存为以后使用。

public class Triangle() {
    ...

    private final int mProgram;

    public Triangle() {
        ...

        int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
                                        vertexShaderCode);
        int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
                                        fragmentShaderCode);

        // create empty OpenGL ES Program
        mProgram = GLES20.glCreateProgram();

        // add the vertex shader to program
        GLES20.glAttachShader(mProgram, vertexShader);

        // add the fragment shader to program
        GLES20.glAttachShader(mProgram, fragmentShader);

        // creates OpenGL ES program executables
        GLES20.glLinkProgram(mProgram);
    }
}

在这一点上,你要添加实际调用,在你绘图的时候。用OpenGL ES绘图的时候要求你要指定多个参数,然后告诉渲染器你想要怎样去绘制它。由于绘画选项可以改变形状,这是一个很好的想法,你的形状类包含他们自己的绘画逻辑。

创建draw()方法来绘图,下面代码设置了位置和颜色值的顶点着色器和片段着色器,然后执行绘图功能。

private int mPositionHandle;
private int mColorHandle;

private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

public void draw() {
    // Add program to OpenGL ES environment
    GLES20.glUseProgram(mProgram);

    // get handle to vertex shader's vPosition member
    mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

    // Enable a handle to the triangle vertices
    GLES20.glEnableVertexAttribArray(mPositionHandle);

    // Prepare the triangle coordinate data
    GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                                 GLES20.GL_FLOAT, false,
                                 vertexStride, vertexBuffer);

    // get handle to fragment shader's vColor member
    mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

    // Set color for drawing the triangle
    GLES20.glUniform4fv(mColorHandle, 1, color, 0);

    // Draw the triangle
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

    // Disable vertex array
    GLES20.glDisableVertexAttribArray(mPositionHandle);
}

一旦你有了这些代码,绘制该对象只需在你的渲染器的ondrawframe()方法中调用draw()方法:

public void onDrawFrame(GL10 unused) {
    ...

    mTriangle.draw();
}

当你运行应用程序,它应该是这个样子的

这里写图片描述

这个代码示例有几个问题。首先,它不会给你留下深刻的印象。其次,当你改变设备的屏幕方向时,这个三角形会被压扁和改变形状。图形被压缩的原因是由于对象的顶点大小没有根据GLSurfaceView屏幕显示区域大小的比例而改正。你可以在下一节课中使用投影和相机视图来解决这个问题。

最后,三角形是固定的,这是一个有点枯燥。可以加入运动,让图形旋转和做更有趣的事,使用OpenGL ES图形流水线。

应用投影和相机视图

在OpenGL ES 环境中,投影和相机视图允许你以更接近的方式显示绘图对象,就像你的眼睛看见的物理对象一样。这个模拟的物理视图是通过数字转化为绘图对象坐标的。

投影 – 调整绘图对象的坐标是基于GLSurfaceView显示的宽和高的。没有这种计算,对象绘制通过OpenGL ES扭曲不同视图窗口的比例。一个阴影的转换通常只有在被计算的时候,当GLSurfaceView视图被建立或者变化在渲染器的onSurfaceChanged()方法中。有关OpenGL ES的阴影和坐标映射,请参见Mapping Coordinates for Drawn Objects.。

相机视图 – 调整绘图对象的坐标是基于虚拟相机的位置。OpenGL ES不定义一个真实的相机是很重要的,而是提供了一个有用的方法,通过改变绘制的对象的显示来模拟一个相机。一个相机视图转化可能被计算一次,当你创建GLSurfaceView时,或者可能动态改变基于用户的动作和应用程序的功能。

本课程介绍如何创建一个投影和相机视图,并将其应用到GLSurfaceView上绘制图形。

1.定义投影

用于投影转换的计算数据在GLSurfaceView.Renderer类的onSurfaceChanged()方法中,以下示例代码获取GLSurfaceView的高度和宽度并用它来​​填充投影变换矩阵在Matrix.frustumM()方法中:

// mMVPMatrix is an abbreviation for "Model View Projection Matrix"
private final float[] mMVPMatrix = new float[16];
private final float[] mProjectionMatrix = new float[16];
private final float[] mViewMatrix = new float[16];

@Override
public void onSurfaceChanged(GL10 unused, int width, int height) {
    GLES20.glViewport(0, 0, width, height);

    float ratio = (float) width / height;

    // this projection matrix is applied to object coordinates
    // in the onDrawFrame() method
    Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
}

这个代码填充了投影矩阵,mProjectionMatrix可以和相机视图结合转化在onDrawFrame()方法中,这个将在下一节中说明。

注意:应用投影转换到图形对象通常会导致一个空的显示。一般情况下,还必须以应用摄影机视图转换为了在任何屏幕上显示。

2.定义相机视图

完成添加摄像机视图转换在渲染器部分绘制的过程,转化为你要的绘制对象的整个过程。下面这个例子代码,相机视图转化被计算使用 Matrix.setLookAtM() 方法,然后结合前面的投影矩阵计算,通过组合转化矩阵画出图形。

@Override
public void onDrawFrame(GL10 unused) {
    ...
    // Set the camera position (View matrix)
    Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

    // Calculate the projection and view transformation
    Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);

    // Draw shape
    mTriangle.draw(mMVPMatrix);
}

3.应用投影和相机变化

为了使用投影和相机视图转化矩阵结合显示在前面的部分,首先添加一个矩阵变量在顶点着色器,定义在Triangle类中:

public class Triangle {

    private final String vertexShaderCode =
        // This matrix member variable provides a hook to manipulate
        // the coordinates of the objects that use this vertex shader
        "uniform mat4 uMVPMatrix;" +
        "attribute vec4 vPosition;" +
        "void main() {" +
        // the matrix must be included as a modifier of gl_Position
        // Note that the uMVPMatrix factor *must be first* in order
        // for the matrix multiplication product to be correct.
        "  gl_Position = uMVPMatrix * vPosition;" +
        "}";

    // Use to access and set the view transformation
    private int mMVPMatrixHandle;

    ...
}

接下来,修改draw() 方法,你的图形对象要接受组合转换矩阵,并将其应用到图形:

public void draw(float[] mvpMatrix) { // pass in the calculated transformation matrix
    ...

    // get handle to shape's transformation matrix
    mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

    // Pass the projection and view transformation to the shader
    GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);

    // Draw the triangle
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

    // Disable vertex array
    GLES20.glDisableVertexAttribArray(mPositionHandle);
}

一旦你正确地计算和应用了投影和相机视图转换,你的图形对象就绘制了正确的比例,看起来像这样:

这里写图片描述

现在,你的应用程序显示的图形是正确的比例,是时候给你的图形添加运动了。

添加运动

在屏幕上绘制对象是OpenGL的一个非常基本的功能,但是你也能够做和其他安卓图形框架类,包括Canvas和Drawable对象,OpenGL ES提供了添加额外的能力,移动和转化图形对象在三个维度或者其他独特的方式,创建一个令人信服的体验。

这节课,你将再向前走的另一步是使用OpenGL ES通学习如何给图形添加旋转的动作。

1.旋转一个图形

使用OpenGL ES 2.0旋转一个图形对象是相对简单的。在你的渲染器中,创建另一个转化矩阵(一个旋转矩阵),然​​后用你的投影和相机视图变换矩阵结合起来:

private float[] mRotationMatrix = new float[16];
public void onDrawFrame(GL10 gl) {
    float[] scratch = new float[16];

    ...

    // Create a rotation transformation for the triangle
    long time = SystemClock.uptimeMillis() % 4000L;
    float angle = 0.090f * ((int) time);
    Matrix.setRotateM(mRotationMatrix, 0, angle, 0, 0, -1.0f);

    // 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);
}

在做出这些改变之后,你的三角形并没有旋转,确保你已经注释掉了 GLSurfaceView.RENDERMODE_WHEN_DIRTY设置,将在下一节进行说明。

2.能够连续绘制

如果你一直在努力地跟踪这个类中的示例代码,到这一点,确保你注释的这行,设置渲染模式只画脏脏的,否则OpenGL的旋转图形是唯一一个增量,然后等待从GLSurfaceView容器回掉requestRender()方法:

public MyGLSurfaceView(Context context) {
    ...
    // Render the view only when there is a change in the drawing data.
    // To allow the triangle to rotate automatically, this line is commented out:
    //setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}

除非你有对象改变,而没有任何用户的交互,它通常是一个好主意,有个标志位打开。准备取消注释这个代码,因为接下来它将会被调用。

响应触摸事件

制作对象移动,根据预先设定的程序,如旋转三角形一样得到一些关注是有用的,但是你想用户怎么和OpenGL ES图形交互呢?是OpenGL ES应用程序的触摸交互的关键是扩大你启用GLSurfaceView的onTouchEvent() 来监听触摸事件。

这节课介绍怎么监听触摸事件,让用户来旋转OpenGL ES对象。

1.设置监听器

为了让你的OpenGL ES应用程序能够响应触摸事件,你必须要在你的GLSurfaceView类实现onTouchEvent()方法。下面的例子展示了监听MotionEvent.ACTION_MOVE事件,然后将他们翻译成图形旋转的角度。

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;
}

注意,在计算旋转角度之后,requestRender()方法被回掉,告诉渲染器是时候渲染每一帧图像了。在例子中这个方法是最有效的,因为这一帧不需要被重绘,除非这个旋转有一个变化。然而,它没有任何影响,除非你对效率的要求,渲染器只重绘当数据发生变化时,在setrendermode()方法中重绘,所以一定要确保在渲染器中这一行代码被取消注释:

public MyGLSurfaceView(Context context) {
    ...
    // Render the view only when there is a change in the drawing data
    setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}

2.揭露旋转角度

这个示例代码要求你揭露旋转角度通过你的渲染器添加一个公有的成员,由于渲染代码是在应用程序子线程中运行的,你必须要声明公共变量volatile。下面的代码声明了这个变量,并且有getter和setter来获取它。

public class MyGLRenderer implements GLSurfaceView.Renderer {
    ...

    public volatile float mAngle;

    public float getAngle() {
        return mAngle;
    }

    public void setAngle(float angle) {
        mAngle = angle;
    }
}

3.应用旋转

通过触摸输入产生应用旋转,注释的代码将产生一个角度和mAngle,其中包含触摸输入生成的角度:

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);
    Matrix.setRotateM(mRotationMatrix, 0, mAngle, 0, 0, -1.0f);

    // 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);
}

当你完成上述步骤,运行该程序并拖动手指在屏幕上旋转的三角形:

这里写图片描述

示例代码:http://download.csdn.net/detail/u012301841/9584471

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。

来仿一仿retrofit - 2016-07-25 14:07:32

为什么要重复造轮子 在开发领域有一句很流行的话就是 不要重复造轮子 , 因为我们在开发中用到的很多东西早已有很多人去实现了, 而且这些实现都是经过时间和开发者检验过的, 一般不会遇到什么坑, 而如果我们自己去实现的话, 那不仅会增加工作量, 最大的隐患还是我们并不能预见以后是否会遇到大坑. 不过大家注意了吗. 上面 不要重复造轮子 的一个前提是 开发中 , 是的, 这句名言在开发中是适用的, 那在学习阶段的? 我可以大概的告诉你- 忘记这句话! , 为什么 不要重复造轮子 不适合在学习阶段使用呢? 如果我
背景 前面一篇总结了Serializable的序列化与反序列化,现在接着总结XML。主要内容:XML基本的序列化与反序列化方法、一些注意事项、以及自定义了一个XML注解框架(简洁代码,解放双手)。 XML的序列化与反序列化 先与Serializable进行简单的对比: Serializable存储的文件,打开后无法正常查看,安全性高。xml文件可通过文本编辑器查看与编辑,可读性高(浏览器会格式化xml文件,更方便查看),安全性低; Serializable文件通过了签名,只能在自己的程序中反序列化,或RM
每日更新关注 : http://weibo.com/hanjunqiang   新浪微博! iOS 开发者交流QQ群 : 446310206   有问题或技术交流可以咨询! 欢迎加入 ! 这篇直接搬了一份官方文档过来看的 由于之前没用markdown搞的乱七八糟的 所以重新做了一份 后面看到官网的中文文档更新不及时看着英文翻译了一点 搞的更乱了 :( 英文好的直接点右边- 官方OC文档 Realm 是一个移动端的数据库, Realm 是 SQLite 和 CoreData 的替代者。它可以节省你成千上万行

2.3.1 存储数据到data目录中 - 2016-07-25 14:07:09

当应用安装到Android后,系统会根据每个应用的包名创建一个/data/data/包名/的文件夹,访问自己包名下的目录是不需要权限的,并且Android已经提供了非常简便的API可以直接去访问该文件夹。 下面以一个案例来演示data目录的使用。 一、需求 如图1-5 所示,在用户将CheckBox选中的前提下点击登录,就将用户名和密码保存在data文件夹中,在用户不勾选CheckBox的前提下点击登录,就不保存用户名和密码,同时删除data文件中的历史数据。 如果数据已经保存到了data目录中,那么下次
本页面更新日期: 2016年07月23日 package 包 前面提到了 包 这个概念. 什么是包? 由于非常多的人参与 Java 的开发, 这难免会遇到一个问题 – 类名冲突 . 也就是说难免会遇到重名的情况. 所以 Java 引入了包这个机制. 提供了类的多层命名空间. 用于解决类的命名冲突 / 类文件管理等问题. Java允许将一组功能相关的类放在同一个 package 下. 从而组成逻辑上的 类库单元 . 如果希望把一个类放在指定的包结构下,应该在Java源程序的第一个非注释行放置如下格式的代码:
目录: APP项目如何与插件化无缝结合(一)  APP项目如何与插件化无缝结合(二)  APP项目如何与插件化无缝结合(三)  搬砖码字不易,转载请注明转自: http://blog.csdn.net/u011176685/article/details/52006474 上面一篇主要介绍了Small的原理,相信大家应该现在心里有个大概的了解。好,我们接下来继续开始! 一、Small的使用 关于Small的使用, Small的使用 这里讲的很详细,关于这里提下我当时遇到的问题和解决办法。 1.Small作
在Windows下试了试用Ionic开发Android应用,试通了。记录了过程。列在下面,供参考。 1. JDK 我用的jdk8,这里下载: http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 。我老早下载的,8u66,i586(32位的),现在是8u102: http://download.oracle.com/otn-pub/java/jdk/8u102-b14/jdk-8u102-wi
最近写的项目中有用到数据库,写了不少蛋疼的sql语句,每次都是好几行代码,而且每次都是重复的没有一点技术含量的代码,虽然也有不少基于sqlite的封装,不过用起来还是感觉不够面向对象! 为了不再写重复的代码,花了几天时间,基于SQLite3简单封装了下,实现了一行代码解决增删改查等常用的功能!并没有太过高深的知识,主要用了runtime和KVC: 首先我们创建个大家都熟悉的Person类,并声明两个属性,下面将以类此展开分析 @interface Person : NSObject @property (

以太网帧格式 - 2016-07-24 18:07:25

浅谈以太网帧格式                                       一、Ethernet帧格式的发展 1980 DEC,Intel,Xerox制订了Ethernet I的标准 1982 DEC,Intel,Xerox又制订了Ehternet II的标准 1982 IEEE开始研究Ethernet的国际标准802.3  1983 迫不及待的Novell基于IEEE的802.3的原始版开发了专用的Ethernet帧格式 1985 IEEE推出IEEE 802.3规范,后来为解决Eth
  谷歌改良了ndk的开发流程,对于Windows环境下NDK的开发,如果使用的NDK是r7之前的版本,必须要安装Cygwin才能使用NDK。而在NDKr7开始,Google的Windows版的NDK提供了一个ndk-build.cmd的脚本,这样,就可以直接利用这个脚本编译,而不需要使用Cygwin了。只需要为Eclipse Android工程添加一个Builders,而为Eclipse配置的builder,其实就是在执行Cygwin,然后传递ndk-build作为参数,这样就能让Eclipse自动编译