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 ~