Android常用控件-ListView

作者: 史慧君 | 来源:发表于2017-02-22 12:44 被阅读244次

    由于手机屏幕有限, 所以ListView的使用非常的普遍. ListView就是用户可以通过手指上下滑动的方式来展现更多的数据.

    ListView的简单使用:

    新建一个ListViewDemo的项目,修改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">
        <ListView
            android:id="@+id/list_view_demo"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </ListView>
    </LinearLayout>
    
    

    为ListView指定一个id, 这样就会在页面出现一个ListView的基本布局.
    下面是MainAcitivity中代码:

    
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.widget.ArrayAdapter;
    import android.widget.ListView;
    
    public class MainActivity extends AppCompatActivity {
    
        private String[] data = {"爸爸","妈妈","姐姐","妹妹","二姑","三姑",
                     "四舅","五伯","哥哥","嫂嫂","爷爷","奶奶","姥姥","姥爷"};
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            //android.R.layout.simple_list_item_1 作为ListView子项布局的id, 
           // 这是一个android内置的布局文件, 里面只有一个textview
            //可用于简单的显示一段文本.
            ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,data);
            ListView listView = (ListView)findViewById(R.id.list_view_demo);
            listView.setAdapter(adapter);
    
        }
    }
    
    

    ListView是用来展示大量数据的, 这里用了一个数组. 数组中的数据是无法直接传递给ListView的, 需要借助适配器来传递. 由于这里提供的数据都是字符串,因此将ArrayAdapter的泛型指定为String, 依次传递参数为, 上下文, 子项布局id, 数据.

    ListView的setAdapter()方法表示将建好的设配器对象传递给listview, 这样页面和数据就进行了关联.

    运行效果:

    定制ListView的界面

    正式的项目开发中, 不可能使用这么简单的, 很多情况是需要根据客户的需求, 进行定制开发, 下面我们就对上面的ListView进行简单的扩展.

    我们把这个列表扩展成一个简单的通讯录功能功能, 会显示头像, 昵称, 和电话.

    我们首先定义一个实体类, 作为ListView适配器的适配类型. 新建类Family, 代码如下:

    public class Family {
        private String name;
        private int imageID;
        private int phoneNum;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getImageID() {
            return imageID;
        }
    
        public void setImageID(int imageID) {
            this.imageID = imageID;
        }
    
        public int getPhoneNum() {
            return phoneNum;
        }
    
        public void setPhoneNum(int phoneNum) {
            this.phoneNum = phoneNum;
        }
    }
    

    name:表示称呼
    imageID: 表示头像id
    phoneNum: 表示电话号码

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

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <ImageView
            android:id="@+id/family_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <TextView
            android:id="@+id/family_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="10dp"/>
        <TextView
            android:id="@+id/family_phone_num"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="10dp"/>
    </LinearLayout>
    

    在这个布局中, 定义了ImageView用于显示头像, TextView分别用来显示称谓和电话. 并且让TextView在垂直方向居中显示.

    接下来我们需要创建自定义的适配器, 这个适配器继承ArrayAdapter, 并将泛型定义为Family类. 新建类FamilyAdapter, 代码如下:

    public class FamilyAdapter  extends ArrayAdapter<Family>{
    
        private  int resourceID;
        public FamilyAdapter(Context context, int resource, List<Family> objects) {
            super(context, resource, objects);
            resourceID = resource;
        }
    
        @NonNull
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            Family family = getItem(position); // 获取当前项的Family实例
            View view = LayoutInflater.from(getContext()).inflate(resourceID, parent, false);
            ImageView  familyImage = (ImageView) view.findViewById(R.id.family_image);
            TextView familyName = (TextView) view.findViewById(R.id.family_name);
            TextView familyPhone = (TextView) view.findViewById(R.id.family_phone_num);
    
            familyImage.setImageResource(family.getImageID());
            familyName.setText(family.getName());
            familyPhone.setText(family.getPhoneNum());
            return view;
        }
    }
    
    

    FamilyAdapter重写了父类的一组构造函数, 用于将上下文, ListView子项布局的id和数据都传递进来. 另外又重写了一个getView()方法, 这个方法在每个子项被滚动到屏幕内的时候会被调用.

    在getView()中, 首先通过getItem()方法得到当前项的Family实例, 然后使用LayoutInflater来为这个子项加载我们传入的布局.

    这里LayoutInflater的inflate()方法接收3个参数, inflate()可以动态加载一个布局文件的id, 布局id, 第二个参数是给加载好的布局添加一个父布局.第三个参数指定成false, 表示只将我们在父布局中声明的layout属性生效, 但不为这个View添加父布局, 因为一旦view有了父布局之后, 它就不能再添加到ListView中了.

    之后我们调用View的findViewById()方法分别获取到ImageView和TextView的实例. 并分别调用他们的setImageResource()和setText()方法来设置显示的图片和文字, 最后将布局完成.

    下面来修改MainActivity中的代码.

    public class MainActivity extends AppCompatActivity {
        private List<Family> familyList = new ArrayList<>();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initFamilys();// 初始化家庭数据
            FamilyAdapter adapter = new FamilyAdapter(MainActivity.this,R.layout.family_item,familyList);
            ListView listView = (ListView)findViewById(R.id.list_view_demo);
            listView.setAdapter(adapter);
    
        }
    
        private void initFamilys() {
            for(int i= 0; i<2; i++){
                Family family1  = new Family("爸爸", R.mipmap.family_1, 
    "13813813888");
                familyList.add(family1);
                Family family2  = new Family("妈妈", R.mipmap.family_2, 
    "13813813888");
                familyList.add(family2);
                Family family3  = new Family("姐姐", R.mipmap.family_1, 
    "13813813888");
                familyList.add(family3);
                Family family4  = new Family("三姑", R.mipmap.family_2, 
    "13813813888");
                familyList.add(family4);
                Family family5  = new Family("妹妹", R.mipmap.family_1, 
    "13813813888");
                familyList.add(family5);
                Family family6  = new Family("二姑", R.mipmap.family_1, 
    "13813813888");
                familyList.add(family6);
                Family family7  = new Family("三姑", R.mipmap.family_2, 
    "13813813888");
                familyList.add(family7);
                Family family8  = new Family("四舅", 
    R.mipmap.family_1, "13813813888");
                familyList.add(family8);
                Family family9  = new Family("三姑", R.mipmap.family_2, 
    "13813813888");
                familyList.add(family9);
                Family family10  = new Family("哥哥", R.mipmap.family_1, 
    "13813813888");
                familyList.add(family10);
                Family family11  = new Family("嫂嫂", R.mipmap.family_1, 
    "13813813888");
                familyList.add(family11);
                Family family12  = new Family("爷爷", R.mipmap.family_2, 
    "13813813888");
                familyList.add(family12);
                Family family13  = new Family("奶奶", R.mipmap.family_1, 
    "13813813888");
                familyList.add(family13);
                Family family14  = new Family("姥姥", R.mipmap.family_2,
     "13813813888");
                familyList.add(family14);
                Family family15  = new Family("姥爷", R.mipmap.family_1, 
    "13813813888");
                familyList.add(family15);
    
            }
    
    
        }
    }
    

    运行效果:

    相信你已经领悟到了诀窍, 只要修改family_item.xml中的内容, 就可以定制出各种复杂的界面了.

    提升ListView的运行效率

    之所以说ListView很难用, 是因为它有很多细节可以优化. 其中效率就是很重要的一点. 目前我们的运行效率极低, 是因为FamilyAdapter的getView()方法中, 每次都将布局重新加载一遍, 当ListView快速滚动的时候, 就会出现性能的瓶颈.

    在getView()方法中还有一个converView参数, 这个参数用于将之前加载好的布局进行缓存, 以便之后可以重用. 所以修改代码如下:

    Family family = getItem(position); // 获取当前项的Family实例
            View view;
    
            if(convertView == null){
                view  = LayoutInflater.from(getContext()).inflate(resourceID, 
    parent, false);
            } else {
                view = convertView;
            }
    
    

    convertView 为null的时候, 才去加载布局, 如果不为null, 则直接重用convertView. 这样就大大提高了ListView的运行效率. 加快滚动的时候也可以出现很好的性能.

    当然这部分代码还是可以继续优化的, 虽然现在不会再去重复加载布局, 但是每次在getView()方法中还是会调用View的findViewById()方法来获取一次控件的实例.

    我们可以借助一个ViewHolder来对这部分性能进行优化. 修改FamilyAdapter中的优化代码.

    
    public View getView(int position, View convertView, ViewGroup parent) {
            Family family = getItem(position); // 获取当前项的Family实例
            View view;
            ViewHolder viewHolder;
    
            if(convertView == null){
                view  = LayoutInflater.from(getContext()).inflate(resourceID, 
    parent, false);
                viewHolder = new ViewHolder();
                viewHolder.familyImage = (ImageView) view.findViewById(R.id.family_image);
                viewHolder.familyName = (TextView) view.findViewById(R.id.family_name);
                viewHolder.familyPhone = (TextView) view.findViewById(R.id.family_phone_num);
                view.setTag(viewHolder); //将ViewHolder储存在View中
            } else {
                view = convertView;
                viewHolder = (ViewHolder) view.getTag();// 重新获取ViewHolder
            }
    
            viewHolder.familyImage.setImageResource(family.getImageID());
            viewHolder.familyName.setText(family.getName());
            viewHolder.familyPhone.setText(family.getPhoneNum());
            return view;
        }
    
        class ViewHolder {
            ImageView familyImage;
            TextView familyName;
            TextView familyPhone;
    
        }
    

    解读:
    新增的ViewHolder用于对控件的实例进行缓存, 当convertView为null的时候, 创建一个ViewHolder对象, 并将控件实例都存在ViewHolder里, 然后调用view的setTag()方法, 将ViewHolder对象储存在view中.

    当convertView不为null的时候, 则调用View的getTag()方法, 把ViewHolder重新取出. 这样所有的控件实例都缓存在了ViewHolder里面. 就没有必要每次通过findViewById()方法来获取控件实例了.

    运行效果:

    源码如下:
    https://github.com/junzaivip/ListViewDemo

    相关文章

      网友评论

      本文标题:Android常用控件-ListView

      本文链接:https://www.haomeiwen.com/subject/qeipwttx.html