第3章 UI
在软件开发过程中,界面设计和功能开发同样重要。界面美观的应用程序不仅可以大大增加用户粘性,还能帮我们吸引到更多的新用户。而Android也是给我们提供了大量的UI开发工具,只要合理地使用它们,就可以编写出各种各样漂亮的界面。
在这里,我无法教会你如何提升自己的审美观,但我可以教会你怎样使用Android提供的UI开发工具来编写程序界面。想必你在上一章中反反复复地使用那几个按钮都快要吐了吧,本章我们就来学习更多的UI开发方面的知识。
3.1 该如何编写程序界面
Android中有好几种编写程序界面的方式可供你选择。比如使用DroidDraw,这是一种可视化的界面编辑工具,允许使用拖拽控件的方式来编写布局。Eclipse和Android Studio中也有相应的可视化编辑器,和DroidDraw用法差不多,都是可以直接拖拽控件,并能在视图上修改控件属性的。不过以上的方式我都不推荐你使用,因为使用可视化编辑工具并不利于你去真正了解界面背后的实现原理,通常这种方式制作出的界面都不具有很好的屏幕适配性,而且当需要编写较为复杂的界面时,可视化编辑工具将很难胜任。因此本书中所有的界面我们都将使用最基本的方式去实现,即编写XML代码。等你完全掌握了使用XML来编写界面的方法之后,不管是进行高复杂度的界面实现,还是分析和修改当前现有界面,对你来说都将是手到擒来。听我这么说,你可能会觉得Eclipse中的可视化编辑器完全就是多余的嘛!其实也不是,你还是可以使用它来进行界面预览的,毕竟你无法直接通过XML就看出界面的样子,而每修改一次界面就重新运行一遍程序显然又很耗时,这时你就可以好好地利用Eclipse的可视化编辑器了。讲了这么多理论的东西,也是时候该学习一下到底如何编写程序界面了,我们就从Android中几种常见的控件开始吧。
3.2 常见控件的使用方法
Android给我们提供了大量的UI控件,合理地使用这些控件就可以非常轻松地编写出相当不错的界面,下面我们就挑选几种常用的控件,详细介绍一下它们的使用方法。首先新建一个UIWidgetTest项目,简单起见,我们还是允许ADT自动创建活动,活动名和布局名都使用默认值。别忘了将其他不相关的项目都关闭掉,始终养成这样一个良好的习惯。
3.2.1 TextView
TextView可以说是Android中最简单的一个控件了,你在前面其实也已经和它打过了一些打交道。它主要用于在界面上显示一段文本信息,比如你在第一章看到的Hello world!下面我们就来看一看关于TextView的更多用法。将activity_main.xml中的代码改成如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This is TextView" />
</LinearLayout>
外面的LinearLayout先忽略不看,在TextView中我们使用android:id给当前控件定义了一个唯一标识符,这个属性在上一章中已经讲解过了。然后使用android:layout_width指定了控件的宽度,使用android:layout_height指定了控件的高度。Android中所有的控件都具有这两个属性,可选值有三种match_parent、fill_parent和wrap_content,其中match_parent和fill_parent的意义相同,现在官方更加推荐使用match_parent。match_parent表示让当前控件的大小和父布局的大小一样,也就是由父布局来决定当前控件的大小。wrap_content表示让当前控件的大小能够刚好包含住里面的内容,也就是由控件内容决定当前控件的大小。所以上面的代码就表示让TextView的宽度和父布局一样宽,也就是手机屏幕的宽度,让TextView的高度足够包含住里面的内容就行。当然除了使用上述值,你也可以对控件的宽和高指定一个固定的大小,但是这样做有时会在不同手机屏幕的适配方面出现问题。接下来我们通过android:text指定了TextView中显示的文本内容,现在运行程序,效果如图3.1所示。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="This is TextView" />
</LinearLayout>
我们使用android:gravity来指定文字的对齐方式,可选值有top、bottom、left、right、center等,可以用“|”来同时指定多个值,这里我们指定的"center",效果等同于"center_vertical|center_horizontal",表示文字在垂直和水平方向都居中对齐。现在重新运行程序,效果如图3.2所示。
另外我们还可以对TextView中文字的大小和颜色进行修改,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="24sp"
android:textColor="#00ff00"
android:text="This is TextView" />
</LinearLayout>
通过android:textSize属性可以指定文字的大小,通过android:textColor属性可以指定文字的颜色。重新运行程序,效果如图3.3所示。
3.2.2 Button
Button是程序用于和用户进行交互的一个重要控件,相信你对这个控件已经是非常熟悉了,因为我们在上一章用了太多次Button。它可配置的属性和TextView是差不多的,我们可以在activity_main.xml中这样加入Button:<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
……
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button" />
</LinearLayout>
加入Button之后的界面如图3.4所示。
图 3.4
然后我们可以在MainActivity中为Button的点击事件注册一个监听器,如下所示:
public class MainActivity extends Activity {
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 在此处添加逻辑
}
});
}
}
这样每当点击按钮时,就会执行监听器中的onClick()方法,我们只需要在这个方法中加入待处理的逻辑就行了。如果你不喜欢使用匿名类的方式来注册监听器,也可以使用实现接口的方式来进行注册,代码如下所示:
public class MainActivity extends Activity implements OnClickListener {
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.button);
button.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
// 在此处添加逻辑
break;
default:
break;
}
}
}
这两种写法都可以实现对按钮点击事件的监听,至于使用哪一种就全凭你喜好了。
3.2.3 EditText
EditText是程序用于和用户进行交互的另一个重要控件,它允许用户在控件里输入和编辑内容,并可以在程序中对这些内容进行处理。EditText的应用场景应该算是非常普遍了,发短信、发微博、聊QQ等等,在进行这些操作时,你不得不使用到EditText。那我们来看一看如何在界面上加入EditText吧,修改activity_main.xml中的代码,如下所示:<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
……
<EditText
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
其实看到这里,我估计你已经总结出Android控件的使用规律了,基本上用法都很相似,给控件定义一个id,再指定下控件的宽度和高度,然后再适当加入些控件特有的属性就差不多了。所以使用XML来编写界面其实一点都不难,完全可以不用借助任何可视化工具来实现。现在重新运行一下程序,EditText就已经在界面上显示出来了,并且我们是可以在里面输入内容的,如图3.5所示。
图 3.5
细心的你平时应该会留意到,一些做得比较人性化的软件会在输入框里显示一些提示性的文字,然后一旦用户输入了任何内容,这些提示性的文字就会消失。这种提示功能在Android里是非常容易实现的,我们甚至不需要做任何的逻辑控制,因为系统已经帮我们都处理好了。修改activity_main.xml,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
……
<EditText
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Type something here"
/>
</LinearLayout>
这里使用android:hint属性来指定了一段提示性的文本,然后重新运行程序,效果如图3.6所示。
图 3.6
可以看到,EditText中显示了一段提示性文本,然后当我们输入任何内容时,这段文本就会自动消失。
不过随着输入的内容不断增多,EditText会被不断地拉长。这时由于EditText的高度指定的是wrap_content,因此它总能包含住里面的内容,但是当输入的内容过多时,界面就会变得非常难看。我们可以使用android:maxLines属性来解决这个问题,修改activity_main.xml,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
……
<EditText
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Type something here"
android:maxLines="2"
/>
</LinearLayout>
这里通过android:maxLines指定了EditText的最大行数为两行,这样当输入的内容超过两行时,文本就会向上滚动,而EditText则不会再继续拉伸,如图3.7所示。
图 3.7
我们还可以结合使用EditText与Button来完成一些功能,比如通过点击按钮来获取EditText中输入的内容。修改MainActivity中的代码,如下所示:
public class MainActivity extends Activity implements OnClickListener {
private Button button;
private EditText editText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.button);
editText = (EditText) findViewById(R.id.edit_text);
button.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
String inputText = editText.getText().toString();
Toast.makeText(MainActivity.this, inputText, Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
}
首先通过findViewById()方法得到EditText的实例,然后在按钮的点击事件里调用EditText的getText()方法获取到输入的内容,再调用toString()方法转换成字符串,最后仍然还是老方法,使用Toast将输入的内容显示出来。
重新运行程序,在EditText中输入一段内容,然后点击按钮,效果如图3.8所示。
图 3.8
3.2.4 ImageView
ImageView是用于在界面上展示图片的一个控件,通过它可以让我们的程序界面变得更加丰富多彩。学习这个控件需要提前准备好一些图片,由于目前drawable文件夹下已经有一张ic_launcher.png图片了,那我们就先在界面上展示这张图吧,修改activity_main.xml,如下所示:<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
……
<ImageView
android:id="@+id/image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher"
/>
</LinearLayout>
可以看到,这里使用android:src属性给ImageView指定了一张图片,并且由于图片的宽和高都是未知的,所以将ImageView的宽和高都设定为wrap_content,这样保证了不管图片的尺寸是多少都可以完整地展示出来。重新运行程序,效果如图3.9所示。
图 3.9
我们还可以在程序中通过代码动态地更改ImageView中的图片。这里我准备了另外一张图片,jelly_bean.png,将它复制到res/drawable-hdpi目录下,然后修改MainActivity的代码,如下所示:
public class MainActivity extends Activity implements OnClickListener {
private Button button;
private EditText editText;
private ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.button);
editText = (EditText) findViewById(R.id.edit_text);
imageView = (ImageView) findViewById(R.id.image_view);
button.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
imageView.setImageResource(R.drawable.jelly_bean);
break;
default:
break;
}
}
}
在按钮的点击事件里,通过调用ImageView的setImageResource()方法将显示的图片改成jelly_bean,现在重新运行程序,然后点击一下按钮,就可以看到ImageView中显示的图片改变了,如图3.10所示。
图 3.10
3.2.5 ProgressBar
ProgressBar用于在界面上显示一个进度条,表示我们的程序正在加载一些数据。它的用法也非常简单,修改activity_main.xml中的代码,如下所示:<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
……
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
重新运行程序,会看到屏幕中有一个圆形进度条正在旋转,如图3.11所示。
图 3.11
这时你可能会问,旋转的进度条表明我们的程序正在加载数据,那数据总会有加载完的时候吧,如何才能让进度条在数据加载完成时消失呢?这里我们就需要用到一个新的知识点,Android控件的可见属性。所有的Android控件都具有这个属性,可以通过android:visibility进行指定,可选值有三种,visible、invisible和gone。visible表示控件是可见的,这个值是默认值,不指定android:visibility时,控件都是可见的。invisible表示控件不可见,但是它仍然占据着原来的位置和大小,可以理解成控件变成透明状态了。gone则表示控件不仅不可见,而且不再占用任何屏幕空间。我们还可以通过代码来设置控件的可见性,使用的是setVisibility()方法,可以传入View.VISIBLE、View.INVISIBLE和View.GONE三种值。
接下来我们就来尝试实现,点击一下按钮让进度条消失,再点击一下按钮让进度条出现的这种效果。修改MainActivity中的代码,如下所示:
public class MainActivity extends Activity implements OnClickListener {
private Button button;
private EditText editText;
private ImageView imageView;
private ProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.button);
editText = (EditText) findViewById(R.id.edit_text);
imageView = (ImageView) findViewById(R.id.image_view);
progressBar = (ProgressBar) findViewById(R.id.progress_bar);
button.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
if (progressBar.getVisibility() == View.GONE) {
progressBar.setVisibility(View.VISIBLE);
} else {
progressBar.setVisibility(View.GONE);
}
break;
default:
break;
}
}
}
在按钮的点击事件中,我们通过getVisibility()方法来判断ProgressBar是否可见,如果可见就将ProgressBar隐藏掉,如果不可见就将ProgressBar显示出来。重新运行程序,然后不断地点击按钮,你就会看到进度条会在显示与隐藏之间来回切换。
另外,我们还可以给ProgressBar指定不同的样式,刚刚是圆形进度条,通过style属性可以将它指定成水平进度条,修改activity_main.xml中的代码,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
……
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"
android:max="100"
/>
</LinearLayout>
指定成水平进度条后,我们还可以通过android:max属性给进度条设置一个最大值,然后在代码中动态地更改进度条的进度。修改MainActivity中的代码,如下所示:
public class MainActivity extends Activity implements OnClickListener {
……
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
int progress = progressBar.getProgress();
progress = progress + 10;
progressBar.setProgress(progress);
break;
default:
break;
}
}
}
每点击一次按钮,我们就获取进度条的当前进度,然后在现有的进度上加10作为更新后的进度。重新运行程序,点击数次按钮后,效果如图3.12所示。
图 3.12
ProgressBar还有几种其他的样式,你可以自己去尝试一下。
3.2.6 AlertDialog
AlertDialog可以在当前的界面弹出一个对话框,这个对话框是置顶于所有界面元素之上的,能够屏蔽掉其他控件的交互能力,因此一般AlertDialog都是用于提示一些非常重要的内容或者警告信息。比如为了防止用户误删重要内容,在删除前弹出一个确认对话框。下面我们来学习一下它的用法,修改MainActivity中的代码,如下所示:public class MainActivity extends Activity implements OnClickListener {
……
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
AlertDialog.Builder dialog = new AlertDialog.Builder (MainActivity.this);
dialog.setTitle("This is Dialog");
dialog.setMessage("Something important.");
dialog.setCancelable(false);
dialog.setPositiveButton("OK", new DialogInterface. OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
dialog.setNegativeButton("Cancel", new DialogInterface. OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
dialog.show();
break;
default:
break;
}
}
}
首先通过AlertDialog.Builder创建出一个AlertDialog的实例,然后可以为这个对话框设置标题、内容、可否取消等属性,接下来调用setPositiveButton()方法为对话框设置确定按钮的点击事件,调用setNegativeButton()方法设置取消按钮的点击事件,最后调用show()方法将对话框显示出来。重新运行程序,点击按钮后,效果如图3.13所示。
图 3.13
3.2.7 ProgressDialog
ProgressDialog和AlertDialog有点类似,都可以在界面上弹出一个对话框,都能够屏蔽掉其他控件的交互能力。不同的是,ProgressDialog会在对话框中显示一个进度条,一般是用于表示当前操作比较耗时,让用户耐心地等待。它的用法和AlertDialog也比较相似,修改MainActivity中的代码,如下所示:public class MainActivity extends Activity implements OnClickListener {
……
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
ProgressDialog progressDialog = new ProgressDialog (MainActivity.this);
progressDialog.setTitle("This is ProgressDialog");
progressDialog.setMessage("Loading...");
progressDialog.setCancelable(true);
progressDialog.show();
break;
default:
break;
}
}
}
可以看到,这里也是先构建出一个ProgressDialog对象,然后同样可以设置标题、内容、可否取消等属性,最后也是通过调用show()方法将ProgressDialog显示出来。重新运行程序,点击按钮后,效果如图3.14所示。
注意如果在setCancelable()中传入了false,表示ProgressDialog是不能通过Back键取消掉的,这时你就一定要在代码中做好控制,当数据加载完成后必须要调用ProgressDialog的dismiss()方法来关闭对话框,否则ProgressDialog将会一直存在。
图 3.14
好了,关于Android控件的使用,我要讲的就只有这么多。一节内容就想覆盖Android控件所有的相关知识不太现实,同样一口气就想学会所有Android控件的使用方法也不太现实。本节所讲的内容对于你来说只是起到了一个引导的作用,你还需要在以后的学习和工作中不断地摸索,通过查阅文档以及网上搜索的方式学习更多控件的更多用法。当然,当本书后面有涉及到一些我们前面没学过的控件和相关用法时,我仍然会在相应的章节做详细的讲解。