范例说明EditText Widget 的设计是为了等待User输入而准备的,那么在User输入的同时,又该如何拦截所输入的文字呢?Android 的多数Widget都有setOnKeyListener事件,以此Listener捕捉User输入的键盘事件。 接着,本范例将以EditText与TextView示范如何在捕捉User键盘输入文字的同时,实时取得文字,同步显示于TextView,类似手机版的Ajax效果,实时输入实时输出。 运行结果 ▲图4-1 在EditText输入的数据,立即出现在TextView里面 范例程序src/irdc.ex04_01/EX04_01.java主程序中唯一也是关键之处,便是利用EditText.OnKeyListener来拦截EditText的键盘输入事件,仅需在其中重写onKey() 方法,在onKey() 方法中,将EditText.getText() 取出的文字,显示于TextView当中,是一个简单易懂的范例练习。 package irdc.ex04_01; import android.app.Activity; import android.os.Bundle; import android.view.KeyEvent; import android.view.View; import android.widget.EditText; import android.widget.TextView; public class EX04_01 extends Activity { /*声明 TextView、EditText对象*/ private TextView mTextView01; private EditText mEditText01; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); /*取得TextView、EditText*/ mTextView01 = (TextView)findViewById(R.id.myTextView); mEditText01 = (EditText)findViewById(R.id.myEditText); /*设置EditText用OnKeyListener事件来启动*/ mEditText01.setOnKeyListener(new EditText.OnKeyListener() { @Override public boolean onKey(View arg0, int arg1, KeyEvent arg2) { // TODO Auto-generated method stub /*设置TextView显示EditText所输入的内容*/ mTextView01.setText(mEditText01.getText()); return false; } }); } } 延伸学习这个实时输入实时显示的效果可以扩展在许多手机应用 程序中,可以试着在OnKeyListener() 里做实时文字过滤效果,例如:当User输入不雅的文字时,可以提示User不接受部分关键字,以输入Shit为例,在TextView就会出现:Sh*t,此种做法可以过滤掉不雅文字的出现。 此外,不仅是Widget才有setOnKeyListener方法可以重写,事实上,在View里也有View.setOnKeyListener,也就是捕捉User点击键盘时的事件处理,但请特别注意,需拦截这个事件,即View要取得焦点(Focus)才能触发onKeyDown 事件。最后提醒你,旧版本当中的View.setKeyListener类已经被删除,1.0r2版之后,已经改用View.setOnKeyListener() 方法替换。 范例说明延续前一章按钮事件的应用范例,重新设计一个具有背景图的按钮,让按钮有美观的背景图片,只是这次不使用先前的Button Widget,而是改以ImageButton Widget来显示。 将按钮背景图预先Import至Drawable里(*.png图形文件),利用这些图片,作为ImageButton的背景图,为了做对照,在Layout配置一个“一般按钮”,运行结果画面中,可以明显看出图片按钮与一般按钮在外观上的差异。 要设置ImageButton背景图有许多方法,此程序使用的方法是ImageButton.setImageResource(),需要传递的参数即是res/drawable/ 下面的Resource ID,除了设置背景图片的方法外,程序需要用到onFocusChange与onClick等按钮事件作为按钮事件点击之后的处理,最后通过TextView来显示目前图片按钮的状态为onClick、onFocus,或offFocus,并且同步更新按钮的背景图,让User有动态交互的感觉。 运行结果 ▲图4-2 随着Focus与Click 动作,画面上的图片与文字会告知你目前图片按钮的状态 范例程序src/irdc.ex04_02/EX04_02.java主程序构造三个对象ImageButton、Button与TextView,并在ImageButton上设置onFocusChangeListener与onClickListener,并实现Image Button图片的置换。 ImageButton.setOnFocusChangeListener() 是处理User点击图片按钮之后需要处理的关键,当点击图片按钮的瞬间,以ImageButton.setImageResource() 来更换背景图片。 package irdc.EX04_02; import android.app.Activity; import android.os.Bundle; import android.view.View; /*使用OnClickListener与OnFocusChangeListener来区分按钮的状态*/ import android.view.View.OnClickListener; import android.view.View.OnFocusChangeListener; import android.widget.Button; import android.widget.ImageButton; import android.widget.TextView; public class EX04_02 extends Activity { /*声明三个对象变量(图片按钮,按钮,与TextView)*/ private ImageButton mImageButton1; private Button mButton1; private TextView mTextView1; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); /*通过findViewById构造三个对象*/ mImageButton1 =(ImageButton) findViewById(R.id.myImageButton1); mButton1=(Button)findViewById(R.id.myButton1); mTextView1 = (TextView) findViewById(R.id.myTextView1); /*通过OnFocusChangeListener来响应ImageButton的onFous事件*/ mImageButton1.setOnFocusChangeListener(new OnFocusChangeListener() { public void onFocusChange(View arg0, boolean isFocused) { // TODO Auto-generated method stub /*若ImageButton状态为onFocus改变ImageButton的图片 * 并改变textView的文字*/ if (isFocused==true) { mTextView1.setText("图片按钮状态为:Got Focus"); mImageButton1.setImageResource(R.drawable.iconfull); } /*若ImageButton状态为offFocus改变ImageButton的图片 *并改变textView的文字*/ else { mTextView1.setText("图片按钮状态为ost Focus"); mImageButton1.setImageResource(R.drawable.iconempty); } } }); /*通过onClickListener来响应ImageButton的onClick事件*/ mImageButton1.setOnClickListener(new OnClickListener() { public void onClick(View v) { // TODO Auto-generated method stub /*若ImageButton状态为onClick改变ImageButton的图片 * 并改变textView的文字*/ mTextView1.setText("图片按钮状态为:Got Click"); mImageButton1.setImageResource(R.drawable.iconfull); } }); /*通过onClickListener来响应Button的onClick事件*/ mButton1.setOnClickListener(new OnClickListener() { public void onClick(View v) { // TODO Auto-generated method stub /*若Button状态为onClick改变ImageButton的图片 * 并改变textView的文字*/ mTextView1.setText("图片按钮状态为ost Focus"); mImageButton1.setImageResource(R.drawable.iconempty); } }); } } 扩展学习除了在运行时用onFocus() 与onClick() 事件来设置按钮背景图片外,Android的MVC设计理念,可以让程序运行之初就以xml定义的方式来初始化ImageButton的背景图,仅需先将图片导入res/drawable。 设置方法为在res/drawable下自行定义一个xml,主要针对按钮的state_focused、state_pressed与drawable属性作设置,如下所示: drawable/advancedbutton.xml xmlns:android="http://schemas.android.com/apk/res/android"> - android:state_focused="true"
android:state_pressed="false" android:drawable="@drawable/btnfocused" /> - android:state_focused="true"
android:state_pressed="true" android:drawable="@drawable/btnfocusedpressed" /> - android:state_focused="false"
android:state_pressed="true" android:drawable="@drawable/btnpressed" /> 然后,在main.xml中将advancedbutton赋值给Button组件中background的属性。 layout/main.xml android:id="@+id/myButton1" android:background="@drawable/advancedbutton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/str_button1" /> 如此一来,即可达到如同本范例程序所展示的效果。 范例说明Toast是Android专属的提示小对象,它的使用方式相当简单,但用途却很广泛,基本上,Toast就是一个简短的小信息,将要告诉用户 的信息,以一个浮动在最上层的View显示,显示Toast之后,静待几秒后便会自动消失,最常见的应用就是音量大小的调整,当点击音量调整钮之后,会看见跳出的音量指示Toast对象,等待调整完之后便会消失。 通过Toast的特性,可以在不影响用户通话或聆听音乐情况下,显示要给User的信息。对于程序员来说,它也是一个非常好用的debug工具,可以在任何程序运行时,通过Toast的方式,显示运行变量或手机环境的概况。 本范例使用一个EditText控件来接受用户输入的文字,以及配置Button按钮Widget,点击按钮时,将EditText里的文字,以Toast.makeText()的方法让文字显示于Toast对象中,这段文字会在显示一段时间后自动消失,读者可借此体验一下Toast对象的使用与显示。 运行结果 ▲图4-3 在EditText字段中填写文字,点击按钮送出后,会发出Toast信息 范例程序src/irdc.ex04_03/EX04_03.java主程序需要构建两个控件EditText与Button,在Button的onClick() 方法中使用Toast对象的makeText() 方法来显示输入的文字。 package irdc.EX04_03; import android.app.Activity; import android.os.Bundle; import android.text.Editable; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; public class EX04_03 extends Activity { /** Called when the activity is first created. */ /*声明两个对象变量(按钮与编辑文字)*/ private Button mButton; private EditText mEditText; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); /*通过findViewById()取得对象 */ mButton=(Button)findViewById(R.id.myButton); mEditText=(EditText)findViewById(R.id.myEditText); /*设置onClickListener给Button对象聆听onClick事件*/ mButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub /*声明字符串变量并取得用户输入的EditText字符串*/ Editable Str; Str=mEditText.getText(); /*使用系统 标准的 makeText()方式来产生Toast信息*/ Toast.makeText( EX04_03.this, "你的愿望 "+Str.toString()+"已送达耶诞老人信箱", Toast.LENGTH_LONG).show(); /*清空EditText*/ mEditText.setText(""); } }); } } 扩展学习Toast显示后会在一定时间内消失,在Toast构造参数中的第二个参数为显示的时间常数,可设置为LENGTH_LONG或LENGTH_SHORT,前者提示时间较长,后者较短,作为传递makeText() 方法的参数使用。 当然,你也可以使用重写Toast对象的方法,自定义Toast显示的Layout,以不同于系统内置的方式显示客制化的Toast对象,如要在Toast里显示图片(Drawable),方式如下: Toast mToast01 = new Toast(this); ImageView mView01 = new ImageView(this); mView01.setImageResource(R.drawable.icon); mToast01.setView(mView01); mToast01.show(); 或显示自定义的Layout Widget(如TextView),则写法如下: Toast mToast01 = new Toast(this); TextView mView01=new TextView(this); mView01.setText("ToastWords"); mToast01.setView(mView01); mToast01.show(); 或者通过AlertDialog.Builder来创建类似Toast的信息对象,读者可以实现看看,比较两者有何不同: AlertDialog mADialog01 =new AlertDialog.Builder(this) mADialog01.setTitle("Android 提示"); mADialog01.setMessage("this is a message"); mADialog01.show(); 范例说明所有的网络 服务在User使用之前,都需要签署同意条款,在手机应用程序、手机游戏 的设计经验中,常看见CheckBox在同意条款情境的使用,其选取的状态有两种isChecked=true与isChecked=false。 以下范例将设计一个TextView放入条款文字,在下方配置一个CheckBox Widget作为选取项,通过Button.onClickListener按钮事件处理,取得User同意条款的状态。 当CheckBox.isChecked为true,更改TextView的文字内容为“你已接受同意!!”,当未选取CheckBox时,Button是不可以选择的(被Disabled)。 运行结果 ▲图4-4 未勾选“我同意”时,“确定”按钮是不可以按的 范例程序src/irdc.ex04_04/EX04_04.java利用CheckBox.OnClickListener里的事件来判断Button该不该显示,其方法就是判断Button.Enabled的值;在一开始时,默认参数为false,当有点击CheckBox时,Button参数就修改为true。 package irdc.ex04_04; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.TextView; public class EX04_04 extends Activity { /** Called when the activity is first created. */ /*声明 TextView、CheckBox、Button对象*/ public TextView myTextView1; public TextView myTextView2; public CheckBox myCheckBox; public Button myButton; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); /*取得TextView、CheckBox、Button*/ myTextView1 = (TextView) findViewById(R.id.myTextView1); myTextView2 = (TextView) findViewById(R.id.myTextView2); myCheckBox = (CheckBox) findViewById(R.id.myCheckBox); myButton = (Button) findViewById(R.id.myButton); /*将CheckBox、Button默认为未选择状态*/ myCheckBox.setChecked(false); myButton.setEnabled(false); myCheckBox.setOnClickListener(new CheckBox.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub if(myCheckBox.isChecked()) { /*设置Button为不能选择对象*/ myButton.setEnabled(true); myTextView2.setText(""); } else { /*设置Button为可以选择对象*/ myButton.setEnabled(false); myTextView1.setText(R.string.text1); /*在TextView2里显示出"请勾选我同意"*/ myTextView2.setText(R.string.no); } } }); myButton.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub if(myCheckBox.isChecked()) { myTextView1.setText(R.string.ok); }else { } } }); } } 扩展学习CheckBox在默认内容为空白时(没有任何默认的提示文字下),可设置提示User的文字,其调用的方法为CheckBox.setHint() 方法;在扩展学习的范例练习,是抓取R.string.hello这个字符串常数,其与默认CheckBox文字的结果是相同的,试试看: myTextView1 = (TextView) findViewById(R.id.myTextView1); myTextView2 = (TextView) findViewById(R.id.myTextView2); myCheckBox = (CheckBox) findViewById(R.id.myCheckBox); myButton = (Button) findViewById(R.id.myButton); myCheckBox.setChecked(false); /*利用setHIT抓取strings里面的值*/ CharSequence hint = getString(R.string.hello); myCheckBox.setHint(hint); /*设置文字颜色*/ myCheckBox.setHintTextColor(Color.RED); 范例说明你使用消费券了吗?消费券只有3600元,但是想要买的东西却是无穷多(∞)。这个范例程序要示范的是CheckBox.setOnCheckedChangeListener,在程序中设计三个CheckBox核取项,分别表示三种物品列表,当User勾选其中一个物品,就在TextView里显示已选择的物品列表。 程序的关键在同时聆听三个CheckBox.OnCheckedChangeListener的状态,并在CheckBox.onChecked() 方法中,重组所有被勾选的物品文字。 运行结果 ▲图4-5 勾选不同的CheckBox,该CheckBox的文字会在TextView中显示出来 范例程序src/irdc.ex04_05/EX04_05.java主程序的重点在于构造三个CheckBox的对象,以及一个TextView对象,并通过setOnCheckedChangeListener实现onCheckedChanged() 方法来更新TextView文字。 package irdc.EX04_05; import android.app.Activity; import android.os.Bundle; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.TextView; public class EX04_05 extends Activity { /*声明对象变量*/ private TextView mTextView1; private CheckBox mCheckBox1; private CheckBox mCheckBox2; private CheckBox mCheckBox3; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); /*通过findViewById取得TextView对象并调整文字内容*/ mTextView1 = (TextView) findViewById(R.id.myTextView1); mTextView1.setText("你所选择的项目有: "); /*通过findViewById取得三个CheckBox对象*/ mCheckBox1=(CheckBox)findViewById(R.id.myCheckBox1); mCheckBox2=(CheckBox)findViewById(R.id.myCheckBox2); mCheckBox3=(CheckBox)findViewById(R.id.myCheckBox3); /*设置OnCheckedChangeListener给三个CheckBox对象*/ mCheckBox1.setOnCheckedChangeListener(mCheckBoxChanged); mCheckBox2.setOnCheckedChangeListener(mCheckBoxChanged); mCheckBox3.setOnCheckedChangeListener(mCheckBoxChanged); } /*声明并构造onCheckedChangeListener对象*/ private CheckBox.OnCheckedChangeListener mCheckBoxChanged = new CheckBox.OnCheckedChangeListener() { /*implement onCheckedChanged方法*/ @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // TODO Auto-generated method stub /*通过getString()取得CheckBox的文字字符串*/ String str0="所选的项目为: "; String str1=getString(R.string.str_checkbox1); String str2=getString(R.string.str_checkbox2); String str3=getString(R.string.str_checkbox3); String plus=";"; String result="但是超过预算啰!!"; String result2="还可以再多买几本喔!!"; /*任一CheckBox被勾选后,该CheckBox的文字会改变TextView的文字内容 * 三个对象总共八种情境*/ if(mCheckBox1.isChecked()==true & mCheckBox2.isChecked()==true & mCheckBox3.isChecked()==true) { mTextView1.setText(str0+str1+plus+str2+plus+str3+result); } else if(mCheckBox1.isChecked()==false & mCheckBox2.isChecked()==true & mCheckBox3.isChecked()==true) { mTextView1.setText(str0+str2+plus+str3+result); } else if(mCheckBox1.isChecked()==true & mCheckBox2.isChecked()==false & mCheckBox3.isChecked()==true) { mTextView1.setText(str0+str1+plus+str3+result); } else if(mCheckBox1.isChecked()==true & mCheckBox2.isChecked()==true & mCheckBox3.isChecked()==false) { mTextView1.setText(str0+str1+plus+str2+result); } else if(mCheckBox1.isChecked()==false & mCheckBox2.isChecked()==false & mCheckBox3.isChecked()==true) { mTextView1.setText(str0+str3+plus+result2); } else if(mCheckBox1.isChecked()==false & mCheckBox2.isChecked()==true & mCheckBox3.isChecked()==false) { mTextView1.setText(str0+str2); } else if(mCheckBox1.isChecked()==true & mCheckBox2.isChecked()==false & mCheckBox3.isChecked()==false) { mTextView1.setText(str0+str1); } else if(mCheckBox1.isChecked()==false & mCheckBox2.isChecked()==false & mCheckBox3.isChecked()==false) { mTextView1.setText(str0); } } }; } 扩展学习读者可以将OnCheckedChangeListener改为OnTouchListener(屏幕触控事件),方法如下: private CheckBox.OnTouchListener mCheckBoxTouch = new CheckBox.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub /* 判断在触控笔指压此控件时的状态 */ if(mCheckBox1.isChecked()==false) { /*当触控笔放开后的动作*/ } else if(mCheckBox1.isChecked()==true) { /*当触控笔压下后的动作*/ } return false; } }; 请试着比较OnCheckedChangeListener与OnTouchListener在使用上的差异。 范例说明接下来要介绍的是RadioGroup的组事件。RadioGroup可将各自不同的RadioButton设限于同一个Radio按钮组,同属一个RadioGroup组里的按钮,只能做出单一选择(单选题),虽然前一章曾经介绍过RadioGroup与RadioButton,但当时使用的是Button事件,在此要示范“点击”的同时就运行事件处理,不再需要按钮(Button)的辅助了。 先设计一个TextView Widget,以及一个RadioGroup,并于该RadioGroup内放置两个RadioButton,默认为都不选择,在程序运行阶段,利用onCheckedChanged作为启动事件装置,让User选择其中一个按钮时,显示被选择的内容,最后将RadioButton的选项文字显示于TextView当中。 运行结果 ▲图4-6 点击帅哥或美女按钮的同时,会立即显示事件结果 范例程序src/irdc.ex04_06/EX04_06.java利用OnCheckedChangeListener来启动RadioGroup的事件,随后将被勾选的RadioButton(mRadio1.getText())的文字显示于TextView。 package irdc.ex04_06; import android.app.Activity; import android.os.Bundle; import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.TextView; public class EX04_06 extends Activity { public TextView mTextView1; public RadioGroup mRadioGroup1; public RadioButton mRadio1,mRadio2; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); /*取得 TextView、RadioGroup、RadioButton对象*/ mTextView1 = (TextView) findViewById(R.id.myTextView); mRadioGroup1 = (RadioGroup) findViewById(R.id.myRadioGroup); mRadio1 = (RadioButton) findViewById(R.id.myRadioButton1); mRadio2 = (RadioButton) findViewById(R.id.myRadioButton2); /*RadioGroup用OnCheckedChangeListener来运行*/ mRadioGroup1.setOnCheckedChangeListener(mChangeRadio); } private RadioGroup.OnCheckedChangeListener mChangeRadio = new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { // TODO Auto-generated method stub if(checkedId==mRadio1.getId()) { /*把mRadio1的内容传到mTextView1*/ mTextView1.setText(mRadio1.getText()); } else if(checkedId==mRadio2.getId()) { /*把mRadio2的内容传到mTextView1*/ mTextView1.setText(mRadio2.getText()); } } }; } 扩展学习在扩展学习里,请加上两个Button在其中,一个为回答,另一个为清除RadioButton的选择状态。程序有随机设置的答案选项,当User点击回答按钮时,比较答案是否正确,若正确,则以AlertDialog对话窗口显示答案结果。 answerButton.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { new AlertDialog.Builder(TEST_56.this) .setIcon(R.drawable.icon) .setTitle(R.string.about_dialog_title) .setPositiveButton(R.string.about_dialog_ok, null) .setMessage(R.string.about_dialog_thanks) .create(); { } }).show(); } 在清除Button.onClickListener的事件处理中,只需将被选择的RadioButton取消掉,回到等待回答的状态。 mRadioGroup1.clearCheck(); 范例说明在设计此范例之前,必须先准备三张图片(两张外框图、一张内框图),将这三张图片放在res/drawable下面,在此使用的图片为PNG图形文件,而图案大小最好是调整成手机屏幕大小,或者依据手机的分辨率,动态调整ImageView的大小。稍后的范例将介绍如何调整ImageView的大小,这里就不赘述了。 准备好之后,开始做这个酷炫的专业相框应用程序,在Layout当中创建了两个ImageView,且以绝对坐标的方式“堆栈”在一起,在其下方放上两个按钮(Button),按钮的目的是为了要用来切换图片,创建完成后,要在Button事件里处理置换图片的动作。 程序目的为点击Button1,ImageView1会出现right的图片,点击Button2,ImageView1会出现left的图片,而ImageView2皆为固定不动(文件名叫oa),这个范例并不难,很快就会知道葫芦里卖的是什么药了,先来看看范例运行结果。 运行结果 ▲图4-7 点击按钮后,会更换ImageView(外框)图片 范例程序src/irdc.ex04_07/EX04_07.java此程序的关键地方在于getResources() 这个方法,这个方法负责访问Resource ID,无论是访问资源里的图文件、文字都要用到getResources();在此使用getResources().getDrawable() 来载入res/drawable里的图文件,并将图片放置在ImageView当中。 package irdc.ex04_07; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.ImageView; public class EX04_07 extends Activity { /*声明 Button、ImageView对象*/ private ImageView mImageView01; private ImageView mImageView02; private Button mButton01; private Button mButton02; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); /*取得 Button、ImageView对象*/ mImageView01 = (ImageView)findViewById(R.id.myImageView1); mImageView02 = (ImageView)findViewById(R.id.myImageView2); mButton01 = (Button) findViewById(R.id.myButton1); mButton02 = (Button) findViewById(R.id.myButton2); /*设置ImageView背景图*/ mImageView01.setImageDrawable(getResources(). getDrawable(R.drawable.right)); mImageView02.setImageDrawable(getResources(). getDrawable(R.drawable.oa)); /*用OnClickListener事件来启动*/ mButton01.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { /*当启动后,ImageView立刻换背景图*/ mImageView01.setImageDrawable(getResources(). getDrawable(R.drawable.right)); } }); mButton02.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { mImageView01.setImageDrawable(getResources(). getDrawable(R.drawable.left)); } }); } } res/layout/main.xml创建两个ImageView,一个为外框、另一个为内框,需注意的是,图片需要做一个排序堆栈顺序,将前景图放在上方(以AbsoluteLayout),将背景图放在前景图的下方,这是最简单的堆栈顺序方法。 android:id="@+id/widget34" android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android" > android:id="@+id/myImageView1" android:layout_width="320px" android:layout_height="280px" android:layout_x="0px" android:layout_y="36px" /> android:id="@+id/myImageView2" android:layout_width="104px" android:layout_height="157px" android:layout_x="101px" android:layout_y="119px" /> android:id="@+id/myButton1" android:layout_width="105px" android:layout_height="66px" android:text="pic1" android:layout_x="9px" android:layout_y="356px" /> android:id="@+id/myButton2" android:layout_width="105px" android:layout_height="66px" android:text="pic2" android:layout_x="179px" android:layout_y="356px" /> 延伸学习学会ImageView之后,在延伸学习里,便可试着将两个ImageButton Widget堆栈在一起,如此一来,不但有背景图,还有按钮事件。 android:id="@+id/myImageButton1" android:state_focused="true" android:layout_width="320px" android:layout_height="280px" android:layout_x="0px" android:layout_y="36px" /> 接着,你就可以自由发挥了。ImageButton的使用方法已经介绍过,而堆栈的技巧可参考这个范例程序,比较不同的地方就是只要点击图片,即可直接做换图的动作,不需要再点击面的Button做更换,需要注意的是图片大小要作调整,不然可能会与ImageButton不合喔! 范例说明Spinner就是下拉菜单,也等于swing的combo box、html的 ,由于手机画面有限,要在有限的范围选择项目,下拉菜单是唯一、也是较好的选择。 Android提供的Spinner Widget的下拉菜单已经非常好用了,样式也还适用。但本范例的示范重点在于自定义下拉菜单里的样式,其关键在于调用setDropDownViewResource方法,以XML的方式定义下拉菜单要显示的模样。本范例除了自定义下拉菜单,还用程序设计了一段动画,当User以触控的方式点击这个自定义的Spinner时,会以一段动画提示User。 运行结果 ▲图4-8 自定义Spinner的下拉菜单模式,具有圆角的效果 范例程序src/irdc.ex04_08/EX04_08.java在new ArrayAdapter中,我们使用ArrayAdapter(Context context, int textViewResourceId, T[] objects) 这个Constructor,textViewResourceId使用Android提供的ResourceID,objects为必须传递的字符串数组(String Array)。 Adapter的setDropDownViewResource可以设置下拉菜单的显示方式,将该xml定义在res/layout目录下面,可针对下拉菜单中的TextView进行设置,如同本程序里的R.layout.myspinner_dropdown即为自定义的下拉菜单TextView样式。除了改变下拉菜单样式外,也对Spinner做了一点动态效果,点击Spinner时,晃动Spinner再出现下拉菜单(myAnimation)。 package irdc.ex04_08; import android.app.Activity; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.TextView; import android.widget.ListView; import android.widget.Spinner; public class EX04_08 extends Activity { private static final String[] countriesStr = { "北京市", "上海市", "天津市", "重庆市" }; private TextView myTextView; private Spinner mySpinner; private ArrayAdapter adapter; Animation myAnimation; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 载入main.xml Layout */ setContentView(R.layout.main); /* 以findViewById()取得对象 */ myTextView = (TextView) findViewById(R.id.myTextView); mySpinner = (Spinner) findViewById(R.id.mySpinner); adapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item, countriesStr); /* myspinner_dropdown为自定义下拉菜单样式定义在res/layout目录下 */ adapter.setDropDownViewResource(R.layout.myspinner_dropdown); /* 将ArrayAdapter添加Spinner对象中 */ mySpinner.setAdapter(adapter); /* 将mySpinner添加OnItemSelectedListener */ mySpinner.setOnItemSelectedListener (new Spinner.OnItemSelectedListener() { @Override public void onItemSelected (AdapterView> arg0, View arg1, int arg2, long arg3) { /* 将所选mySpinner的值带入myTextView中 */ myTextView.setText("选择的是" + countriesStr[arg2]); /* 将mySpinner显示 */ arg0.setVisibility(View.VISIBLE); } @Override public void onNothingSelected(AdapterView> arg0) { // TODO Auto-generated method stub } }); /* 取得Animation定义在res/anim目录下 */ myAnimation = AnimationUtils.loadAnimation(this, R.anim.my_anim); /* 将mySpinner添加OnTouchListener */ mySpinner.setOnTouchListener(new Spinner.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { /* 将mySpinner运行Animation */ v.startAnimation(myAnimation); /* 将mySpinner隐藏 */ v.setVisibility(View.INVISIBLE); return false; } }); mySpinner.setOnFocusChangeListener(new Spinner.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { // TODO Auto-generated method stub } }); } } res/layout/myspinner_dropdown.xml改变下拉菜单样子的xml,里面所使用的组件为TextView。 xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/text1" android:layout_width="wrap_content" android:layout_height="24sp" android:singleLine="true" style="?android:attr/spinnerDropDownItemStyle" /> res/anim/my_anim.xmlAndroid的动画(animation)由四种类型(type)所组成:alpha、scale、translate,以及rotate,以下的自定义动画将使用当中的两种。 xmlns:android="http://schemas.android.com/apk/res/android"> android:fromXDelta="0" android:toXDelta="-100%p" android:duration="300" > android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="300"> 扩展学习Animation主要有两种动态方式,一种是tweened animation(渐变动画),另一种是frame by frame animation(画面转换动画)。tweened animation则有以下四种基本转换方式: l AlphaAnimation (transparency changes):透明度转换 l RotateAnimation (rotations):旋转转换 l ScaleAnimation (growing or shrinking):缩放转换 l TranslateAnimation (position changes):位置转换 定义好你想要的动画xml后,用AnimationUtils.loadAnimation将动画加载,在想要加上动态效果的组件中使用startAnimation方法。 范例说明前面的范例对Spinner的自定义菜单、交互事件已大致掌握了设计方法,但在Android的Spinner里的元素,若要动态增减Spinner下拉菜单的选项,就必须利用ArrayList的依赖性来完成。 以下范例将设计一个EditText,当User输入了新的文字,在点击“添加”按钮的同时,就会将输入的值添加Spinner(至下拉菜单的最后一项),接着Spinner会停留在刚添加好的选项上;当点击“删除”按钮,则删除选择的Spinner选项,常应用于未知Spinner选项数量的To-Do List、或添加维护市县数据等等。 运行结果 ▲图4-9 随User的输入文字,可动态添加/删除的Spinner菜单 范例程序src/irdc.ex04_09/EX04_09.javaSpinner添加了OnItemSelectedListener事件,当点击下拉菜单后,将值带到上方的TextView。上一个范例在new adapter时传入String数组,这次因为要添加及删除adapter,所以要传入的是ArrayList,否则,在添加删除时会出现错误。 package irdc.ex04_09; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; import java.util.ArrayList; import java.util.List; public class EX04_09 extends Activity { private static final String[] countriesStr = { "北京市", "上海市", "天津市", "重庆市" }; private TextView myTextView; private EditText myEditText; private Button myButton_add; private Button myButton_remove; private Spinner mySpinner; private ArrayAdapter adapter; private List allCountries; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 载入main.xml Layout */ setContentView(R.layout.main); allCountries = new ArrayList(); for (int i = 0; i < countriesStr.length; i++) { allCountries.add(countriesStr); } /* new ArrayAdapter对象并将allCountries传入 */ adapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item, allCountries); adapter .setDropDownViewResource (android.R.layout.simple_spinner_dropdown_item); /* 以findViewById()取得对象 */ myTextView = (TextView) findViewById(R.id.myTextView); myEditText = (EditText) findViewById(R.id.myEditText); myButton_add = (Button) findViewById(R.id.myButton_add); myButton_remove = (Button) findViewById(R.id.myButton_remove); mySpinner = (Spinner) findViewById(R.id.mySpinner); /* 将ArrayAdapter添加Spinner对象中 */ mySpinner.setAdapter(adapter); /* 将myButton_add添加OnClickListener */ myButton_add.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View arg0) { String newCountry = myEditText.getText().toString(); /* 先比较添加的值是否已存在,不存在才可添加 */ for (int i = 0; i < adapter.getCount(); i++) { if (newCountry.equals(adapter.getItem(i))) { return; } } if (!newCountry.equals("")) { /* 将值添加至adapter */ adapter.add(newCountry); /* 取得添加的值的位置 */ int position = adapter.getPosition(newCountry); /* 将Spinner选择在添加的值的位置 */ mySpinner.setSelection(position); /* 将myEditText清空 */ myEditText.setText(""); } } }); /* 将myButton_remove添加OnClickListener */ myButton_remove.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View arg0) { if (mySpinner.getSelectedItem() != null) { /* 删除mySpinner的值 */ adapter.remove(mySpinner.getSelectedItem().toString()); /* 将myEditText清空 */ myEditText.setText(""); if (adapter.getCount() == 0) { /* 将myTextView清空 */ myTextView.setText(""); } } } }); /* 将mySpinner添加OnItemSelectedListener */ mySpinner.setOnItemSelectedListener (new Spinner.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView> arg0, View arg1, int arg2, long arg3) { /* 将所选mySpinner的值带入myTextView中 */ myTextView.setText(arg0.getSelectedItem().toString()); } @Override public void onNothingSelected(AdapterView> arg0) { } }); } } 扩展学习setDropDownViewResource主要是设置User点击Spinner后出现的下拉菜单样式,除了前一个范例使用自设方式改变TextView内容之外,android亦提供两种基本的样式: l android.R.layout.simple_spinner_item:TextView的下拉菜单。 l android.R.layout.simple_spinner_dropdown_item:除了有TextView,右边有radio的下拉菜单。 查看Android 源代码中的simple_spinner_dropdown_item.xml,内容如下: xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/text1" android:layout_width="fill_parent" android:layout_height="?android:attr/listPreferredItemHeight" android:singleLine="true" style="?android:attr/spinnerDropDownItemStyle" /> 以下为自定义修改后,适用于spinner的Layout: xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/text1" android:layout_width="fill_parent" android:layout_height="12sp" android:singleLine="true" style="?android:attr/spinnerDropDownItemStyle" android:textSize="10sp" /> 范例说明还记得在第三章“简单的Gallery相片画廊”范例,为了简化问题,使用了Android默认的Icon作为Gallery显示的内容吗?现在,将数张PNG图片导入Drawable当中,并于onCreate的同时,载入于Gallery Widget中,试着再添加一个OnItemClick的事件,以取得图片的ID编号来响应用户点击图片时的状态,完成Gallery的高级使用。本范例的另一个重点,就是如何设置Gallery图片的宽高以及放置图片Layout的大小,在此我们改写一个继承自BaseAdapter的ImageAdapter容器来存放图片,通过ImageView.setScaleType() 的方法来改变图片的显示,再通过setLayoutParams() 方法来改变Layout的宽高。 运行结果 ▲图4-10 程序启动后会显示res/drawable中的图片,点击任一图片会Toast该图片的编号 范例程序src/irdc.ex04_10/EX04_10.java主程序有两大重点,第一、是ImageAdapter继承BaseAdapter class的未实现方法的重写构造,第二、则是Gallery的OnItemClick() 方法与图片及Layout宽高设置。 package irdc.EX04_10; import android.app.Activity; import android.os.Bundle; /* 本范例需使用到的class */ import android.content.Context; import android.content.res.TypedArray; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.Gallery; import android.widget.ImageView; import android.widget.Toast; import android.widget.AdapterView.OnItemClickListener; public class EX04_10 extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); /*通过findViewById取得*/ Gallery g = (Gallery) findViewById(R.id.mygallery); /* 添加一ImageAdapter并设置给Gallery对象 */ g.setAdapter(new ImageAdapter(this)); /* 设置一个itemclickListener并Toast被点击图片的位置 */ g.setOnItemClickListener(new OnItemClickListener() { public void onItemClick (AdapterView> parent, View v, int position, long id) { Toast.makeText (EX04_10.this, getString(R.string.my_gallery_text_pre) + position+ getString(R.string.my_gallery_text_post), Toast.LENGTH_SHORT).show(); } }); } /* 改写BaseAdapter自定义一ImageAdapter class */ public class ImageAdapter extends BaseAdapter { /*声明变量*/ int mGalleryItemBackground; private Context mContext; /*ImageAdapter的构造器*/ public ImageAdapter(Context c) { mContext = c; /* 使用在res/values/attrs.xml中的定义 * 的Gallery属性.*/ TypedArray a = obtainStyledAttributes(R.styleable.Gallery); /*取得Gallery属性的Index id*/ mGalleryItemBackground = a.getResourceId (R.styleable.Gallery_android_galleryItemBackground, 0); /*让对象的styleable属性能够反复使用*/ a.recycle(); } /* 重写的方法getCount,返回图片数目 */ public int getCount() { return myImageIds.length; } /* 重写的方法getItemId,返回图像的数组id */ public Object getItem(int position) { return position; } public long getItemId(int position) { return position; } /* 重写的方法getView,返回一View对象 */ public View getView (int position, View convertView, ViewGroup parent) { /*产生ImageView对象*/ ImageView i = new ImageView(mContext); /*设置图片给imageView对象*/ i.setImageResource(myImageIds[position]); /*重新设置图片的宽高*/ i.setScaleType(ImageView.ScaleType.FIT_XY); /*重新设置Layout的宽高*/ i.setLayoutParams(new Gallery.LayoutParams(136, 88)); /*设置Gallery背景图*/ i.setBackgroundResource(mGalleryItemBackground); /*返回imageView对象*/ return i; } /*构建一Integer array并取得预加载Drawable的图片id*/ private Integer[] myImageIds = { R.drawable.photo1, R.drawable.photo2, R.drawable.photo3, R.drawable.photo4, R.drawable.photo5, R.drawable.photo6, }; } } res/values/attrs.xml定义layout外部resource的xml文件,用来改变layout的背景图。 扩展学习在Android:ScaleType中定义了下列常数可供使用,通过“ObjectView.ScaleType常数名称”的方式,就可以改变图片的显示方式。
常数名称 值 Matrix 0 fitXY 1 fitStart 2 fitCenter 3 fitEnd 4 center 5 centerCrop 6 centerInside 7 另外,在主程序中,我们使用了下面这一段写法: TypedArray a = obtainStyledAttributes(R.styleable.Gallery); 这是一个引用自制layout元素的用法,必须在res/values下面添加一个attrs.xml,并在其中定义 标签TAG,目的是自定义layout的背景风格,并且通过TypeArray的特性,让相同的Layout元素可以重复用于每一张图片。 范例说明大家都应该用过操作系统 中的文件搜索功能 吧!它可以快速帮我们找到想要的文件。如果要在手机制作一个文件搜索的功能,又该如何实现呢?其实这个功能并不困难,Java I/O的API中提供了java.io.File对象,只要利用File对象的方法,再搭配Android的EditText、TextView等对象,就可以轻松做出一个手机的文件搜索引擎。 本范例中使用EditText、Button与TextView三种对象来实现此功能,用户将要搜索的文件名称或关键字输入EditText中,点击Button后,程序会在根目录中寻找符合的文件,并将搜索结果显示于TextView中;如果找不到符合的文件,则显示找不到文件。 运行结果 ▲图4-11 快速搜索手机根目录中的文件 范例程序src/irdc.ex04_11/EX04_11.java范例中以java.io.File对象来取得根目录下的文件,经过比较后,将符合结果的文件路径写入TextView中,若要在TextView中换行,需使用 /n来作换行符号。 package irdc.ex04_11; /* import相关class */ import java.io.File; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; public class EX04_11 extends Activity { /*声明对象变量*/ private Button mButton; private EditText mKeyword; private TextView mResult; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 载入main.xml Layout */ setContentView(R.layout.main); /* 初始化对象 */ mKeyword=(EditText)findViewById(R.id.mKeyword); mButton=(Button)findViewById(R.id.mButton); mResult=(TextView) findViewById(R.id.mResult); /* 将mButton添加onClickListener */ mButton.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { /*取得输入的关键字*/ String keyword = mKeyword.getText().toString(); if(keyword.equals("")) { mResult.setText("请勿输入空白的关键字!!"); } else { mResult.setText(searchFile(keyword)); } } }); } /* 搜索文件的method */ private String searchFile(String keyword) { String result=""; File[] files=new File("/").listFiles(); for( File f : files ) { if(f.getName().indexOf(keyword)>=0) { result+=f.getPath()+"/n"; } } if(result.equals("")) result="找不到文件!!"; return result; } } 扩展学习在本范例中,searchFile(String keyword) 这个方法的功用为搜索根目录下符合关键字的文件,在搜索文件的过程中,只搜索根目录中的文件,并没有再对子目录下的文件作进一步的比较,如果要再强化这个文件搜索的功能,让它也能搜索包含子目录下的所有文件,可以在程序中利用File.isDirectory() 这个方法来判断其是否为目录。如果是的话,就继续往下一层寻找;不是的话,就终止向下寻找的动作。这个做法在后面的范例中会有详细的示范,要注意的是手机硬件环境是否能负荷程序做大规模的文件搜索,毕竟手机的硬件配备(处理器、内存)是比不上一般计算机的。 另外,在范例中仅针对关键字做了空白检查,在比较文件名称时也没有忽略大小写(要大小写完全符合才搜索的出来),如果要让这个搜索功能更完备,当然可以多做一点变化,例如,检查关键字是否包含特殊的字符、可选择比较文件时是否忽略大小写、可选择要搜索的文件类型,甚至是可自己指定要搜索的目录等等,就看各位要如何运用了。 范例说明Android默认的按钮通常都是方方正正的,也许前面的范例已经看过如何通过ImageButton来置换按钮背景图片了,但本范例的练习则是在两个按钮之间的交互,点击A按钮,恢复B按钮图片;点击B按钮,恢复A按钮的图片。使用的方法为点击的瞬间置换图片,置换的图片方式与先前介绍的相同(ImageButton.setImageDrawable)。 程序项目预先import了三张图片:p1.png、p2.png以及p3.png,而在onCreate() 时,画面Layout上的两个ImageButton各自显示p1.png以及p2.png,当点击其一按钮,则改变自己的图片为p3.png,并还原另一按钮的为默认的图文件,以按钮事件及图文件置换的方式来提醒User现在所“选择”的图像按钮为何。 运行结果 ▲图4-12 被点击的ImageButton因变换图片而被Hightlight出来 范例程序src/irdc.ex04_12/EX04_12.java范例程序主要通过setImageDrawable方法来改变按钮的图片,图片是放在res/drawable目录下面。而ImageButton.setOnClickListener() 的角色则是本程序的关键,通过同时换两个按钮的图片来表示被选择的图片,是一种类似被选择的假象手法,常应用于切换开关(Turn On/Turn Off)之用。 package irdc.ex04_12; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.ImageButton; import android.widget.TextView; public class EX04_12 extends Activity { TextView myTextView; ImageButton myImageButton_1; ImageButton myImageButton_2; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 载入main.xml Layout */ setContentView(R.layout.main); /* 以findViewById()取得TextView及ImageButton对象 */ myTextView = (TextView) findViewById(R.id.myTextView); myImageButton_1=(ImageButton)findViewById(R.id.myImageButton_1); myImageButton_2=(ImageButton)findViewById(R.id.myImageButton_2); /* myImageButton_1添加OnClickListener */ myImageButton_1.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { myTextView.setText("你点击的是myImageButton_1"); /* 点击myImageButton_1时将myImageButton_1图片置换成p3图片 */ myImageButton_1.setImageDrawable(getResources().getDrawable( R.drawable.p3)); /* 点击myImageButton_1时将myImageButton_2图片置换成p2图片 */ myImageButton_2.setImageDrawable(getResources().getDrawable( R.drawable.p2)); } }); /* myImageButton_2添加OnClickListener */ myImageButton_2.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { myTextView.setText("你点击的是myImageButton_2"); /* 点击myImageButton_2时将myImageButton_1图片置换成p1图片 */ myImageButton_1.setImageDrawable(getResources().getDrawable( R.drawable.p1)); /* 点击myImageButton_2时将myImageButton_2图片置换成p3图片 */ myImageButton_2.setImageDrawable(getResources().getDrawable( R.drawable.p3)); } }); } } 扩展学习除了自己在res/drawable放置图片方式外,也可以用系统Android操作系统默认的图片,如打电话、简短提示的图标等等,只要修改main.xml里ImageButton的属性: android:src="@drawable/p1" 将其改成 android:src="@android:drawable/sym_action_call" 看到了吗?标识“@android:”就表示是引用android所提供,而非自行导入的。 范例说明通过Google 上网 搜索时,只要输入几个文字,就会显示可能的关键字让你挑选,这种效果在Android中是非常容易达到的。事实上,Android的AutoCompleteTextView Widget,只要搭配ArrayAdapter就能设计出类似Google搜索提示的效果。 本范例先在Layout当中布局一个AutoCompleteTextView Widget,然后通过预先设置好的字符串数组,将此字符串数组放入ArrayAdapter,最后利用AutoCompleteTextView.setAdapter方法,就可以让AutoCompleteTextView Widget具有自动完成提示的功能。例如,只要输入ab,就会自动带出包含ab的所有字符串列表。 运行结果 ▲图4-13 只要输入ab,就会自动带出所有包含ab的字符串列表 范例程序src/irdc.ex04_13/EX04_13.java本范例程序主要示范AutoCompleteTextView用法,再次使用到ArrayAdapter,只要是有下拉菜单的项目,都必须使用到ArrayAdapter对象。此外,将ArrayAdapter添加AutoCompleteTextView对象中,所使用的方法为setAdapter,当中传输唯一的参数类型即为字符串类型的ArrayAdapter。 package irdc.ex04_13; import android.app.Activity; import android.os.Bundle; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; public class EX04_13 extends Activity { private static final String[] autoStr = new String[] { "a", "abc", "abcd", "abcde" }; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 载入main.xml Layout */ setContentView(R.layout.main); /* new ArrayAdapter对象并将autoStr字符串数组传入 */ ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_dropdown_item_1line, autoStr); /* 以findViewById()取得AutoCompleteTextView对象 */ AutoCompleteTextView myAutoCompleteTextView = (AutoCompleteTextView) findViewById(R.id.myAutoCompleteTextView); /* 将ArrayAdapter添加AutoCompleteTextView对象中 */ myAutoCompleteTextView.setAdapter(adapter); } } 扩展学习有个类似AutoCompleteTextView的对象,称为MultiAutoCompleteTextView,它继承了AutoCompleteTextView,差别在于它可以在输入框一直增加新的选择值,其编写方式也有些不同,一定要setTokenizer,否则会出现错误,以下范例是传入CommaTokenizer类,结果会将原本选择框里的值往后加逗号及空白。 package irdc.ex04_13; import android.app.Activity; import android.os.Bundle; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.MultiAutoCompleteTextView; public class EX04_13 extends Activity { private static final String[] autoStr = new String[] { "a", "abc", "abcd", "abcde" }; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 载入main.xml Layout */ setContentView(R.layout.main_1); /* new ArrayAdapter对象并将autoStr字符串数组传入 */ ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_dropdown_item_1line, autoStr); /* 以findViewById()取得MultiAutoCompleteTextView对象 */ MultiAutoCompleteTextView myAutoCompleteTextView = (MultiAutoCompleteTextView) findViewById(R.id.myAutoCompleteTextView); /* 将ArrayAdapter添加AutoCompleteTextView对象中 */ myAutoCompleteTextView.setAdapter(adapter); myAutoCompleteTextView.setTokenizer (new MultiAutoCompleteTextView.CommaTokenizer()); } } 范例说明Android里的AnalogClock Widget是一个时钟对象,本范例将配置一个小时钟,并在其下放置一个TextView,为了做对照,上面放置的为模拟时钟,下面的TextView则模拟电子时钟,将AnalogClock的时间以数字钟形式显示。 本范例的重点,在在android.os.Handler、java.lang.Thread以及android.os.Message三对象的整合应用,通过产生Thread对象,在进程内同步调用System.currentTimeMillis() 取得系统时间,并通过Message对象来通知Handler对象,Handler则扮演联系Activity与Thread之间的桥梁,在收到Message对象后,将时间变量的值,显示于TextView当中,产生数字时钟的外观与功能。 运行结果 ▲图4-14 程序启动后,除了AnalogClock会显示目前系统时间外,下方会显示数字钟 范例程序src/irdc.ex04_14/EX04_14.java主程序需要另外加载Java的Calendar与Thread对象,在onCreate() 中构造Handler与Thread两对象,并实现handelMessage() 与run() 两个方法,如下所述: package irdc.EX04_14; import android.app.Activity; import android.os.Bundle; /*这里我们需要使用Handler类与Message类来处理进程*/ import android.os.Handler; import android.os.Message; import android.widget.AnalogClock; import android.widget.TextView; /*需要使用Java的Calendar与Thread类来取得系统时间*/ import java.util.Calendar; import java.lang.Thread; public class EX04_14 extends Activity { /*声明一常数作为判别信息用*/ protected static final int GUINOTIFIER = 0x1234; /*声明两个widget对象变量*/ private TextView mTextView; public AnalogClock mAnalogClock; /*声明与时间相关的变量*/ public Calendar mCalendar; public int mMinutes; public int mHour; /*声明关键Handler与Thread变量*/ public Handler mHandler; private Thread mClockThread; /** Called when the activity is first created. */ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); /*通过findViewById取得两个widget对象*/ mTextView=(TextView)findViewById(R.id.myTextView); mAnalogClock=(AnalogClock)findViewById(R.id.myAnalogClock); /*通过Handler来接收进程所传递的信息并更新TextView*/ mHandler = new Handler() { public void handleMessage(Message msg) { /*这里是处理信息的方法*/ switch (msg.what) { case EX04_14.GUINOTIFIER: /* 在这处理要TextView对象Show时间的事件 */ mTextView.setText(mHour+" : "+mMinutes); break; } super.handleMessage(msg); } }; /*通过进程来持续取得系统时间*/ mClockThread=new LooperThread(); mClockThread.start(); } /*改写一个Thread Class用来持续取得系统时间*/ class LooperThread extends Thread { public void run() { super.run(); try { do { /*取得系统时间*/ long time = System.currentTimeMillis(); /*通过Calendar对象来取得小时与分钟*/ final Calendar mCalendar = Calendar.getInstance(); mCalendar.setTimeInMillis(time); mHour = mCalendar.get(Calendar.HOUR); mMinutes = mCalendar.get(Calendar.MINUTE); /*让进程休息一秒*/ Thread.sleep(1000); /*重要关键程序:取得时间后发出信息给Handler*/ Message m = new Message(); m.what = EX04_14.GUINOTIFIER; EX04_14.this.mHandler.sendMessage(m); }while(EX04_14.LooperThread.interrupted()==false); /*当系统发出中断信息时停止本循环*/ } catch(Exception e) { e.printStackTrace(); } } } } 扩展学习其实要达到本范例效果的代码应该只有两行,也就是将笔者对于TextView与进程Thread的处理,改为使用widget.DigitalClock的方式,写法如下: import android.widget.AnalogClock mDigitalClock =(DigitalClock)findViewById(R.id.myDigitalClock); 而本程序使用TextView来模拟DigitalClock的做法,实际上,也是参考AnalogClock与DigitalClock 这两个widget的程序代码所做的练习,对于将来实现Timer相关的小对象,会有所帮助。 Android提供了System.currentTimeMillis()、uptimeMillis()、elapsedRealtime()这三种不同特性的System Clock给开发 者使用。本范例使用System.currentTimeMillis() 就是标准的Clock用法,需要搭配真实的日期与时间使用,另外两者则是适用于interval与elapse time来控制程序与UI之用,读者可以自己练习看看。 范例说明许多的网络服务,例如网络订票、网络订房、网络订旅游行程、或在注册会员输入基本数据时,都会需要用户输入日期与时间格式的数据,有些网站会提供贴心的小功能:直接由万年历上来选择日期与时间,选择完毕,就会自动将日期与时间带入需要填写的字段中。 Android有没有提供类似的组件可以实现这样的功能呢?答案是肯定的!在这个范例中,将示范以Android API中提供的DatePicker与TimePicker两种对象来实现动态输入日期与时间的功能。 范例中使用了DatePicker、TimePicker与TextView三种对象,以TextView来显示日期与时间,默认带入目前系统的日期与时间,DatePicker与TimePicker可让用户动态调整日期与时间,当用户调整了DatePicker的日期或TimePicker时间时,则TextView中所显示的日期与时间亦会跟着改变。 运行结果 ▲图4-15 由手机屏幕上动态输入日期与时间 范例程序src/irdc.ex04_15/EX04_15.java程序中以updateDisplay() 这个方法来设置TextView中所显示的日期时间,以java.util.Calendar对象来取得目前的系统时间,并预先带入TextView中。 当用户更改了DatePicker里的年、月、日时,将触发DatePicker的onDateChange() 事件,运行updateDisplay() 来重新设置TextView中显示的日期;同样的原理,当用户更改了TimePicker里的时间,会触发TimePicker的onTimeChange() 事件,运行updateDisplay() 来重新设置TextView中显示的时间。 package irdc.ex04_15; /* import相关class */ import java.util.Calendar; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; import android.widget.DatePicker; import android.widget.TimePicker; public class EX04_15 extends Activity { /*声明日期及时间变量*/ private int mYear; private int mMonth; private int mDay; private int mHour; private int mMinute; /*声明对象变量*/ TextView tv; TimePicker tp; DatePicker dp; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { /*取得目前日期与时间*/ Calendar c=Calendar.getInstance(); mYear=c.get(Calendar.YEAR); mMonth=c.get(Calendar.MONTH); mDay=c.get(Calendar.DAY_OF_MONTH); mHour=c.get(Calendar.HOUR_OF_DAY); mMinute=c.get(Calendar.MINUTE); super.onCreate(savedInstanceState); /* 载入main.xml Layout */ setContentView(R.layout.main); /*取得TextView对象,并调用updateDisplay() 来设置显示的初始日期时间*/ tv= (TextView) findViewById(R.id.showTime); updateDisplay(); /*取得DatePicker对象,以init() 设置初始值与onDateChangeListener() */ dp=(DatePicker)findViewById(R.id.dPicker); dp.init(mYear,mMonth,mDay,new DatePicker.OnDateChangedListener() { @Override public void onDateChanged(DatePicker view,int year, int monthOfYear,int dayOfMonth) { mYear=year; mMonth= monthOfYear; mDay=dayOfMonth; /*调用updateDisplay()来改变显示日期*/ updateDisplay(); } }); /*取得TimePicker对象,并设置为24小时制显示*/ tp=(TimePicker)findViewById(R.id.tPicker); tp.setIs24HourView(true); /*setOnTimeChangedListener,并重写onTimeChanged event*/ tp.setOnTimeChangedListener(new TimePicker.OnTimeChangedListener() { @Override public void onTimeChanged(TimePicker view, int hourOfDay, int minute) { mHour=hourOfDay; mMinute=minute; /*调用updateDisplay()来改变显示时间*/ updateDisplay(); } }); } /*设置显示日期时间的方法*/ private void updateDisplay() { tv.setText( new StringBuilder().append(mYear).append("/") .append(format(mMonth + 1)).append("/") .append(format(mDay)).append(" ") .append(format(mHour)).append(":") .append(format(mMinute)) ); } /*日期时间显示两位数的方法*/ private String format(int x) { String s=""+x; if(s.length()==1) s="0"+s; return s; } } 扩展学习本范例的重点在于学习如何使用DatePicker与TimePicker对象来达成动态调整日期与时间的功能,眼尖的读者应该发现,在范例中,DatePicker实现OnDateChangedListener() 的方法与TimePicker实现OnTimeChangedListener() 的方法是不太相同的。DatePicker对象以init() 这个方法来指定DatePicker初始的年、月、日及OnDateChangedListener() 的事件;而TimePicker对象则是直接以setOnTimeChangedListener() 事件来处理时间改变时程序要做的操作。 在旧版的Android SDK (1.0r2版以前的SDK版本)中,DatePicker对象有提供setOnDateChangedListener() 这个方法,但是在新版的SDK(1.0r2),这个方法被删除了,所以要实现OnDateChangedListener() 时,必须以init() 方式来重写OnDateChangedListener();而TimePicker则直接以 setOnTimeChangedListener() 来实现即可。 Android API另外提供了其它的对象来实现动态修改日期时间的功能:DatePickerDialog与TimePickerDialog。这两种类型的对象最大的差别在于DatePicker与TimePicker是直接显示在屏幕画面上,而DatePickerDialog与TimePickerDialog对象则是以跳出Dialog的方式来显示,如下图所示。 ▲图4-16 DatePickerDialog与TimePickerDialog DatePickerDialog与TimePickerDialog的实现方式又为何?以下提供简单的范例供各位参考: /* 取得更改日期的Button,添加onClickListener */ Button dButton=(Button)findViewById(R.id.dPicker); dButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { /* onClick时跳出DatePickerDialog */ new DatePickerDialog(EX04_15_1.this, new DatePickerDialog.OnDateSetListener() { public void onDateSet(DatePicker view,int year, int monthOfYear,int dayOfMonth) { /* 这里放更新日期的方法 */ } },mYear,mMonth,mDay).show(); } }); /* 取得更改时间的Button,添加onClickListener */ Button tButton=(Button)findViewById(R.id.tPicker); tButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { /* onClick时跳出TimePickerDialog */ new TimePickerDialog(EX04_15_1.this, new TimePickerDialog.OnTimeSetListener() { public void onTimeSet(TimePicker view,int hourOfDay, int minute) { /* 这里放更新时间的方法 */ } },mHour,mMinute,true).show(); } }); 不论是DatePicker、TimePicker,或DatePickerDialog、TimePickerDialog,都可以实现动态更改日期时间的功能,要用哪一种方式来实现,端视各位的应用程序需要啰!