从零开始--系统深入学习android(实践-让我们开始写代码-Android框架学习-1.用户界面和布局)

第1章 用户界面和布局

应用程序的用户界面就是用户能看到并可以与它交互的任何东西。Android提供多种预置的UI组件,如结构化布局对象和允许你为应用程序创建图形用户界面的UI控件。Android也会为特殊的接口提供其他UI模块,如对话框,通知和菜单。在一个Android应用中,所有用户界面元素都是由View和ViewGroup对象创建的。View 是一种可以在屏幕上绘制某种画面并且可以与用户互动的对象。ViewGroup对象则是为了定义布局的接口而保存其他View(和ViewGroup)对象。Android提供一个View和ViewGroup子类的集合,这个集合能为你提供相同的输入控制(例如按钮和文本框)和各种各样的布局模式(例如一个线性或者相对布局)

1.1 用户界面布局

对应用程序的每个组件来说,用户界面都是由View对象和ViewGroup对象的层次结构来定义的,如图1-1所示。每一个view group都是用来组织子view的一个不可见容器,然而子 views可能是输入控制UI或者绘制UI某些部分的其他widgets。这个树形结构可以根据你的需要简单化或复杂化。(但是对于性能来说简单最好)

 

图1-1  定义UI布局的view层次结构图

为了声明你的布局,你可以在代码中实例化View对象然后启动构建树,但定义布局最容易、最有效的方法是利用XML文件。XML文件可以为布局提供一个可读结构,这与HTML文件相似。一个View的XML节点名称与它代表的Android类相对应。所以UI里的一个<TextView>节点会创建一个TextView widget,一个<LinearLayout>节点会创建一个LinearLayout view group。例如,包含一个文本视图和一个按钮的简单纵向布局,正如代码清单1-1所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="fill_parent" 
              android:layout_height="fill_parent"
              android:orientation="vertical" >
    <TextView android:id="@+id/text"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="I am a TextView" />
    <Button android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="I am a Button" />
</LinearLayout>

代码清单1-1

当你在应用程序中载入一个布局资源时,Android会初始化每个进入到正运行对象的布局节点,这时你可以用它来定义附加行为,查询对象状态,或修改布局。

1.2 用户界面组件

你不需要用View和ViewGroup对象来构建你所有的UI。Android系统提供了几个标准UI布局的应用程序组件,因此你只需要定义内容。这些应用程序组件都有一组唯一的API,如Action Bar,Dialogs,和Status Notifications,这些都会在他们各自的文档中被一一介绍。

1.3 布局(Layout)

布局为用户界面定义了一个可视化结构。可以用两种方式声明一个布局:

◆在XML中声明UI元素

Android提供了简单的XML元素,它的元素名字与View类以及子类对应,就像布局和widgets一样。

◆在运行时动态实例化布局元素

使用代码创建布局元素(并且操作他们的属性)

 

Android提供了非常灵活的方法来声明和管理应用UI。例如,可以先在XML中声明默认布局,屏幕元素会根据它们的属性显示。接下来可以在应用中添加代码来修改屏幕对象的状态,也可以在运行时修改在XML中声明的对象。 在XML中声明UI的好处是,可以更好地区分显示和控制这些行为的代码。UI描述与应用代码无关,也就是说可以修改和调整XML中的UI布局但是不用修改源java代码。例如,能够为不同的屏幕目标、不用的设备屏幕大小、不同的语言创建不同的XML布局文件。另外,在XML中声明布局使得UI更容易可视化,这样更容易调试界面。本章主要用于教会你如何在xml中声明布局。如果你对运行时动态创建布局感兴趣,那么请参考viewgroup以及view类说明。 一般来说,xml声明UI元素的词汇和类的命名以及方法名密切相关,元素根据类名、属性名根据方法名来命名。实际上,能猜到什么XML属性对应一个类的方法, 或者能够猜到哪个类对应给定的XML元素,这往往是直接的对应。但是,注意并不是所有的词汇都是等同的。在某些情况下,有的命名有些许不同。例如,EditText元素有个text属性对应EditText.setText()方法。

1.4 写XML

使用android的XML词汇,可以快速的设计UI布局和它们包含的屏幕元素。跟创建web页面使用html类似(一系列的嵌套)。 每一个布局文件必须包含一个根节点。这个根节点必须是一个View 或者ViewGroup对象。一旦你定义了根节点,可以添加任意的布局对象或者widgets作为子元素,逐步构建一个View层次布局。例如,这是一个XML布局文件使用了纵向的线性布局(LinearLayout)来排列一个TextView和Button,如代码清单1-2所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="fill_parent" 
              android:layout_height="fill_parent" 
              android:orientation="vertical" >
    <TextView android:id="@+id/text"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="Hello, I am a TextView" />
    <Button android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello, I am a Button" />
</LinearLayout>


代码清单1-2

这个文件应该是在当前android工程/res/layout/目录下.xml的扩展名来保存这个文件,这样才会正确编译。我们接下来会讨论这里显示的每个属性。

1.5 加载XML资源

当编译应用程序的时候,每一个XML布局文件都被编译到view资源中。应该在Activity.onCreate()回调方法中实现加载布局资源。通过调用setContentView()来设置布局资源(按照R.layout.layout_file_name的格式)。例如,如果你的XML布局文件被保存为main_layout.xml,那么可以在activity中这样加载,如代码清单1-3所示:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_layout);
}

 

代码清单1-3

1.6 属性

每一个View和ViewGroup对象支持他们各自的XML属性。有些属性是特定的View对象(例如,TextView支持textSize属性)但是只要继承这个类,这些属性也可以在其他地方使用。有些属性是对所有的的View对象都适用的,因为它们是从最原始抽象的View类继承下来的(像id属性)。其它的属性被认为是布局参数,比较容易的理解是这样的,ViewGroup代表布局,View代表布局中的元素(比如Button之类),但是ViewGroup是继承自View的,所以你可以理解为,一切皆View。

1.6.1 ID

每一个View对象都可能有一个int型的ID和它相关,这是在树中View对象的唯一标识。当程序编译完,这个ID就是一个引用,但是ID属性在XML布局文件里面是通过string类型赋值的。下面让我们看一下XML中如何定义一个View对象的ID:

android:id="@+id/my_button"

 

这个@符号在字符串开头表明xml解析器会解析和扩展剩余的ID字符串,并把它定义为ID资源。“+”表示这是一个新的资源名字,要创建并且增加的我们的资源中(在R.java文件里)。Android framework层也提供了一部分ID资源。如果直接使用android 资源ID的话,你不需要”+”,但是要加上android包名命名空间,如下所示:

android:id="@android:id/empty"

 

Android的包命名空间中,我们现在引用android.R资源类的ID,而不是本地的资源类中引用。 为了创建views,并在应用中使用,常用的流程如下:

1.在布局文件中创建view/widget,并为他们分配一个唯一的ID

<Button android:id="@+id/my_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/my_button_text"/>

 

2.接下来在代码中创建view对象实例,从布局中找到它(一般在OnCreate()方法中)

Button myButton = (Button) findViewById(R.id.my_button);

 

如果是在相对布局(RelativeLayout)中的话定义好ID是非常重要的。因为它们的布局定义就是需要依赖ID。一个ID在整个树中不一定要求唯一,但是你搜索的部分树应该是唯一的(经常是整个树,所以最好的办法是在整个树中是唯一的)尽量让你的ID全局唯一。

1.6.1布局参数

XML布局属性命名为layout_something 的文件,在其所在的视图组中定义了合适的view的布局参数。

每一个ViewGroup类都是继承了Viewgroup.LayoutParams。一般子类使用父类的布局参数,刚开始也许有些难理解,让我们看下图1-2::

 

图1-2 带布局参数的View层级

每一个布局参数子类拥有自己的语法赋值。每一个子元素必须根据父元素来定义恰当的布局参数,尽管父元素也为它的子元素定义了不同的布局参数。所有的ViewGroup包含宽度和高度,每一个View都要定义这两个属性。许多布局参数也包含可选边距。宽度和高度可以使用精确的测量工具,但一般不这么做。往往,会使用这些常量来设置宽度和高度。

◆wrap_content会根据内容自动调整视图到合适的大小(就是内容多大,宽高就多大)。

◆fill_parent会告诉你的View变成和父ViewGroup的范围一样大。

一般说来,不推荐使用用绝对单位像是像素来指定布局的宽度和高度。而使用相对的测量工具像是与密度无关的单位(dp),wrap_content,或者fill_parent是一种较好的方法,因为它能够保证您的应用程序在不同屏幕大小的设备上都能够正常显示。

1.7 布局位置

一个View的几何形状是一个矩形。一个View有一个坐标、用left和top参数,和两个表示宽高尺寸的参数表示,你可以理解为一个左上角的点,和根据这个点的宽高就是一个矩形了。坐标和尺寸的单位是像素。调用getLeft()和getTop()方法能够得到视图的坐标。前者返回left、或者X轴的坐标。后者返回top,或者Y轴坐标。这些方法都返回的View坐标是相对于父视图的坐标。比如,如果getLeft()返回20,这表示当前视图在父视图左侧边缘向右20个像素的地方。另外,提供了许多便利的方法避免了不必要的计算,像是getRight()和getBottom()这些方法返回视图矩形的右侧和底侧边缘。例如,调用getRight()和进行这个计算是一样的:getLeft()+getWidth()。

1.8 大小,填充(padding)和边距(Margins)

View大小是使用宽度和高度表示的。一个View实际上有两对宽高值。第一对是测量得到的宽度和高度。这些尺寸定义了一个View想在父类中占多大。测量尺寸能够通过调用getMeasuredWidth()和getMeasuredHeight()得到。第二对被简单的成为宽度和高度,有时成为绘制的宽度和高度。这些尺寸定义了在绘制时和布局后视图在屏幕上的实际大小。它或许和测量得到的宽度和高度不同。这些参数能够通过调用getWidth()和getHeight()得到。

为了测量尺寸,需要考虑padding。padding 表示View的左侧、上侧、右侧和下侧部分。padding能够用来偏移视图中的内容、通过指定一定数量的像素。比如,左侧padding是2,会让视图内容偏移视图左边缘2个像素。padding能够通过使用i setPadding(int,int,int,int)方法进行设置,调用getPaddingLeft(),getPaddingTop(),getPaddingRight()和getPaddingBottom()进行查询。尽管View能够定义padding,它不提供任何margins的支持。只有view group提供了这样的支持。

1.9 一般布局

ViewGroup的每个子类提供了唯一的方法来显示View,接下来是在Android 平台常用一些的布局类型。 注意:虽然为了UI设计,可以在一个布局里面放置一个或者多个布局,但是应该力求让布局层次尽可能的少。这样性能更高如果视图层次很少会绘制的很快(一个广度视图层次比深度层次视图好很多)

线性布局:这个布局是让其孩子组织成一个单一的水平或垂直行。如果窗口长度超出了屏幕,它会自动创建滚动条。

相对布局:让你能够指定子对象之间的相对位置(孩子A在孩子B的左侧)或者和父对象之间的相对位置(和父对象顶端对齐)

页面视图:显示web页面

1.10 使用适配器构建布局

如果布局是动态的或者非预定义的,可以在运行时使用一个布局子类AdapterView来填充布局。AdapterView类的子类使用一个适配器将数据绑定到它的布局。适配器表现为数据源和AdapterView布局之间的中间人-适配器检索数据(从像是数组或者数据库查询这样的数据源)并将它转换成可以添加到AdapterView布局视图中的条目。通用的适配器布局包括:

List View:显示滚动的列的列表

Grid View:显示滚动的网格的行和列

1.10.1用数据填充适配器布局

可以填充一个AdapterView,如ListView和GridView,通过将AdapterView实例绑定到一个适配器上,这个适配器从外部数据源检索数据并为每个数据条目创建一个布局。 Android提供了许多适配器子类用来检索各种各样的数据并为AdapterView构建布局。最常用的通用适配器是:

1. ArrayAdapter

当数据源是数组的时候,就可以使用这个适配器。默认情况下,ArrayAdapter为每个数组元素创建一个布局,通过在每个元素调用toString()后,将数据存放在TextView里面。

例如,如果您想将一个字符串数组显示在ListView中,使用构造器为每个字符串和字符串数组指定布局初始化一个ArrayAdapter,如代码清单1-4所示:

ArrayAdapter adapter = new ArrayAdapter<String>(this, 
        android.R.layout.simple_list_item_1, myStringArray);

 

代码清单1-4

构造器参数:

◆应用程序Context

◆一个针对字符串数组中的字符串都有一个TextView的布局

◆字符串数组

然后只需要在ListView上调用SetAdapter(),如代码清单2-5所示:

ListView listView = (ListView) findViewById(R.id.listview);
listView.setAdapter(adapter);

 

代码清单2-5

为了调整元素外观,你可以为了数组中的对象重写toString()函数。或者,为每个元素创建非TextView的视图(比如,想获取每个数组元素的ImageView)扩展AdapterArray,重写getView()方法返回想要的View类型。

2. SimpleCursorAdapter

当数据来源是Cursor时,则使用这个适配器。在使用SimpleCursorAdapter时,必须为Cursor的每一行数据指定使用的布局,也必须为Cursor的每一栏指定要使用布局中的哪个控件。比如,创建一个包含用户名称和电话号码的列表。首先执行Cursor数据库的查询,返回的是Cursor一行用户的信息,其中有用户的名称和电话号码等信息;然后创建一个字符串数组用于指定将显示Cursor的哪一列,创建一个integer数组为每一列指定相应的视图来放置数据,如代码清单1-6所示:

String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME, 
                        ContactsContract.CommonDataKinds.Phone.NUMBER};
int[] toViews = {R.id.display_name, R.id.phone_number};

 

代码清单1-6

初始化SimpleCursorAdapter时,传入的布局参数和两个数组对Cursor每个结果都是适用的,代码清单1-7所示:

SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, 
        R.layout.person_name_and_number, cursor, fromColumns, toViews, 0);
ListView listView = getListView();
listView.setAdapter(adapter);

 

代码清单1-7

SimpleCursorAdapter接下来为Cursor每一行使用提供的布局来创建View,通过将每个fromColumns元素插入到指定的View。

 

在应用程序的生命周期中,如果适配器对应数据被改变了,应该调用notifyDataSetChanged()。这会通知相应的View数据变化了,它会自我刷新。

1.10.2处理点击(click)事件

您可以通过实现AdapterView.OnItemClickListener接口,来处理AdapterView每个item的click事件。如代码清单1-8所示:

复制代码
private OnItemClickListener mMessageClickedHandler = new OnItemClickListener() {
    public void onItemClick(AdapterView parent, View v, int position, long id) {
        // Do something 
    }
};
 
listView.setOnItemClickListener(mMessageClickedHandler); 
复制代码

 

代码清单1-8

1.11 线性布局

线性布局(Linear Layout)是一个ViewGroup,它所有的子视图都在一个方向对齐,水平或者垂直。你可以通过android:orientation 属性来指定布局的方向。线性布局的所有子视图排列都是一个靠着另一个,因此垂直列表每行仅仅有一个子视图,不管有多宽。水平列表只能有一行的高度(最高子视图的高度加上边距距离)。线性布局对于每一个子视图涉及到边缘在子视图和权重(左边或者右边以及中间对齐)之间。

 

1.11.1布局权重(Weight)

线性布局支持给个别的子视图设定权重,通过android:layout_weight属性。就一个视图在屏幕上占多大的空间而言,这个属性给其设定了一个重要的值。一个大的权重值,允许它扩大到填充父视图中的任何剩余空间。子视图可以指定一个权重值,然后视图组剩余的其他的空间将会分配给其声明权重的子视图。默认的权重是0。例如,如果有三个文本框,其中两个声明的权重为1,另外一个没有权重,没有权重第三个文本字段不会增加,只会占用其内容所需的面积。其他两个同样的会扩大以填补剩余的空间,在三个文本域被测量后。如果第三个字段,然后给定的权重为2(而不是0),那么它现在的声明比其他的更重要,所以它得到一半2/(2+1+1)的总的剩余空间,而前两个平均分配剩余的。

若在线性布局中创建占有相同空间的子视图,设置每个子视图的android:layout_height属性值为"0dp"(对于垂直线性布局来说),或者设置每个子视图的android:layout_width属性值为"0dp"(对于水平线性布局来说)。然后在设置每个子视图的android:layout_weight属性值为"1"。如果想更详细的了解线性布局的每个子视图的可用属性,可以参考LinearLayout.LayoutParams

下面让我们来看下代码清单1-9的实例:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:paddingLeft="16dp"
    android:paddingRight="16dp"
    android:orientation="vertical" >
    <EditText
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:hint="@string/to" />
    <EditText
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:hint="@string/subject" />
    <EditText
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="top"
        android:hint="@string/message" />
    <Button
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:text="@string/send" />
</LinearLayout>


代码清单1-9

例子运行的结果如下图1-3所示:

 


图1-3  运行后的效果图

1.12 相对布局

RelativeLayout顾名思义,相对布局,在这个容器内部的子元素们可以使用彼此之间的相对位置(例如,在某个视图左边left-of)或者和容器间的相对位置(例如,与父视图左对齐,底部对齐或者居中等)来进行定位。

RelativeLayout(相对布局)是一个为用户界面设计,非常强大的工具.因为它可以消除嵌套视图组,并保持你的布局层次更简洁,从而提高性能。如果你发现自己使用多个嵌套的LinearLayout组,您可能能够取代单一RelativeLayout。

1.12.1定位View

相对布局可以让它的子View指定自己的相对于父View的位置或者视图元素之间的相对位置(通过指定的ID)。你可以使两个元素右边界对齐,或者使一个View在另一个View下方, 或者使View在屏幕居中偏左等等。默认情况下,所有的子View在布局的左上角。所以你必须通过使用布局属性RelativeLayout.LayoutParams中各种不同的可用属性值来定义每个View的位置。

相对布局View的一些可用属性包括:

◆android:layout_alignParentTop

如果设置为“true”,使这一View的顶部边缘匹配父类的顶部边缘

◆android:layout_centerVertical

如果“true”,设置此子视图在父View中垂直居中。

◆android:layout_below

设置此视图的上边缘位于通过资源ID指定的View的下方。

◆android:layout_toRightOf

设置此视图的左边缘位于通过资源ID指定的View的右方。


这仅仅是几个例子,所有的布局属性我们可以在RelativeLayout.LayoutParams中找到。

每个布局属性的值既可以是boolean类型的值来确定布局相对于父布局的位置,也可以是某个子View的ID,来指定布局相对于这个子View的位置。

在你的xml布局文件中,依赖于其他视图的布局可以在声明的时候没有顺序。例如:

你可以声明“View1”在“VIew2”的下方,即使View2是在视图层次结构中最后一个被声明的。下面的例子演示了这种情况。(这是官方的说法,但笔者遇到过的实际情况是顺序是有影响的),下面让我们看下代码清单1-10:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:paddingLeft="16dp"
    android:paddingRight="16dp" >
    <EditText
        android:id="@+id/name"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:hint="@string/reminder" />
    <Spinner
        android:id="@+id/dates"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_below="@id/name"
        android:layout_alignParentLeft="true"
        android:layout_toLeftOf="@+id/times" />
    <Spinner
        android:id="@id/times"
        android:layout_width="96dp"
        android:layout_height="wrap_content"
        android:layout_below="@id/name"
        android:layout_alignParentRight="true" />
    <Button
        android:layout_width="96dp"
        android:layout_height="wrap_content"
        android:layout_below="@id/times"
        android:layout_alignParentRight="true"
        android:text="@string/done" />
</RelativeLayout>


代码清单1-10

例子运行的结果如下图1-4所示:

 

 

图1-4  运行后的效果图

1.13 List View

列表视图是一个纵向显示滚动项的View组合。每一列的项都通过Adapter被自动插入到列表中,其中,Adapter适配器可以从数组或者数据库查询中提取出内容并转化成列表View中的item。

 

1.13.1使用一个装载器

使用一个CursorLoade是避免一个异步任务查询光标Curso时阻塞程序主线程的标准途径。当CursorLoader收到一个Cursor结果,LoaderCallbacks会收到一个对onLoadFinished()的回调,这时可以利用新的Cursor和列表视图更新Adapter并显示结果。

虽然CursorLoader函数在Android3.0(API级别 11)中才第一次引入,程序可以通过引入Support Library使用它们来支持运行Android 1.6及以上的设备。

要查看更多关于利用Loader异步加载数据的信息,请参看Loaders

下面的例子是把ListView作为唯一默认布局元素的活动ListActivity。它完成向Contacts Provider查询姓名和电话号码清单的功能。

为了使用CursorLoader向列表动态加载数据,这个活动实现了LoaderCallbacks接口。如代码清单1-11所示:

public class ListViewLoader extends ListActivity
        implements LoaderManager.LoaderCallbacks<Cursor> {
 
      SimpleCursorAdapter mAdapter;
 
 
    static final String[] PROJECTION = new String[] {ContactsContract.Data._ID,
            ContactsContract.Data.DISPLAY_NAME};
 
    static final String SELECTION = "((" + 
            ContactsContract.Data.DISPLAY_NAME + " NOTNULL) AND (" +
            ContactsContract.Data.DISPLAY_NAME + " != '' ))";
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        ProgressBar progressBar = new ProgressBar(this);
        progressBar.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
                LayoutParams.WRAP_CONTENT, Gravity.CENTER));
        progressBar.setIndeterminate(true);
        getListView().setEmptyView(progressBar);
 
              ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
        root.addView(progressBar);
 
        
        String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME};
        int[] toViews = {android.R.id.text1}; 
     
        mAdapter = new SimpleCursorAdapter(this, 
                android.R.layout.simple_list_item_1, null,
                fromColumns, toViews, 0);
        setListAdapter(mAdapter);
 
      
        getLoaderManager().initLoader(0, null, this);
    }
 
      public Loader<Cursor> onCreateLoader(int id, Bundle args) {
      
        return new CursorLoader(this, ContactsContract.Data.CONTENT_URI,
                PROJECTION, SELECTION, null, null);
    }
 
      public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
     
        mAdapter.swapCursor(data);
    }
 
        public void onLoaderReset(Loader<Cursor> loader) {
    
        mAdapter.swapCursor(null);
    }
 
    @Override 
    public void onListItemClick(ListView l, View v, int position, long id) {
    }
}


代码清单1-11

注意:因为这个例子要向Contacts Provider请求查询数据,程序需要在制作清单文件中请求READ_CONTACTS权限:

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

 

1.14 Grid View

GridView是一个在可滚动的二维网格空间中显示item的ViewGroup。元件会使用ListAdapter自动插入网格布局中。

 

在本教程中,将创建一个缩略图网格。当一个item被选中,会弹出显示该图像的位置的消息框。

1. 首先创建一个名为HelloGridView的工程

2. 找出一些将要使用的的图像。将准备好的图像放在工程的res/drawable/目录下。

3. 打开 res/layout/main.xml文件,并插入以下代码清单1-12:

<?xml version="1.0" encoding="utf-8"?>
<GridView xmlns:android="http://schemas.android.com/apk/res/android" 
    android:id="@+id/gridview"
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent"
    android:columnWidth="90dp"
    android:numColumns="auto_fit"
    android:verticalSpacing="10dp"
    android:horizontalSpacing="10dp"
    android:stretchMode="columnWidth"
    android:gravity="center"
/>


代码清单1-12

创建的GridView将填满整个屏幕。这些属性的含义都很明显。更多关于属性的信息,请参阅的GridView的参考。

4、打开 HelloGridView.java 并在其中的onCreate()函数中插入以下代码清单1-13:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
 
    GridView gridview = (GridView) findViewById(R.id.gridview);
    gridview.setAdapter(new ImageAdapter(this));
 
    gridview.setOnItemClickListener(new OnItemClickListener() {
        public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
            Toast.makeText(HelloGridView.this, "" + position, Toast.LENGTH_SHORT).show();
        }
    });
}


代码清单1-13

当main.xml文件完成内容布局以后,GridView会被findViewById(int)方法从布局中捕捉到。setAdapter()方法设置一个自定义的适配器(ImageAdapter)作为被显示在网格中的元件的源。ImageAdapter会在下一步中创建。

setOnItemClickListener()会传递一个新的 AdapterView.OnItemClickListener消息以便于响应网格元件被选中的事件。匿名实例定义了由onItemClick()回调函数弹出一个Toast消息框显示所选中的网格元件的位置索引号(索引号从零开始计算。在实际程序中可以通过位置索引号获取其全尺寸图像以备其他用途)。

5. 创建一个扩展BaseAdapter并调用ImageAdapter的新类,如代码清代1-14:

public class ImageAdapter extends BaseAdapter {
    private Context mContext;
 
    public ImageAdapter(Context c) {
        mContext = c;
    }
 
    public int getCount() {
        return mThumbIds.length;
    }
 
    public Object getItem(int position) {
        return null;
    }
 
    public long getItemId(int position) {
        return 0;
    }
 
    public View getView(int position, View convertView, ViewGroup parent) {
        ImageView imageView;
        if (convertView == null) { 
            imageView = new ImageView(mContext);
            imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
            imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
            imageView.setPadding(8, 8, 8, 8);
        } else {
            imageView = (ImageView) convertView;
        }
 
        imageView.setImageResource(mThumbIds[position]);
        return imageView;
    }
 
      private Integer[] mThumbIds = {
            R.drawable.sample_2, R.drawable.sample_3,
            R.drawable.sample_4, R.drawable.sample_5,
            R.drawable.sample_6, R.drawable.sample_7,
            R.drawable.sample_0, R.drawable.sample_1,
            R.drawable.sample_2, R.drawable.sample_3,
            R.drawable.sample_4, R.drawable.sample_5,
            R.drawable.sample_6, R.drawable.sample_7,
            R.drawable.sample_0, R.drawable.sample_1,
            R.drawable.sample_2, R.drawable.sample_3,
            R.drawable.sample_4, R.drawable.sample_5,
            R.drawable.sample_6, R.drawable.sample_7
    };
}


代码清单1-14

首先,实例化一些继承自BaseAdapter的必要函数。构造函数和getCount()不用多解释。通常情况下,getItem(int)应该返回一个适配器中指定位置的真实对象,但是在本例中这点被忽略了。同样的,getItemId(int)应该返回元件的真实编号,但是本例中不需要这样。

第一个必须的方法是getView()。这个方法为每一个加入到ImageAdapter的图像创建一个新的View视图。当调用它时,一个View视图对象会被传入并且是可重复使用的(在被调用至少一次以后),所以需要确认对象是否为空。如果为空,就要实例化一个ImageView并根据要呈现的图像设置属性参数。

setLayoutParams(ViewGroup.LayoutParams)设置视图的高度和宽度,这样可以确保不论原图像的大小如何都能适当的调整大小和裁减。

setScaleType(ImageView.ScaleType)声明了图像将依照中心进行裁减(如果需要的话)。

int, int, int) setPadding(int, int, int, int)定义了各边如何进行填充。(需要注意的是,如果图像有不同的纵横比,那么当图像不匹配ImageView给定的尺寸时,较少的填充就会导致图像更多的裁减)。

如果传给android.view.View, android.view.ViewGroup) getView()的View视图不为空,则本地的ImageView会由可重复使用的View初始化。

在getView()方法的最后,被传入的position参数会用于从被作为ImageView资源的mThumbIds数组中选择图像。

剩下的就是定义绘画资源的mThumbIds数组。

6. 运行程序。

可以通过调整GridVie和ImageView的元素体验其使用方法。例如,使用setAdjustViewBounds(boolean)而不使用setLayoutParams(ViewGroup.LayoutParams)。

你可能感兴趣的:(从零开始--系统深入学习android(实践-让我们开始写代码-Android框架学习-1.用户界面和布局))