Android控件——ListView基本使用

1. ListView简单介绍

ListView组件可以实现循环显示自定义组件的功能。

简单给出一个ListView的使用实例:

  • 首先在你需要显示ListView的XML文件里声明一个ListView:
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#dee9f7">

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

  • 设计你的ListView。这里简单显示一行字符串,直接使用ArrayAdapter里模板
  • 在主函数中调用ListView
public class MainActivity extends AppCompatActivity {
	//准备数据源
    private String[] data = { "Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape",
    "Pineapple", "Strawberry", "Cherry", "Mango", "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中
        listView.setAdapter(adapter);
    }
}

1.1 定制ListView界面

上面简单显示一行字符,多余比较复杂的ListView界面,可以自定义其界面,这里假定需要显示的界面是图片加文字

  • 首先,自定义一个布局
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <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_vertical"
        android:layout_marginLeft="10dp" />

</LinearLayout>
  • 这里的ListView界面是图片加文字,准备好一组图片,定义好布局后,定义一个实体类,作为ListView适配器的适配类型
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;
    }
}
  • 创建一个自定义适配器,继承ArrayAdapter
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);
        View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
        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;
    }
}

getView()这个方法在每个子项被滚动到屏幕内部都会被调用。首先通过getItem()方法得到当前项的Fruit实例,然后使用LayoutInflater为这个子项加载我们传入的布局。

不能为View添加父局,否则不能再添加到ListView中。调用View的findViewById()方法获取ImageView和TextView示例,调用其方法setImageResource()setText()设置显示的图片和文字。最后将布局返回。

  • 最后修改主函数
public class MainActivity extends AppCompatActivity {

    private List<Fruit> fruitList = new ArrayList<>();

    @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() {
        for (int i = 0; i < 2; i++) {
            Fruit apple = new Fruit("Apple", R.drawable.apple_pic);
            fruitList.add(apple);
            Fruit banana = new Fruit("Banana", R.drawable.banana_pic);
            fruitList.add(banana);
            Fruit orange = new Fruit("Orange", R.drawable.orange_pic);
            fruitList.add(orange);
            Fruit watermelon = new Fruit("Watermelon", R.drawable.watermelon_pic);
            fruitList.add(watermelon);
            Fruit pear = new Fruit("Pear", R.drawable.pear_pic);
            fruitList.add(pear);
            Fruit grape = new Fruit("Grape", R.drawable.grape_pic);
            fruitList.add(grape);
            Fruit pineapple = new Fruit("Pineapple", R.drawable.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit("Strawberry", R.drawable.strawberry_pic);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit("Cherry", R.drawable.cherry_pic);
            fruitList.add(cherry);
            Fruit mango = new Fruit("Mango", R.drawable.mango_pic);
            fruitList.add(mango);
        }
    }

}

如果需要定制更加复杂的界面,只需要修改展示的ListView的界面XML内容,修改对应的代码。

1.2 ListView性能优化

前述的FruitAdapter的getView()方法,每次都将布局重新加载一遍,如果ListView快速滚动,则会成为性能瓶颈。
getView()中还有一个convertView参数,用于将之前加载好的布局进行缓存,之后可以进行重用

修改前述的getView()方法:

@Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Fruit fruit = getItem(position);
        View view;
        if (convertView == null) {
            view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
        } else {
            view = convertView;
        }
        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;
    }

上述的代码在每次getView()方法中还是会调用View的findViewById()方法来获取一次控件的实例。可以借助ViewHolder进行优化。

@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, parent, false);
            viewHolder = new ViewHolder();
            viewHolder.fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
            viewHolder.fruitName = (TextView) view.findViewById(R.id.fruit_name);
            view.setTag(viewHolder);
        } else {
            view = convertView;
            viewHolder = (ViewHolder) view.getTag();
        }
        viewHolder.fruitImage.setImageResource(fruit.getImageId());
        viewHolder.fruitName.setText(fruit.getName());
        return view;
    }

    class ViewHolder {
        ImageView fruitImage;
        TextView fruitName;
    }

新增内部类ViewHolder对控件实例进行缓存。当convertView == null,创建该对象,存放控件的实例,然后调用View的setTag()方法,将ViewHolder对象存储在View中。当convertView不为null,调用getTag()方法,把ViewHoler重新取出来。

1.3 ListView 点击事件

在主函数中修改代码:

listView.setOnItemClickListener(new AdapterView.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();
            }
        });

使用setOnItemClickListener()方法为ListView注册了一个监听器,当用户点击了ListView中任何一个子项,就会回调onItemClick()方法。该方法中可以通过position参数判断出用户点击的是哪一个子项。

1.4 自定义BaseAdapter

简单示例

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

    <ImageView
        android:id="@+id/img_icon"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:paddingLeft="8dp" />

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

        <TextView
            android:id="@+id/txt_aName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingLeft="8dp"
            android:textColor="#1D1D1C"
            android:textSize="20sp" />

        <TextView
            android:id="@+id/txt_aSpeak"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingLeft="8px"
            android:textColor="#B4B4B9"
            android:textSize="14sp" />

    </LinearLayout>
</LinearLayout>
public class Animal {

    private String aName;
    private String aSpeak;
    private int aIcon;

    public Animal() {
    }

    public Animal(String aName, String aSpeak, int aIcon) {
        this.aName = aName;
        this.aSpeak = aSpeak;
        this.aIcon = aIcon;
    }

    public String getaName() {
        return aName;
    }

    public String getaSpeak() {
        return aSpeak;
    }

    public int getaIcon() {
        return aIcon;
    }

    public void setaName(String aName) {
        this.aName = aName;
    }

    public void setaSpeak(String aSpeak) {
        this.aSpeak = aSpeak;
    }

    public void setaIcon(int aIcon) {
        this.aIcon = aIcon;
    }

}
public class AnimalAdapter extends BaseAdapter {

    private LinkedList<Animal> mData;
    private Context mContext;

    public AnimalAdapter(LinkedList<Animal> mData, Context mContext) {
        this.mData = mData;
        this.mContext = mContext;
    }

    @Override
    public int getCount() {
        return mData.size();
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list_animal,parent,false);
        ImageView img_icon = (ImageView) convertView.findViewById(R.id.img_icon);
        TextView txt_aName = (TextView) convertView.findViewById(R.id.txt_aName);
        TextView txt_aSpeak = (TextView) convertView.findViewById(R.id.txt_aSpeak);
        img_icon.setBackgroundResource(mData.get(position).getaIcon());
        txt_aName.setText(mData.get(position).getaName());
        txt_aSpeak.setText(mData.get(position).getaSpeak());
        return convertView;
    }


}
public class MainActivity extends AppCompatActivity {

    private List<Animal> mData = null;
    private Context mContext;
    private AnimalAdapter mAdapter = null;
    private ListView list_animal;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = MainActivity.this;
        list_animal = (ListView) findViewById(R.id.list_animal);
        mData = new LinkedList<Animal>();
        mData.add(new Animal("狗说", "你是狗么?", R.mipmap.ic_launcher));
        mData.add(new Animal("牛说", "你是牛么?", R.mipmap.ic_launcher));
        mData.add(new Animal("鸭说", "你是鸭么?", R.mipmap.ic_launcher));
        mData.add(new Animal("鱼说", "你是鱼么?", R.mipmap.ic_launcher));
        mData.add(new Animal("马说", "你是马么?", R.mipmap.ic_launcher));
        mAdapter = new AnimalAdapter((LinkedList<Animal>) mData, mContext);
        list_animal.setAdapter(mAdapter);
    }
}

这里的流程与前述的都差不多,主要区别在于AnimalAdapter,这里继承了BaseAdapter,这是一个抽象类,需要重写其中的四个方法:

  • getCount() 适配器中数据集的数据个数
  • getItem() 获取数据集中与指定索引对应的数据项
  • getItemId() 获取指定行对应的ID
  • getView() 获取每一个Item的显示内容

1.5 设置表头表尾与分割线

ListView可以自己设置表头,表尾以及分割线

  • footerDividersEnabled:是否在footerView(表尾)前绘制一个分隔条,默认为true
  • headerDividersEnabled:是否在headerView(表头)前绘制一个分隔条,默认为true
  • divider:设置分隔条,可以用颜色分割,也可以用drawable资源分割
  • dividerHeight:设置分隔条的高度

翻遍了了API发现并没有可以直接设置ListView表头或者表尾的属性,只能在Java中写代码 进行设置了,可供我们调用的方法如下:

  • addHeaderView(View v):添加headView(表头),括号中的参数是一个View对象
  • addFooterView(View v):添加footerView(表尾),括号中的参数是一个View对象
  • addHeaderView(headView, null, false):和前面的区别:设置Header是否可以被选中
  • addFooterView(View,view,false):同上

对了,使用这个addHeaderView方法必须放在listview.setAdapter前面,否则会报错。

示例

  • 首先编写表头与表尾的布局
<?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:gravity="center">

    <TextView
        android:layout_height="48dp"
        android:layout_width="match_parent"
        android:textSize="18sp"
        android:text="表头"
        android:gravity="center"
        android:background="#43BBEB"
        android:textColor="#FFFFFF"/>

</LinearLayout>
  • 修改MainActivity代码
public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener{

    private List<Animal> mData = null;
    private Context mContext;
    private AnimalAdapter mAdapter = null;
    private ListView list_animal;
    private LinearLayout ly_content;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = MainActivity.this;
        list_animal = (ListView) findViewById(R.id.list_animal);
        //动态加载顶部View和底部View
        final LayoutInflater inflater = LayoutInflater.from(this);
        View headView = inflater.inflate(R.layout.view_header, null, false);
        View footView = inflater.inflate(R.layout.view_footer, null, false);

        mData = new LinkedList<Animal>();
        mData.add(new Animal("狗说", "你是狗么?", R.mipmap.ic_icon_dog));
        mData.add(new Animal("牛说", "你是牛么?", R.mipmap.ic_icon_cow));
        mData.add(new Animal("鸭说", "你是鸭么?", R.mipmap.ic_icon_duck));
        mData.add(new Animal("鱼说", "你是鱼么?", R.mipmap.ic_icon_fish));
        mData.add(new Animal("马说", "你是马么?", R.mipmap.ic_icon_horse));
        mAdapter = new AnimalAdapter((LinkedList<Animal>) mData, mContext);
        //添加表头和表尾需要写在setAdapter方法调用之前!!!
        list_animal.addHeaderView(headView);
        list_animal.addFooterView(footView);

        list_animal.setAdapter(mAdapter);
        list_animal.setOnItemClickListener(this);
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Toast.makeText(mContext,"你点击了第" + position + "项",Toast.LENGTH_SHORT).show();
    }
}

如果想要列表一开始显示列表的最下面,可以使用:android:stackFromBttom属性设置为true即可。

ListView焦点问题

  • 只需为抢占了ListView Item焦点的控件设置**android:focusable=“false”**即可解决这个问题 或者在代码中获得控件后调用:setFocusable(false) !!另外,EditText却不行,
  • 在Item布局的根节点添加上述属性,android:descendantFocusability=“blocksDescendants” 即可,另外该属性有三个可供选择的值:
    • beforeDescendants:viewgroup会优先其子类控件而获取到焦点
    • afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点
    • blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点

1.6 ListView一些重要属性

  • android:divider="#f9b68b" //分割线颜色或样式
  • android:scrollbars=“none” //不显示滚动条
  • android:fadingEdge=“none” 去掉上边和下边黑色的阴影
  • android:divider="@drawable/@null" 不想显示分割线
  • android:scrollbars=“none” setVerticalScrollBarEnabled(true); 隐藏listView的滚动条
  • android:fadeScrollbars=“true” 设置为true就可以实现滚动条的自动隐藏和显示
  • android:transcriptMode=“alwaysScroll” 用ListView或者其它显示大量Items的控件实时跟踪或者查看信息,希望最新的条目可以自动滚动到可视范围内。通过设置的控件transcriptMode属性可以将Android平台的控件(支持ScrollBar)自动滑动到最底部。
  • android:fastScrollEnabled = “true” 加快滑动速度
  • android:listSelector="@color/pink" listView item 选中时的颜色

猜你喜欢

转载自blog.csdn.net/weixin_43499030/article/details/89493379