ListView详解

1.使用adapter

最简单的是ArrayAdapter,处理的是字符串

ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_expandable_list_item_1,strs);

当然,也可以自定义BaseAdapter

public class BaseAdapterDemo extends BaseAdapter {
        private Wrapper wrapper;

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

        @Override
        public Object getItem(int arg0) {
            return userList.get(arg0);
        }

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

        @Override
        public View getView(int position, View v, ViewGroup parent) {
            View row = v;
            if(row == null){
                row = getLayoutInflater().inflate(R.layout.rankitem, parent, false);//加载布局文件
                wrapper = new Wrapper(row);

                row.setTag(wrapper);
            }else{
                wrapper = (Wrapper)row.getTag();
            }

            TextView txtUser = wrapper.getName();
            TextView txtScore = wrapper.getScore();

            txtUser.setText(userList.get(position).getUserName());
            txtScore.setText(userList.get(position).getUserScore()+"");
            return row;
        }
    }

    class Wrapper{              //持有者模式
        private TextView tvName, tvScore;
        private View row;

        public Wrapper(View row) {
            this.row = row;
        }

        public TextView getName(){
            if(tvName == null){
                tvName = (TextView)row.findViewById(R.id.txtUser);
            }
            return tvName;
        }

        public TextView getScore(){
            if(tvScore == null){
                tvScore = (TextView)row.findViewById(R.id.txtScore);
            }
            return tvScore;
        }
    }

当然还有其他adapter。个人认为至少掌握这两种。只是字符串就用ArrayAdapter,用到图文混排的就用自定义的

2.listview的优化(convertView,viewholder)

首先来了解一下ListView的工作原理。 ListView 针对每个item,要求 adapter“返回一个视图” (getView),也就是说ListView在开始绘制的时候,系统首先调用getCount()函数,根据他的返回值得到ListView的长度,然后根据这个长度,调用getView()一行一行的绘制ListView的每一项。 如果我们有几千几万甚至更多的item要显示怎么办?为每个Item创建一个新的View?不可能!!!

用convertView减少文件解析次数 Android系统本身为我们考虑了ListView的优化问题,在复写的Adapter的类中,比较重要的两个方法是getCount()和getView()。界面上有多少个条显示,就会调用多少次的getView()方法;因此如果在每次调用的时候,如果不进行优化,每次都会使用View.inflate(….)的方法,都要将xml文件解析,并显示到界面上,这是非常消耗资源的.所以,可以复用旧的内容。为了节约内存,可以在convertView不为null的时候,对其进行复用

用viewholder减少findViewById次数 findViewById()这个方法是比较耗性能的操作,因为这个方法要找到指定的布局文件,进行不断地解析每个节点:从最顶端的节点进行一层一层的解析查询,找到后在一层一层的返回,如果在左边没找到,就会接着解析右边,并进行相应的查询,直到找到位置。但是当xml文件被解析的时候,只要被创建出来了,其孩子的id就不会改变了。根据这个特点,可以将孩子id存入到指定的集合中,每次就可以直接取出集合中对应的元素就可以了。

3.分页加载

为了让分页的时候有提示,可以加个addFooterView 先上主代码:MainActivity.java

package com.example.androidtest;

import java.util.Vector;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class MainActivity extends Activity implements OnScrollListener {
    private ListView listView;
    private MyAdapter myAdapter;
    private Vector<News> news = new Vector<News>();;
    private View loadView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.listview);

        listView = (ListView) findViewById(R.id.listView);
        initData();
        loadView = getLayoutInflater().inflate(R.layout.load, null);
        listView.addFooterView(loadView);

        myAdapter = new MyAdapter();
        listView.setAdapter(myAdapter);
        listView.setOnScrollListener(this);
    }

    private int index = 1;

    private void initData() {
        for (int i = 0; i < 10; i++) {
            News n = new News();
            n.titleString = "title--" + index;
            n.contentString = "content--" + index;
            ++index;
            news.add(n);
        }
    }

    // 线程间通信机制
    private Handler myhHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 0x1) {
                myAdapter.notifyDataSetChanged();
                listView.addFooterView(loadView);//
            }
        }
    };

    class LoadDataThread extends Thread {
        @Override
        public void run() {
            initData();//每次加载10个item
            try {
                Thread.sleep(200);
                // myAdapter.notifyDataSetChanged();//子线程不能直接访问UI线程组件
                myhHandler.sendEmptyMessage(0x1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    class MyAdapter extends BaseAdapter {
        @Override
        public int getCount() {
            return news.size();
        }

        @Override
        public Object getItem(int index) {
            return news.get(index);
        }

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

        @Override
        public View getView(int position, View view, ViewGroup parent) {
            ViewHolder vH;

            if (view == null) {
                view = getLayoutInflater().inflate(R.layout.listitem, parent, false);
                vH = new ViewHolder();
                vH.tv_title = (TextView) view.findViewById(R.id.textViewTitle);
                vH.tv_content = (TextView) view.findViewById(R.id.textViewContent);
                view.setTag(vH);
            } else {
                vH = (ViewHolder) view.getTag();
            }
            News n = news.get(position);
            vH.tv_title.setText(n.titleString);
            vH.tv_content.setText(n.contentString);

            return view;
        }

        class ViewHolder {
            public TextView tv_title;
            public TextView tv_content;
        }
    }

    private int visibleLastIndex = 0;

    @Override
    public void onScroll(AbsListView view, int first, int visibleItem, 
            int totalItem) {
        visibleLastIndex = first + visibleItem - 1;
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (myAdapter.getCount() == visibleLastIndex
                && scrollState == SCROLL_STATE_IDLE) {
            new LoadDataThread().start();
            listView.removeFooterView(loadView);//加载的时候可以移除
        }
    }
}

News.java

package com.example.androidtest;

public class News {
    public String titleString;
    public String contentString;
}

主界面布局listview.xml

<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:dividerHeight="10sp"
        android:scrollbars="horizontal|vertical"
        android:entries="@array/cities" >
    </ListView>

</LinearLayout>

listview中每一个item布局;listitem.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="horizontal"
    android:gravity="center_vertical" >

    <TextView
        android:id="@+id/textViewTitle"
        android:layout_width="match_parent"
        android:layout_height="50sp" />

    <TextView
        android:id="@+id/textViewContent"
        android:layout_width="match_parent"
        android:layout_height="50sp" />

</LinearLayout>

最后是footerview的布局load.xml

<?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/LinearLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".MainActivity" >

    <ProgressBar
        android:id="@+id/progressBar1"
        style="?android:attr/progressBarStyleLarge"
        android:layout_width="wrap_content"
        android:layout_height="30sp" />

    <TextView
        android:gravity="center"
        android:textSize="25sp"
        android:id="@+id/textView1"
        android:layout_width="match_parent"
        android:layout_height="30sp"
        android:text="@string/loading" />

</LinearLayout>

4.和ScrollView事件冲突

activity_main.xml(ScrollView里面嵌套listview)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/LinearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.dispatchevent.MainActivity" >

    <ScrollView
        android:id="@+id/scrollView1"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

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

            <ListView
                android:id="@+id/listView1"
                android:layout_width="match_parent"
                android:layout_height="364dp" >
            </ListView>

            <ImageView
                android:id="@+id/imageView1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_launcher" />

            <ImageView
                android:id="@+id/imageView2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_launcher" />

            <ImageView
                android:id="@+id/imageView3"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_launcher" />

            <ImageView
                android:id="@+id/imageView4"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_launcher" />

        </LinearLayout>
    </ScrollView>

</LinearLayout>

MainActivity.java:

package com.example.dispatchevent;

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

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.ScrollView;

public class MainActivity extends Activity {
    private ListView listView;
    private ArrayAdapter<String> adapter;
    private boolean isMove, isScrollDown;
    private ScrollView scrollView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        scrollView = (ScrollView) findViewById(R.id.scrollView1);
        listView = (ListView) findViewById(R.id.listView1);
        adapter = new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1, getData());
        listView.setAdapter(adapter);
        /**********************************/
    }

    private List<String> getData() {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 20; i++) {
            list.add("XXX" + i);
        }
        return list;
    }
}

上面的代码实验效果如下

listview显示不全,主要是因为Touch事件没有分发出去 添加如下代码

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_MOVE) {
            // 想让哪个先执行,直接调用
            if (isScrollDown) {
                scrollView.dispatchTouchEvent(ev);
            } else {
                listView.dispatchTouchEvent(ev);
            }
        }
        return super.dispatchTouchEvent(ev);
    }
        listView.setOnScrollListener(new OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                if (isMove && scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
                    // The view is not scrolling.
                    // Note navigating the list using the trackball counts as
                    // being in the idle state since these transitions are not
                    // animated.
                    isScrollDown = true;
                } else if (isMove == false) {
                    isScrollDown = false;
                }
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem,
                    int visibleItemCount, int totalItemCount) {
                isMove = (firstVisibleItem + visibleItemCount == totalItemCount);
            }
        });

可以有效实现事件从scrollview分发到listview,但是页面往上拉时依然会有问题。为了解决这个问题(如何将事件消费从listview转到scrollview,就是子传父)我考虑过return true将事件消费掉,但是dispatchTouchEvent两者都会调用,不能这么做。我还考虑过通过设置标志位来处理,但还是不行。求大神支招啊?

另外,我查看过API

You should never use a ScrollView with a ListView, because ListView takes care of its own vertical scrolling. Most importantly, doing this defeats all of the important optimizations in ListView for dealing with large lists, since it effectively forces the ListView to display its entire list of items to fill up the infinite container supplied by ScrollView. 

翻译过来就是: 你不应该同时使用ScrollView 和ListView,因为ListView负责自己的垂直滚动。最重要的是,这样优化ListView来处理大的列表的最大缺陷是,因为它迫使ListView显示整个项目列表填满由ScrollView 提供的空间。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券