范例说明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<?xml version="1.0" encoding="utf-8"?>
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_focused="true"
android:state_pressed="false"
android:drawable="@drawable/btnfocused" />
<item
android:state_focused="true"
android:state_pressed="true"
android:drawable="@drawable/btnfocusedpressed" />
<item
android:state_focused="false"
android:state_pressed="true"
android:drawable="@drawable/btnpressed" />
<item android:drawable="@drawable/btndefault" />
</selector>
然后,在main.xml中将advancedbutton赋值给Button组件中background的属性。
layout/main.xml<Button
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),将背景图放在前景图的下方,这是最简单的堆栈顺序方法。
<?xml version="1.0" encoding="utf-8"?>
<AbsoluteLayout
android:id="@+id/widget34"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<!--创建第一个ImageView (第二层图片)-->
<ImageView
android:id="@+id/myImageView1"
android:layout_width="320px"
android:layout_height="280px"
android:layout_x="0px"
android:layout_y="36px"
/>
<!--创建第二个ImageView (第一层图片)-->
<ImageView
android:id="@+id/myImageView2"
android:layout_width="104px"
android:layout_height="157px"
android:layout_x="101px"
android:layout_y="119px"
/>
<!--创建第一个Button -->
<Button
android:id="@+id/myButton1"
android:layout_width="105px"
android:layout_height="66px"
android:text="pic1"
android:layout_x="9px"
android:layout_y="356px"
/>
<!--创建第二个Button -->
<Button
android:id="@+id/myButton2"
android:layout_width="105px"
android:layout_height="66px"
android:text="pic2"
android:layout_x="179px"
android:layout_y="356px"
/>
</AbsoluteLayout>
延伸学习学会ImageView之后,在延伸学习里,便可试着将两个ImageButton Widget堆栈在一起,如此一来,不但有背景图,还有按钮事件。
<ImageButton
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的 <select>,由于手机画面有限,要在有限的范围选择项目,下拉菜单是唯一、也是较好的选择。
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<String> 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<String>(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。
<?xml version="1.0" encoding="utf-8"?>
<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,以下的自定义动画将使用当中的两种。
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="0"
android:toXDelta="-100%p"
android:duration="300"
>
</translate>
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:duration="300">
</alpha>
</set>
扩展学习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<String> adapter;
private List<String> 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<String>();
for (int i = 0; i < countriesStr.length; i++)
{
allCountries.add(countriesStr);
}
/* new ArrayAdapter对象并将allCountries传入 */
adapter = new ArrayAdapter<String>(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,内容如下:
<?xml version="1.0" encoding="utf-8"?>
<TextView
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:
<?xml version="1.0" encoding="utf-8"?>
<TextView
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中的<declare-styleable>定义
* 的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的背景图。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Gallery">
<attr name="android:galleryItemBackground" />
</declare-styleable>
</resources>
扩展学习在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,并在其中定义 <declare-styleable> 标签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<String> adapter = new ArrayAdapter<String>(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<String> adapter = new ArrayAdapter<String>(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,都可以实现动态更改日期时间的功能,要用哪一种方式来实现,端视各位的应用程序需要啰!