Android开发——控件_ListView

当要显式的数据很多时,屏幕无法完全装下,这时就要用到控件ListView。只需在屏幕上滑动,未在屏幕显示的数据将滚动到屏幕内。


属性

android:divider="#fffff"  分割线颜色
android:dividerHeight="1px"  分割线高度

初见ListView

创建项目,项目名称为ListView_simple。

  • 首先,在布局文件中添加控件ListView
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/simple_list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>
  • 然后,修改主活动MainActivity类
    我们需要向上面的ListView提供要显示的数据。首先准备好这样一组数据,比如下面示例中的字符串数组data。然后通过adapter向ListView传递数据。
package com.example.victoria.listview_simple;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MainActivity extends AppCompatActivity {
    private ListView simpleListView;
    private String[] data = {"apple", "banana", "orange", "watermalon", "lemon", "mango", "cherry", "pear", "peach", "apple", "grape"};//希望显示在屏幕上的一组数据
    @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);//创建适配器:第二个参数表示子项布局的id,这里传入的android.R.layout.simple_list_item_1是内置布局
        simpleListView = (ListView) findViewById(R.id.simple_list_view);
        simpleListView.setAdapter(adapter);//通过adapter向ListView传递数据
    }
}

深入ListView

这里使用《第一行代码》第三章的聊天例子。消息是一条条显示在屏幕上的,而且屏幕一般无法显示所有的聊天记录,这里就可以用到ListView。

  • 首先,创建nine patch图片作为消息的背景
    参考绘制nine patch图片
    得到图片message_left.9.png和message_right.9.png。
定义适配器的适配类Msg

类Msg相当于初见ListView中的String类型,存储的是想要展示的子项的内容。

package com.example.victoria.myapplication;

public class Msg{ //消息的实体类
    public static final int TYPE_RECEIVED = 0;  //属于类的不可变变量
    public static final int TYPE_SEND = 1;

    private String content; //消息内容
    private int type; //消息类型,可以是TYPE_RECEIVED或者TYPE_SEND

    public Msg(String content, int type){
        this.content = content;
        this.type = type;
    }

    public String getContent(){
        return content;
    }

    public int getType(){
        return type;
    }
}

在创建适配器实例时,需要把一组Msg传给适配器构造函数。

创建子项的布局msg_item
<?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"
    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"
            android:textColor="#fff"/>
    </LinearLayout>
</LinearLayout>

这里包含左右两个LinearLayout,因为聊天消息是以一个接收消息和一个发送消息(或者反过来)为一个显示单位的,聊天记录就是重复显式接收-发送对。
但是消息毕竟是一条一条的,接受-发送之间应该是独立的。如何控制其独立性?答案是通过设置控件的可见属性。

创建适配器类MsgAdapter

继承自ArrayAdapter类,泛型指定为Msg。

  • 重写父类的构造函数
    public MsgAdapter(Context context, int textViewResourceId, List < Msg > objects)
        context:
        textViewResourceId:子项布局id
        objects:待显示消息列表

  • 重写getView方法
    这个方法在每个子项被滚动到屏幕内的时候被调用,用于返回每个位置应该显示的子项。首先通过参数textResourceId动态加载子项布局,然后按id读取子项布局中的控件或者布局,根据提供的消息数据设置它们的属性。
    这里有两处重复操作,一个是加载子项布局,一个是按id取出子项布局的控件或者嵌套布局。前者可以通过使用convertView参数进行优化,因为converView中缓存了子项布局;后者可以通过定义类ViewHolder进行优化,ViewHolder中存储了控件实例,通过把view的tag设为ViewHolder方便后面直接读取。

package com.example.victoria.myapplication;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.util.List;

/**
 * Created by victoria on 2017/2/6.
 */

public class MsgAdapter extends ArrayAdapter<Msg> {
    private int resourceId;

    public MsgAdapter(Context context, int textViewResourceId, List<Msg> objects) {
        super(context, textViewResourceId, objects);
        resourceId = textViewResourceId; //子项布局的id
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent){
        //convertView参数的目的是把之前加载好的布局缓存以便之后可以重用
        Msg msg = (Msg) getItem(position);

        /*******************************************************************************/
        /*经过两个优化操作,加载子项布局以及读取控件实例*/
        View view;
        ViewHolder viewHolder; //viewHolder的目的是避免重复获取控件实例
        if(convertView == null){ //没有缓存子项布局:缓存子项布局
            view = LayoutInflater.from(getContext()).inflate(resourceId, null); //动态加载一个布局文件,inflate方法的两个参数分别代表加载的布局文件的id、给加载的布局再添加一个父布局。
            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); //把子项布局的控件以更简单的格式保存到view自身,方面后面直接的读取,而不用每次都要按id获取控件实例
        }else{//有缓存的子项布局
            view = convertView;
            viewHolder = (ViewHolder) view.getTag();
        }
        /*******************************************************************************/

        /*******************************************************************************/
        /*根据消息Msg数据设置控件属性*/
        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_SEND){
            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;
    }
}
主活动的布局activity_main
<?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:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    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="something here"
            android:maxLines="2"/>

        <Button
            android:id="@+id/send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Send"/>

    </LinearLayout>
</LinearLayout>
编写主活动MainActivity类

提供Msg数据列表,并通过适配器向ListView传递数据。

package com.example.victoria.myapplication;

import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;

import com.google.android.gms.appindexing.Action;
import com.google.android.gms.appindexing.AppIndex;
import com.google.android.gms.appindexing.Thing;
import com.google.android.gms.common.api.GoogleApiClient;

import java.util.ArrayList;
import java.util.List;


public class MainActivity extends AppCompatActivity {
    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);

        /*******************************************************************************/
        /*通过适配器向ListView传递数据*/
        initMsg();//提供待显示数据
        adapter = new MsgAdapter(MainActivity.this, R.layout.msg_item, msgList);#创建适配器
        msgListView = (ListView) findViewById(R.id.msg_list_view);
        msgListView.setAdapter(adapter);
        /*******************************************************************************/

        inputText = (EditText) findViewById(R.id.input_text);

        /*******************************************************************************/
        /*为Button的点击事件注册监听器:当点击按钮时,获取EditText控件中的内容,并添加到消息列表中,并通知ListView有新消息需要显示*/
        send = (Button) findViewById(R.id.send);
        send.setOnClickListener(new View.OnClickListener() { 
            @Override
            public void onClick(View v) {
                String content = inputText.getText().toString();//获取编辑内容
                if(! "".equals(content)){
                    Msg msg = new Msg(content, Msg.TYPE_SEND);
                    msgList.add(msg);
                    adapter.notifyDataSetChanged();//当有新消息时,刷新ListView中的显示
                    msgListView.setSelection(msgList.size());//将显式的数据定位到最后一行,以确保一定可以看到最后一行消息
                    inputText.setText(""); //清空输入框中的内容
                }
            }
        });
    /*******************************************************************************/
    }

    private void initMsg(){
        Msg msg1 = new Msg("hello guy.", Msg.TYPE_RECEIVED);
        msgList.add(msg1);
        Msg msg2 = new Msg("hi. who is that?.", Msg.TYPE_SEND);
        msgList.add(msg2);
        Msg msg3 = new Msg("victoria.", Msg.TYPE_RECEIVED);
        msgList.add(msg3);
        Msg msg4 = new Msg("bye", Msg.TYPE_SEND);
        msgList.add(msg4);
    }


}

ListView的点击事件

video_list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
       @Override
       public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                VideoInfo video = videoList.get(position);
                Intent intent = new Intent(VideoList.this, PlayVideo.class);
                intent.putExtra("data", "video.data");
                startActivity(intent);
       }
});

实时更新

adapter.notifyDatasetChanged();

子项获得焦点

在子项目布局文件中的根布局中添加

android:descendantFocusability="blocksDescendants"

ListView中嵌入ListView

在ListView中嵌入ListView的时候,发现嵌入的ListView无法显式完全。可以通过统计每个子项的高度来确定ListView应该有多高:

public class Utility {
        public static void setListViewHeightBasedOnChildren(ListView listView) {
            ListAdapter listAdapter = listView.getAdapter();
            if (listAdapter == null) {
                // pre-condition
                return;
            }

            int totalHeight = 0;
            for (int i = 0; i < listAdapter.getCount(); i++) {
                View listItem = listAdapter.getView(i, null, listView);
                listItem.measure(0, 0);
                totalHeight += listItem.getMeasuredHeight();
            }

            ViewGroup.LayoutParams params = listView.getLayoutParams();
            params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
            listView.setLayoutParams(params);
        }
}

在对ListView设置好适配器后,将其传入此静态方法。


ListView中将某一行滚动到第一行

比如一个菜单应用,左边显示食物种类,右边具体的食物。当点击左边的某个种类时,右边对应的食物应该滑动到第一行开始显式。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值