自学Android第八天——ListView

listView绝对可以称得上是android中最常用的控件之一,几乎所有的应用程序都会用到它。由于手机屏幕的空间很有限,能够一次性在屏幕上显示的内容并不度,当我们又需要展示大量的数据时,就可以用到listView。ListView允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,原有的数据则滚动出屏幕。我们其实每天都在用这个控件,例如微博,头条,QQ,微信等等。相比其他的控件ListView就显得尤为重要,当然也很难。我们单独花一天来学。

ListView简单用法

先新建一个ListViewTest项目,并让android Studio自动帮我们创建好活动。然后修改activity_main.xml代码,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:id="@+id/activity_main"

    android:layout_width="match_parent"

    android:layout_height="match_parent">

        <ListView

            android:layout_width="match_parent"

            android:layout_height="match_parent"

            android:id="@+id/list_view"/>

</LinearLayout>

然后修改MainActivity中的代码,如下所示:

public class MainActivityextends AppCompatActivity {

    private String[] data={"Apple","Banana","Orange","Pear","Grape","Cherry","Mango","Strawberry","Watermelon","Pear","Apple"};

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        ArrayAdapter adapter=new ArrayAdapter(MainActivity.this,android.R.layout.simple_list_item_1,data);

        ListView listView=(ListView)findViewById(R.id.list_view);

        listView.setAdapter(adapter);

    }

}

ArrayAdapter的构造函数中依次传入当前上下文、ListView子项布局的id,以及要适配的数据。

注意:我们使用了android:R.id.layout.simple_list_item_1作为ListView子项布局的id,这是android内置的布局文件,里面只有一个TextView,可用于简单的显示一段文本。

现在运行一下程序吧,看看效果如何。


ListView的运行效果

定制ListView的界面

只能显示一段文本的ListView太单调了,我们给它添加一些图片吧(图片不能过大,不然系统会报错)。

接着我们在定义一个实体类,作为ListView适配器适配类型。新建类Fruit,代码如下:

public class Fruit {

    private Stringname;

    private int imageId;

    public Fruit(String name,int imageId){

        this.name=name;

        this.imageId=imageId;

    }

    public StringgetName(){

        return name;

    }

    public int getImageId(){

        return imageId;

    }

}

Fruit类中只有两个字段,name表示水果名,imageId表示水果对应图片的资源id。

然后在layout下新建fruit_item.xml文件,代码如下:

<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:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:id="@+id/fruit_img"/>

        <TextView

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:id="@+id/fruit_name"

        android:layout_gravity="center_vertical"

        android:layout_marginLeft="10dp"/>

</LinearLayout>

接下来需要创建一个自定义的适配器,这个适配器继承自ArrayAdapter,并将泛型指定为Fruit类。新建类FruitAdapter,代码如下:

public class FruitAdapterextends ArrayAdapter {

    private int resourceId;

    public FruitAdapter(Context context, int textViewResourceId, List objects){

        super(context,textViewResourceId,objects);

        resourceId=textViewResourceId;

    }

@Override

    public ViewgetView(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_img);

        TextView fruitname=(TextView)view.findViewById(R.id.fruit_name);

        fruitimage.setImageResource(fruit.getImageId());

        fruitname.setText(fruit.getName());

        return view;

    }

}

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

这里LayoutInflater的inflate()方法接收3个参数,第三个参数指定成false,表示只让我们在父布局中声明layout属性生效,但不会为这个View添加父布局,因为一旦View有了父布局,就不能再添加到ListView中了。

最后修改MainActivity中的代码,如下所示:

public class MainActivityextends AppCompatActivity {

  private ListfruitList=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);

            fruitList.add(apple);

            Fruit banana=new Fruit("banana",R.drawable.banana);

            fruitList.add(banana);

            Fruit orange=new Fruit("orange",R.drawable.orange);

            fruitList.add(orange);

            Fruit water=new Fruit("watermelon",R.drawable.water);

            fruitList.add(water);

            Fruit pear=new Fruit("pear",R.drawable.pear);

            fruitList.add(pear);

            Fruit Str=new Fruit("Strawberry",R.drawable.stra);

            fruitList.add(Str);

            Fruit Cherry=new Fruit("Cherry",R.drawable.cherry);

            fruitList.add(Cherry);

            Fruit Mango=new Fruit("Mango",R.drawable.mango);

            fruitList.add(Mango);

            Fruit Grape=new Fruit("Grape",R.drawable.grape);

            fruitList.add(Grape);

        }

}

}

这里添加了一个initFruits()的方法,用于初始化所以得水果数据。在Friut类的构造函数中将水果的名字和对应的图片id传入,然后把创建好的对象添加到水果列表中。

现在运行一下程序,看看效果图吧。


自定义的ListView效果图

提升ListView的运行效率

之所以说ListView控件很难用,是因为他有很多的细节可以优化,运行效率很重要。目前我们ListView的运行效率是很低的,因为在前面FruitAdapter的getView()方法中,每次都将布局重新加载一遍,当ListView快速滚动时,这会成为性能的瓶颈。

仔细观察会发现,getView()方法中还有一个concertView参数,这个参数用于将之前加载好的布局进行缓存,以便之后可以进行重用。修改FruitAdapter中的代码,如下所示:

public class FruitAdapterextends ArrayAdapter {

private int resourceId;

    public FruitAdapter(Context context, int textViewResourceId, List objects){

        super(context,textViewResourceId,objects);

        resourceId=textViewResourceId;

    }

@Override

    public ViewgetView(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;

        }

        //View view= LayoutInflater.from(getContext()).inflate(resourceId,parent,false);

        ImageView fruitimage=(ImageView)view.findViewById(R.id.fruit_img);

        TextView fruitname=(TextView)view.findViewById(R.id.fruit_name);

        fruitimage.setImageResource(fruit.getImageId());

        fruitname.setText(fruit.getName());

        return view;

    }

}

修改加粗部分,//后面是之前使用的。

可以看到现在我们在getView()方法中进行判断,如果convertView为null,则使用LayoutInflater去加载布局,如果不为null则直接对convertView进行重用。这样就大大提高了ListView的运行效率,在快速滚动的时候也可以表现出更好的性能。

不过还可以进一步的优化。内容如下:

public class FruitAdapterextends ArrayAdapter {

private int resourceId;

    public FruitAdapter(Context context, int textViewResourceId, List objects){

super(context,textViewResourceId,objects);

        resourceId=textViewResourceId;

    }

@Override

    public ViewgetView(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_img);

            viewHolder.fruitname=(TextView)view.findViewById(R.id.fruit_name);

            //将viewHolder储存在view中

            view.setTag(viewHolder);

        }else {

view= convertView;

            //重新获取ViewHolder

            viewHolder=(ViewHolder)view.getTag();

        }

        viewHolder.fruitimage.setImageResource(fruit.getImageId());

        viewHolder.fruitname.setText(fruit.getName());

        return view;

    }

class ViewHolder{

ImageViewfruitimage;

        TextViewfruitname;

    }

}

可能有点难度。来解释一下吧。我们新增了一个内部类ViewHolder,用于对控件的实例进行缓存。当convertView为null时,创建一个ViewHolder对象,并将控件的实例存放在ViewHolder里,然后调用View的setTag()方法,将viewHolder对象储存在view中。当convertView不为null时,则调用getTag()方法,把ViewHolder重新取出来。这样所以得控件的实例都缓存在了viewHolder里,就没必要每次都通过findViewById()方法来获取控件实例了。

当然,如果你的技术支持你还可以继续优化。

ListView的点击事件

接下来我们来响应用户的点击事件吧。修改MainActivity中的代码,如下所示:

public class MainActivityextends AppCompatActivity {

       private ListfruitList=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 parent, View view, int position, long id) {

                Fruit fruit=fruitList.get(position);

                Toast.makeText(MainActivity.this,fruit.getName(),Toast.LENGTH_SHORT).show();

            }

    });

}

private void initFruits() {

        for(int i=0;i <2;i++){

            Fruit apple=new Fruit("Apple",R.drawable.apple);

            fruitList.add(apple);

            Fruit banana=new Fruit("banana",R.drawable.banana);

            fruitList.add(banana);

            Fruit orange=new Fruit("orange",R.drawable.orange);

            fruitList.add(orange);

            Fruit water=new Fruit("watermelon",R.drawable.water);

            fruitList.add(water);

            Fruit pear=new Fruit("pear",R.drawable.pear);

            fruitList.add(pear);

            Fruit Str=new Fruit("Strawberry",R.drawable.stra);

            fruitList.add(Str);

            Fruit Cherry=new Fruit("Cherry",R.drawable.cherry);

            fruitList.add(Cherry);

            Fruit Mango=new Fruit("Mango",R.drawable.mango);

            fruitList.add(Mango);

            Fruit Grape=new Fruit("Grape",R.drawable.grape);

            fruitList.add(Grape);

        }

    }

}

这样我们点击listView中任意一个Item就会响应了。我们使用setOnItemClickListener()方法为ListView注册了一个监听器,当用户点击了ListView中任何一个子项时,就会回调onItemClick()方法。通过这个方法的position参数判断出用户点击的是哪一个子项,然后获取相应的水果,通过Toast将获取的水果名显示出来。


点击ListView的效果
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343