专栏首页刘晓杰ListView详解

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 条评论
登录 后参与评论

相关文章

  • 多功能时钟应用

    提莫队长
  • Android UI 设计技巧

    <merge/>标签帮助你排除把一个布局插入到另一个布局时产生的多余的View Group.如,你的被复用布局是一个垂直的线性布局,包含两个子视图,当它作为一个...

    提莫队长
  • Android从ftp服务器获取文件

    window搭建ftp服务器的步骤在这里,亲测可行: http://blog.sina.com.cn/s/blog_3f7e47f20100haur.htm...

    提莫队长
  • Android VideoView 视频播放完成例子(进度条,播放时间,暂停,拖动)

    中国广东省深圳市望海路半岛城邦三期 518067 +86 13113668890 <netkiller@msn.com>

    netkiller
  • android AutoCompleteTextView 自定义BaseAdapter

    最近项目中需要做搜索功能,实现类似 Google、Baidu 搜索的 下拉提示效果。Android为我们提供了 AutoCompleteTextView 控件来...

    庞小明
  • 可支持快速搜索筛选的Android自定义选择控件

    项目中遇到选择控件选项过多,需要快速查找匹配的情况。 做了简单的Demo,效果图如下:

    砸漏
  • AndroidStudio项目制作倒计时模块

    大家好,我是 Vic,今天给大家带来AndroidStudio项目制作倒计时模块的概述,希望你们喜欢

    达达前端
  • 学习使用Material Design控件(二)使用DrawerLayout实现侧滑菜单栏效果

    本文介绍如何使用DrawerLayout和NavigationView实现侧滑菜单栏的效果。

    砸漏
  • 滑动吸顶效果

    需求是先滑动里面的列表,滑动到一个位置时外面滑动,外面滑动一段距离后再里面滑动。最初想用 CoordinatorLayout 加 RecyclerView,但效...

    七适散人
  • 多功能时钟应用

    提莫队长

扫码关注云+社区

领取腾讯云代金券