开发Android应用主要使用的语言是Java,布局文件和界面则一般用XML来描述。整个应用的GUI开发与Java SWT、QT等其实区别不是特别大。如果有任何一种GUI程序开发经验,学习Android应用开发就是一件非常容易的事。这篇文章里我们来开发一个支持四则混合运算的简易计算器APP,这个APP演示了开发Android项目的一般流程以及基本方法。
环境:Windows 7 x64、JDK8、adt-bundle-windows-x86_64-20140702。
一、安装JDK8与ADT
JDK8是Java SDK的最新版本,ADT-bundle则是Android官方发布的以Eclipse为基础的Android开发环境。两个软件包都可以从官网下载,点击下载JDK8、adt-bundle,ADT官方被墙了,所以只能从其它地方下载。
双击JDK8的安装程序,按照正常的步骤即可安装成功。ADT-bundle不需要安装,直接解压就可以启动eclipse.exe使用了,但是注意一定要设置JAVA_HOME到环境变量。
二、新建Android项目
打开Eclipse后,File -> New -> Other,在弹出的对话框中选择Android项目中的Android Application Project,并点击 Next。然后输入Application Name、Project Name、Package Name,并分别选择最低要求的SDK版本、目标SDK版本、项目编译版本、应用主题,这里由于我们是用来测试的,所以前三项均选择Android 4.0,第四项默认。接下来的两个对话框主要是用来设置应用的图标及一个新的Activity。
完成后,项目的目录结构差不多是下图这个样子:
最重要的是res目录、src目录,以及AndroidManifest.xml文件。res目录中的layout目录存放了项目中每个Activity的界面布局,values目录则存放项目中所有用到的明文字符串及主题等资源。src目录就是应用的所有java源代码。AndroidManifest.xml是项目的主工程文件。
无论多简单的项目都少不了上面所列出的这些目录和文件。下面我们来逐个解析这些文件。
三、工程管理文件AndroidManifest.xml
manifest中的package指定了本工程的源代码在哪个包里面。uses-sdk中的内容在之前新建项目的时候就已经配置好了,即说明本APP支持的最低版本及目标版本。application中的icon、label、theme等分别指定了APP的图标、名称、主题风格,这里用@符号来从文件中引用内容,如app_name是strings.xml中定义的一个字符串,在这里被引用为label。
这个配置文件中最关键的是activity项。
Android应用由一个或多个Activity组成,Activity有自己有界面布局和控制代码,不同的Activity可以互相跳转并组合完成各种任务。Activity需要指定name和label,name可以确定源代码中对应的Activity是哪一个,label是Activity显示在界面上的一个标题。
不同的Activity之间跳转时,必须由intent来控制。用户启动APP跳转到APP的入口Activity时,也需要一个特定的intent来说明。上面代码中第一个Activity就有intent-filter字段,将action指定为"android.intent.action.MAIN"说明该Activity是当前APP的主Activity,将category指定为"android.intent.category.LAUNCHER"说明该Activity将由用户启动。
四、res资源目录及其包含的文件
先看values/strings.xml文件。这个文件中定义了APP需要用到的各种字符串。APP使用这些字符串的时候最好以引用的方式从这个文件中包含进来,而不要直接硬编码到程序中。strings.xml内容很简单,类似于下面这样:
iCalculator
iCalculator
关于
关于
返回
Hail to Me
The World Will See My Might !
错误
退出
当需要引用这些字符串的时候,用@string/about这样的方式就可以了。例如@string/about实际表示“关于”这个字符串。
values/styles.xml文件中指定了APP的主题风格,一般没有特殊要求的话可以不必改动。
跟界面布局有关的最重要的文件都在res/layout目录中,例如我们可以把主Activity的布局写在res/layout/activity_main.xml文件中,内容如下:
这个文件中实际包含了两类跟界面有关的内容:layout和widget。LinearLayout属于layout,layout指明了该如何在界面上排列组件。这里用到的LinearLayout说明使用线性布局来部署所有的组件,LinearLayout在指定orientation属性(orientation=“vertical"或orientation="horizontal")后,可以分别以垂直或水平的方式进行线性布局。layout_width和layout_height指定了此布局的宽度和高度,其值为"match_parent"时,子布局(或组件)的宽度/高度与其父布局/父组件保持一致,值为"wrap_content"时,子布局(或组件)将按照自己的实际大小来显示。
文件中的EditText和Button属于widget,即组件(或构件),用来完成某个特定的动作(如输入文字、显示文字、作为一个按钮接收点击信号等)。EditText是编辑框组件,通常用来输入文本,在设置editable="false"后禁止编辑,用来作为显示屏使用。gravity属性决定了编辑框中的文本对齐方式,有"center""right""left"等。Button是按钮组件。
所有的widget都有一个id,这个id非常重要,因为id是源代码中获取组件句柄的惟一方式。@+id/digit_plus_button表示新增一个名为"digit_plus_button"的id,并将此id指定给相关联的widget。
layout和widget的weight(权重)属性也值得说明一下。例如有三个layout,名称分别为layout_1/layout_2/layout_3,这三个layout的weight分别设置1、1、1时,则三个layout分别各占据了父layout(或父widget)三分一的空间,如果它们的wieght分别设置为1、2、1,,则父layout或父widget的空间被平均分为4份,layout_1和layout_3各占据1份,layout_2则占据2份。上面这个activity_main.xml文件所绘制的界面如下图所示:
左上角那个五颜六色的图标是我从网上找来的,可以在设置APP的图标时指定,也可以在Androidmanifest.xml文件application项的icon中指定。
本示例涉及到的另一个activity_about.xml内容如下:
五、Java类代码
在Android应用中,基本的项目开发单位就是Activity,一个Activity分别对应一个java类和一个activity.xml布局文件。
Activity的类代码控制所有的资源和APP流程。
本示例中主Activity相关联的类MainActivity位于com.example.icalculator包中,代码清单如下:
package com.example.icalculator;
import com.example.icalculator.R.id;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Button;
import android.widget.EditText;
import android.content.Intent;
/**
*
* @author Administrator
*
* 由于按钮较多,需要监听的类似事件比较多,所以用Switch方法来监听,
* 需要实现OnClickListener接口
*
*/
public class MainActivity extends Activity implements OnClickListener{
/**
* 这个是全局的字符串,用来存储显示在显示屏上的计算式及结果
*/
private String calculator_string;
private static final int quit_menu_item = Menu.FIRST;
private EditText displayEditText;
private Button aboutButton;
private Button leftButton;
private Button rightButton;
private Button ceButton;
private Button dig7Button;
private Button dig8Button;
private Button dig9Button;
private Button digDivButton;
private Button dig4Button;
private Button dig5Button;
private Button dig6Button;
private Button digMultiButton;
private Button dig1Button;
private Button dig2Button;
private Button dig3Button;
private Button digMinusButton;
private Button dig0Button;
private Button digDotButton;
private Button digEqualButton;
private Button digPlusButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
calculator_string = "";
displayEditText = ( EditText )findViewById( R.id.display_edittext );
aboutButton = ( Button )findViewById( R.id.digit_about_button );
aboutButton.setOnClickListener( this );
leftButton = ( Button )findViewById( R.id.digit_left_button );
leftButton.setOnClickListener( this );
rightButton = ( Button )findViewById( R.id.digit_right_button );
rightButton.setOnClickListener( this );
ceButton = ( Button )findViewById( R.id.digit_ce_button );
ceButton.setOnClickListener( this );
dig7Button = ( Button ) findViewById( R.id.digit_7_button );
dig7Button.setOnClickListener( this );
dig8Button = ( Button ) findViewById( R.id.digit_8_button );
dig8Button.setOnClickListener( this );
dig9Button = ( Button ) findViewById( R.id.digit_9_button );
dig9Button.setOnClickListener( this );
digDivButton = ( Button ) findViewById( R.id.digit_div_button );
digDivButton.setOnClickListener( this );
dig4Button = ( Button ) findViewById( R.id.digit_4_button );
dig4Button.setOnClickListener( this );
dig5Button = ( Button ) findViewById( R.id.digit_5_button );
dig5Button.setOnClickListener( this );
dig6Button = ( Button ) findViewById( R.id.digit_6_button );
dig6Button.setOnClickListener( this );
digMultiButton = ( Button ) findViewById( R.id.digit_multi_button );
digMultiButton.setOnClickListener( this );
dig1Button = ( Button ) findViewById( R.id.digit_1_button );
dig1Button.setOnClickListener( this );
dig2Button = ( Button ) findViewById( R.id.digit_2_button );
dig2Button.setOnClickListener( this );
dig3Button = ( Button ) findViewById( R.id.digit_3_button );
dig3Button.setOnClickListener( this );
digMinusButton = ( Button ) findViewById( R.id.digit_minus_button );
digMinusButton.setOnClickListener( this );
dig0Button = ( Button ) findViewById( R.id.digit_0_button );
dig0Button.setOnClickListener( this );
digDotButton = ( Button ) findViewById( R.id.digit_dot_button );
digDotButton.setOnClickListener( this );
digEqualButton = ( Button ) findViewById( R.id.digit_equal_button );
digEqualButton.setOnClickListener( this );
digPlusButton = ( Button ) findViewById( R.id.digit_plus_button );
digPlusButton.setOnClickListener( this );
}
/**
* 新建Menu
*
*/
@Override
public boolean onCreateOptionsMenu( Menu menu ){
super.onCreateOptionsMenu(menu);
menu.add( 0, Menu.FIRST, 0, R.string.menu_quit );
return true;
}
/**
* Menu被选择的时候,捕获及处理相关事件
*
*/
@Override
public boolean onOptionsItemSelected( MenuItem item ){
switch( item.getItemId() ){
case quit_menu_item:
/**
* 选择了“退出”的菜单项,则退出
*
*/
finish();
System.exit( 0 );
break;
}
return true;
}
/**
* 捕获及处理按钮事件
*
*/
public void onClick( View v ){
/**
* 通过区分不同的id来确定当前监听到的是哪个控件的事件
*
*/
switch( v.getId() ){
case R.id.digit_about_button:
onAbout( v );
break;
case R.id.digit_left_button:
appendToDisplay( "(" );
break;
case R.id.digit_right_button:
appendToDisplay( ")" );
break;
case R.id.digit_ce_button:
cleanDisplay();
break;
case R.id.digit_7_button:
appendToDisplay( "7" );
break;
case R.id.digit_8_button:
appendToDisplay( "8" );
break;
case R.id.digit_9_button:
appendToDisplay( "9" );
break;
case R.id.digit_div_button:
appendToDisplay( "/" );
break;
case R.id.digit_4_button:
appendToDisplay( "4" );
break;
case R.id.digit_5_button:
appendToDisplay( "5" );
break;
case R.id.digit_6_button:
appendToDisplay( "6" );
break;
case R.id.digit_multi_button:
appendToDisplay( "*" );
break;
case R.id.digit_1_button:
appendToDisplay( "1" );
break;
case R.id.digit_2_button:
appendToDisplay( "2" );
break;
case R.id.digit_3_button:
appendToDisplay( "3" );
break;
case R.id.digit_minus_button:
appendToDisplay( "-" );
break;
case R.id.digit_0_button:
appendToDisplay( "0" );
break;
case R.id.digit_dot_button:
appendToDisplay( "." );
break;
case R.id.digit_equal_button:
calculating();
break;
case R.id.digit_plus_button:
appendToDisplay( "+" );
break;
default:
;
}
}
/**
* 将传来的字符追加到显示屏字符串上,并重新写到显示屏上
*
*/
private void appendToDisplay( String keyChar ){
calculator_string = calculator_string + keyChar;
displayEditText.setText( calculator_string );
}
/**
* 显示屏复位
*
*/
private void cleanDisplay(){
calculator_string = "";
displayEditText.setText( "" );
}
/**
*
* 用户点击了“关于”按钮后,激活About的Activity
*/
private void onAbout( View v ){
/**
* 创建一个Intent来跳转
*
*/
Intent intent = new Intent();
intent.setClass( MainActivity.this, AboutActivity.class );
startActivity( intent );
}
/**
* 执行这个方法说明用户点击了等于号,要开始根据calculator_string进行计算了,
* 并将执行结果显示在显示屏上
*
*/
private void calculating(){
/**
* 这里我们获得了一个原始的、尚未确定准备性的运算表达式,
* 包含左右括号、加、减、乘、除,第一步要进行准备性验证,
* 如果验证未通过,则在显示屏上提出出错及错误原因,
* 验证通过后,即可进行计算并将结果输出到显示屏上。
*
* 表达式验证和计算需要用到的数据结构是栈,计算时的算法
* 则跟处理前缀表达式有关。
*
*/
}
}
可以看到Android开发的流程和一些特点。第一步当然是导入开发中用到的各种包,android.app.Activity和android.os.Bundle必不可少,前者是所有自定义Activity的基类,后者被用来在Activity之间传递数据。根据需要还应当导入android.view.Menu和android.widget.Button等各种类。
然后就是新建自己的Activity,从android.app.Activity继承而来。注意本例的MainActivity同时实现了OnClickListener接口,以方便在界面存在大量组件的时候监听事件。自定义Activity类中首先要做的就是执行一个父类的onCreate方法,目的是作一些初始化操作。setContentView函数用来设置本Activity对应的布局,这里要注意,系统自动生成了一个名为R的类,这个类中包含了所有已经按照要求定义的资源。在类代码中引用资源时可以随时使用R类。然后findViewById方法顾名思义,就是根据layout_..xml文件中的id来获取组件的句柄。获取到句柄后就可以对其进行操作了,本例中在获取到句柄后直接定义了所有组件的监听函数。
由于MainActivity实现了OnClickListener接口,我们可以直接在类的内部实现onClick方法,根据传来的View来确定是哪一个组件发出了onClick的信号,并作对应处理。
onCreateOptionsMenu方法用来新建一个菜单,该菜单在用户按下菜单键/设置键时被呼出。
onOptionsItemSelected方法用来响应菜单选项,里面有一个Switch结构可以区分出是哪个菜单项被选择了,然后就可以作对应处理。
这里要注意onAbout方法。当界面上的“关于”按钮被按下时,onAbout方法被调用。该方法中新建并激活了一个Intent,用来启动一个指定的新的Activity。intent.setClass()第一个参数是发起Intent的源Activity,第二个参数是目标Activity。startActivity( intent )将新建一个目标Activity。源Activity是否关闭取决于是否在startActivity方法之后执行了finish()方法。如果执行finish(),则关闭当前Activity,并跳到新的Activity;如果不执行finish(),则当前Activity会被保存到系统的"Back Stack"中,然后跳到新Activity,当新Activity关闭后,源Activity就又会被激活变为当前Activity。
主界面上有个“关于”按钮,点击之后就会跳到另一个名为AboutActivity的Activity中,代码清单如下:
package com.example.icalculator;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Button;
import android.content.Intent;
import android.view.View;
import android.view.View.OnClickListener;
public class AboutActivity extends Activity {
private Button backButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
backButton = ( Button ) findViewById( R.id.about_info_back_button );
backButton.setOnClickListener( new OnClickListener(){
public void onClick( View v ){
/**
* 这个按钮的目的仅仅是关闭“关于”的Activity。
* 如果这里又新建立了一个Intent,则在跳转的时候又会新建立一个MainActivity。
* 因此这里不应该新建Intent,直接关闭AboutActivity就可以了。
* 因为之前从MainActivity跳转过来的时候并没有把该MainActivity,
* 本AboutActivity关闭后,就会自动恢复到之前的那个MainActivity。
*
*/
/*
Intent intent = new Intent();
intent.setClass( AboutActivity.this, MainActivity.class );
startActivity( intent );
*/
finish();
}
});
}
}
看到这里大家肯定会奇怪,整个程序中并没有包含对运算表达式进行计算的代码。当用户按下等于号"="时,APP会调用calculating()方法,可以看到这个方法仍然是空的。这部分代码的可移植性比较强,但是写起来需要考虑的东西比较多。很久以前我用C++写过一个,有空再移植到Java下面吧。