Android代码范例():活用<历史状态>

  就软件开发者而言,基于高度精确性的模型图,可迅速拟定出软件的架构。例如,在下述的模型图里,共有4个状态: 

 

  看到这4个状态,就会联想到软件代码中,可以规划出goto_state_1()、goto_state_2()、goto_state_3()和goto_state_4()共四个基本函数。于是,开发者就将这些函数的代码写进Android Studio的开发工程项目里,如下述的代码:

  

   在这个模型图里,有一个特别的地方是:它含有一个历史状态(History State)的符号,以H表示之。其意义是,假设目前是处于状态state_3,发生了onClick[Back]事件,就离开状态state_3,转移到状态state_1。此时如果再发生onClick[Draw Cube]事件,就又进入状态state_3,因为刚才是由此状态state_3出来的,这就是历史状态(以H表示)的用意了。一样地,假设目前是处于状态state_4,发生了onClick[Back]事件,就离开状态state_4,转移到状态state_1。此时如果再发生onClick[Draw Cube]事件,就又进入状态state_4,因为刚才是由此状态state_4出来的,这就是历史状态(以H表示)的用意了。

    于是,软件开发者就会定义一个变量(如state_var_B)去纪录所离开的状态,等到后续再返回到state_2时,就能依据state_var_B的值来决定返回到state_3或state_4状态了。如下述的代码:

  

  现在,我们看看上图的系统交互行为。每次系统启动时,默认会先进入状态state_1,执行了show_layout_01()函数,显示出Layout_01画面,接着执行draw_3d_graphic(),绘出了3D图形。如下图:

 

   当您按下<DRAW CUBE>按钮时,这个新事件(即Android的OnClick事件)触发系统转移到状态state_2,也引发了响应行为:执行show_layout_02()函数。

在state_2状态里的 l代表预设或默认(Default)之意,每次进入状态state_2时,执行了show_layout_02()函数之后,必先进入状态state_3,执行了draw_pyramid()函数,绘出三角锥体的图形。如下图:

 

   当用户按下<Cube>的勾选按钮时,这个事件(即Android的OnCheckedChanged事件)会触发状态转移到state_4,除了绘出一个锥型体之外,还绘出一个正立方体。

 

  此时,如果按下<BACK]按钮,发生了onClick[Back]事件,就离开状态state_4,转移到状态state_1。如下图:

 

  此时如果按下<DRAW CUBE>按钮,这个新事件触发系统返回到状态state_2里的小状态state_4,因为刚才是由此状态state_4出来的,这就是历史状态。如下图:

 

   如果用户再按下<Cube>的勾选按钮时,这个事件会触发状态转移回到state_3,只绘出一个锥型体。如下图: 

 

 此时,如果按下<BACK]按钮,就离开状态state_3,转移到状态state_1。如下图:

 

  此时如果按下<DRAW CUBE>按钮,这个新事件触发系统返回到状态state_2里的小状态state_3,因为刚才是由此状态state_3出来的,这就是历史状态。如下图:

 

此时,如果按下<BACK]按钮,就离开状态state_3,转移到状态state_1。如下图:

 

   当按下<EXIT>按钮时,这事件(即Android的OnClick事件)触发系统来结束这App的执行了。

   还有,开发者看到模型图里的状态state_1对应到画面Layout_01,就会创建一个XML格式的Layout_01定义檔;然后参考视觉设计的颜色和型等,就可撰写详细代码。也就是完整的Layout_01定义的完整代码了,如下:

 

/* Layout_01.xml代码 */

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    xmlns:tools="http://schemas.android.com/tools"

    android:id="@+id/content"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:paddingBottom="@dimen/activity_vertical_margin"

    android:paddingLeft="@dimen/activity_horizontal_margin"

    android:paddingRight="@dimen/activity_horizontal_margin"

    android:paddingTop="@dimen/activity_vertical_margin"

    android:orientation="vertical"

    app:layout_behavior="@string/appbar_scrolling_view_behavior"

    tools:context="com.example.queena.ac001.MainActivity"

    tools:showIn="@layout/layout_01_main">

 

     <TextView

        android:id="@+id/txx"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:textColor="@color/colorWhite"

        android:text="CCC"/>

    <SurfaceView

        android:id="@+id/sv"

        android:layout_width="250dp"

        android:layout_height="250dp"

        android:layout_marginLeft="1dp"

        android:layout_marginTop="5dp"/>

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

        android:id="@+id/ly02"

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        android:paddingTop="10dp"

        android:orientation="horizontal">

        <Button

            android:id="@+id/draw_cube"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:background="@drawable/bbb"

            android:layout_marginLeft="1dp"

            android:layout_marginTop="1dp"

            android:text="Draw Cube"/>

        <Button

            android:id="@+id/exit"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:background="@drawable/xxx"

            android:layout_marginLeft="20dp"

            android:layout_marginTop="1dp"

            android:text="Exit"/>

    </LinearLayout>

</LinearLayout>

 

  同样的,开发者看到模型图里的状态state_2对应到画面Layout_02,就会创建一个XML格式的Layout_02定义檔;然后参考视觉设计的颜色和型等,就可撰写详细代码。也就是完整的Layout_02定义的完整代码了,如下:

 

/* Layout_02.xml代码 */

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:app="http://schemas.android.com/apk/res-auto"

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/content"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:paddingBottom="@dimen/activity_vertical_margin"

android:paddingLeft="@dimen/activity_horizontal_margin"

android:paddingRight="@dimen/activity_horizontal_margin"

android:paddingTop="@dimen/activity_vertical_margin"

android:orientation="vertical"

app:layout_behavior="@string/appbar_scrolling_view_behavior"

tools:context="com.example.queena.ac001.MainActivity"

tools:showIn="@layout/layout_02_main">

 

    <TextView

        android:id="@+id/tx"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:textColor="@color/colorWhite"/>

    <SurfaceView

        android:id="@+id/sv"

        android:layout_width="250dp"

        android:layout_height="250dp"

        android:layout_marginLeft="10dp"

        android:layout_marginTop="10dp"/>

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

        android:id="@+id/ly02"

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        android:orientation="horizontal">

        <Button

            android:id="@+id/btn"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:background="@drawable/zzz"

            android:layout_marginLeft="40dp"

            android:layout_marginTop="15dp"

            android:text="Back"/>

        <CheckBox

            android:id="@+id/cb"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_marginLeft="40dp"

            android:layout_marginTop="15dp"

            android:text="Cube"/>

    </LinearLayout>

</LinearLayout>

 

  上述的模型图与视觉设计两者是互补的结合。视觉设计非常有利于从用户或买主取得操作上的交互需求。而模型图则非常有利于协助开发建立软件架构,一旦模型图与视觉设计两者组合起来,就能将需求明确而完整的传达给软件开发者了。软件开发者会使用Android Studio工具来把相关代码组织成为一个开发工程项目(Project)。   现在,就来看看这些软件代码的完整内容了,其中包括MainActivity.java、MRenderer.java和MyCube.java共三个代码档案。

 
/* MainActivity.java */
package com.example.queena.ac003;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.SurfaceView;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.LinearLayout;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity implements View.OnClickListener, CompoundButton.OnCheckedChangeListener {
    private final int WC = LinearLayout.LayoutParams.WRAP_CONTENT;
    private int state_var_A, state_var_B;;
    private CoordinatorLayout layout_001, layout_002;
    private CheckBox cb;
    private MyRenderer mr;
    private TextView tv1, tv2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        state_var_A = 0; state_var_B = 0;
        mr = new MyRenderer(this);
        mr.setCube(false);
        inflate_layout_01();
        inflate_layout_02();
        goto_state_1();
    }
    void goto_state_1() {
            state_var_A = 1;
            this.show_layout_01();
            tv1.setText("   state_1 ");
       }
    void goto_state_2() {
        state_var_A = 2;
        this.show_layout_02();
        if(state_var_B == 0) goto_state_3();
        else if(state_var_B == 3) goto_state_3();
            else goto_state_4();
    }
   void goto_state_3() {
        state_var_B = 3;
        tv2.setText("  state_3 ");
        mr.setCube(false);
        cb.setChecked( false );
    }
    void goto_state_4() {
        state_var_B = 4;
        tv2.setText("  state_4 ");
        mr.setCube(true);
        cb.setChecked( true );
    }
     void show_layout_01() {
            setContentView(layout_001);
        }
    void show_layout_02() {
        setContentView(layout_002);
    }
    void inflate_layout_01() {
        LayoutInflater inflate = (LayoutInflater) getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
        layout_001 = (CoordinatorLayout) inflate.inflate(R.layout.layout_01_main, null);
        LinearLayout ly01 =  (LinearLayout) layout_001.findViewById(R.id.content);
        ly01.setBackgroundColor(Color.GRAY);
        Toolbar toolbar = (Toolbar) layout_001.findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        FloatingActionButton fab = (FloatingActionButton) layout_001.findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                 Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                       .setAction("Action", null).show();
            } });
        Button btn11 = (Button) layout_001.findViewById(R.id.draw_cube);
        btn11.setOnClickListener(this);
        Button btn12 = (Button) layout_001.findViewById(R.id.exit);
        btn12.setOnClickListener(this);
        SurfaceView sv = (SurfaceView) layout_001.findViewById(R.id.sv);
        sv.getHolder().addCallback(mr);
        tv1 = (TextView) layout_001.findViewById(R.id.txx);
   }
    void inflate_layout_02() {
        LayoutInflater inflate = (LayoutInflater) getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
        layout_002 = (CoordinatorLayout) inflate.inflate(R.layout.layout_02_main, null);
        LinearLayout ly02 =  (LinearLayout) layout_002.findViewById(R.id.content);
        ly02.setBackgroundColor(Color.GRAY);
        Toolbar toolbar = (Toolbar) layout_002.findViewById(R.id.toolbar);
        toolbar.setTitle("ac003");
        setSupportActionBar(toolbar);
        FloatingActionButton fab = (FloatingActionButton) layout_001.findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }         });
        Button btn21 = (Button) layout_002.findViewById(R.id.btn);
        btn21.setOnClickListener(this);
        cb = (CheckBox) layout_002.findViewById(R.id.cb);
        cb.setOnCheckedChangeListener(this);
        SurfaceView sv = (SurfaceView) layout_002.findViewById(R.id.sv);
        sv.getHolder().addCallback(mr);
        tv2 = (TextView) layout_002.findViewById(R.id.tx);
    }
    @Override
    public void onClick(View v) {
        if (state_var_A == 1) {
            if (v.getId() == R.id.draw_cube) goto_state_2();
            else if(v.getId() == R.id.exit) this.finish();
        }
        else if(state_var_A == 2) goto_state_1();
    }
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        if ( state_var_B == 3 )  goto_state_4();
        else if( state_var_B == 4) goto_state_3();
    }
   }

  上述完整的MainActivity.java代码主要是显示出画面布局,以及调用MyRenderer.java和MyCube.java代码来绘出3D图形;此外,也明确控制对事件的处理流程。

 /* MyRenderer.java */

import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGL11;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
import javax.microedition.khronos.opengles.GL10;

 class MyRenderer implements SurfaceHolder.Callback {

    private SurfaceHolder mHolder;
    private GLThread mGLThread;
    private MyCube mCube_cb, mCube_pr;
    private float mAngle;
    private boolean isCube = false;
    private Context c;
    MyRenderer(Context context) { c = context;  }
    public void setCube(boolean cb) {    isCube = cb;   }
    public boolean getCube() { return isCube; }
    public void surfaceCreated(SurfaceHolder holder) {
        mHolder = holder;
        mGLThread = new GLThread();
        mGLThread.start();
     }
    public void surfaceDestroyed(SurfaceHolder holder) {
        mHolder = holder;
        mGLThread.requestExitAndWait();
        mGLThread = null;
    }
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        mHolder = holder;
        mGLThread.onWindowResize(w, h);
    }
    // ----------------------------------------------------------------------
    class GLThread extends Thread {
        private boolean mDone;
        private boolean mSizeChanged = true;
        private int mWidth, mHeight;

        GLThread() {
            super();
            mDone = false;   mWidth = 0;   mHeight = 0;
            byte indices_cb[] = {
                    0, 7, 3,    7, 0, 4,
                    4, 0, 5,    5, 0, 1,
                    5, 1, 2,    5, 2, 6,
                    6, 2, 7,    7, 2, 3,
                    2, 1, 3,    3, 1, 0,
                    7, 4, 5,    6, 7, 5,
            };
            byte indices_pr[] = {
                    6, 0, 1,  5, 1, 0,
                    1, 5, 6,  0, 6, 5,
            };
            mCube_cb = new MyCube(indices_cb);
            mCube_pr = new MyCube(indices_pr);
        }
        @Override  public void run() {
            EGL10 egl = (EGL10)EGLContext.getEGL();
            EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
            int[] version = new int[2];
            egl.eglInitialize(dpy, version);
            int[] configSpec = {
                    EGL10.EGL_RED_SIZE,      8,
                    EGL10.EGL_GREEN_SIZE,    8,
                    EGL10.EGL_BLUE_SIZE,     8,
                    EGL10.EGL_DEPTH_SIZE,   16,
                    EGL10.EGL_NONE
            };
            EGLConfig[] configs = new EGLConfig[1];
            int[] num_config = new int[1];
            egl.eglChooseConfig(dpy, configSpec, configs, 1, num_config);
            EGLConfig config = configs[0];
            EGLContext glc = egl.eglCreateContext(dpy, config,
                    EGL10.EGL_NO_CONTEXT, null);
            EGLSurface surface = null;
            GL10 gl = null;
            while (!mDone) {
                int w, h;
                boolean changed;
                synchronized(this) {
                    changed = mSizeChanged;
                    w = mWidth;
                    h = mHeight;
                    mSizeChanged = false;
                }
                if (changed) {
                    surface = egl.eglCreateWindowSurface(dpy, config, mHolder, null);
                    egl.eglMakeCurrent(dpy, surface, surface, glc);
                    gl = (GL10)glc.getGL();
                    gl.glDisable(GL10.GL_DITHER);
                    gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
                            GL10.GL_FASTEST);
                    gl.glClearColor(1,1,1,1);
                    gl.glEnable(GL10.GL_CULL_FACE);
                    gl.glShadeModel(GL10.GL_SMOOTH);
                    gl.glEnable(GL10.GL_DEPTH_TEST);
                    gl.glViewport(0, 0, w, h);
                    float ratio = (float)w / h;
                    gl.glMatrixMode(GL10.GL_PROJECTION);
                    gl.glLoadIdentity();
                    gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
                }
                drawFrame(gl);
                egl.eglSwapBuffers(dpy, surface);
                if (egl.eglGetError() == EGL11.EGL_CONTEXT_LOST) {
                    if (c instanceof Activity) {
                        ((Activity)c).finish();
                    }}}
       egl.eglMakeCurrent(dpy, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
            egl.eglDestroySurface(dpy, surface);
             egl.eglDestroyContext(dpy, glc);
             egl.eglTerminate(dpy);
        }
        private void drawFrame(GL10 gl) {
            gl.glClear(GL10.GL_COLOR_BUFFER_BIT |GL10.GL_DEPTH_BUFFER_BIT);
            gl.glMatrixMode(GL10.GL_MODELVIEW);
            gl.glLoadIdentity();
            gl.glTranslatef(0, 0, -3.0f);
            gl.glRotatef(mAngle,        0, 1, 0);
            gl.glRotatef(mAngle*0.25f,  1, 0, 0);
            gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
            gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
            mCube_pr.draw(gl);
            gl.glRotatef(mAngle*2.0f, 0, 1, 1);
            gl.glTranslatef(0.5f, 0.5f, 0.5f);
            if(isCube) mCube_cb.draw(gl);
            mAngle += 1.2f;
        }

        public void onWindowResize(int w, int h) {
            synchronized(this) {
                mWidth = w;  mHeight = h;
                mSizeChanged = true;
            }}
       
public void requestExitAndWait() {
            mDone = true;
            try {  join();  }
            catch (InterruptedException ex) { }
        }}}

 

上述完整的MyRenderer.java代码主要是绘出三角椎体图形,以及调用MyCube.java代码来绘出立方体图形。

 /* MyCube.java */

package com.example.queena.ac002;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import javax.microedition.khronos.opengles.GL10;
public class MyCube {
    private int mTriangles;
    public MyCube(byte [] indices) {
        int one = 0x10000;
        int vertices[] = {
                -one, -one, -one,
                one, -one, -one,
                one,  one, -one,
                -one,  one, -one,
                -one, -one,  one,
                one, -one,  one,
                one,  one,  one,
                -one,  one,  one,
        };
        int colors[] = {
                0,    0,    0,  one,
                one,    0,    0,  one,
                one,    0,  one,  one,
                0,  one,    0,  one,
                0,    0,  one,  one,
                one,  one,    0,  one,
                one,  one,  one,  one,
                0,  one,  one,  one,
        };
        ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);
        vbb.order(ByteOrder.nativeOrder());
        mVertexBuffer = vbb.asIntBuffer();
        mVertexBuffer.put(vertices);
        mVertexBuffer.position(0);
        ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length*4);
        cbb.order(ByteOrder.nativeOrder());
        mColorBuffer = cbb.asIntBuffer();
        mColorBuffer.put(colors);
        mColorBuffer.position(0);
        mIndexBuffer = ByteBuffer.allocateDirect(indices.length);
        mIndexBuffer.put(indices);
        mIndexBuffer.position(0);
        mTriangles = indices.length;
    }
    public void draw(GL10 gl) {
        gl.glFrontFace(GL10.GL_CW);
        gl.glVertexPointer(3, gl.GL_FIXED, 0, mVertexBuffer);
        gl.glColorPointer(4, gl.GL_FIXED, 0, mColorBuffer);
        gl.glDrawElements(gl.GL_TRIANGLES, mTriangles, gl.GL_UNSIGNED_BYTE, mIndexBuffer);
    }
    private IntBuffer mVertexBuffer;
    private IntBuffer  mColorBuffer;
    private ByteBuffer  mIndexBuffer;
}

   上述完整的MyCube.java代码主要是绘出立方体图形。

 ~ End ~