Android Custom view 3D Cube Effect Example

  • 2021-12-13 17:12:30
  • OfStack

Preface to the table of contents 1. Tips 2. Change the sensor to an event distribution mechanism 3. Use 4. Source summary

Preface

I wrote an article about the effect of 3D before. With the help of sensor display, a small partner asked if it could be changed to gesture sliding operation (event distribution), so I wrote an article

Sensor related article link: Android 3D effect realization

1. Tips

In contrast to the common custom view, the inherited GLSurfaceView has only two constructors. It can be understood that there is no method to get custom attributes.


    public TouchSurfaceView(Context context) {
        super(context);
    }

    public TouchSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

2. Change the sensor to an event distribution mechanism


    @Override
    public boolean onTouchEvent(MotionEvent e) {
        float x = e.getX();
        float y = e.getY();
        switch (e.getAction()) {
            case MotionEvent.ACTION_MOVE:
                float dx = x - mPreviousX;
                float dy = y - mPreviousY;
                mRenderer.mAngleX += dx * TOUCH_SCALE_FACTOR;
                mRenderer.mAngleY += dy * TOUCH_SCALE_FACTOR;
                requestRender();
        }
        mPreviousX = x;
        mPreviousY = y;
        return true;
    }

Note that there is also a rolling ball event


    @Override
    public boolean onTrackballEvent(MotionEvent e) {
        mRenderer.mAngleX += e.getX() * TRACKBALL_SCALE_FACTOR;
        mRenderer.mAngleY += e.getY() * TRACKBALL_SCALE_FACTOR;
        requestRender();
        return true;
    }

STEP 3 Use


  mGLSurfaceView = new TouchSurfaceView(this);
        setContentView(mGLSurfaceView);
        mGLSurfaceView.requestFocus();
        mGLSurfaceView.setFocusableInTouchMode(true);

Note that it should be handled in the corresponding life cycle


    @Override
    protected void onResume() {
        super.onResume();
        mGLSurfaceView.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        mGLSurfaceView.onPause();
    }

4. Source code

TouchSurfaceView.java

Except for the previous modifications, most of them are the same as the linked articles, except that the sensor is changed to event distribution. (The difficulties in the code are commented)


public class TouchSurfaceView extends GLSurfaceView {
    private final float TOUCH_SCALE_FACTOR = 180.0f / 320;
    private final float TRACKBALL_SCALE_FACTOR = 36.0f;
    private CubeRenderer mRenderer;
    private float mPreviousX;
    private float mPreviousY;

    public TouchSurfaceView(Context context) {
        super(context);
        mRenderer = new CubeRenderer();
        setRenderer(mRenderer);
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }

    public TouchSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }


    @Override
    public boolean onTrackballEvent(MotionEvent e) {
        mRenderer.mAngleX += e.getX() * TRACKBALL_SCALE_FACTOR;
        mRenderer.mAngleY += e.getY() * TRACKBALL_SCALE_FACTOR;
        requestRender();
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        float x = e.getX();
        float y = e.getY();
        switch (e.getAction()) {
            case MotionEvent.ACTION_MOVE:
                float dx = x - mPreviousX;
                float dy = y - mPreviousY;
                mRenderer.mAngleX += dx * TOUCH_SCALE_FACTOR;
                mRenderer.mAngleY += dy * TOUCH_SCALE_FACTOR;
                requestRender();
        }
        mPreviousX = x;
        mPreviousY = y;
        return true;
    }


    private class CubeRenderer implements GLSurfaceView.Renderer {

        private Cube mCube;
        public float mAngleX;
        public float mAngleY;
        public CubeRenderer() {
            mCube =new Cube();
        }

        public void onDrawFrame(GL10 gl) {
// | GL10.GL_DEPTH_BUFFER_BIT
            gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
            gl.glMatrixMode(GL10.GL_MODELVIEW);
            gl.glLoadIdentity();
            gl.glTranslatef(0, 0, -3.0f);
            gl.glRotatef(mAngleX, 0, 1, 0);
            gl.glRotatef(mAngleY, 1, 0, 0);
            gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
            gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
            mCube.draw(gl);
        }


        @Override
        public void onSurfaceCreated(GL10 gl, javax.microedition.khronos.egl.EGLConfig config) {
            gl.glDisable(GL10.GL_DITHER);
            gl.glClearColor(1,1,1,1);
        }

        public void onSurfaceChanged(GL10 gl, int width, int height) {
            gl.glViewport(0, 0, width, height);
            // Set the projection matrix. But you don't need to do it every time you draw. Usually, when the view is resized, you need to set 1 A new projection. 
            float ratio = (float) width / height;
            gl.glMatrixMode(GL10.GL_PROJECTION);
            gl.glLoadIdentity();
            gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
        }

    }



    public class Cube {
        //opengl In the coordinate system, the 3 Dimensional coordinates :
        private FloatBuffer mVertexBuffer;
        private FloatBuffer mColorBuffer;
        private ByteBuffer mIndexBuffer;

        public Cube() {
            final float vertices[] = {
                    -1, -1, -1,		 1, -1, -1,
                    1,  1, -1,	    -1,  1, -1,
                    -1, -1,  1,      1, -1,  1,
                    1,  1,  1,     -1,  1,  1,
            };

            final float colors[] = {
                    0,  1,  1,  1,  1,  1,  1,  1,
                    1,  1,  0,  1,  1,  1,  1,  1,
                    1,  1,  1,  1,  0,  1,  1,  1,
                    1,  1,  1,  1,  1,  1,  0,  1,
            };

            final byte indices[] = {
                    0, 4, 5,    0, 5, 1,
                    1, 5, 6,    1, 6, 2,
                    2, 6, 7,    2, 7, 3,
                    3, 7, 4,    3, 4, 0,
                    4, 7, 6,    4, 6, 5,
                    3, 0, 1,    3, 1, 2
            };

            ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);
            vbb.order(ByteOrder.nativeOrder());
            mVertexBuffer = vbb.asFloatBuffer();
            mVertexBuffer.put(vertices);
            mVertexBuffer.position(0);

            ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length*4);
            cbb.order(ByteOrder.nativeOrder());
            mColorBuffer = cbb.asFloatBuffer();
            mColorBuffer.put(colors);
            mColorBuffer.position(0);

            mIndexBuffer = ByteBuffer.allocateDirect(indices.length);
            mIndexBuffer.put(indices);
            mIndexBuffer.position(0);
        }

        public void draw(GL10 gl) {
            // Enable the server side GL Function. 
            gl.glEnable(GL10.GL_CULL_FACE);
            // Defines the front and back sides of a polygon. 
            // Parameter :
            //mode The direction of the front of the polygon. GL_CW And GL_CCW Allowed, with an initial value of GL_CCW . 
            gl.glFrontFace(GL10.GL_CW);
            // Select constant or smooth shading mode. 
            //GL Primitives can be in constant or smooth shading mode, and the default value is smooth shading mode. When the primitive is rasterized, the color calculation of the insert vertex will be caused, and different colors will be evenly distributed to each pixel segment. 
            // Parameters: 
            //mode --Indicate 1 Symbolic constants to represent the shading technique to be used. The allowed values are GL_FLAT  And GL_SMOOTH With an initial value of GL_SMOOTH . 
            gl.glShadeModel(GL10.GL_SMOOTH);
            // Definition 1 Vertex coordinate matrix. 
            // Parameters: 
            //
            //size --The coordinate dimension of each vertex must be 2, 3 Or 4 The initial value is 4 . 
            //
            //type --Indicates the data type of each vertex coordinate. The allowed sign constants are GL_BYTE, GL_SHORT, GL_FIXED And GL_FLOAT With an initial value of GL_FLOAT . 
            //
            //stride --Indicates the bit offset between consecutive vertices, if 0 The vertices are considered to be compact indentation matrices with initial values of 0 . 
            //
            //pointer --Indicates the buffer for vertex coordinates, if null No buffer is set. 
            gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer);
            // Definition 1 A color matrix. 
            //size Indicates the number of elements per color, which must be 4 . type Indicates the data type of each color element, stride Indicates that the 1 Three colors to the bottom 1 The byte amplification of the allowed vertices, and the attribute values are squeezed into the simple matrix or stored in a separate matrix (simple matrix storage may be in 1 More efficient in some versions). 
            gl.glColorPointer(4, GL10.GL_FLOAT, 0, mColorBuffer);
            // Rendering primitives from matrix data 
            // Independent vertex, normal, color and texture coordinate matrices can be specified in advance and can be called glDrawElements Method to use them to create sequence primitives. 
            gl.glDrawElements(GL10.GL_TRIANGLES, 36, GL10.GL_UNSIGNED_BYTE, mIndexBuffer);
        }
    }
}

MainActivity.java


public class MainActivity extends AppCompatActivity {
    private GLSurfaceView mGLSurfaceView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mGLSurfaceView = new TouchSurfaceView(this);
        setContentView(mGLSurfaceView);
        mGLSurfaceView.requestFocus();
        mGLSurfaceView.setFocusableInTouchMode(true);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mGLSurfaceView.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        mGLSurfaceView.onPause();
    }


}

Summarize


Related articles: