第一行代码Android

第三章

最常用和最难用的控件--ListView

文章目录

目录

第一行代码Android

文章目录

简介

一、ListView

1.ListView的简单用法

2.定制ListView的界面

3.提升ListView的运行效率

4.ListView的点击事件

总结


简介

由于手机屏幕的空间比较有限,能够一次在屏幕上显示的内容不多,我们可以借助滚动来显示更多的数据。

一、ListView

1.ListView的简单用法

先创建一个新的活动,然后修改布局里面的代码,指定一个id,宽度和高度都占满布局

<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"
    />

</LinearLayout>

然后修改MianActivity里面的代码

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
    public void onCreate(@Nullable 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);
    }
}

数组中的数据是没有办法直接传递给ListView的,我们还要借助适配器ArrayAdapter,将泛型指定为String,然后在ArrayAdapter的构造函数中依次传递当前的上下文,ListView子项布局的id,和要适配的数据。

注意,我们使用了安卓自己定义的android.R.layout_simple_list_item_1,这是一个安卓内置的布局文件,里面只有一个TextView,可以用于简单的显示一段文本。

 

2.定制ListView的界面

只有一段文字的界面实在是太单调了,所以我们要自己定制一个界面,可以让他显示自己的图片

我们定义一个实体的类Fruit,作为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;
    }
}

name表示水果的名字,imageId表示水果对应图片的资源id。

然后我们给ListView的子项指定一个我们自定义的布局,在layout目录下,新建一个fruit_item.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="wrap_content"
    android:layout_width="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_vertical"
        android:layout_marginLeft="10dp"
        />
</LinearLayout>

我们定义了一个ImageView用于显示水果的图片,又定义了一个TextView用于显示水果的名称

接下来我们就需要创建一个自定义的适配器,这个适配器继承ArrayAdapter,并将泛型指定为Fruit类,新建一个FruitAdapter

public class FruitAdapter extends ArrayAdapter<Fruit> {
    private int resourceId;

    public FruitAdapter(@NonNull Context context, int textViewResourceId, 
@NonNull List<Fruit> objects) {
        super(context, textViewResourceId, objects);
        resourceId = textViewResourceId;
    }


    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, 
@NonNull 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;
    }
}

FruitAdapter重写了父类的一组构造函数,用于将上下文中,ListView子项布局的id和数据都传递进来。另外重写了getView()方法,这个方法在每个子项被滚动到屏幕内的时候会被调用。首先通工getItem方法得到当前Fruit实例,然后使用LayoutInflater来为这个子项加载我们传入的布局

LayoutInflater的inflate()方法接受三个参数,前两个参数我们已经知道是什么了,第三个参数指定完成false,表示只让我们在父布局中声明的layout属性失效,但是不会为了这个View添加父布局,因为一旦View有了父布局之后,它就不能再添加到ListView中。这个就是ListView中的标准写法。

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 < 3; i++){
            Fruit chutian = new Fruit("雏田",R.drawable.chutian);
            fruitList.add(chutian);

            Fruit guhe = new Fruit("古河",R.drawable.guhe);
            fruitList.add(guhe);
            Fruit huangmao = new Fruit("黄毛",R.drawable.huangmao);
            fruitList.add(huangmao);
            Fruit leimu = new Fruit("蕾姆",R.drawable.leimu);
            fruitList.add(leimu);
            Fruit mayi = new Fruit("麻衣",R.drawable.mayi);
            fruitList.add(mayi);
            Fruit nvdi = new Fruit("女帝",R.drawable.nvdi);
            fruitList.add(nvdi);
            Fruit paojie = new Fruit("炮姐",R.drawable.paojie);
            fruitList.add(paojie);
            Fruit wang = new Fruit("吾王",R.drawable.wang);
            fruitList.add(wang);
            Fruit xiaolan = new Fruit("小兰",R.drawable.xiaolan);
            fruitList.add(xiaolan);
            Fruit xuexiaoban = new Fruit("血小板",R.drawable.xuexiaoban);
            fruitList.add(xuexiaoban);
            Fruit yasina = new Fruit("亚丝娜",R.drawable.yasina);
            fruitList.add(yasina);
        }
    }
}

新建一个drawable-xhdpi,想要的照片传入其中

 

3.提升ListView的运行效率

目前我们的ListView的运行效率是很低的,因为在FruitAdapter的grtView()方法,每一次都将布局重新加载了一遍,当ListView快速滚动的时候,就会成为性能的瓶颈。

仔细发现,getView()的方法还有一个convertView参数,这个参数可以将之前的布局进行缓存,以便之后可以重用。

    public View getView(int position, @Nullable View convertView, @NonNull 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()方法中进行了判断,如果converView为null,则使用LayoutInflater去加载布局,如果不为null则converView对进行重用

但是我们的getView()方法还是会调用View的findViewById()方法来获取一次控件的实例,我们可以借助ViewHolder来对这部分性能进行优化

public class FruitAdapter extends ArrayAdapter<Fruit> {
    private int resourceId;

    public FruitAdapter(@NonNull Context context, int textViewResourceId, @NonNull List<Fruit> objects) {
        super(context, textViewResourceId, objects);
        resourceId = textViewResourceId;
    }

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull 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时候,创建一个ViewHolder对象,并将控件的实例都存到ViewHolder里,然后调用View的setTag()方法,将ViewHolder对象存储在View中。当convertView不为null时,则调用View的getTag()方法,把ViewHolder重新取出。

4.ListView的点击事件

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);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                Fruit fruit = fruitList.get(i);
                Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_LONG).show();
            }
        });
    }

    private void initFruits(){
        for(int i = 0; i < 3; i++){
            Fruit chutian = new Fruit("雏田",R.drawable.chutian);
            fruitList.add(chutian);

            Fruit guhe = new Fruit("古河",R.drawable.guhe);
            fruitList.add(guhe);
            Fruit huangmao = new Fruit("黄毛",R.drawable.huangmao);
            fruitList.add(huangmao);
            Fruit leimu = new Fruit("蕾姆",R.drawable.leimu);
            fruitList.add(leimu);
            Fruit mayi = new Fruit("麻衣",R.drawable.mayi);
            fruitList.add(mayi);
            Fruit nvdi = new Fruit("女帝",R.drawable.nvdi);
            fruitList.add(nvdi);
            Fruit paojie = new Fruit("炮姐",R.drawable.paojie);
            fruitList.add(paojie);
            Fruit wang = new Fruit("吾王",R.drawable.wang);
            fruitList.add(wang);
            Fruit xiaolan = new Fruit("小兰",R.drawable.xiaolan);
            fruitList.add(xiaolan);
            Fruit xuexiaoban = new Fruit("血小板",R.drawable.xuexiaoban);
            fruitList.add(xuexiaoban);
            Fruit yasina = new Fruit("亚丝娜",R.drawable.yasina);
            fruitList.add(yasina);
        }
    }
}

我们使用了setOnItemClickListener()方法为ListView注册了一个监听器,当用户点击率ListView中的任何一个子项中的时候,就会回调onItemClick()方法,在这个方法中可以通过position参数判断用户点击的是哪一个子项,然后得到响应的水果,并通过Toast将水果的名字显示出来。

 

总结

恭喜你,ListView的大部分知识点你已经学完!

参考Android第一行代码(第2版)

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐