购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.4 基本UI组件

本小节主要介绍的是Android提供的UI组件中最基本的组件。这些组件大部分是可以通过给定的属性以及方法直接使用的,是构成Android界面的基础,了解这些基本组件的使用,对于其它高级组件的理解很有帮助。

3.4.1 文本框(TextView)

3.4.1.1 文本框的基本使用

之前讲解Android布局的时候,就已经说明,所有布局都是View的子类或者间接子类。而TextView是View的直接子类,位于"android.widget.TextView"包下。它是一个文本显示组件,提供了基本的显示文本的功能,并且是大部分UI组件的直接或间接父类。

TextView主要就是一个显示文本的组件,Android为了控制它,提供了一些XML属性与方法供我们使用:

XML属性 相关方法 说明
android:autoLink setAutoLinkMask(int) 文本转换成可点击的超链接
android:ellipsize 设定文本超出组件宽度的处理模式
android:height setHeight(int) 设定组件的高度
android:width setWidth(int) 设定组件的宽度
android:text setText(CharSequence) 设定组件内文本的内容
android:textColor setTextColor
(ColorStateList)
设定组件内文本的颜色
android:textSize setTextSize(float) 设定组件内文本的大小
android:textStyle setTypeface(Typeface) 设定组件内文本的字体风格
android:typeface setTypeface(Typeface) 设定组件内文本的字体

其中最重要的是android:text属性,从它的对应方法可以看出,它传递的是一个CharSequenece类型的对象。CharSequence为接口类型,读者可能对其有点陌生,但是它的子类肯定会让读者有熟悉的感觉,String、StringBuffer、StringBuilder、SpannableString、SpannableStringBuilder都是其子类,它包括了字符串的所有类,因为面向对象的多态性,在这里把它理解成字符串类的抽象即可。

android:ellipsize属性,用来处理文本内容超过UI组件宽度的情况。它有几个选项,被枚举类型TextUtils.TruncateAt所定义,其中选项包括:

· END:在文本结尾处进行省略。

· MARQUEE:在文本结尾处以淡出的效果省略。

· MIDDLE:在文本的中间部分进行省略。

· START:在文本的头部进行省略。

· none:不做任何处理。

android:autoLink属性可以在显示文本中设定内容符合条件的标注为超链接形式,如URL地址、邮箱地址、电话号码等,通过点击可以触发相应的服务,它有如下几个常用属性:

· web:将文本内容中URL地址转换为超链接。

· email:将文本内容中Email地址转换为超链接。

· phone:将文本中的电话号码转换为超链接。

· all:把文本中符合转换条件的内容均转换成对应的超链接。

示例:通过一个简单的实例演示TextView的各种效果。

代码清单:\codes\03\3.4\TextViewBaseDemo\res\layout\activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:padding="20sp"
        android:text="红色,30dp,粗体,字体monospace"
        android:textColor="#ff0000"
        android:textSize="15dp"
        android:textStyle="bold"
        android:typeface="monospace" />
    <!-- 文本超出部分末尾省略 -->
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:ellipsize="end"
        android:padding="20sp"
        android:singleLine="true"
        android:text="对于android:ellipsize属性,它用来设定文本内容超过UI组件的宽度的时候,如何处理" />

    <!-- 响应连接,启动浏览器访问 -->
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:autoLink="web"
        android:padding="20sp"
        android:text="http://www.baidu.com" />

</LinearLayout>

在模拟器上运行效果:

3.4.1.2 使用HTML标签定义文本效果

TextView还预定义了一些类似于HTML的标签,通过这些标签可以使TextView显示不同颜色、大小、字体、图片、连接等效果。这些HTML标签都需要android.text.Html类的支持,但是并不包括所有的HTML标签。

常用的可以在TextView中设定的标签有:

· <font>:设置颜色和字体。

· <big>:设置字体大号

· <small>:设置字体小号

· <i>\<b>:斜体\粗体

· <a>:连接网址

· <img>:图片

使用这些标签可以用Html.fromHtml()方法将这些标签的字符串转换成CharSequence接口,然后在TextView.setText()中进行设置。如果需要对设置的HTML标签进行响应,需要设置TextView.setMovementMethod(LinkMovementMethod.getInstance())。

示例:使用HTML标签定义TextView的显示内容

代码清单:\03\3.4\TextViewHtmlDemo\src\com\bookdemo\textviewhtmldemo\ MainActivity.java

public class MainActivity extends Activity {
    private TextView textView1,textView2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 通过Id获得两个TextView控件
        textView1 = (TextView) findViewById(R.id.textView1);
        textView2 = (TextView) findViewById(R.id.textView2);

        // 设置需要显示的字符串
        String html = "<font color ='red'>Hello android</font><br/>";
        html += "<font color='#0000ff'><big><i>Hello android</i></big></font><p>";
        html += "<big><a href='http://www.baidu.com'>百度</a></big>";
        // 使用Html.fromHtml,把含HTML标签的字符串转换成可显示的文本样式
        CharSequence charSequence = Html.fromHtml(html);
        // 通过setText给TextView赋值
        textView1.setText(charSequence);
        // 设定一个点击的响应
        textView1.setMovementMethod(LinkMovementMethod.getInstance());

        String text = "我的URL:http://www.cnblogs.com/plokmju/\n";
        text += "我的email:plokmju@sina.com\n";
        text += "我的电话:+86 010-12345678";
        // 因为textView2中有autoLink="all"的属性设定,所以会自动识别对应的连接,点击出发对应的Android程序
        textView2.setText(text);
    }
}

在模拟器上运行效果:

3.4.1.3 在文本框中添加图片

TextView不光可以显示文本内容,还可以用于显示图片。TextView显示图片使用的是HTML.fromHtml()的另外一个静态的重载方法,可以设定<img>标签中的图像资源。

HTML.fromHtml()方法的完整签名如下:

static Spanned fromHtml(String source,Html.ImageGetter imageGetter,Html.TagHandler tagHandler)

对于这个方法的参数而言,Html.ImageGetter是一个接口,其中定义了一个需要实现的抽象方法 getDrawable(String),用于解析source中遇到的<img>标签。Html.TagHandler也是一个接口,其中定义了一个需要实现的抽象方法handTag(),用于解析source中遇到的无法识别的标签,一般不会用上,传null值即可。

示例:使用TextView显示图片。

代码清单:\codes\03\3.4\TextViewImageDemo\src\com\bookdemo\textviewimagedemo\ MainActivity.java

public class MainActivity extends Activity {
    private TextView textViewImg;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textViewImg = (TextView) findViewById(R.id.textImg);
        textViewImg.setTextColor(color.white);
        textViewImg.setBackgroundColor(color.black);
        textViewImg.setTextSize(20);
        // 设定HTML标签样式,图片3为一个超链接标签a
        String html = "图像1<img src='image1'/>图像2<img src='image2'/>";
        html += "图像3<a href='http://www,baidu.com'><img src='image3'/></a>";
        // fromHtml中ImageGetter选择html中<img>的图片资源
        CharSequence cs = Html.fromHtml(html, new ImageGetter() {

            public Drawable getDrawable(String source) {
                // source为html字符串中定义的<img>中的src的内容
                // 返回值Drawable就是对应的<img>显示的图片资源
                Drawable draw = null;
                if (source.equals("image1")) {
                    draw = getResources().getDrawable(R.drawable.image1);
                    draw.setBounds(0, 0, draw.getIntrinsicWidth(),
                            draw.getIntrinsicHeight());
                } else if (source.equals("image2")) {
                    // 设定image2尺寸等比缩小
                    draw = getResources().getDrawable(R.drawable.image2);
                    draw.setBounds(0, 0, draw.getIntrinsicWidth() / 2,
                        draw.getIntrinsicHeight() / 2);
                } else {
                    // 使用反射会更简便,无需知道src与资源Id的对应关系
                    draw = getResources().getDrawable(getResourceId(source));
                    draw.setBounds(0, 0, draw.getIntrinsicWidth()/ 3,
                        draw.getIntrinsicHeight()/ 3);
                }
                return draw;
            }
        }, null);
        textViewImg.setText(cs);
        textViewImg.setMovementMethod(LinkMovementMethod.getInstance());
    }

    public int getResourceId(String source) {
        try {
            // 使用反射机制,通过属性名称,得到其内的值
            Field field = R.drawable.class.getField(source);
            return Integer.parseInt(field.get(null).toString());
        } catch (Exception e) {
            // TODO: handle exception
        }
        return 0;
    }
}

在模拟器上运行效果:

3.4.1.4 为文本内容增加点击事件

TextView不光可以用来显示文本,还可以用来触发点击事件,当然也需要借助于HTML标签来定义。从上面对于Html.fromHtml()方法的介绍可以看出,它具有一个返回值Spanned,它是一个接口类型,用于对文本的某些返回进行标记,我们一般使用其间接子类SpannableString即可。使用SpannableString的setSpan()方法设定一段文本块需要响应点击的事件。与之类似的还有SpannableBuilder类,它们的关系类似于String与StringBuilder。

下面是SpannableString.setSpan()方法的完整签名:

void setSpan(Object what,int start,int end,int flags)

对于setSpan()方法的参数,what参数中传递一个抽象类ClickableSpan,需要实现其onClick()方法,此为指定文本的点击相应事件。start和end分别指定需要响应onClick()方法的文本开始与结束。flags设定一个标识,确定使用什么方式选择文本块,一般使用Spanned接口下的SPAN_EXCLUSIVE_EXCLUSIVE对其进行赋值,表示遵循设定的开始与结束位置的文本块。

示例:响应其中TextView的点击事件。

代码清单:\codes\03\3.4\TextViewClickDemo\res\layout\activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<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/clickTextView1"
        android:textSize="15dp"
        android:layout_marginTop="30dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:textSize="15dp"
        android:layout_marginTop="30dp"
        android:id="@+id/clickTextView2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

代码清单:\codes\03\3.4\TextViewClickDemo\src\com\bookdemo\textviewclickdemo\ MainActivity.java

public class MainActivity extends Activity {
    private TextView clickTextView1, clickTextView2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        clickTextView1 = (TextView) this.findViewById(R.id.clickTextView1);
        clickTextView2 = (TextView) this.findViewById(R.id.clickTextView2);
        // 使用SpannableString包装字符串
        SpannableString spannableString1 = new SpannableString(
"响应其中TextView的点击事件");
        SpannableString spannableString2 = new SpannableString(
"响应其中TextView的click事件");
        // 通过setSpan设定文本块响应的点击事件
        spannableString1.setSpan(new ClickableSpan() {
            @Override
            public void onClick(View widget) {
                // 显示消息
                Toast.makeText(MainActivity.this, "'TextView'\n被点击了",
                    Toast.LENGTH_SHORT).show();
            }
        }, 4, 12, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

        spannableString2.setSpan(new ClickableSpan() {
            @Override
            public void onClick(View widget) {
                Toast.makeText(MainActivity.this, "'click'\n被点击了",
                    Toast.LENGTH_SHORT).show();
            }
        }, 13, 18, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

        // 对TextView文本进行赋值
        clickTextView1.setText(spannableString1);
        // 设置点击响应
        clickTextView1.setMovementMethod(LinkMovementMethod.getInstance());
        clickTextView2.setText(spannableString2);
        clickTextView2.setMovementMethod(LinkMovementMethod.getInstance());
    }
}

在模拟器上运行效果:

3.4.2 编辑框(EditText)

3.4.2.1 编辑框的基本使用

EditText是一个编辑框控件,提供了文本输入的功能,而且直接继承自TextView,可以理解为可编辑的TextView。因为继承的关系,在EditText中可以使用很多TextView的方法与属性。

很多平台上都会用到EditText,它最大的用处就是供用户输入一些信息,所以主要有两个继承自TextView的方法用于编辑内容:

· setText():设置当前组件中显示的内容。

· getText():获取当前组件中显示的内容。

示例:简单使用EditText。

代码清单:\codes\03\3.4\EditTextBaseDemo\res\layout\ activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <EditText
        android:id="@+id/ev_edit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/btn_gettext"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="获取文本框的值" >

    </Button>

    <Button
        android:id="@+id/btn_settext"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="设置文本框的值" >
    </Button>
</LinearLayout>

代码清单:\codes\03\3.4\EditTextBaseDemo\src\com\bookdemo\edittextbasedemo\ MainActivity.java

public class MainActivity extends Activity {
    private EditText et_edit;
    private Button btn_gettext, btn_settext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        et_edit = (EditText) findViewById(R.id.ev_edit);
        btn_gettext = (Button) findViewById(R.id.btn_gettext);
        btn_settext = (Button) findViewById(R.id.btn_settext);

        btn_gettext.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                // 获取EditText输入的值
                String text = et_edit.getText().toString();
                if(!TextUtils.isEmpty(text)){
                    Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT)
                    .show();
                }else{
                    Toast.makeText(MainActivity.this, "请在文本框中输入值", Toast.LENGTH_SHORT)
                    .show();
                }
            }
        });
        btn_settext.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                et_edit.setText("Hello Android!");
            }
        });
    }
}

模拟器上运行效果:

3.4.2.2 输入控制

Android提供了一些XML属性,用于对EditText中输入的内容进行验证。这些属性也是从TextView中继承而来,但是TextView只是用于显示,无需验证其内容的正确性,所以单独放在EditText中讲解。

与内容验证相关的属性如下:

这里介绍的几个属性的选值,如果需要多选,可以使用"|"符号进行分割。可以使用EditText.setError()方法对输入内容中的错误信息进行提示,如果设定了错误提示信息,会在EditText旁边以感叹号的形式显示。

示例:限制输入内容并且对其中一个进行验证。

代码清单:\codes\03\3.4\EditTextVerifyDemo\res\layout\ activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="使用Android:digits属性(仅输入数字与xyz)" />

    <EditText
        android:id="@+id/etNum"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:digits="123456789xyz" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="使用Android:inputtype属性(输入密码)" />

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:inputType="textPassword" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="使用Android:inputtype属性(仅输入带符号的数字)" />

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
    vandroid:numeric="decimal|signed" />

    <Button
    vandroid:id="@+id/btnValidation"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="验证第一个输入框是否为xyz" />

</LinearLayout>

代码清单:\codes\03\3.4\EditTextVerifyDemo\src\com\bookdemo\edittextverifydemo\ MainActivity.java

public class MainActivity extends Activity {
    private Button btnValidation;
    private EditText etNum;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnValidation = (Button) findViewById(R.id.btnValidation);
        etNum = (EditText) findViewById(R.id.etNum);

        btnValidation.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                // 获取文本框的值
                String num = etNum.getText().toString().trim();
                // 验证是否是xyz
                if (!num.equals("xyz")) {
                }
            }
        });
    }
}

在模拟器上运行效果:

3.4.2.3 输入图片

EditText不仅可以输入文本,还可以插入图片。类似于手机QQ或者微信插入表情的功能。

在Android中,使用图片资源会用到Bitmap类,此类代表一个位图资源,是一个final类,除了使用它的构造方法创建一个可编辑的Bitmap对象之外,大多数情况下如果直接使用位图资源,会使用BitmapFactory类的一些静态方法decodeXxx()转化获得,此静态方法有多种重载方法,可以适应不同的资源来源。

示例:在EditText中插入图片。

在这个实例中,需要用到一些位图的资源,随机找十张位图资源,资源的文件名必须小写,放在/res/drawable/目录下,既然用到了位图资源,这里简单介绍一下。在/res/drawable/目录下的位图文件,会在R.java中自动手录与资源对应的ID,对于在清R.java中的资源,可以通过R类直接访问,但是访问到的是一个int类型的资源ID,如果需要访问详细内容,需要使用getResource()方法访问到所有的资源,在其中有特定资源的访问方法。关于Android资源的内容,会在后面的章节中慢慢讲解。

实例的布局就是一个LinearLayout布局中包裹了一个EditText和Button,这里就不再给出布局代码了。

代码清单:\codes\03\3.4\EditTextInsertImageDemo\src\com\bookdemo\edittextinsertimagedemo\ MainActivity.java

public class MainActivity extends Activity {
    private Button btn_InImg;
    private EditText et_Image;
    // 获取Drawable资源的Id数组
    private final int[] DRAW_IMG_ID = { R.drawable.image0, R.drawable.image1,
              R.drawable.image2, R.drawable.image3, R.drawable.image4,
              R.drawable.image5, R.drawable.image6, R.drawable.image7,
              R.drawable.image8, R.drawable.image9 };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btn_InImg = (Button) findViewById(R.id.btn_InImg);
        et_Image = (EditText) findViewById(R.id.et_Image);

        btn_InImg.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 参数一个0-9的随机数
                int random = new Random().nextInt(9);
                // 通过bitmapFactory获得位图资源
                Bitmap bit = BitmapFactory.decodeResource(getResources(),
DRAW_IMG_ID[random]);
                // 一个ImageSpan,用于插入的存放待插入的图片
                ImageSpan imageSpan = new ImageSpan(MainActivity.this, bit);
                SpannableString spannableString = new SpannableString("img");
                // 把img文本块替换成Image图片
spannableString.setSpan(imageSpan, 0, 3,
                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                // 把图片添加到文本的末尾
                et_Image.append(spannableString);
            }
        });
    }
}

在模拟器上运行效果:

3.4.3 按钮(Button)

3.4.3.1 按钮的基本使用

Button是一个按钮,直接继承自TextView。它提供一个按钮的功能,当用户对按钮进行点击操作的时候,触发onClick事件。一般而言,响应这个onClick事件,做必要的逻辑处理即可。

对于一个Button而言,用的最多的就是点击事件,Button间接继承自View,而Android UI中大部分事件监听器都定义在View中,这里讲解点击事件监听器,其它事件的使用方式与此类似,只是触发的时间不同而已。

点击事件需要实现View.OnClickListener这个事件监听器接口,实现其中的onClick(View v)方法,其中参数v为当前触发事件的组件。

Button在一般情况下,只是单纯的显示一个文本的按钮,如果觉得单调了,可以通过android:background属性为其设置背景图,这个属性可以设置一个Drawable资源或一个RGB的颜色值。注意,这里设置的是背景图,所以是不会遮挡android:text属性值的,而且如果为图片设置了背景图,则这里的背景图就被固定了,不会因为用户的点击而有任何点击效果。

示例:Button的基本使用。

代码清单:\codes\03\3.4\ButtonBaseDemo\res\layout\activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<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/btn_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="按钮" />

    <Button
        android:id="@+id/btn_img"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/imagebtn" />

    <Button
        android:id="@+id/btn_imgtext"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/imagebtn"
        android:text="按钮" />

</LinearLayout>

代码清单:\codes\03\3.4\ButtonBaseDemo\src\com\bookdemo\buttonbasedemo\ MainActivity.java

public class MainActivity extends Activity {
    private Button btn_text, btn_img, btn_imgtext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btn_text = (Button) findViewById(R.id.btn_text);
        btn_img = (Button) findViewById(R.id.btn_img);
        btn_imgtext = (Button) findViewById(R.id.btn_imgtext);

        btn_text.setOnClickListener(click);
        btn_img.setOnClickListener(click);
        btn_imgtext.setOnClickListener(click);
    }

    private View.OnClickListener click = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            Toast.makeText(MainActivity.this, "按钮被点击了", Toast.LENGTH_SHORT)
                .show();
        }
    };
}

在模拟器上运行效果图:

3.4.3.2 图片按钮(ImageButton)

ImageButton是Android为图片按钮提供的专用的类,虽然它也是一个按钮,使用方法也和Button类似,但是ImageButton并不继承于Button,ImageButton继承于ImageView。

为ImageButton设置显示的图片,需要用到android:src属性,其中接受一个Drawable资源。因为ImageButton继承自ImageView,所以android:text这样的TextView下的属性,对它是没有效果的。

示例:图片按钮的简单使用。

代码清单:\codes\03\3.4\ImageButtonDemo\res\layout\ activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <ImageButton
        android:id="@+id/ibtn_click"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher"
        android:text="为ImageButton设置text属性无效" />

</LinearLayout>

模拟器运行效果:

3.4.3.3 图文混排的按钮

在实际项目中,经常会需要设置按钮展示为图文混排的效果,这样可以通过图表更直观的把按钮的功能展示给用户。虽然ImageButton也可以实现图片按钮的效果,但是对于ImageButton而言,设置Text属性是没有作用的,所以这里不讲解ImageButton的使用。对于Button控件,图文混排需要用到一个android:drawableXxx属性,这个属性配合android:text,就可以实现图文混排的效果。

对于android:drawableXxx几个属性的介绍:

· android:drawableTop:在按钮文本的上面绘图。

· android:drawableBottom:在按钮文本的下面绘图。

· android:drawableLeft:在按钮文本的左侧绘图。

· android:drawableRight:在按钮文本的右侧绘图。

· android:drawableStart:在按钮文本的开始地方绘图。

· android:drawableEnd:在按钮文本的结束地方绘图。

· android:drawablePadding:设置按钮文本与绘制图像的距离。

示例:使用Button实现图文混排效果。

代码清单:\codes\03\3.4\ButtonImageTextDemo\res\layout\ 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" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:orientation="horizontal" >
        <!-- 图片在上,项目中常用这样的设置 -->
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:drawableTop="@drawable/ic_launcher"
            android:text="b1" />
        <!-- 图片在下 -->
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:drawableBottom="@drawable/ic_launcher"
            android:drawablePadding="10dp"
            android:text="b2" />
        <!-- 图片在左 -->
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:drawableLeft="@drawable/ic_launcher"
            android:text="b3" />
        <!-- 图片在右 -->
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:drawableRight="@drawable/ic_launcher"
            android:text="b4" />
        </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <!-- 图片在开始 -->
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:drawableStart="@drawable/ic_launcher"
            android:text="b5" />
        <!-- 图片在结束 -->
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:drawableEnd="@drawable/ic_launcher"
            android:text="b6" />
    </LinearLayout>
    <!-- 声明一个空的按钮,用于进行代码设置 -->
    <Button
        android:id="@+id/btn_Sty"
        android:layout_width="200dp"
        android:layout_height="80dp" />

</LinearLayout>

代码清单:\codes\03\3.4\ButtonImageTextDemo\src\com\bookdemo\buttonimagetextdemo\ MainActivity.java

public class MainActivity extends Activity {
    private Button btn_Sty;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 获取按钮控件
        btn_Sty = (Button) findViewById(R.id.btn_Sty);
        // 生成SpannableString,用于图片的载体
        SpannableString spannebleLeft = new SpannableString("left");
        // 得到图片资源的Bitmap对象
        Bitmap bitmapleft = BitmapFactory.decodeResource(getResources(),
            R.drawable.image1);
        ImageSpan imageSpanLeft = new ImageSpan(MainActivity.this, bitmapleft);
        // 设置使用图片替换文本块
        spannebleLeft.setSpan(imageSpanLeft, 0, 4,
            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

        SpannableString spannebleRight = new SpannableString("right");
        Bitmap bitmapRight = BitmapFactory.decodeResource(getResources(),
            R.drawable.image2);
        ImageSpan imageSpanRight = new ImageSpan(MainActivity.this, bitmapRight);
        spannebleRight.setSpan(imageSpanRight, 0, 5,
S            panned.SPAN_EXCLUSIVE_EXCLUSIVE);

        // 把生成的SpannableString追加到按钮上
        btn_Sty.append(spannebleLeft);
        btn_Sty.append("aLi");
        btn_Sty.append(spannebleRight);
    }
}

在模拟器上运行效果:

上面的实例,分别展示了android:drawableXxx属性的使用,并且生成一个通过Java代码动态生成图文混排按钮。因为Button是继承自TextView的,所以通过代码设置图文混排的方式与TextView类似,都需要用到SpannableString类,关于SpannableString的使用,在TextView小节中已经讲解过了。

3.4.3.4 使用Nine-Patch图片作为组件背景图

在Android中,对于背景图片,如果图片的尺寸与设置背景的组件尺寸不符合,会自动对图片进行缩放,以保证背景图片能覆盖整个按钮。但是这种缩放整张图片的效果可能并不太好。大多数情况下,我们只想缩放图片中的某一部分,而使图片的重要位置保持原图的效果。

为了实现只缩放图片某个部分的效果,可以使用Nine-Patch图片来实现。Nine-Patch图片是一种特殊的PNG图片,这种图片以9.png结尾,它会按照规则在原图的四周各添加一个宽度为1像素的线条,这四个线条决定了图片的缩放规则,这四个线条包裹的矩形中间的区域,不会被缩放拉伸,只会拉伸矩形之外的区域。

Android为制作Nine-Patch图片提供了draw9patch工具,该工具位于Android SDK安装路径的tool目录下,双击draw9patch.bat文件即可运行draw9patch工具,通过该工具的主菜单"File→Open 9-Patch"菜单选项,打开一个PNG图片,然后通过该工具来定义图片的缩放区域。如下图所示:

选择了缩放区域之后,右边就显示出缩放之后的效果,这个效果也在Android中,为组件指定背景之后的效果。

3.4.4 单选、复选按钮(RadioButton、CheckBox)

3.4.4.1 CompoundButton

RadioButton(单选按钮)、CheckBox(复选按钮)都继承自android.widget.CompoundButton,

而CompoundButton又继承自Button类,所以它带有Button的一些属性及方法。CompoundButton是一个抽象类,在其中封装了一个Checked属性,用于判断是否被选中,这是也它与Button不同的地方,对其进行了扩展。

CompoundButton提供了一个android:checked属性,用于在初始化的时候指定组件是否被选中。围绕Checked属性提供了一些方法,常用的方法有:

· boolean isChecked():判断组件当前是否选中。

· void setChecked(boolean checked):设定组件的选中状态。

· void toggle():反转当前组件的选中状态。

· void setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener listener):设定属性状态变换的监听器。

CompoundButton内部定义了一个事件监听器CompoundButton.OnCheckedChangeListener,用于监听组件状态的变化,使用它的时候需要实现其内部方法onCheckedChanged(),下面是这个方法的完整签名:

abstract void onCheckedChange(CompoundButton buttonView,Boolean isChecked)

其中,buttonView为当前触发事件的选择组件,isChecked为当前组件的选中状态。

CompoundButton是一个抽象类,它之下有四个子类:CheckBox、RadioButton、Switch、ToggleButton,后文会对这四个组件一一介绍。

3.4.4.2 单选按钮(RadioButton)

RadioButton是一个单选按钮,一般配合RadioGroup一起使用,可以把RadioGroup理解成一个承装RadioButton的容器,在同一RadioGroup内,所有的RadioButton的选中状态是互斥的,它们有且只有一个RadioButton可以被选中,但是在不同的RadioGroup中的RadioButton将不会互相影响。

示例:单选按钮的简单使用。

代码清单:\codes\03\3.4\RadioButtonDemo\res\layout\ activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Gender:" />
    <!-- 定义一个RadioGroup用于包装RadioButton -->
    <RadioGroup
        android:id="@+id/rg_gender"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" >

        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="male" />

        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="female" />
    </RadioGroup>

    <Button
        android:id="@+id/btn_Gender"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="选择性别" />
</LinearLayout>

代码清单:\codes\03\3.4\RadioButtonDemo\src\com\bookdemo\radiobuttondemo\ MainActivity.java

public class MainActivity extends Activity {
    private RadioGroup rg_gender;
    private Button btn_Gender;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        rg_gender = (RadioGroup) findViewById(R.id.rg_gender);
        btn_Gender = (Button) findViewById(R.id.btn_Gender);
        btn_Gender.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 获取单选按钮的选项个数
                int len = rg_gender.getChildCount();
                String msgString = "";
                for (int i = 0; i < len; i++) {
                    // RadioGroup中包含的子View就是一个RadioButton
                    RadioButton radiobutton = (RadioButton) rg_gender.getChildAt(i);
                    if (radiobutton.isChecked()) {
                        // 如果被选中,则break循环,并且记录选中信息
                        msgString = "您选择的性别是: "
                            + radiobutton.getText().toString();
                        break;
                    }
                }
                if (msgString.equals("")) {
                    Toast.makeText(MainActivity.this,
                        "请先选择性别!", Toast.LENGTH_SHORT)
                        .show();
                } else {
                    Toast.makeText(MainActivity.this, msgString,
                        Toast.LENGTH_SHORT).show();
                }
            }
        });
    }
}

在模拟器上运行效果:

3.4.4.3 复选按钮(CheckBox)

CheckBox是一个复选框,它的用法与RadioButton类似,但是与之不同的是,它可以被多选,所以无需用一个容器包裹起来。

在实际项目中,无论是RadioButton还是CheckBox的选项,都很少在XML布局中固定条数,一般而言都是通过Java代码根据数据动态生成的。下面通过讲解如何动态生成CheckBox选项,说明在Android下动态增加组件的两种常用方式。

这两种方式都需要先得到外围布局组件的对象,一般使用findViewById(int)方法通过布局组件的id找到即可,再动态生成一个待添加的组件并为其设定各项属性,最后把这个生成的组件,通过View.addView(View)方法动态添加到布局组件中。这两种方式的区别在于动态生成组件的过程,这两种方式分别是:

· 通过UI组件的构造方法,实例化一个UI组件对象。

· 通过XML布局文件定义一个UI组件,再在Activity中使用动态填充的方式:getLayoutInflater().inflate(int,VIewGroup)的方式获取到XML布局文件中定义的UI组件。

示例:动态生成CheckBox选项,并得到选中的结果。

清单文件:\codes\03\3.4\CheckBoxDemo\res\layout\ activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/btn_new"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="构造函数动态生成CheckBox选项" />

    <Button
        android:id="@+id/btn_inflate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="inflate加载XML生成CheckBox选项" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="选择喜欢的颜色:" />

    <LinearLayout
        android:id="@+id/ll_checkgroup"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical" >
    </LinearLayout>

    <Button
        android:id="@+id/btn_check"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="确定" />

</LinearLayout>

代码清单:\codes\03\3.4\CheckBoxDemo\res\layout\ checkboxtemp.xml

<?xml version="1.0" encoding="utf-8"?>
<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
</CheckBox>

代码清单:\codes\03\3.4\CheckBoxDemo\src\com\bookdemo\checkboxdemo\ MainActivity.java

public class MainActivity extends Activity {
    private Button btn_new, btn_inflate, btn_check;
    private LinearLayout ll_checkgroup;
    private String[] checkboxCN = new String[] { "红色", "黄色", "蓝色", "绿色" };
    private String[] checkboxEN = new String[] { "red", "yellow", "blue",
        "    green" };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btn_new = (Button) findViewById(R.id.btn_new);
        btn_inflate = (Button) findViewById(R.id.btn_inflate);
        btn_check = (Button) findViewById(R.id.btn_check);
        ll_checkgroup = (LinearLayout) findViewById(R.id.ll_checkgroup);

        btn_new.setOnClickListener(click);
        btn_inflate.setOnClickListener(click);
        btn_check.setOnClickListener(click);
    }

    private View.OnClickListener click = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            switch (v.getId()) {
            case R.id.btn_new:
                // 在添加前确保布局为空
            if (ll_checkgroup.getChildCount() > 0) {
                ll_checkgroup.removeAllViews();
            }
            for (String cn : checkboxCN) {
                // 实例化一个Ch    eckBox
                CheckBox checkbox = new CheckBox(MainActivity.this);
                checkbox.setText(cn);
                // 把实例化的CheckBox对象加到布局中
                ll_checkgroup.addView(checkbox);
            }
            break;
        case R.id.btn_inflate:
            if (ll_checkgroup.getChildCount() > 0) {
                ll_checkgroup.removeAllViews();
            }
            for (String en : checkboxEN) {
                // 加载XML资源,得到一个CheckBox对象
                CheckBox checkbox = (CheckBox) getLayoutInflater().inflate(
                    R.layout.checkboxtemp, null);
                checkbox.setText(en);
                ll_checkgroup.addView(checkbox);
            }
            break;
        case R.id.btn_check:
            String msg = "";
            if (ll_checkgroup.getChildCount() > 0) {
                for (int i = 0; i < ll_checkgroup.getChildCount(); i++) {
                    View view = ll_checkgroup.getChildAt(i);
                    // 判断子View是否是ChechBox
                    if (view instanceof CheckBox) {
                        CheckBox box = (CheckBox) view;
                        // 当前CheckBox是否被选中
                        if (box.isChecked()) {
                            msg += box.getText() + "\n";
                        }
                    }
                }
                if (TextUtils.isEmpty(msg)) {
                    Toast.makeText(MainActivity.this, "请选择您喜欢的颜色",
                        Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(MainActivity.this, "您喜欢的颜色有:\n" + msg,
                        Toast.LENGTH_SHORT).show();
                }
            } else {
                    Toast.makeText(MainActivity.this, "请生成颜色选择列表",
                        Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
            }
        }
    };
}

  

在模拟器上运行效果:

3.4.5 开关按钮(ToggleButton、Switch)

CompoundButton这个抽象类还有两个子类,ToggleButton和Switch,这两个都是开关按钮。与RadioButton和CheckBox不同的是,它们并不需要成组存在,只有一个单独组件,其功能与实际生活中的开关一样,两种状态在点击的时候相互切换。这两个组件的功能实现非常类似,只是外观不同,下面就这两个UI组件进行详细的介绍。

3.4.5.1 Toggle开关(ToggleButton)

ToggleButton,开关组件,在其父类中定义了一个Checked属性,设定两种状态:开与关。ToggleButton通常用于切换程序中两种不同的状态。

ToggleButton除了继承自CompoundButton的android:checked属性和与之对应的方法之外,还额外增加了两个特殊的属性,用于设定开与关两种状态显示的文本内容:

· android:textOff:ToggleButton被设置为关的状态时,显示的文本内容。

· android:textOn:ToggleButton被设置为开的状态时,显示的文本内容。

Android提供了如下几个方法用于操作开与关状态下显示的文本内容:

· CharSequence getTextOff():得到当前ToggleButton设置为关的状态时,显示的文本。

· CharSequence getTextOn():得到当前ToggleButton设置为开的状态时,显示的文本。

· void setTextOff(CharSequence textOff):设置当前ToggleButton设置为关的状态时,显示的文本内容。

· void setTextOn(CharSequence textOn):设置当前ToggleButton设置为开的状态时,显示的文本内容。

对于ToggleButton组件开关切换的响应,可以使用CompoundButton类中的监听器CompoundButton.OnCheckedChangeListener,来监听开关状态的改变。

示例:通过ToggleButton,其它LinearLayout的排列方向。

代码清单:\codes\03\3.4\ToggleButtonDemo\res\layout\ activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <ToggleButton
        android:id="@+id/tbtn_toggle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:checked="true"
        android:textOff="横向排列"
        android:textOn="纵向排列" />

    <LinearLayout
        android:id="@+id/llayout_orient"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="btn1" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="btn2" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="btn3" />
    </LinearLayout>

</LinearLayout>

代码清单:\codes\03\3.4\ToggleButtonDemo\src\com\bookdemo\togglebuttondemo\ MainActivity.java

public class MainActivity extends Activity {
    private ToggleButton tbtn_toggle;
    private LinearLayout llayout_orient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tbtn_toggle = (ToggleButton) findViewById(R.id.tbtn_toggle);
        llayout_orient = (LinearLayout) this.findViewById(R.id.llayout_orient);
        tbtn_toggle.setOnCheckedChangeListener(new OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView,
                    boolean isChecked) {
                // 通过判断是否选中,来设置LinearLayout的横向纵向排列
                if (isChecked) {
                    llayout_orient.setOrientation(LinearLayout.VERTICAL);
                } else {
                    llayout_orient.setOrientation(LinearLayout.HORIZONTAL);
                }
            }
        });
    }
}

在模拟器上运行效果:

3.4.5.2 Switch开关(Switch)

Switch也是一个开关组件,实现的功能和ToggleButton一样,是Android4.0(API level 14)之后新增的组件。

Switch以一个滑条的形式呈现,滑条的两端分别代表了开与关两种状态:

Switch和ToggleButton相比,除了外观不一样之外,还增加了一些XML属性以及对应方法,设置其开关的显示效果等。

Switch新增的属性及方法如下:

XML属性 相关方法 说明
android:switchMinWidth setSwitchMinWidth(int) Switch的最小宽度
android:switchPadding setSwitchPadding(int) 开关与描述文字的距离
android:textStyle setSwitchTypeface
(Typeface)
设置文本的风格
android:typeface setSwitchTypeface
(Typeface)
设置文本的风格
android:thumb setThunbResource(int) 设置状态滑条的效果
android:track setThunbResource(int) 设置状态的切换效果

Switch的基本使用与ToggleButton一致,这里就不再提供实例演示了。

3.4.6 进度条组件(ProgressBar)

3.4.6.1 进度条(ProgressBar)

ProgressBar是一个进度条组件,位于androdi.widget.ProgressBar包中,直接继承于View类。它是Android UI组件中一个非常实用的组件,通常用于在做某个比较耗时的任务时,展示给用户任务进度的组件,从而避免因为执行某个耗时任务,让用户感觉程序已经失去响应了。

有研究表明,当应用执行某个耗时任务的时候,如果有可以显示当前任务执行完成的百分比,将大大提高用户的等待耐心而不是直接关掉应用,所以ProgressBar是一个非常实用的UI组件。

虽然ProgressBar只是为了展示耗时任务的进度,但是为了适应不同的应用场景,Android为ProgressBar内置了几种不同风格的进度条,可以通过android:style属性设置ProgressBar的显示风格,下面提供一些常用的属性:

· @android:style/Widget.ProgressBar.Horizontal:水平进度条(可以显示刻度,常用)。

· @android:style/Widget.ProgressBar.Small:小进度条。

· @android:style/Widget.ProgressBar.Large:大进度条。

· @android:style/Widget.ProgressBar.Inverse:不断跳跃、旋转画面的进度条。

· @android:style/Widget.ProgressBar.Large.Inverse:不断跳跃、旋转动画的大进度条。

· @android:style/Widget.ProgressBar.Small.Inverse:不断跳跃、旋转动画的小进度条。

上面提到的这么多的风格中,只有Widget.ProgressBar.Horizontal风格的进度条,才可以设置进度的递增,其它的风格显示为一个循环的动画,而把ProgressBar的显示风格设定为Widget.ProgressBar.Horizontal后,需要用到一些属性设置递增的进度以及进度的最大值,这些属性Android都提供了对应的setter、getter方法,这些属性有:

· android:max:设置进度的最大值。

· android:progress:设置当前第一进度值。

· android:secondaryProgress:设置当前第二进度值。

· android:visibility:设置是否显示,默认为显示。

ProgressBar提供了两种进度显示,第一进度和第二进度,分别使用不同的XML属性以及对应方法进行设置,它们的关系类似于在线播放的实际播放进度和缓存进度的效果。

Android提供了对应的方法来操作ProgressBar的当前进度,这些方法如下:

· int getProgress():获取当前的第一进度值。

· int getSecondaryProgress():获取当前的第二进度值。

· void setProgress(int progress):设置当前的第一进度值。

· void setSecondaryProgress(int secondaryProgress):设置当前的第二进度值。

· void incrementProgressBy(int diff):设置当前的第一进度值的增量。

· void incrementSecondaryProgressBy(int diff):设置当前的第二进度值的增量。

注意:上面给出的方法均加了同步锁的,所以在多线程改变进度值的时候,不需要担心线程安全的问题。

示例:ProgressBar的各种风格以及改变其进度。

代码清单:\codes\03\3.4\ProgressBarDemo\res\layout\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:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="android:style/Widget.ProgressBar.Small" />

    <ProgressBar
        style="@android:style/Widget.ProgressBar.Small"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="android:style/Widget.ProgressBar.Inverse" />

    <ProgressBar
        style="@android:style/Widget.ProgressBar.Inverse"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="android:style/Widget.ProgressBar.Large" />

    <ProgressBar
        style="@android:style/Widget.ProgressBar.Large"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="android:style/Widget.ProgressBar.Horizontal" />

    <ProgressBar
        android:id="@+id/pb_Hor"
        style="@android:style/Widget.ProgressBar.Horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:max="100"
        android:progress="20"
        android:secondaryProgress="60" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal" >

        <!-- 设置一个按钮控制水平进度的递增 -->

        <Button
            android:id="@+id/btn_add"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="  +  " />
        <!-- 设置一个按钮控制水平进度的递减 -->

        <Button
            android:id="@+id/btn_reduce"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="30dp"
            android:text="  -  " />
        <!-- 设置一个按钮控制Style为large的进度显示与隐藏 -->

        <Button
            android:id="@+id/btn_run"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="30dp"
            android:text="Run" />
    </LinearLayout>

</LinearLayout>

代码清单:\codes\03\3.4\ProgressBarDemo\src\com\bookdemo\progressbardemo\MainActivity.java

public class MainActivity extends Activity {
    private Button btn_add, btn_reduce, btn_run;
    private ProgressBar pb_Hor;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_add = (Button) findViewById(R.id.btn_add);
        btn_reduce = (Button) findViewById(R.id.btn_reduce);
        btn_run = (Button) findViewById(R.id.btn_run);
        pb_Hor = (ProgressBar) findViewById(R.id.pb_Hor);
        btn_add.setOnClickListener(mathClick);
        btn_reduce.setOnClickListener(mathClick);
        btn_run.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                // 初始进度条为0
                pb_Hor.setProgress(0);
                pb_Hor.setSecondaryProgress(0);
                // 使用多线程的方式增加进度
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        boolean flag = true;
                        while (flag) {
                            try {
                                if (pb_Hor.getProgress() < 100) {
                                    pb_Hor.incrementProgressBy(10);
                                } else {
                                    // 如果第一进度到100了,就停止循环
                                    flag = false;
                                }
                                if (pb_Hor.getSecondaryProgress() < 100) {
                                    pb_Hor.incrementSecondaryProgressBy(20);
                                }
                                Thread.sleep(300);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                        // 循环过后初始到页面启动时进度的值
                        pb_Hor.setProgress(20);
                        pb_Hor.setSecondaryProgress(60);
                    }
                }).start();
            }
        });
    }
    private View.OnClickListener mathClick = new OnClickListener() {

        @Override
        public void onClick(View v) {
            switch (v.getId()) {
            case R.id.btn_add:
                // 如果是增加按钮,因为进度条的最大值限制在100,第一刻度限制在90.
                // 在此限度内,以1.2倍递增
                // 使用setProgress(int)
                if (pb_Hor.getProgress() < 90) {
                    pb_Hor.setProgress((int) (pb_Hor.getProgress() * 1.2));
                }
                if (pb_Hor.getSecondaryProgress() < 100) {
                    pb_Hor.setSecondaryProgress((int) (pb_Hor
                        .getSecondaryProgress() * 1.2));
                }
                break;
            case R.id.btn_reduce:
                // 如果是增加按钮,因为进度条的最大值限制在100,第一刻度限制在10.第二刻度限制在20
                // 在此限度内,以10点为基数进行递减。
                // 使用incrementXxxProgressBy(int)
                if (pb_Hor.getProgress() > 10) {
                    pb_Hor.incrementProgressBy(-5);
                }
                if (pb_Hor.getSecondaryProgress() > 20) {
                    pb_Hor.incrementSecondaryProgressBy(-10);
                }
                break;
            }
        }
    };
}

  

在模拟器上运行效果:

3.4.6.2 拖动条(SeekBar)

SeekBar是一个拖动条的组件,它位于android.widget.SeekBar,继承自AbsSeekBar,而AbsSeekBar类又继承自ProgressBar,所以实际上SeekBar是对ProgressBar的一种扩展。它在ProgressBar的基础上增加一个拖动的滑块。

SeekBar也继承了ProgressBar的属性及方法,而SeekBar增加了一个滑块的概念,所以新增了一个android:thumb属性,设置一个Drawable对象来指定自定义滑块的显示效果,如果不设置,默认使用Android自带的风格。

按住滑块进行滑动,会触发一个SeekBar.OnSeekBarChangeListener的事件,如果需要响应用户滑动SeekBar滑块的操作,只需要监听这个事件即可。事件是一个接口,其中需要实现三个方法:

· onProgressChanged(SeekBar seekBar,int progress,boolean fromUser):滑块在移动的时候响应。seekBar为触发事件的SeekBar控件,progress为当前SeekBar的滑块数值,fromUser为是否是用户拖动产生的响应。

· onStartTrackingTouch(SeekBar seekBar):滑块开始移动的时候响应。

· onStopTrackingTouch(SeekBar seekBar):滑块结束移动的时候响应。

示例:SeekBar的简单使用。

代码清单:\codes\03\3.4\SeekBarDemo\res\layout\activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/textview1"
        android:layout_width="match_parent"
        android:layout_height="30dp" />

    <TextView
        android:id="@+id/textview2"
        android:layout_width="match_parent"
        android:layout_height="30dp" />

    <SeekBar
        android:id="@+id/seekbar1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:max="100"
        android:progress="30" />
    <!-- 使用自定义的滑块 -->
    <SeekBar
        android:id="@+id/seekbar2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:max="100"
        android:progress="20"
        android:secondaryProgress="80"
        android:thumb="@drawable/bar" />
</LinearLayout>

代码清单:\codes\03\3.4\SeekBarDemo\src\com\bookdemo\seekbardemo\MainActivity.java

public class MainActivity extends Activity {
    private TextView textview1, textview2;
    private SeekBar seekbar1, seekbar2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textview1 = (TextView) findViewById(R.id.textview1);
        textview2 = (TextView) findViewById(R.id.textview2);
        seekbar1 = (SeekBar) findViewById(R.id.seekbar1);
        seekbar2 = (SeekBar) findViewById(R.id.seekbar2);

        // 为SeekBar绑定改变的事件监听器
        seekbar1.setOnSeekBarChangeListener(seekBarChange);
        seekbar2.setOnSeekBarChangeListener(seekBarChange);
    }

    private OnSeekBarChangeListener seekBarChange = new OnSeekBarChangeListener() {

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
            // 停止拖动的时候触发
            if (seekBar.getId() == R.id.seekbar1) {
                textview1.setText("seekbar1停止拖动");
            } else {
                textview1.setText("seekbar2停止拖动");
            }
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
            // 开始拖动的时候触发
            if (seekBar.getId() == R.id.seekbar1) {
                textview1.setText("seekbar1开始拖动");
            } else {
                textview1.setText("seekbar2开始拖动");
            }
        }

        @Override
        public void onProgressChanged(SeekBar seekBar, int progress,
                boolean fromUser) {
            // 拖动改变其值的时候触发
            if (seekBar.getId() == R.id.seekbar1) {
                textview2.setText("seekbar1的当前位置是:" + progress);
            } else {
                textview2.setText("seekbar2的当前位置是:" + progress);
            }
        }
    };
}

在模拟器上运行效果:

3.4.6.3 星级评分组件(RatingBar)

星级评分组件(RatingBar)也是直接继承自AbsSeekBar,而间接继承自ProgressBar,所以它和SeekBar有许多相通的地方。但是RatingBar一般不做为一个进度组件来使用,而是作为一个评分组件来使用。

RatingBar对ProgressBar进行了扩展,新增了一些自有的属性及对应的方法,也屏蔽了一些无用的属性,如RatingBar中不存在第二进度的概念。它新增的属性如下:

· android:isIndicator:设置是否允许用户修改,true为不允许,默认为false,允许。

· android:numStars:设置评分控件一共展示多少个星级,默认5个。

· android:rating:设置初始默认星级数。

· android:stepSize:设置每次需要修改多少个星级。

对RatingBar而言,当改变其星级数的时候,会触发一个RatingBar.OnRatingBarChangeListener事件,如果需要响应用户对RatingBar星级的改变,可以监听这个事件。这个事件需要实现其中的onRatingChanged()抽象方法,下面是它的完整签名:

void onRatingChanged(RatingBar ratingBar,float rating ,boolean fromUser)

onRatingChanged()方法的参数中,ratingBar表示当前触发事件的组件,rating表示改变后的星级,fromUser表示是否是用户触发的修改事件。

在使用RatingBar的时候需要注意,RatingBar推荐使用android:rating属性及对应方法来设置RatingBar的星级,因为继承关系,RatingBar也存在android:progress属性及对应方法。这两个属性代表的意义是有区别的,区别在于android:progress属性针对android:max属性设置的值而言的,而android:rating只是单纯的表示当前是第几颗星,是针对android:stepSize属性而言的。

示例:RatingBar的简单使用。

代码清单:\codes\03\3.4\RatingBarDemo\res\layout\ 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:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="评分控件的使用"
        android:textSize="20dp" />

    <RatingBar
        android:id="@+id/rbRating"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <RatingBar
        android:id="@+id/rbRating1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:isIndicator="false"
        android:max="100"
        android:numStars="4"
        android:rating="2.5"
        android:stepSize="0.5" />

</LinearLayout>

代码清单:\codes\03\3.4\RatingBarDemo\src\com\bookdemo\ratingbardemo\ MainActivity.java

public class MainActivity extends Activity {
    private RatingBar rbRating, rbRating1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        rbRating = (RatingBar) findViewById(R.id.rbRating);
        rbRating1 = (RatingBar) findViewById(R.id.rbRating1);
        // 手动设置第一个RatingBar的属性值
        rbRating.setMax(50);
        rbRating.setProgress(20);
        // 为RatingBar设定改变的事件监听器
        rbRating.setOnRatingBarChangeListener(change);
        rbRating1.setOnRatingBarChangeListener(change);
    }

    private RatingBar.OnRatingBarChangeListener change = new RatingBar.OnRatingBarChangeListener() {
        @Override
        public void onRatingChanged(RatingBar ratingBar, float rating,
                boolean fromUser) {
            // 分别显示Progress属性和rating属性的不同
            int progress = ratingBar.getProgress();
            Toast.makeText(MainActivity.this,
                    "progress:" + progress + " rating :" + rating,
                    Toast.LENGTH_SHORT).show();
        }
    };
}

在模拟器上,运行效果:

3.4.7 日期、时间相关组件

本小节主要讲解Android UI中与日期、时间相关的UI组件,包括AnalogClock(模拟时钟组件)、DigitalClock(数字时钟组件)、DatePicker(日期选择组件)、TimePicker(时间选择组件)、DatePickerDialog(日期选择对话框)、TimePickerDialog(时间选择对话框)。

3.4.7.1 模拟时钟、数字时钟(AnalogClick、DigitalClock)

AnalogClock(模拟时钟)、DigitalClock(数字时钟)是两个系统时间显示的组件,它们的区别在于展示风格的不同。这两个时间显示组件的时间是不能修改的,仅为当前系统时间,当然在极端的情况下,可以更改系统时间来改变其值。

AnalogClock因为显示的是一个模拟时钟,所以它直接继承于View类,而因为模拟时钟的缺陷,它是无法显示当前的秒数的,也就是它显示的表盘没有秒针。而DigitalClock显示一个数字时钟,它直接继承于TextView,是可以显示当前秒数的。在Android 4.2( API level 17)之后就已经对DigitalClock弃用了,所以一般不推荐使用它。

示例:AnalogClock、DigitalClock的简单使用。

代码清单:\codes\03\3.4\AnalogDigitalClockDemo\res\layout\ 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:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="模拟时钟:" />

    <AnalogClock
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="数字时钟:" />

    <DigitalClock
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="30dp"
        />
</LinearLayout>

在模拟器上运行效果:

3.4.7.2 日期选择、时间选择(DatePicker、TimePicker)

DatePicker(日期选择组件)、TimePicker(时间选择组件)都继承自android.widget.FrameLayout,并且默认显示的风格与操作风格也非常的类似。DatePicker用于显示一个日期选择组件,TimePicker用于显示一个时间选择组件,下面分别就这两个组件进行介绍。

DatePicker可以通过设置属性来确定日期选择的范围,也可以通过定义好的方法获取当前选中的时间,并且在修改日期的时候,由响应日期修改的事件对其进行响应。

DatePicker常用设置日期的属性:

· android:calendarViewShown:是否显示日历。

· android:startYear:设置可选开始年份。

· android:endYear:设置可选结束年份。

· android:maxDate:设置可选最大日期,以mm/dd/yyyy格式设置。

· android:minDate:设置可选最小日期,以mm/dd/yyyy格式设置。

对于DatePicker的方法而言,除了常用获取以上介绍的属性的对应的方法之外,还需要特别注意一个初始化的方法init(),用于在启动的时候对DatePicker组件进行初始化的工作,它可以设定DatePicker的初始日期,以及日期被修改后,回调的响应事件。init()方法的完整签名如下:

void init(int year, int monthOfYear, int dayOfMonth, DatePicker.OnDateChangedListener onDateChangedListener)

DatePicker.init()方法的参数中,year、monthOfYear、datOfMonth分别设定DatePicker初始的日期值,而DatePicker.OnDateChangedListener onDateChangedListener是日期修改时触发的事件监听器,事件是一个接口类型,如果需要监听DatePicker日期值的修改,需要实现其中的onDateChange()方法,下面是onDateChange()方法的完整签名:

void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth)

作为一个时间选择组件来说,TimePicker除了需要与时间相关的getter、setter方法之外,还需要时间被修改时,回调的响应事件。

就这个思路先来介绍一下TimePicker与时间相关的几个常用方法:

· is24HourView():判断是否为24小时制。

· setIs24HourView():设置是否为24小时制显示。

· getCurrentHour():获取当前时间小时数。

· getCurrentMinute():获取当前时间的分钟数。

· setCurrentHour ():设置当前时间的小时数。

· setCurrentMinute():设置当前时间的分钟数。

· setOnTimeChangedListener():设置时间被修改的回调方法。

TimePicker组件的时间值被修改的事件,是通过setOnTimeChangedListener()方法来设定事件的监听器,其传递一个TimePicker的内部接口TimePicker.OnTimeChangedListenerf,如果需要监听TimePicker对时间值的修改事件,需要实现其内部的方法onTimeChanged(),下面是它的完整签名:

void onTimeChanged(TimePicker view,int hourOfDay,int minute)

示例:DatePicker和TimePicker的简单使用。

代码清单:\codes\03\3.4\DateTimePickerDemo\res\layout\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" >

    <DatePicker
        android:id="@+id/dpPicker"
        android:calendarViewShown="false"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TimePicker
        android:id="@+id/tpPicker"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

代码清单:\codes\03\3.4\DateTimePickerDemo\src\com\bookdemo\datetimepickerdemo\MainActivity.java

public class MainActivity extends Activity {
    private DatePicker datePicker;
    private TimePicker timePicker;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        datePicker = (DatePicker) findViewById(R.id.dpPicker);
        timePicker = (TimePicker) findViewById(R.id.tpPicker);

        // 初始化DatePicker,设定日期为2013-8-20
        // 并在日期改变的时候打印改变后的日期
        datePicker.init(2013, 7, 20, new OnDateChangedListener() {

            @Override
            public void onDateChanged(DatePicker view, int year,
                    int monthOfYear, int dayOfMonth) {
                // 获取一个日历对象,并初始化为当前选中的时间
                Calendar calendar = Calendar.getInstance();
                calendar.set(year, monthOfYear, dayOfMonth);
                SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日");
                Toast.makeText(MainActivity.this,
                    ;  format.format(calendar.getTime()), Toast.LENGTH_SHORT)
                    ;  .show();
            }
        });

        // 设置TimePicker为24小时制
        timePicker.setIs24HourView(true);
        // 添加时间更改事件的监听器,在时间更改后打印出来
        timePicker
            .setOnTimeChangedListener(new TimePicker.OnTimeChangedListener() {
                @Override
                public void onTimeChanged(TimePicker view, int hourOfDay,
                        int minute) {
                    Toast.makeText(MainActivity.this,
                            hourOfDay + "点" + minute + "分钟",
                            Toast.LENGTH_SHORT).show();
                }
            });
    }
}

在模拟器上运行效果:

3.4.7.3 日期、时间选择对话框(DatePickerDialog、TimePickerDialog)

日期选择对话框(DatePickerDialog)、时间选择对话框(TimePickerDialog)是两个弹出对话框组件,DatePickerDialog弹出一个对话框选择日期,而TimePickerDialog弹出一个对话框选择时间,这两个组件都继承于android.app.AlertDialog,关于AlertDialog的内容,将在后面的章节中讲解到。

DatePickerDialog与DatePicker的功能非常类似,都是用于选择一个日期值,而DatePickerDialog是作为一个弹出的对话框进行选择的。对于DatePickerDialog而言,通过它的构造方法可以设置弹出的日期对话框组件的日期初始值,并且可以指定选日期后,回调事件的监听器。它有两个构造方法,这里介绍一个常用的,构造方法的完整签名如下:

DatePickerDialog(Context context,DatePickerDialog.OnDateSetListener callback,int year,int monthOfYear,int dayOfMonth)

DatePickerDialog构造方法的参数中,context为当前应用的上下文,callBack为日期选中后的响应事件,year、monthOfYear、dayOfMonth为日期对话框初始显示时候的默认日期值。一般需要监听其日期选择的事件DatePickerDialog.OnDateSetListener,这个事件中需要实现其中的onDateSet()方法,在这个方法中,可以得到用户选中的日期值,下面是onDateSet()方法的完整签名:

void onDateSet(DatePicker view,int year,int monthOfYear,int dayOfMonth)

TimePickerDialog这个时间选择对话框和DatePickerDialog的使用方法一样,也是可以通过构造方法设置初始值以及选中时间后的事件监听器,TimePickerDialog同样有两个构造方法,其中一个多了一个可以设置主题资源ID的参数,这里介绍另外一个常用的构造方法,下面是它的完整签名:

TimePickerDialog(Context context, TimePickerDialog.OnTimeSetListener callBack, int hourOfDay, int minute, boolean is24HourView)

TimePickerDialog构造方法的参数中,context为当前应用的上下文,callBack为时间选中后的响应事件,hourOfDay、minute为时间对话框初始显示时候的默认时间值,is24HourView表示是否是24小时制显示。一般需要监听时间选择的事件TimePickerDialog.OnTimeSetListener,这个事件中需要实现其中的onTimeSet()方法,在这个方法中,可以得到用户选中的时间值,下面是onTimeSet ()方法的完整签名:

void onTimeSet(TimePicker view,int hourOfDay,int minute)

无论是DatePickerDialog还是TimePickerDialog,它们实际上都是一个对话框,当得到它们的对象实例之后,需要调用AlertDialog.show()方法展示其对话框。

示例:通过一个简单的实例来演示一下DatePickerDialog和TimePickerDialog的使用。

代码清单:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/btnTimePickerDialog"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Show TimePickerDialog" />

    <Button
        android:id="@+id/btnDatePickerDialog"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Show DatePickerDialog" />
</LinearLayout>

代码清单:

public class MainActivity extends Activity {
    private Button btnDate, btnTime;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    vbtnDate = (Button) findViewById(R.id.btnDatePickerDialog);
        btnTime = (Button) findViewById(R.id.btnTimePickerDialog);
        btnDate.setOnClickListener(click);
        btnTime.setOnClickListener(click);
    }

    private View.OnClickListener click = new OnClickListener() {

        @Override
        public void onClick(View v) {
            switch (v.getId()) {
            case R.id.btnDatePickerDialog:
                DatePickerDialog datePicker = new DatePickerDialog(
                        MainActivity.this, new OnDateSetListener() {

                            @Override
                            public void onDateSet(DatePicker view, int year,
                                int monthOfYear, int dayOfMonth) {
                              Toast.makeText(
                                    MainActivity.this,
                                    year + "年 " + (monthOfYear + 1)
                                        + "月 " + dayOfMonth + "日",
                                    Toast.LENGTH_SHORT).show();
                            }
                      }, 2013, 7, 20);
                datePicker.show();
                break;

        case R.id.btnTimePickerDialog:
            TimePickerDialog time = new TimePickerDialog(MainActivity.this,
                new OnTimeSetListener() {

                    @Override
                    public void onTimeSet(TimePicker view,
                            int hourOfDay, int minute) {
                        Toast.makeText(
                                MainActivity.this,
                                hourOfDay + "点 " + minute + "分",
                                Toast.LENGTH_SHORT).show();
                        }
                    }, 18, 25, true);
                time.show();
                break;
            }
        }
    };
}

在模拟器上运行效果:

3.4.8 图像控件(ImageView)

图像视图(ImageView),直接继承自View类,它的主要功能是用于显示各种来源的图片,其次还可以对图片进行缩放、旋转、着色等。

ImageView在View的基础上增加了一些XML属性及对应的方法,以便操作图片资源,这些属性有:

XML属性 相关方法 说明
android:adjustViewBounds setAdjustViewBounds
(boolean)
设定ImageView是否调整自己的便捷来保持所显示图片的长宽比
android:maxHeight setMaxHeight(int) 设定ImageView的最大高度
android:maxWidth setMaxWidth(int) 设定ImageView的最大宽度
android:scaleType setScaleType
(ImageView.ScaleType)
设定所显示的图片如何缩放或移动以适应ImageView的大小
android:src setImageResource(int) 设定ImageVIew说显示的Drawable资源的ID
android:tint setColorFilter
(ColorFilter)
设定ImageView显示图片的着色度,可以是一个RGB或ARGB值

对于android:scaleType属性而言,它设定:如果图片与ImageView的尺寸不合适的时候,如何对图片进行缩放以适应ImageView的显示,其值都设定在ImageView的内部枚举ImageVIew.ScaleType中,有以下几个值供选择使用:

XML属性值 ScaleType枚举值 说明
center CENTER 把图片放在ImageView的中央,但是不进行任何缩放
centerCrop CENTER_CROP 保持纵横比缩放图片,以使图片能完全覆盖ImageView
centerInside CENTER_INSIDE 保持纵横比缩放图片,以使得ImageView能完全显示该图片
fitCenter FIT_CENTER 保持纵横比缩放图片,缩放完成后将图片放在ImageView的中央
fitEnd FIT_END 保持纵横比缩放图片,缩放完成后将图片放在ImageView的右下角
fitStart FIT_START 保持纵横比缩放图片,并且将图片放在ImageView的左上角
fitXY FIT_XY 横向、纵向独立缩放,以适应该ImageView
matrix MATRIX 使用matrix方式进行缩放

ImageView提供了多个setImageXxx()的方法,以适应不同来源的图片资源,这些方法有如下几个:

· void setImageBitmap(Bitmap bm):设定一个Bitmap对象作为ImageView的显示图像。

· void setImageDrawable(Drawable drawable):设定一个Drawable对象作为ImageView的显示图像。

· void setImageResource(int resId):设定一个Drawable资源ID作为ImageView的显示图像。

· void setImageURI(Uri uir):设定一个Uri地址作为ImageView的显示对象,可以是网络Uri或者本地内容提供者的Uri地址。

示例:ImageView的简单使用。

代码清单:\codes\03\3.4\ImageView\res\layout\ activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="scaleType:center,未缩放,在ImageView的中心" />

    <ImageView
        android:id="@+id/imageview1"
        android:layout_width="200dp"
        android:layout_height="100dp"
        android:background="#F00"
        android:scaleType="center"
        android:src="@drawable/green" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="scaleType:fitCenter,按比例缩放" />

    <ImageView
        android:id="@+id/imageview2"
        android:layout_width="300dp"
        android:layout_height="200dp"
        android:background="#FFF"
        android:padding="10dp"
        android:scaleType="fitCenter"
        android:src="@drawable/green" />

</LinearLayout>

在模拟器上运行效果:

3.5 高级UI组件

在前一小节中,介绍了Android的一些基本UI组件,它们都是单独存在的,与数据源并不相关,而接下来将讲解Android UI的一些高级组件,就是涉及到数据源的组件。将会讲解Android的Adapter,数据适配器,Android与数据源相关的高级UI组件,均围绕着Adapter进行使用。

3.5.1 数据适配器(Adapter)浅析

Adapter(数据适配器)是Android中UI组件中与数据源相关的非常重要的一个元素。它作为一个适配器,在Data与VIew之间形成了一个非常重要的纽带,Adapter提供数据源的访问,还负责生成一个View来显示数据源中的每一条数据。在常见的UI数据组件中(如ListView、GridView、Spinner),都依赖Adapter来解析数据,展示数据视图。下图直观的表现了Data source、Adapter、View三者的关系:

Adapter只是一个接口,并不能直接使用。Android为Adapter提供了多种适应不同场景的间接子类供我们使用,在使用过程中可以根据需求实现特定的接口或者使用对继承类进行一定的扩展,下图是Adapter的继承关系:

比较常用的Adapter的继承子类和接口有:BaseAdapter、ArrayAdapter<T>、SimpleAdapter,下面是它们的一些简单说明:

· BaseAdapter:是一个抽象类,实现了Adapter的两个基础接口ListAdapter、SpinnerAdapter,继承使用它需要实现较多的方法,所以也就具有较高的灵活性,是其它Adapter类的直接或间接父类,其它Adapter类都是在BaseAdapter的基础上进行了扩展与简化。

· ArrayAdapter<T>:它是BaseAdapter的直接子类。支持一个任意的数组或者泛型对象数据,这个类在一般情况下使用一个TextView XMl资源的ID来设定其布局,然后设定其数据源即可。

· SimpleAdapter:它是BaseAdapter的直接子类,是一个简单的适配器,映射一个静态的XML布局资源到View中,可以指定一个List<Map<P,T>>格式的数据进行数据配置,List中的每一条数据对于一条数据行,而Map中每一条数据对应数据行的一列。

对于Adapter的使用,会在后面讲解具体与数据源相关的UI组件的时候进行详细介绍。

3.5.2 自动完成文本框(AutoCompleteTextView)

3.5.2.1 自动完成文本框简介

自动完成文本框(AutoCompleteTextView)位于android.widget.AutoCompleteTextView包下,直接继承于EditText,是一个文本编辑框,具有对输入内容自动补全的功能。

当用户在AutoCompleteTextView中输入内容后,AutoCompleteTextView会根据数据源提供的数据项对输入内容进行匹配,并弹出一个下拉列表显示与之匹配的数据项供用户选择,当用户选择某个数据项之后,会自动根据选择的数据项对AutoCompleteTextView中的内容进行智能补全。

AutoCompleteTextView除了可以使用EditText的属性和方法之外,还扩展了一些特殊的属性及对于的方法,这里介绍一些常用的:

XML属性 相关方法 说明
android:completionHint setCompletionHint
(CharSequence)
设置弹出的提示列表中的标题
android:completionThreshold setThreshold(int) 设置最少输入几个字符才会弹出提示列表
android:dropDownWidth setDropDownWidth(int) 设置提示列表的宽度
android:dropDownHeight setDropDownHeight(int) 设置提示列表的高度
android:dropDownHorizontalOffset 设置提示列表与文本框之间的水平偏移量
android:dropDownVerticalOffset 设置提示列表与文本框之间的垂直偏移量
android:popupBackground setDropDown
BackgroundResource(int)
设置提示列表的背景,可以是一个Drawable资源iD
android:dropDownSelector 设置选中提示列表中项的效果

3.5.2.2 数组适配器(ArrayAdapter)

对于AutoCompleteTextView组件来说,一般使用数组适配器ArrayAdapter<T>来适配Data与View。下面先来简单介绍一下ArrayAdapter<T>。

ArrayAdapter直接继承于BaseAdapter,它支持一个任意的数组或者泛型对象数据,这个类在一般情况下使用一个TextView XMl资源的ID来设定其布局,然后设定其数据源。ArrayAdapter有多个构造方法,这里使用其中的一个,下面是它的完整签名:

ArrayAdapter(Context context,int resource,T[] objects)

其中,参数resource是一个简单的布局资源ID,objects参数为数据的数组。在Android SDK的\android-sdk-windows\platforms\<android-Xxx>\data\res\layout目录下,定义了很多不同的布局,可以直接通过android.R.layout.Xxx获取到这个布局资源的Id。

下面通过一个简单的实例来演示一下如何通过ArrayAdapter来适配AutoCompleteTextView的数据。

示例:AutoCompleteTextView配合ArrayAdapter的简单使用。

代码清单:\codes\03\3.5\AutoCompletTextViewDemo\res\layout\ activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="AutoConpaletText-City" />
    <!-- 声明一个AutoCompleteTextView控件,设定其下拉框标题为"Famous Citi" 并且输入一个字符开始提示 -->
    <AutoCompleteTextView
        android:id="@+id/autotext"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:completionHint="世界闻名的城市"
        android:completionThreshold="1" />

</LinearLayout>

代码清单:\03\3.5\AutoCompletTextViewDemo\src\com\bookdemo\autocomplettextviewdemo\ MainActivity.java

public class MainActivity extends Activity {
    private AutoCompleteTextView autotext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 获取布局文件中的控件对象
        autotext = (AutoCompleteTextView) findViewById(R.id.autotext);

        // 设置数据源
        String[] autoStrings = new String[] { "New York", "Tokyo", "Beijing",
                "london", "Seoul Special", "Los Angeles" };
        // 设置ArrayAdapter,并且设定以单行下拉列表风格展示(第二个参数设定)。
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(
                MainActivity.this, android.R.layout.simple_dropdown_item_1line,
                autoStrings);
        // 设置AutoCompleteTextView的Adapter
        autotext.setAdapter(adapter);
    }
}

在模拟器上运行效果:

3.5.2.3 多次自动完成文本框(MultiAutoCompleteTextView)

MultiAutoCompleteTextView直接继承自AutoCompleteTextView。新扩展的功能是:可以进行多次提示,并且每次指定完成的内容通过符号进行分隔显示。使用MultiAutoCompleteTextView必须实现一个MultiAutoCompleteTextView.Tokenizer接口,用于声明选项与选项之间分隔的符号,一般如不特殊指定,可以使用Android为我们提供的Tokenizer接口的实现类MultiAutoCompleteTextView.CommaTokenizer,它设定使用英文逗号","进行分隔选项。

示例:MultiAutoCompleteTextView的简单使用。

代码清单:\03\3.5\MultiAutoCompleteTextViewDemo\res\layout\ activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/multiautoText" />

    <MultiAutoCompleteTextView
        android:id="@+id/multiautotext"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:completionHint="世界闻名的城市"
        android:completionThreshold="1" />

</LinearLayout>

代码清单:\03\3.5\MultiAutoCompleteTextViewDemo\src\com\bookdemo\multiautocompletetextviewdemo\ MainActivity.java

public class MainActivity extends Activity {
    private MultiAutoCompleteTextView multiautotext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 获取布局文件中的控件对象
        multiautotext = (MultiAutoCompleteTextView) findViewById(R.id.multiautotext);

        // 设置数据源
        String[] autoStrings = new String[] { "New York", "Tokyo", "Beijing",
                "london", "Seoul Special", "Los Angeles" };
        // 设置ArrayAdapter,并且设定以单行下拉列表风格展示(第二个参数设定)。
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(
                MainActivity.this, android.R.layout.simple_dropdown_item_1line,
                autoStrings);

        // 设置MultiAutoCompleteTextView的Adapter
        multiautotext.setAdapter(adapter);
        // 设定选项间隔使用逗号分隔。
        multiautotext
                .setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
    }
}

在模拟器上运行效果:

3.5.3 列表选择组件(Spinner)

3.5.3.1 列表选择组件基本使用

Spinner是一个列表选择组件,会在用户选择后,展示一个选择列表供用户选择。Spinner是ViewGroup的间接子类,因为需要在弹出列表中展示数据,所以它也需要配合Adapter进行使用。

下面先介绍一下Spinner的常用XML属性,Android为它们提供了相应的getter、setter方法:

· android:spinnerMode:列表显示的模式,有两个选择,为dialog(弹出列表)以及dropdown(下拉列表),如果不特别设置,为dropdown模式。

· android:entries:使用<string-array.../>资源配置数据源。

· android:prompt:对当前下拉列表设置标题,仅在dialog模式下有效。

· android:dropDownHorizontalOffset:dropdown模式下,弹出列表对于Spinner组件的水平偏移量。

· android:dropVerticalOffset:dropdown模式下,弹出列表对于Spinner组件的垂直偏移量。

· android:dropDownSelector:dropdown模式下,选中项的模式。

· android:dropDownWidth:dropdown模式下,选项列表的宽度。

· android:popupBackgroud:选项列表的背景。

Spinner作为一个列表选择组件,需要监听其组件的数据列表中,选中项时触发的事件。但Spinner本身并没有定义这些事件,均继承自它的间接父类AdapterView。Spinner支持的几个常用事件如下:

· AdapterView.OnItemLongClickListener:列表项被长按时触发。

· AdapterView.OnItemSelectedListener:列表项被选择时触发。

因为Spinner可以设置各种不同的选择样式,有选择、单选、多选等,所以OnItemCLickListener和OnItemSelectedListener可以按照需求适应不同的场景。如果需要监听这些事件,需要实现其中的方法。方法中position为当前选择数据源中选项的位置,基本上每个事件方法中都有用到。

对于大多数Android中需要用到数据源的UI组件,都有两种方式设定数据源,这里简单介绍一下:

· 通过XML资源文件设置,这种方式比较死板,但是如果仅仅需要展示固定的、简单的数据,这种方式比较直观,可以考虑。

· 使用Adapter接口设置,这是最常见的方式,动态、灵活,可以设定各种样式以及数据来源。

3.5.3.2 使用XML数组资源设置数据

Android下用到数据源的UI组件,可以使用两种方式设置数据源,其中就有XML数组资源的方式,这里就Spinner组件来讲解如何使用XML定义的数组资源,并配置Spinner的数据源。

在Android中使用XML数组资源,需要在/res/values目录下新建XML格式的资源文件,名字并不重要,但是一般会使用string.xml。在其中<resourse…/>标签下,定义<string-array…/>标签,使用定义<item…/>标签来设置每一个数据项。

XML数组资源的通用格式如下:

   
<resource>
    <string-array name="arrayname">
        <item>item1</item>
        <item>item2</item>
        <item>item3</item>
    </string-array>
<resource>

示例:使用XML定义Spinner数据。

代码清单:\codes\03\3.5\SpinnerXMLDateDemo\res\values\strings.xml

<resources>
    <string name="app_name">SpinnerXMLDateDemo</string>
    <string name="action_settings">Settings</string>
    <string name="beij_prompt">北京区域</string>
    <string-array name="beijing">
        <item>朝阳区</item>
        <item>海淀区</item>
        <item>房山区</item>
        <item>丰台区</item>
        <item>东城区</item>
        <item>西城区</item>
    </string-array>
</resources>

代码清单:\codes\03\3.5\SpinnerXMLDateDemo\res\layout\activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="entries绑定数据源" />

    <Spinner
        android:id="@+id/spinnerBase1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:entries="@array/beijing" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="弹出带标题的Dialog,并且使用entries绑定数据源" />

    <Spinner
        android:id="@+id/spinnerBase2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:entries="@array/beijing"
        android:prompt="@string/beij_prompt"
        android:spinnerMode="dialog" />

</LinearLayout>

代码清单:\03\3.5\SpinnerXMLDateDemo\src\com\bookdemo\spinnerxmldatedemo\ MainActivity.java

public class MainActivity extends Activity {
    private Spinner spinnerBase1, spinnerBase2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        spinnerBase1 = (Spinner) findViewById(R.id.spinnerBase1);
        spinnerBase2 = (Spinner) findViewById(R.id.spinnerBase2);
        spinnerBase1.setOnItemSelectedListener(select);
        spinnerBase2.setOnItemSelectedListener(select);
    }

    private AdapterView.OnItemSelectedListener select = new AdapterView.OnItemSelectedListener() {

        @Override
        public void onItemSelected(AdapterView<?> parent, View view,
                int position, long id) {
            // 选中项的时候触发
            // 使用XML直接绑定的时候,view参数传递的是一个TextView
            TextView textview = (TextView) view;
            Toast.makeText(MainActivity.this, textview.getText(),
                    Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {

        }
    };
}

在模拟器上运行效果:

3.5.3.3 使用SimpleAdapter适配Spinner数据

对于一些简单的数据项,可以使用ArrayAdapter进行数据的配置,数据源复杂时可以考虑使用SimpleAdapter来配置。

SimpleAdapter同样是Adapter的子类,它是一个简单的适配器,映射一个静态的XML布局资源到View中,可以指定一个List<Map<P,T>>格式的数据进行数据配置,List中的每一条数据对应一条数据行,而Map中每一条数据对应数据行的一列。SimpleAdapter映射一个XML布局资源,数据与UI组件的对应关系,通过SimpleAdapter的构造方法中两个参数来指定。先来介绍一下SimpleAdapter的构造方法,下面是它的完整签名:

SimpleAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)

SimpleAdapter构造方法中参数的意义如下:

· context:上下文对象,一般就是当前的Activity。

· data: List<Map<S,T>>类型的数据。

· resource:XML资源的Id,通过R对象选中。

· from:一个String类型数组,每条数据对应data数据中,Map定义的Key。

· to:一个int类型数组,对应XML资源中控件的ID,注意顺序必须与from中指定数据的顺序一致。

SimpleAdapter适配数据,首先会判断SimpleAdapter的一个内部接口SimpleAdapter.ViewBinder是否可用,如果可用,调用其中的setViewValue(View view,Object date,String textRepresentation)方法,返回true,则绑定数据,返回false,尝试使用其它方法绑定数据。

如果SimpleAdapter.ViewBinder不可用,或者setViewValue()方法返回false,则如何判断当前to数组中对应的ID指向的是什么UI组件,有以下规则:

· 如果是CheckBox,则尝试从from参数中,取数组对应位置的boolean值赋予CheckBox。

· 如果是TextView,则尝试从from参数中,取数组对应位置的String值,调用SimpleAdapter.setViewText()方法

· 如果是ImageView,则尝试从from参数中,取数组对应位置的int或String值,分别调用对应的SimpleAdapter.setViewImage()方法。

从上面SimpleAdapter的数据适配规则可以看出,虽然SimpleAdapter可以映射一个复杂的XML布局对数据项进行布局,但是XML布局中需要绑定数据的UI组件,只能是CheckBox、TextView、ImageView或其子类。

示例:使用SimpleAdapter适配Spinner的数据。

代码清单:\codes\03\3.5\SpinnerDateBySimpleAdapterDemo\res\layout\activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="请选择当前天气:" />

    <Spinner
        android:id="@+id/spinnerAdapter"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

代码清单:\codes\03\3.5\SpinnerDateBySimpleAdapterDemo\res\layout\items.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal" >

    <ImageView
        android:id="@+id/imageview"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:paddingLeft="10dp"
        android:src="@drawable/ic_launcher" />

    <TextView
        android:id="@+id/textview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:paddingLeft="10dp"
        android:textColor="#000"
        android:textSize="16dp" />

</LinearLayout>

代码清单:\codes\03\3.5\SpinnerDateBySimpleAdapterDemo\src\com\bookdemo\spinnerdatebysimpleadapterdemo\ MainActivity.java

public class MainActivity extends Activity {
    private Spinner spinner;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        spinner = (Spinner) findViewById(R.id.spinnerAdapter);
        // 声明一个SimpleAdapter独享,设置数据与对应关系
        SimpleAdapter simpleAdapter = new SimpleAdapter(MainActivity.this,
                getData(), R.layout.items, new String[] { "ivLogo",
                        "weather" }, new int[] { R.id.imageview,
                        R.id.textview });
        // 绑定Adapter到Spinner中
        spinner.setAdapter(simpleAdapter);
        // Spinner被选中事件绑定。
        spinner.setOnItemSelectedListener(new OnItemSelectedListener() {

            @Override
            public void onNothingSelected(AdapterView<?> arg0) {

            }

            @Override
            public void onItemSelected(AdapterView<?> parent, View view,
                    int position, long id) {
                // parent为一个Map结构的数据
                Map<String, Object> map = (Map<String, Object>) parent
                        .getItemAtPosition(position);
                Toast.makeText(MainActivity.this,
                        map.get("weather").toString(),
                        Toast.LENGTH_SHORT).show();

            }
        });
    }

    public List<Map<String, Object>> getData() {
        // 生成数据源
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
        // 每个Map结构为一条数据,key与Adapter中定义的String数组中定义的一一对应。
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("ivLogo", R.drawable.fine);
        map.put("weather", "晴天");
        list.add(map);
        Map<String, Object> map2 = new HashMap<String, Object>();
        map2.put("ivLogo", R.drawable.cloudy);
        map2.put("weather", "多云");
        list.add(map2);
        Map<String, Object> map3 = new HashMap<String, Object>();
        map3.put("ivLogo", R.drawable.rain);
        map3.put("weather", "小雨");
        list.add(map3);
        Map<String, Object> map4 = new HashMap<String, Object>();
        map4.put("ivLogo", R.drawable.snow);
        map4.put("weather", "小雪");
        list.add(map4);
        Map<String, Object> map5 = new HashMap<String, Object>();
        map5.put("ivLogo", R.drawable.thundershower);
        map5.put("weather", "雷阵雨");
        list.add(map5);
        return list;
    }
}

在模拟器上运行效果:

3.5.4 列表视图(ListView)

3.5.4.1 列表视图的基本使用

ListView(列表视图组件)直接继承了AbsListView,是一个以垂直列表的方式展示View的组件。ListView的数据源,来自一个继承了ListAdapter接口的Adapter。

ListView的展示效果图如下:

ListView增加了一些用来设置列表的间隔、分割线、表头、表尾等的XML属性,常用属性有以下几个,并且Android对这些XML属性也提供了对应的getter、setter方法:

· android:divider:使用一个Drawable或者color设置数据项之间的间隔样式。

· android:dividerHeight:设置数据项之间的间隔距离。

· android:entries:设置一个资源Id用于填充ListView的数据项。

· android:footerDividersEnabled:设定列表表尾是否显示分割线,如果有表尾的话。

· android:headerDividerEnabled:设定列表表头是否显示分割线,如果有表头的话。

ListView还提供了一些方法,用于对ListVIew进行操作,这里介绍一些常用的方法:

· void addFooterView(View v):添加表尾View视图。

· boolean removeFooterView(View v):移除一个表尾View视图。

· void addHeaderView(View v):添加一个表头View视图。

· boolean removeHeaderView(View v):移除一个表头View视图。

· ListAdapter getAdapter():获取当前绑定的ListAdapter适配器。

· void setAdapter(ListAdapter adapter):设置一个ListAdapter适配器到当前ListView中。

· void setSelection(int posotion):设定当前选中项。

· long[] getCheckItemIds():获取当前选中项。

作为一个列表视图组件,ListView具有一些选中可以触发的事件,但它本身并没有定义这些事件,和Spinner一样,均继承自间接父类AdapterView。AdapterView定义了如下几个事件:

· AdapterView.OnItemCLickListener:列表项被点击时触发。

· AdapterView.OnItemLongClickListener:列表项被长按时触发。

· AdapterView.OnItemSelectedListener:列表项被选择时触发。

在Android中使用ListView,一般有两种方式:

· 通过一个继承了Activity的子类ListActivity,在其中设定ListAdapter。对于这种方式,比较适用于整个页面就只有一个ListView的场景。

· 在普通Activity中直接使用ListView组件,这种方式也是实际开发中比较常用的方式。

示例:使用ListView在Activity中定义一个列表。

代码清单:\codes\03\3.5\ListViewBaseDemo\res\layout\activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <!-- 设定列表项的分割线为蓝色,并且数据项之间分割10个dp -->
    <ListView
        android:id="@+id/listviewsimple"
        android:layout_width="match_parent"
        android:layout_height="match_parent" 
        android:divider="#00ff00"
        android:dividerHeight="10dp"/>
</LinearLayout>

代码清单:\03\3.5\ListViewBaseDemo\src\com\bookdemo\listviewbasedemo\ MainActivity.java

public class MainActivity extends Activity {
    private ListView listview;
    private ArrayAdapter<String> adapter;
    private List<String> data;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getData();// 填充数据
        listview = (ListView) findViewById(R.id.listviewsimple);
        // 设定列表项的选择模式为单选
        adapter = new ArrayAdapter<String>(MainActivity.this,
                android.R.layout.simple_list_item_single_choice, data);
        listview.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
        listview.setAdapter(adapter);
    }

    private void getData() {
        data = new ArrayList<String>();
        data.add("北京");
        data.add("上海");
        data.add("广州");
        data.add("深圳");
        data.add("武汉");
        data.add("宜昌");
        data.add("成都");
        data.add("贵阳");
        data.add("杭州");
        data.add("济南");
        data.add("天津");
    }
}

在模拟器上运行效果:

3.5.4.2 使用ListActivity显示列表

ListActivity继承了Activity,并通过绑定一个ListAdapter来适配一个数据列表。如果对列表项的数据格式没有特殊的要求,ListActivity完全可以不适用布局资源即可创建一个ListView,因为ListActivity类本身已经封装了一个ListView。因此在Activity.onCreate()方法中,不需要调用setContentView来从一个布局资源文件中加载用户UI界面。

在ListActivity的onCreate()方法中,可以直接使用ListActivity.setListAdapter()方法为这个ListView设定ListAdapter。可以使用ListActivity.getListView()方法获得并操作这个ListActivity封装的ListView。

示例:不使用布局资源,使用ListActivity定义一个列表。

代码清单:\codes\03\3.5\ListActivityDemo\src\com\bookdemo\listactivitydemo\ MainActivity.java

public class MainActivity extends ListActivity {
    private String[] presidents = { "北京", "深圳", "上海", "广州" };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // setContentView(R.layout.activity_main);
        ListView listview = getListView();
        // 添加一个TextView作为表头
        TextView tvHeader = new TextView(MainActivity.this);
        tvHeader.setText("城市列表头");
        listview.addHeaderView(tvHeader);
        // 添加一个TextView作为表尾
        TextView tvFooter = new TextView(MainActivity.this);
        tvFooter.setText("城市列表尾");
        listview.addFooterView(tvFooter);
        listview.setAdapter(new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1, presidents));

    }

    @Override
    protected void onListItemClick(ListView parent, View view, int position,
            long id) {
        // 注意,如果加了表头,position的位置也包含表头表尾
        TextView textView = (TextView) view;
        Toast.makeText(this, "你选择的是" + textView.getText(), Toast.LENGTH_SHORT)
                .show();
    }
}

在模拟器中运行效果:

3.5.4.3 使用SimpleAdapter适配ListView数据

ListView不仅仅用来显示简单的数据,还可以使用SimpleAdapter这个适配器对复杂样式的数据进行数据源的适配。这里通过ListView单独讲解在同一个XML布局中如何为ListView的数据项设定布局样式。

示例:使用SimpleAdapter映射自布局资源,适配ListView的数据。

代码清单:\codes\03\3.5\ListViewSimpleAdapterDemo\res\layout\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" >

    <TableLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:stretchColumns="1" >

    <!-- LinnerLayout定义的三个控件,将成为ListView控件的模板 -->
    <TableRow>
        <TextView
        android:id="@+id/tvDate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_weight="1"
        android:text="日期" />

        <ImageView
        android:id="@+id/ivIcon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="3dp"
        android:maxHeight="5dp"
        android:maxWidth="5dp"
        android:src="@drawable/ic_launcher" />

        <TextView
        android:id="@+id/tvTemp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_weight="4"
        android:text="温度" />
    </TableRow>
    </TableLayout>

    <ListView
    android:id="@+id/lvArray"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

</LinearLayout>

代码清单:\03\3.5\ListViewSimpleAdapterDemo\src\com\bookdemo\listviewsimpleadapterdemo\ MainActivity.java

public class MainActivity extends Activity {
    private ListView listview;
    private SimpleAdapter simpleAdapter;
    private List<Map<String, Object>> data;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        listview = (ListView) findViewById(R.id.lvArray);
        // 填充数据
        putData();
        // 这里使用当前的布局资源作为ListView的模板。
        // 使用这种方式,SimpleAdapter会忽略ListView控件,仅以ListView之外的控件作为模板。
        simpleAdapter = new SimpleAdapter(MainActivity.this, data,
                R.layout.activity_main,
                new String[] { "date", "icon", "temp" }, new int[] {
                        R.id.tvDate, R.id.ivIcon, R.id.tvTemp });
        listview.setAdapter(simpleAdapter);
    }

    private void putData() {
        data = new ArrayList<Map<String, Object>>();
        Map<String, Object> map1 = new HashMap<String, Object>();
        map1.put("date", "2014-1-1");
        map1.put("icon", R.drawable.fine);
        map1.put("temp", "-5~5°");
        Map<String, Object> map2 = new HashMap<String, Object>();
        map2.put("date", "2014-1-2");
        map2.put("icon", R.drawable.rain);
        map2.put("temp", "-7~3°");
        Map<String, Object> map3 = new HashMap<String, Object>();
        map3.put("date", "2014-1-3");
        map3.put("icon", R.drawable.thundershower);
        map3.put("temp", "-6~6°");
        Map<String, Object> map4 = new HashMap<String, Object>();
        map4.put("date", "2014-1-4");
        map4.put("icon", R.drawable.snow);
        map4.put("temp", "-9~2°");
        data.add(map1);
        data.add(map2);
        data.add(map3);
        data.add(map4);
    }
}

在模拟器上运行效果:

3.5.5 网格视图组件(GridView)

3.5.5.1 网格视图组件基本使用

GridView是一个以二维网格的形式展示数据的组件,它与ListView同样继承自AbsListView,并且使用一个ListAdapter接口的适配器来适配数据源。它的展示效果有点类似于TableLayout布局,下图是GridView的效果图:

GridView新增了一些XML属性来匹配网格显示,并为其提供了对应的getter、setter方法,这些XML属性有:

· android:columnWidth:指定每一列的固定宽度。

· android:gravity:指定对其方式。

· android:horizontalSpacing:指定网格中每个元素的水平间距。

· android:verticalSpacing:指定网格中每个元素的垂直间距。

· android:numColumns:指定列数。

· android:stretchMode:设置拉伸模式。

以上介绍的属性中,android:numColumns属性如果设定为1,可以当成一个ListView来使用。而android:stretchMode具有多个选项,其值被常量的形式定义在GridView中,在XML属性中可以直接使用,GridView提供了如下几个拉伸模式:

· none:不拉伸,默认值。

· spacingWidth:仅拉伸网格中元素本身。

· columnWidth:进拉伸网格本身。

· spacingWidthUniform:网格本身与元素之间的间距一起拉伸。

示例:GridView的简单使用。

代码清单:\codes\03\3.5\GridViewDemo\res\layout\activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <GridView
        android:id="@+id/gridview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:horizontalSpacing="6dp"
        android:numColumns="3"
        android:padding="20dp"
        android:verticalSpacing="6dp" />

    <ImageView
        android:id="@+id/iamgeview"
        android:layout_width="match_parent"
        android:layout_height="150dp" />

</LinearLayout>

代码清单:\codes\03\3.5\GridViewDemo\res\layout\cell.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/ivCell" android:layout_width="48dp" android:layout_height="48dp"/>
    
</LinearLayout>

代码清单:\codes\03\3.5\GridViewDemo\src\com\bookdemo\gridviewdemo\ MainActivity.java

public class MainActivity extends Activity {

    private ImageView imageView;
    // 构建一个Drawable ID数组
    private int[] resIds = new int[] { R.drawable.bmp1, R.drawable.bmp2,
            R.drawable.bmp3, R.drawable.bmp4, R.drawable.bmp5, R.drawable.bmp6,
            R.drawable.bmp7, R.drawable.bmp8, R.drawable.bmp9 };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        GridView gridView = (GridView) findViewById(R.id.gridview);
        imageView = (ImageView) findViewById(R.id.iamgeview);
        
        // 构造数据
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
        for (int i = 0; i < resIds.length; i++) {
            Map<String, Object> cell = new HashMap<String, Object>();
            cell.put("imageview", resIds[i]);
            list.add(cell);
        }
        
        // 构造Adapter
        SimpleAdapter simpleAdapter = new SimpleAdapter(MainActivity.this,
                list, R.layout.cell, new String[] { "imageview" },
                new int[] { R.id.ivCell });
        // 为GridView指定Adapter
        gridView.setAdapter(simpleAdapter);
        // 为GridView指定点击项事件监听器
        gridView.setOnItemClickListener(itemClick);
        imageView.setImageResource(resIds[0]);
    }
    
    private OnItemClickListener itemClick = new OnItemClickListener() {

        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position,
                long id) {
            // 点击后,修改预览图
            imageView.setImageResource(resIds[position]);
        }
    };
}

在模拟器上运行效果:

3.5.5.2 使用BaseAdapter适配GridView数据

GridView的数据源只需要使用实现了ListAdapter接口的适配器即可,对于一些简单的展示效果,可以直接使用ArrayAdapter,稍微复杂的也可以使用SimpleAdapter进行适配数据。还有一个更灵活的BaseAdapter适配器,它是ArrayAdapter和SimpleAdapter的父类。但是BaseAdapter是一个抽象类,需要实现其中的一些方法才可以使用,所以做为底层更为灵活。一般如果数据项的显示定制性非常的强,一般推荐使用BaseAdapter,下面开始介绍BaseAdapter的内容。

因为BaseAdapter是一个抽象类,使用它需要实现如下的几个方法:

· int getCount():当前适配器中存在多少个数据项。

· Object getItem(int position):返回数据集中指定位置的数据项。

· long getItemId(int position):返回数据集中指定位置的数据行Id。

· View getView(int position,View convertView,ViewGroup parent):返回一个视图,用于显示数据集中指定位置的数据项。

为BaseAdapter指定数据之后,BaseAdapter会根据getCount()方法返回数据项的总数,对这些数据进行循环遍历,并调用getView()方法,在其中会获得当前循环的位置的显示视图。所以对于一个BaseAdapter适配器,其主要的代码量就在getView()方法当中。

示例:使用一个BaseAdapter的继承类来适配GridView的数据。

代码清单:\codes\03\3.5\GridViewBaseAdapterDemo\res\layout\activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <GridView
        android:id="@+id/gridview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:horizontalSpacing="6dp"
        android:numColumns="3"
        android:padding="20dp"
        android:verticalSpacing="6dp" />

    <ImageView
        android:id="@+id/iamgeview"
        android:layout_width="match_parent"
        android:layout_height="150dp" />

</LinearLayout>

代码清单:codes\03\3.5\GridViewBaseAdapterDemo\src\com\bookdemo\gridviewbaseadapterdemo\ MainActivity.java

public class MainActivity extends Activity {

    private ImageView imageView;
    // 构建一个Drawable ID数组
    private int[] resIds = new int[] { R.drawable.bmp1, R.drawable.bmp2,
            R.drawable.bmp3, R.drawable.bmp4, R.drawable.bmp5, R.drawable.bmp6 };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        GridView gridView = (GridView) findViewById(R.id.gridview);
        imageView = (ImageView) findViewById(R.id.iamgeview);        

        // 为GridView指定Adapter
        gridView.setAdapter(new ImageAdapter(this));
        // 为GridView指定点击项事件监听器
        gridView.setOnItemClickListener(itemClick);
        imageView.setImageResource(resIds[0]);
    }

    private OnItemClickListener itemClick = new OnItemClickListener() {

        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position,
                long id) {
            // 点击后,修改预览图
            imageView.setImageResource(resIds[position]);
        }
    };

    public class ImageAdapter extends BaseAdapter {

        Context context;

        public ImageAdapter(Context c) {
            this.context = c;
        }

        @Override
        public int getCount() {
            // 数据源的数据总数
            return resIds.length;
        }

        @Override
        public Object getItem(int position) {
            // position位置的数据
            return resIds[position];
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ImageView imageview;
            // 如果convertView为null,使用数据源的数据
            // 构造数据项显示的View
            if (convertView == null) {
                imageview = new ImageView(context);
                imageview.setImageResource(resIds[position]);
                imageview.setScaleType(ImageView.ScaleType.FIT_XY);
            } else {
                imageview = (ImageView) convertView;
            }
            return imageview;
        }
    }
}

在模拟器上运行效果:

3.5.6 滚动视图组件(ScrollView)

ScrollView位于android.widget.ScrollView包下,直接继承自FrameLayout,所以可以把ScrollView看成一个特殊的FrameLayout,因为它可以使用户滚动显示一个占据的空间尺寸大于设备物理屏幕显示的尺寸的视图列表。值得注意的是,ScrollView只能包含一个子View,在实际项目中,通常包含一个垂直的LinearLayout。

ScrollView不能和ListView一起使用,因为ListView已经对垂直方向的滚动做了处理,它会使ListView的内容大于设备物理屏幕显示的内容的时候,强制具有垂直滚动的效果,所以这里使用ScrollView和ListView混合使用是没有意义的。还需注意EditText自带的多行输入的滚动效果,也不可以与ScrollView混合使用,如果在ScrollView中包含了多行的EditText,那么EditText中自带的滚动效果将失效。其概括起来就是ScrollView本身是一个滚动视图的容器,对于一些自带了滚动效果的UI组件,是无法和它一起混合使用的。

在Android中,与ScrollView组件类似的还有一个HorizontalScrollView组件,这个组件与ScrollView的作用相反,主要作用于水平方向的滚动,了解了ScrollView就基本上了解了HorizontalScrollView,所以这里不再对它进行专门讲解。

示例:使用ScrollView实现滚动显示。

代码清单:\codes\03\3.5\ScrollDemo\res\layout\ activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="垂直滚动视图"
            android:textSize="30dp" />

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:src="@drawable/bmp1" />

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:src="@drawable/bmp2" />

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:src="@drawable/bmp3" />

        <EditText
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:maxLines="2"
            android:singleLine="false"
            android:text="ScrollView位于android.widget.ScrollView包下,直接继承自FrameLayout。" />

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:src="@drawable/bmp4" />

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:src="@drawable/bmp5" />

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:src="@drawable/bmp6" />
    </LinearLayout>

</ScrollView>

在模拟器上运行效果:

3.5.7 图片切换器(ImageSwitcher)

3.5.7.1 ImageSwitcher简介

ImageSwitcher控件与ImageView类似,都可以用于显示图片,但是ImageSwitcher主要是用于多张图片的切换显示。

ImageSwitcher是一个图片切换器,它间接继承自FrameLayout类,和ImageView相比,多了一个功能,那就是它所显示的图片切换时,可以设置动画效果,类似于淡进淡出效果,以及左进右出滑动等效果。

既然ImageSwitcher是用来显示图片的控件,AndroidAPI为我们提供了三种不同方法来设定不同的图像来源,方法有:

· setImageDrawable(Drawable):指定一个Drawable对象,用来给ImageSwitcher显示。

· setImageResource(int):指定一个资源的ID,用来给ImageSwitcher显示。

· setImageURL(URL):指定一个URL地址,用来给ImageSwitcher显示URL指向的图片资源。

3.5.7.2 ImageSwitcher切换图片效果

ImageSwitcher可以设置图片切换时动画的效果。对于动画效果的支持,是因为它继承了ViewAnimator类,这个类中定义了两个属性,用来确定切入图片的动画效果和切出图片的动画效果:

· android:inAnimation:切入图片时的效果。

· android:outAnimation:切出图片时的效果。

以上两个属性如果在XML中设定的话,当然可以通过XML资源文件自定义动画效果,但是如果只是想使用Android自带的一些简单的效果的话,需要设置参数为"@android:anim/AnimName"来设定效果,其中AnimName为指定的动画效果。如果在代码中设定的话,可以直接使用setInAnimation()和setOutAnimation()方法。它们都传递一个Animation的抽象对象,Animation用于描述一个动画效果,一般使用一个AnimationUtils的工具类获得。

对于动画效果,一般定义在android.R.anim类中,它是一个final类,以一些int常量的形式,定义的样式,这里仅仅介绍两组样式,淡进淡出效果,以及左进右出滑动效果,如果需要其他效果,可以查阅官方文档。

· fede_in:淡进。

· fade_out:淡出

· slide_in_left:从左滑进。

· slide_out_right: 从右滑出。

一般使用的话,通过这些常量名称就可以看出是什么效果,这里并不是强制Xxx_in_Xxx就一定对应了setInAnimation()方法,但是一般如果不成组设定的话,效果会很丑,建议还是成组的对应In和Out设定效果。

在使用ImageSwitcher的时候,有一点特别需要注意的,需要通过setFactory()方法为它设置一个ViewSwitcher.ViewFactory接口,设置这个ViewFactory接口时需要实现makeView()方法,该方法通常会返回一个ImageView,而ImageSwitcher则负责显示这个ImageView。如果不设定ViewFactory,ImageSwitcher将无法使用。因为setFactory()方法被声明在ViewSwitcher类中,而ImageSwitcher直接继承自ViewSwitcher类。ViewSwitcher的功能与ImageSwitcher类似,只是ImageSwitcher用于展示图片,而ViewSwitcher用于展示一些View视图。

可以这么理解,通过ViewFactory中的makeView()方法返回一个新的View视图,用来放入ViewSwitcher中展示,而对于ImageSwitcher而言,这里通常返回的是一个ImageView。

3.5.7.3 使用ImageSwitcher切换图片

接下来通过一个示例来演示如何通过一个ImageSwitcher切换图片,并在切换的时候增加淡进淡出的效果。

首先定义一个布局资源文件,其中包含一个ImageSwitcher和两个Button。

代码清单:\codes\03\3.5\ImageSwitcherDemo\res\layout\activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   &nbspxmlns;tools="http://schemas.android.com/tools"
   &nbspandroid;layout_width="match_parent"
   &nbspandroid;layout_height="match_parent"
   &nbspandroid;paddingBottom="@dimen/activity_vertical_margin"
   &nbspandroid;paddingLeft="@dimen/activity_horizontal_margin"
   &nbspandroid;paddingRight="@dimen/activity_horizontal_margin"
   &nbspandroid;paddingTop="@dimen/activity_vertical_margin"
   &nbsptools;context=".ImageSwitcherActivity" android:orientation="vertical">
    
    <ImageSwitcher
       &nbspandroid;id="@+id/imageSwitcher1"
       &nbspandroid;layout_width="fill_parent"
       &nbspandroid;layout_height="150dp"
         />
    <Button
       &nbspandroid;id="@+id/btnadd"
       &nbspandroid;layout_width="fill_parent"
       &nbspandroid;layout_height="wrap_content"
       &nbspandroid;text="上一张" />
    <Button
       &nbspandroid;id="@+id/btnSub"
       &nbspandroid;layout_width="fill_parent"
       &nbspandroid;layout_height="wrap_content"
       &nbspandroid;text="下一张" />
</LinearLayout>

在Activity中响应两个按钮的点击事件,切换ImageSwitcher显示的图片。

代码清单:\codes\03\3.5\ImageSwitcherDemo\src\com\bookdemo\imageswitcherDemo\ImageSwitcherActivity.java

public class ImageSwitcherActivity extends Activity {
   &nbspprivate Button btnAdd, btnSub;
   &nbspprivate ImageSwitcher imageSwitcher;
   &nbspprivate int index = 0;
   &nbspprivate List<Drawable> list;

    @Override
   &nbspprotected void onCreate(Bundle savedInstanceState) {
       &nbspsuper.onCreate(savedInstanceState);
       &nbspsetContentView(R.layout.activity_main);
       &nbspputData();
       &nbspimageSwitcher = (ImageSwitcher) findViewById(R.id.imageSwitcher1);
       &nbspbtnAdd = (Button) findViewById(R.id.btnadd);
       &nbspbtnSub = (Button) findViewById(R.id.btnSub);
       &nbspbtnAdd.setOnClickListener(myClick);
       &nbspbtnSub.setOnClickListener(myClick);

        // 通过代码设定从左缓进,从右换出的效果。
       &nbspimageSwitcher.setInAnimation(AnimationUtils.loadAnimation(
               &nbspImageSwitcherActivity.this, android.R.anim.slide_in_left));
       &nbspimageSwitcher.setOutAnimation(AnimationUtils.loadAnimation(
               &nbspImageSwitcherActivity.this, android.R.anim.slide_out_right));
       &nbspimageSwitcher.setFactory(new ViewFactory() {

            @Override
           &nbsppublic View makeView() {
                // makeView返回的是当前需要显示的ImageView控件,用于填充进ImageSwitcher中。
               &nbspreturn new ImageView(ImageSwitcherActivity.this);
            }
        });
       &nbspimageSwitcher.setImageDrawable(list.get(0));
    }

   &nbspprivate View.OnClickListener myClick = new OnClickListener() {

        @Override
       &nbsppublic void onClick(View v) {
           &nbspswitch (v.getId()) {
           &nbspcase R.id.btnadd:
               &nbspindex--;
               &nbspif (index < 0) {
                    // 用于循环显示图片
                   &nbspindex = list.size() - 1;
                }
                // 设定ImageSwitcher显示新图片
               &nbspimageSwitcher.setImageDrawable(list.get(index));
               &nbspbreak;

           &nbspcase R.id.btnSub:
               &nbspindex++;
               &nbspif (index >= list.size()) {
                    // 用于循环显示图片
                   &nbspindex = 0;
                }
               &nbspimageSwitcher.setImageDrawable(list.get(index));
               &nbspbreak;
            }
        }
    };

   &nbspprivate void putData() {
        // 填充图片的Drawable资源数组
       &nbsplist = new ArrayList<Drawable>();
       &nbsplist.add(getResources().getDrawable(R.drawable.bmp1));
       &nbsplist.add(getResources().getDrawable(R.drawable.bmp2));
       &nbsplist.add(getResources().getDrawable(R.drawable.bmp3));
       &nbsplist.add(getResources().getDrawable(R.drawable.bmp4));
       &nbsplist.add(getResources().getDrawable(R.drawable.bmp5));
    }
}

在模拟器上运行效果如下:

3.5.8 惰性装载组件 (ViewStub)

<include…/>标签实现了把其它布局资源包含进某个特定的布局中的功能,在很多平台的开发中,都有类似的概念。Android中也对其进行了支持,它的主要功能是可以在不同的文件中编辑界面的布局组件。

ViewStub(惰性装载组件),直接继承于View类,是一个无形的、零大小的视图,可以在程序运行的过程中,通过懒加载的模式,把设定布局资源inflate进目标布局中。当一个ViewStub的inflate()方法被调用或者调用setVisibility()显示ViewStub的时候,这个ViewStub被设定的View才会被加载,并替换当前ViewStub的位置。因此,ViewStub存在于视图层,直到inflate()或setVisibility()方法被调用,否则将不加载此组件,所以消耗的资源小,通常也叫做"懒惰的include"。

ViewStub的优点是,在某些场景中,可以隐藏一些View组件,待用户需要展示的时候再加载到目标布局中,这个时候就可以用到ViewStub组件了,这样可以减少资源的消耗,使初始化的加载速度变快。

ViewStub在View的基础上新增了两个XML属性,也为其提供了对应的getter、setter方法,这两个增加的XML属性如下:

· android:inflateId:重写ViewStub的父布局控件的Id。

· android:layout:设置ViewStub被inflate的布局控件Id。

对于ViewStub而言,它需要监听的事件只有一个,在被加载的时候触发的事件ViewStub.OnInflateListener,在这个事件中,需要实现一个onInflate()方法,下面是onInflate()方法的完整签名:

void onInflate(ViewStub stub, View inflated);

在VIewStub.OnInflateListener事件的onInflate()方法的参数中,stub为当前待inflate的ViewStub控件,inflated参数为当前被inflate的View视图,可以对其进行一些额外的操作。

在使用ViewStub的过程中,有一点需要特别注意。对于一个ViewStub而言,当inflate()或setVisibility()方法被调之后,这个ViewStub在布局中将被指定的View替换,所以调用过inflate()方法的ViewStub,如果被隐藏后想要在此可见,将不能在此调用inflate()方法了,而是需要调用setVisibility()方法将其设置为可见,这就是这两个方法的区别。

示例:使用ViewStub加载一个RatingBar,并操作它。

代码清单:\codes\03\3.5\ViewStubDemo\res\layout\activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   &nbspxmlns;tools="http://schemas.android.com/tools"
   &nbspandroid;layout_width="match_parent"
   &nbspandroid;layout_height="match_parent"
   &nbspandroid;orientation="vertical" >
    <!-- 使用include标签加载一个id为start的控件 -->
    <include
       &nbspandroid;layout_width="wrap_content"
       &nbspandroid;layout_height="wrap_content"
       &nbsplayout="@layout/start" />

    <LinearLayout
       &nbspandroid;layout_width="wrap_content"
       &nbspandroid;layout_height="wrap_content"
       &nbspandroid;background="#FFCCDD"
       &nbspandroid;orientation="vertical" >
        <!-- 定义一个ViewStub 给其父Layout指定Id为inflatedStart -->
        <ViewStub
           &nbspandroid;id="@+id/stub"
           &nbspandroid;inflatedId="@+id/inflatedStart"
           &nbspandroid;layout_width="wrap_content"
           &nbspandroid;layout_height="wrap_content"
           &nbspandroid;layout="@layout/start" />
    </LinearLayout>

    <Button
       &nbspandroid;id="@+id/btn1"
       &nbspandroid;layout_width="wrap_content"
       &nbspandroid;layout_height="wrap_content"
       &nbspandroid;text="动态添加布局" />

    <Button
       &nbspandroid;id="@+id/btn2"
       &nbspandroid;layout_width="wrap_content"
       &nbspandroid;layout_height="wrap_content"
       &nbspandroid;text="动态隐藏布局" />
    <Button
       &nbspandroid;id="@+id/btn3"
       &nbspandroid;layout_width="wrap_content"
       &nbspandroid;layout_height="wrap_content"
       &nbspandroid;text="操作StubInflate的控件" />
</LinearLayout>

代码清单:\codes\03\3.5\ViewStubDemo\res\layout\start.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <RatingBar
        android:id="@+id/ratingBar1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

代码清单:\codes\03\3.5\ViewStubDemo\src\com\bookdemo\viewstubdemo\ MainActivity.java

public class MainActivity extends Activity {
    private Button btn1, btn2,btn3;
    private ViewStub viewStub;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //获取控件,绑定事件
        btn1 = (Button) findViewById(R.id.btn1);
        btn2 = (Button) findViewById(R.id.btn2);
        btn3 = (Button) findViewById(R.id.btn3);
        viewStub = (ViewStub) findViewById(R.id.stub);
        viewStub.setOnInflateListener(inflateListener);
        btn1.setOnClickListener(click);
        btn2.setOnClickListener(click);
        btn3.setOnClickListener(click);
    }

    private OnInflateListener inflateListener=new OnInflateListener() {
        
        @Override
        public void onInflate(ViewStub stub, View inflated) {
            // inflaye ViewStub的时候显示
            Toast.makeText(MainActivity.this, "ViewStub is loaded!", Toast.LENGTH_SHORT).show();
        }
    };
    
    private View.OnClickListener click = new OnClickListener() {
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
            case R.id.btn1:
                try {
                    //如果没有被inflate过,使用inflate膨胀
                    LinearLayout layout=(LinearLayout)viewStub.inflate();
                    RatingBar bar=(RatingBar)layout.findViewById(R.id.ratingBar1);
                    bar.setNumStars(4);
                } catch (Exception e) {
                    //如果使用inflate膨胀报错,就说明已经被膨胀过了,使用setVisibility方法显示
                    viewStub.setVisibility(View.VISIBLE);
                }                
                break;

            case R.id.btn2:
                //隐藏ViewStub
                viewStub.setVisibility(View.GONE);
                break;
            case R.id.btn3:
                //操作被inflate的控件,需要得到当前布局的对象
                //然后通过这个对象去找到被inflate的控件。
                //因为否则在这个示例中,会找到include标签引入的控件
                LinearLayout linearLayout=(LinearLayout)findViewById(R.id.inflatedStart);
                RatingBar rBar=(RatingBar)linearLayout.findViewById(R.id.ratingBar1);
                float numStart=rBar.getRating();
                numStart++;
                if(numStart>4)
                {
                    numStart=0;
                }
                rBar.setRating(numStart);
                break;
            }
        }
    };
}

在模拟器上运行效果:

3.5.9 网络视图(WebView)

WebView组件本身实现了一个浏览器的实现,它的内核是基于WebKit引擎的。所以在某些情况下,可以直接使用WebView显示加载一个网页,当然这是需要连接互联网的。如果有需要,完全可以使用WebView轻松的开发出自己的浏览器。

WebView使用WebKit渲染引擎显示Web页面,并且可以对打开的页面进行向前、后退导航、放大或者缩小、执行文本搜索等等操作。这些操作可以直接通过WebView提供的方法进行操作,这些方法包括:

· void goBack():后退导航。

· void goForward():前进导航。

· void goBackOrForward(int steps):根据steps参数确定导航方向。

· void loadUrl(String url):加载指定的URL对应的网页。

· void reload():重新加载当前URL。

· void stopLoading():停止当前正在加载的URL继续加载。

· boolean zoomln():放大网页。

· boolean zoomOut():缩小网页。

· WebSetting getSettings():获得Web设置。

· void setWebViewClient(WenViewClient client):监听WebView的请求与状态。

· void setWebChromeClient(WebChromeClient client):处理Web页面的响应。

WebView不仅可以使用loadUrl()方法加载一个网络Uri,同时也可以加载本地HTML文件,如下:

· 加载一个网络Uri:webView.loadUrl("http://developer.android.com/guide/components/index.html");

· 加载一个assets下的HTML文件:webView.loadUrl("file:///android_asset/Xxx.html");

如果需要加载网络地址,还需要在清单文件AndroidManifest.xml中为应用注册访问网络的权限:

<uses-permission android:name="android.permission.INTERNET" />

因为WebView的内核是基于WebKit的,所以可以说它是非常的强大的,基本上所有浏览器可以完成的工作它都可以完成,这里着重介绍三个类,WebSetting、WebViewClient和WebChromeClient,它们分别表示WebView的设置、WebView的状态以及WebView处理Web页面。

在创建WebView的时候,系统会给WebView一个默认的设置,开发人员可以通过getSettings()方法获得这个WebSettings对象,进而修改WebView的设置信息,这里介绍几个常用的设置:

· setAllowFileAccess:启用或者禁止WebView访问文件数据。

· setBloackNetworkImage:是否显示网络图片。

· setBuiltInZoomControls:是否使用内置的放大机制。

· setCacheMode:设置缓存的模式,默认支持。

· setJavaSciptEnabled:是否支持JavaScript脚本,默认不支持。

· setDefaultFontSize:设置默认的字体大小。

· setDefaultTextEncodingName:设置解码时使用的默认编码。

· setFixedFontFamily:设置固定的字体。

· setLayoutAlorithm:设置布局方式。

· setSupportZoom:设置是否支持缩放。

WebView还可以通过setWebViewClient()方法,为WebView指定一个WebViewClient对象,WebViewClient主要用来处理WebView的各种通知与请求事件,比如:

· onPageStart():开始加载网页的时候被回调。

· onLoadResource():获取网页中包含的每一条资源时被回调。

· onPageFinish():网页被加载完毕时回调。

· onReceiveError():加载网页时,出错回调此事件。

· onReceivedHttpAuthRequest():收到Http身份验证请求时回调此事件。

WebView还支持JavaScript,并且可以获取网页的固有信息的改版,如图表和标题的改变,这需要使用setWebChromeClient()方法为WebView指定一个WebChromeClient对象。WenChromeClient对象主要辅助WebView处理JavaScript的对话框、网站图标、网站标题、加载进度等操作。它有如下回调方法监听这些操作:

· onCreateWindow():创建WebView。

· onCloseWindow():关闭WebView。

· onJsAlert():处理JavaScript中的Alert对话框。

· onJsPrompt():处理JavaScript中的Prompt对话框。

· onJsConfim():处理JavaScript中的Confim对话框。

· onProgressChanged():处理加载进度的改变。

· onReceivedIcon():网页图标的更改。

· onReceivedTitle():网页Title的更改。

示例:讲解如何使用ViewView加载一个Web页面,并对其支持JavaScript。

代码清单:\codes\03\3.5\WebViewDemo\src\com\bookdemo\timelinedemo\ TimeLineActivity.java

public class TimeLineActivity extends Activity {
    private WebView wvShow;
    private static final String TAG = "main";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_timeline);
        wvShow = (WebView) findViewById(R.id.wvShow);
        // 启用对JavaScript的支持
        wvShow.getSettings().setJavaScriptEnabled(true);

        final Activity activity = this;
        wvShow.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                super.onPageStarted(view, url, favicon);
                Log.i(TAG, "onPageStarted被调用,开始请求网页,资源Url是"+url);
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                Log.i(TAG, "onPageFinished被调用,结束请求网页,资源Url是"+url);
            }

            @Override
            public void onLoadResource(WebView view, String url) {
                super.onLoadResource(view, url);
                Log.i(TAG, "onLoadResource被调用,资源Url是"+url);
            }

            @Override
            public void onReceivedError(WebView view, int errorCode,
                    String description, String failingUrl) {
                Log.i(TAG, "onReceivedError被调用,请求网页发生错误");
            }
        });
        // 加载Web网页
        wvShow.loadUrl("http://wx.iyaohe.cc/timeline/timeline.html");
    }
}

在模拟器上运行效果如下:

通过LogCat查看日志输出信息,关于日志的内容,会在后面的章节中讲解:

通过日志输出可以看出,Web页面上每一个资源都是被单独请求的,将会回调onLoadResource()方法,这也符合浏览器的HTTP请求规则。

注意,读者在运行这个示例的时候,如果加载网页出错,改变请求的网址即可。 Cs8vUetE+Aq6tcy5gp+3zgTrHCHmXAO7U709ESjykITLzY5o6OlUj6gA67apHHgV

点击中间区域
呼出菜单
上一章
目录
下一章
×