03UI

来源:互联网 发布:qq农场蜂箱升级数据 编辑:程序博客网 时间:2024/06/06 03:41

本节内容:常见控件的使用方法、详解四种基本布局、引入布局、创建自定义控件、最常用和最难用的控件——ListView、单位和尺寸、编写界面的最佳实践----聊天界面;


常见控件的使用方法:

    android:id:给当前控件定义了一个唯一标识符

    android:layout_width、android:layout_height:match_parent、fill_parent 和wrap_content


TextView:

    android:text :指定TextView 中显示的文本内容

    android:gravity:指定文字的对齐方式,可选值有top、bottom、left、right、center, 可以用“ | ” 来同时指定多个值, 这里我们指定的"center" , 效果等同于"center_vertical|center_horizontal",表示文字在垂直和水平方向都居中对齐。

    android:textSize: 属性可以指定文字的大小(android:textSize="24sp"

    android:textColor: 属性可以指定文字的颜色(android:textColor="#00ff00"

Button:

    android:text :指定Button中显示的文本内容

    主要是为Button 的点击事件注册一个监听器如果你不喜欢使用匿名类的方式来注册监听器,也可以使用实现接口的方式来进行注册,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MainActivity extends Activity implements OnClickListener {
    private Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(this);
    }
 
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.button:
            // 在此处添加逻辑
            break;
        default:
            break;
        }
    }
}


EditText:

    android:hint:属性来指定了一段提示性的文本

    android:maxLines :指定了EditText 的最大行数为两行(android:maxLines="2"

    使用EditText和Button组合通过点击按钮获取输入内容:(同02Activity节中的计算器用法);


ImageView:

    android:src: 属性给ImageView 指定了一张图片(android:src="@drawable/ic_launcher"

    也可以在程序中通过代码动态地更改ImageView 中的图片:在按钮的点击事件里,通过调用ImageView 的setImageResource()方法;


ProgressBar:


    android:visibility:visible(默认)、invisible 和gone。

    还可以通过代码来设置进度条的可见性,使用的是setVisibility()方法,可以传入View.VISIBLE、View.INVISIBLE 和View.GONE 三种值;通过getVisibility()方法来判断ProgressBar 是否可见;

    style:指定不同的样式(style="?android:attr/progressBarStyleHorizontal"


    指定成水平进度条后,我们还可以通过android:max 属性给进度条设置一个最大值,然后在代码中动态地更改进度条的进度。getProgress()和setProgress()(进度为int型数据);


AlertDialog:

    在当前的界面弹出一个对话框,这个对话框是置顶于所有界面元素之上的,能够屏蔽掉其他控件的交互能力,因此一般AlertDialog 都是用于提示一些非常重要的内容或者警告信息。比如为了防止用户误删重要内容,在删除前弹出一个确认对话框。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class MainActivity extends Activity implements OnClickListener {
    ……
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.button:
            AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);
            dialog.setTitle("This is Dialog");
            dialog.setMessage("Something important.");
            dialog.setCancelable(false);
            dialog.setPositiveButton("OK"new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which){}
            });
 
            dialog.setNegativeButton("Cancel"new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {}
            });
            dialog.show();
            break;
        default:
            break;
        }
    }
}


ProgressDialog:


    AlertDialog 有点类似,都可以在界面上弹出一个对话框,都能够屏蔽掉其他控件的交互能力。不同的是,ProgressDialog 会在对话框中显示一个进度条,一般是用于表示当前操作比较耗时,让用户耐心地等待。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MainActivity extends Activity implements OnClickListener {
    ……
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.button:
            ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
            progressDialog.setTitle("This is ProgressDialog");
            progressDialog.setMessage("Loading...");
            progressDialog.setCancelable(true);
            progressDialog.show();
            break;
        default:
            break;
        }
    }
}

    注意:如果在setCancelable()中传入了false,表示ProgressDialog 是不能通过Back 键取消

掉的,这时你就一定要在代码中做好控制,当数据加载完成后必须要调用ProgressDialog 的

dismiss()方法来关闭对话框,否则ProgressDialog 将会一直存在。


详解四种基本布局:


LinearLayout:


    android:orientation:vertical、horizontal(默认);

    注意:排列方向是horizontal,内部的控件就绝对不能将宽度指定为match_parent;同样排列方向是vertical,内部的控件就不能将高度指定为match_parent。

    android:layout_gravity:指定控件在布局中的对齐方式。android:layout_gravity 的可选值和android:gravity(文字在控件中的对齐方式) 差不多, 但是需要注意, 当LinearLayout 的排列方向是horizontal 时,只有垂直方向上的对齐方式才会生效;当LinearLayout 的排列方向是vertical 时,只有水平方向上的对齐方式才会生效。

    android:layout_weight:所有控件按权重比例分配宽度,此时控件的width属性不起作用;两个控件均为1则平分宽度;一个为1一个不指定weight,而width为wrap_content,则一个占满剩余宽度;


RelativeLayout:

相对于父布局进行定位:

    android:layout_alignParentLeft="true"

    android:layout_alignParentTop="true"

    android:layout_centerInParent="true"


    android:layout_alignParentBottom="true"

    android:layout_alignParentRight="true"


相对于控件进行定位

    android:layout_above="@id/button3"

    android:layout_toLeftOf="@id/button3"


    android:layout_below="@id/button3"

    android:layout_toRightOf="@id/button3"

    注意:当一个控件去引用另一个控件的id 时,该控件一定要定义在引用控件的后面;

    android:layout_alignLeft、android:layout_alignRight、android:layout_alignTop 、android:layout_alignBottom;


FrameLayout(不常用):

    没有任何的定位方式,所有的控件都会摆放在布局的左上角;


TableLayout(不常用):

    在TableLayout 中每加入一个TableRow 就表示在表格中添加了一行,然后在TableRow中每加入一个控件,就表示在该行中加入了一列,TableRow 中的控件是不能指定宽度的;

    用于输入密码的EditText , 我们通过将android:inputType="textPassword",把EditText 变为密码输入框;

    使用android:layout_span="2"让登录按钮占据两列的空间;

    使用android:stretchColumns=1表示如果表格不能完全占满屏幕宽度,就将第二列进行拉伸;指定成0 就是拉伸第一列;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    android:stretchColumns="1"
 
    <TableRow>
        <TextView
            android:layout_height="wrap_content"
            android:text="Account:" />
        <EditText
            android:id="@+id/account"
            android:layout_height="wrap_content"
            android:hint="Input your account" />
    </TableRow>
 
    <TableRow>
        <TextView
            android:layout_height="wrap_content"
            android:text="Password:" />
        <EditText
            android:id="@+id/password"
            android:layout_height="wrap_content"
            android:inputType="textPassword" />
    </TableRow>
 
    <TableRow>
        <Button
            android:id="@+id/login"
            android:layout_height="wrap_content"
            android:layout_span="2"
            android:text="Login" />
    </TableRow>
 
</TableLayout>


引入布局:解决了重复编写布局代码的问题

    新建一个布局title.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<LinearLayout   xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/title_bg" >
 
    <Button
        android:id="@+id/title_back"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="5dip"
        android:background="@drawable/back_bg"
        android:text="Back"
        android:textColor="#fff" />
 
    <TextView
        android:id="@+id/title_text"
        android:layout_width="0dip"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_weight="1"
        android:gravity="center"
        android:text="Title Text"
        android:textColor="#fff"
        android:textSize="24sp" />
 
    <Button
        android:id="@+id/title_edit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="5dip"
        android:background="@drawable/edit_bg"
        android:text="Edit"
        android:textColor="#fff" />
 
</LinearLayout>

    两个Button 中我们都使用了android:layout_margin 这个属性,它可以指定控件在上下左右方向上偏移的距离,当然也可以使用android:layout_marginLeft或android:layout_marginTop 等属性来单独指定控件在某个方向上偏移的距离。

    引入布局:修改activity_main.xml 中的代码,如下所示:

1
2
3
4
5
6
7
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
 
    <include layout="@layout/title" />
 
</LinearLayout>


    注意:要将系统自带的标题栏隐藏掉;


创建自定义控件:解决了重复编写响应事件代码的问题


    新建TitleLayout 继承自LinearLayout,让它成为我们自定义的标题栏控件,代码如下所示:


1
2
3
4
5
6
public class TitleLayout extends LinearLayout {
    public TitleLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.title, this);
    }
}

    首先我们重写了LinearLayout 中的带有两个参数的构造函数,在布局中引入TitleLayout控件就会调用这个构造函数。然后在构造函数中需要对标题栏布局进行动态加载,这就要借助LayoutInflater 来实现了。通过LayoutInflater 的from()方法可以构建出一个LayoutInflater对象,然后调用inflate()方法就可以动态加载一个布局文件,inflate()方法接收两个参数,第一个参数是要加载的布局文件的id,这里我们传入R.layout.title,第二个参数是给加载好的布局再添加一个父布局,这里我们想要指定为TitleLayout,于是直接传入this。


    然后我们需要在布局文件中添加这个自定义控件,修改activity_main.xml 中的代码,如下所示:

1
2
3
4
5
6
7
8
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <com.example.uicustomviews.TitleLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        ></com.example.uicustomviews.TitleLayout>
</LinearLayout>

    添加自定义控件和添加普通控件的方式基本是一样的,只不过在添加自定义控件的时候我们需要指明控件的完整类名,包名在这里是不可以省略的。

    然后我们来尝试为标题栏中的按钮注册点击事件,修改TitleLayout 中的代码,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TitleLayout extends LinearLayout {
    public TitleLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.title, this);
        Button titleBack = (Button) findViewById(R.id.title_back);
        Button titleEdit = (Button) findViewById(R.id.title_edit);
        titleBack.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    ((Activity) getContext()).finish();
                }
        });
        titleEdit.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getContext(), "You clicked Edit button",Toast.LENGTH_SHORT).show();
            }
        });
    }
}

    这样的话,每当我们在一个布局中引入TitleLayout,返回按钮和编辑按钮的点击事件就已经自动实现好了。


最常用和最难用的控件——ListView:


简单用法:

    activity_main.xml 中:


1
2
3
4
5
6
7
8
9
10
11
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
 
     <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </ListView>
 
</LinearLayout>

    MainActivity 中:


1
2
3
4
5
6
7
8
9
10
11
public class MainActivity extends Activity {
    private String[] data = { "Apple""Banana""Orange""Watermelon","Pear""Grape""Pineapple""Strawberry""Cherry","Mango" };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ArrayAdapter<String> adapter = new ArrayAdapter<String>( MainActivity.this, android.R.layout.simple_list_item_1, data);
        ListView listView = (ListView) findViewById(R.id.list_view);
        listView.setAdapter(adapter);
    }
}


    我们使用一个data 数组来测试,数组中的数据是无法直接传递给ListView 的,我们还需要借助适配器来完成。Android 中提供了很多适配器的实现类,最好用的就是ArrayAdapter。它可以通过泛型来指定要适配的数据类型,然后在构造函数中把要适配的数据传入即可。ArrayAdapter有多个构造函数的重载,这里由于我们提供的数据都是字符串,因此将ArrayAdapter 的泛型指定为String,然后在ArrayAdapter 的构造函数中依次传入当前上下文ListView 子项布局的id,以及要适配的数据。注意我们使用了android.R.layout.simple_list_item_1 作为ListView 子项布局的id,这是一个Android 内置的布局文件,里面只有一个TextView,可用于简单地显示一段文本。这样适配器对象就构建好了。最后,还需要调用ListView 的setAdapter()方法,将构建好的适配器对象传递进去,这样ListView 和数据之间的关联就建立完成了。


定制ListView 的界面:

    新建类Fruit,作为ListView 适配器的适配类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Fruit {
    private String name;
    private int imageId;
 
    public Fruit(String name, int imageId) {
        this.name = name;
        this.imageId = imageId;   
    }
    public String getName() {
        return name;
    }
    public int getImageId() {
        return imageId;
    }
}

    为ListView 的子项指定一个我们自定义的布局, 在layout 目录下新建fruit_item.xml,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
 
    <ImageView
        android:id="@+id/fruit_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
 
    <TextView
        android:id="@+id/fruit_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginLeft="10dip" />
 
</LinearLayout>

    新建类FruitAdapter,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class FruitAdapter extends ArrayAdapter<Fruit> {
    private int resourceId;
    public FruitAdapter(Context context, int textViewResourceId,List<Fruit> objects) {
        super(context, textViewResourceId, objects);
        resourceId = textViewResourceId;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Fruit fruit = getItem(position); // 获取当前项的Fruit实例
        View view = LayoutInflater.from(getContext()).inflate(resourceId, null);
        ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
        TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
        fruitImage.setImageResource(fruit.getImageId());
        fruitName.setText(fruit.getName());
        return view;
    }
}

    FruitAdapter 重写了父类的一组构造函数,用于将上下文、ListView 子项布局的id 和数据都传递进来。另外又重写了getView()方法,这个方法在每个子项被滚动到屏幕内的时候会被调用。在getView 方法中,首先通过getItem()方法得到当前项的Fruit 实例,然后使用LayoutInflater 来为这个子项加载我们传入的布局,接着调用View 的findViewById()方法分别获取到ImageView 和TextView 的实例,并分别调用它们的setImageResource()和setText()方法来设置显示的图片和文字,最后将布局返回。

    MainActivity 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MainActivity extends Activity {
    private List<Fruit> fruitList = new ArrayList<Fruit>();
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits(); // 初始化水果数据
        FruitAdapter adapter = new FruitAdapter(MainActivity.this,R.layout.fruit_item, fruitList);
        ListView listView = (ListView) findViewById(R.id.list_view);
        listView.setAdapter(adapter);
    }
 
    private void initFruits() {
        Fruit apple = new Fruit("Apple", R.drawable.apple_pic);
        fruitList.add(apple);
        Fruit banana = new Fruit("Banana", R.drawable.banana_pic);
        fruitList.add(banana);
        ......
    }
}


提升ListView 的运行效率:


1.在FruitAdapter 的getView()方法中每次都将布局重新加载了一遍,当ListView 快速滚动的时候这就会成为性能的瓶颈。getView()方法中还有一个convertView 参数,这个参数用于将之前加载好的布局进行缓存,以便之后可以进行重用。

2.每次在getView()方法中还是会调用View 的findViewById()方法来获取一次控件的实例。我们可以新增一个内部类ViewHolder,用于对控件的实例进行缓存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class FruitAdapter extends ArrayAdapter<Fruit> {
    ……
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Fruit fruit = getItem(position);
        View view;
        ViewHolder viewHolder;
 
        if (convertView == null) {
            view = LayoutInflater.from(getContext()).inflate(resourceId, null);
            viewHolder = new ViewHolder();
            viewHolder.fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
            viewHolder.fruitName = (TextView) view.findViewById(R.id.fruit_name);
            view.setTag(viewHolder); // 将ViewHolder存储在View中
        else {
            view = convertView;
            viewHolder = (ViewHolder) view.getTag(); // 重新获取ViewHolder
        }
 
        viewHolder.fruitImage.setImageResource(fruit.getImageId());
        viewHolder.fruitName.setText(fruit.getName());
        return view;
    }
 
    class ViewHolder {
        ImageView fruitImage;
        TextView fruitName;
    }
}


ListView 的点击事件:

    MainActivity 中:

1
2
3
4
5
6
7
listView.setOnItemClickListener(new OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view,int position, long id) {
        Fruit fruit = fruitList.get(position);
        Toast.makeText(MainActivity.this, fruit.getName(),Toast.LENGTH_SHORT).show();
    }
});


单位和尺寸:

    px:像素;

    pt:磅数,1 磅等于1/72 英寸,一般pt 都会作为字体的单位来使用;

    dp:密度无关像素,也被称作dip,和px 相比,它在不同密度的屏幕中的显示比例将保持一致;

    sp:可伸缩像素;

    Android 中的密度就是屏幕每英寸所包含的像素数,通常以dpi 为单位。根据Android 的规定,在160dpi 的屏幕上,1dp 等于1px,而在320dpi 的屏幕上,1dp就等于2px。sp 的原理和dp 是一样的,它主要是用于指定文字的大小。

    总结一下,在编写Android 程序的时候,尽量将控件或布局的大小指定成match_parent或wrap_content,如果必须要指定一个固定值,则使用dp 来作为单位,指定文字大小的时候使用sp 作为单位。


编写界面的最佳实践----聊天界面:

  1. 制作Nine-Patch 图片:

    在Android sdk 目录下有一个tools 文件夹,在这个文件夹中找到draw9patch.bat 文件,

我们就是使用它来制作Nine-Patch 图片的。

    2.编写精美的聊天界面:

    activity_main.xml中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#d8e0e8"
    android:orientation="vertical" >
 
    <ListView
        android:id="@+id/msg_list_view"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:divider="#0000" >
    </ListView>
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
 
        <EditText
            android:id="@+id/input_text"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
           android:hint="Type somthing here"
            android:maxLines="2" />
 
        <Button
            android:id="@+id/send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Send" />
 
    </LinearLayout>
 
</LinearLayout>

    定义消息的实体类,新建Msg,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Msg {
    public static final int TYPE_RECEIVED = 0;
    public static final int TYPE_SENT = 1;
    private String content;
    private int type;
 
    public Msg(String content, int type) {
       this.content = content;
        this.type = type;
    }
    public String getContent() {
        return content;
    }
    public int getType() {
        return type;
    }
}

    接着来编写ListView 子项的布局,新建msg_item.xml,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="10dp" >
 
    <LinearLayout
        android:id="@+id/left_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:background="@drawable/message_left" >
 
        <TextView
            android:id="@+id/left_msg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_margin="10dp"
            android:textColor="#fff" />
 
    </LinearLayout>
 
    <LinearLayout
        android:id="@+id/right_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:background="@drawable/message_right" >
 
        <TextView
            android:id="@+id/right_msg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_margin="10dp" />
 
    </LinearLayout>
 
</LinearLayout>

    接下来需要创建ListView 的适配器类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class MsgAdapter extends ArrayAdapter<Msg> {
    private int resourceId;
 
    public MsgAdapter(Context context, int textViewResourceId, List<Msg>    objects) {
        super(context, textViewResourceId, objects);
        resourceId = textViewResourceId;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Msg msg = getItem(position);
        View view;
        ViewHolder viewHolder;
 
        if (convertView == null) {
            view = LayoutInflater.from(getContext()).inflate(resourceId, null);
            viewHolder = new ViewHolder();
            viewHolder.leftLayout = (LinearLayout) view.findViewById(R.id.left_layout);
            viewHolder.rightLayout = (LinearLayout) view.findViewById(R.id.right_layout);
            viewHolder.leftMsg = (TextView) view.findViewById(R.id.left_msg);
            viewHolder.rightMsg = (TextView) view.findViewById(R.id.right_msg);
            view.setTag(viewHolder);
        else {
            view = convertView;
            viewHolder = (ViewHolder) view.getTag();
        }
 
        if (msg.getType() == Msg.TYPE_RECEIVED) {
            // 如果是收到的消息,则显示左边的消息布局,将右边的消息布局隐藏
            viewHolder.leftLayout.setVisibility(View.VISIBLE);
            viewHolder.rightLayout.setVisibility(View.GONE);
            viewHolder.leftMsg.setText(msg.getContent());
        else if(msg.getType() == Msg.TYPE_SENT) {
            // 如果是发出的消息,则显示右边的消息布局,将左边的消息布局隐藏
            viewHolder.rightLayout.setVisibility(View.VISIBLE);
            viewHolder.leftLayout.setVisibility(View.GONE);
            viewHolder.rightMsg.setText(msg.getContent());
        }
        return view;
    }
    class ViewHolder {
        LinearLayout leftLayout;
        LinearLayout rightLayout;
        TextView leftMsg;
        TextView rightMsg;
    }
}

    最后修改MainActivity 中的代码,来为ListView 初始化一些数据,并给发送按钮加入事件响应,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class MainActivity extends Activity {
    private ListView msgListView;
    private EditText inputText;
    private Button send;
    private MsgAdapter adapter;
 
    private List<Msg> msgList = new ArrayList<Msg>();
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
 
        initMsgs(); // 初始化消息数据
        adapter = new MsgAdapter(MainActivity.this, R.layout.msg_item, msgList);
        inputText = (EditText) findViewById(R.id.input_text);
        send = (Button) findViewById(R.id.send);
        msgListView = (ListView) findViewById(R.id.msg_list_view);
        msgListView.setAdapter(adapter);
 
        send.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                String content = inputText.getText().toString();
                if (!"".equals(content)) {
                    Msg msg = new Msg(content, Msg.TYPE_SENT);
                    msgList.add(msg);
                    adapter.notifyDataSetChanged(); // 当有新消息时,刷新ListView中的显示
                    msgListView.setSelection(msgList.size()); // 将ListView定位到最后一行
                    inputText.setText(""); // 清空输入框中的内容
                }
            }
        });
    }
    private void initMsgs() {
        Msg msg1 = new Msg("Hello guy.", Msg.TYPE_RECEIVED);
        msgList.add(msg1);
        Msg msg2 = new Msg("Hello. Who is that?", Msg.TYPE_SENT);
        msgList.add(msg2);
        Msg msg3 = new Msg("This is Tom. Nice talking to you. ", Msg.TYPE_RECEIVED);
        msgList.add(msg3);
    }
}