android-ListView使用技巧以及优化

Posted hankzhouandroid

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android-ListView使用技巧以及优化相关的知识,希望对你有一定的参考价值。

引子

ListView:android里面经典的列表view,一般我们都会去自定义adapter来达到业务需求,必要的时候还会继承ListView,重写其中某些方法形成 我特有的ListView。本文总结一些ListView的使用技巧,以及性能优化的方法。

 

使用技巧:

1)ViewHolder 模式提高效率 

这里,有一个知识背景需要强调,用过自定义Adapter的同学一定知道,继承BaseAdapter之后,需要重写几个方法,其中代码篇幅最多的就是getView(...)。而 getView()在list滑动的过程中,有可能重复调用。这里,涉及到ListView的View缓存机制,已经生成的item,会在重新显示(从不可见到可见)之后,再次执行getView。所以,如果在getView方法中打印日志,可以看到,position值相同的item 的getView,会被多次执行。具体的机制,我暂时想不起来了。

而重写getView,我们会在里面用LayoutInflater去实例化item的布局,每一次实例化都是需要消耗资源,消耗时间的,为了防止没有意义的资源消耗,我们可以采用“ViewHolder复用模式”;

下面是示例代码: 这样可以保证,就算有多次滑动,多次getView,每一个item的inflate都只执行一次,避免了没必要的资源消耗,提高了效率。

(重点注意红色代码) 

 1 @Override
 2     public View getView(int position, View convertView, ViewGroup parent) {
 3         //观察一下这个方法什么时候会被调用
 4         Log.d("getViewTag", "*****************************");
 5         //我发现,以一个item为准,如果它从不可见到可见,这个getView都会被调用一次
 6 
 7         // 取得bean对象
 8         final ViewHolder viewHolder;
 9         if (convertView == null) {//参数中的convertView实际上就是 itemView的缓存
10             convertView = layoutInflater.inflate(R.layout.tweet_item, null);//只有当缓存的convertView是空,才进行itemView实例化
11             viewHolder = new ViewHolder();//实例化之后,用viewHolder统筹管理item中的所有view
12             viewHolder.tv_username = convertView.findViewById(R.id.tv_username);
13             viewHolder.tv_content = convertView.findViewById(R.id.tv_content);
14             viewHolder.img_user = convertView.findViewById(R.id.img_user);
15             viewHolder.ll_img_content = convertView.findViewById(R.id.ll_img_content);
16             viewHolder.ll_comments = convertView.findViewById(R.id.ll_comments);
17 
18             convertView.setTag(viewHolder);//然后将holder设置到convertView的tag里面去,Tag可以是任何Object
19         } else {
20             viewHolder = (ViewHolder) convertView.getTag();//如果缓存中convertView不是空,那就直接取tag中的holder
21         }
22 
23         Tweet tweet = tweetList.get(position);
24 
25         //对adapter自定义布局内的元素进行赋值处理
26         if (tweet.sender != null)
27             viewHolder.tv_username.setText(tweet.sender.username);
28         viewHolder.tv_content.setText(tweet.content);
29 
30         // 加载用户资料
31 //        Picasso.with(context).load(URL_TEST).into(viewHolder.img_user);//测试版的
32         Picasso.with(context).load(tweet.sender.avatar).into(viewHolder.img_user);//正式版的
33 
34         //对tweet.images进行遍历,每次加载一张图
35         List<String> imgList = tweet.images;
36         ImageView imageView;
37         viewHolder.ll_img_content.removeAllViews();
38         if (null != imgList) {
39             Log.d("imgListTag", "here:position=" + position + ";viewHolder.ll_img_content.getChildCount():" + viewHolder.ll_img_content.getChildCount());
40             // 每一次都检查ll_img_content中是否有元素,当且仅当内部元素为空时执行添加;否则,不予理会
41             for (int i = 0; i < imgList.size(); i++) {
42                 imageView = new ImageView(context);
43 
44                 //设置图片view的最大尺寸
45                 imageView.setAdjustViewBounds(true);
46                 imageView.setMaxHeight(100);
47                 imageView.setMaxWidth(100);
48 
49                 Picasso.with(context).load(imgList.get(i)).into(imageView);//正式版的
50                 viewHolder.ll_img_content.addView(imageView);
51             }
52         }
53 
54         //加载评论区
55         List<Comments> commentsList = tweet.comments;// 这是获取到的评论列表
56         //循环读取列表,每一次在ll_comments中加载一条。
57         TextView textView;
58 
59         //由于getView可能被重复绘制,因为 假如说编号为0 的item 从隐藏到显示,必然会调用getView,那么这部分代码就会被多次调用,所以在添加以前,必须移除其中的所有子view
60         viewHolder.ll_comments.removeAllViews();
61         if (commentsList != null && commentsList.size() > 0) {//除非一定有
62             for (int i = 0; i < commentsList.size(); i++) {
63                 textView = new TextView(context);
64                 String senderName = commentsList.get(i).sender.nick;
65                 String content = commentsList.get(i).content;
66 
67                 Spanned textContent = html.fromHtml("<font color=\"#7FB446\">" + senderName + "</font>" + " : " + content);
68                 textView.setText(textContent);
69                 viewHolder.ll_comments.addView(textView);
70             }
71         }
72         return convertView;
73     }
74 
75 
76     static class ViewHolder {//用内部类ViewHolder统筹item中的所有元素
77         TextView tv_username;
78         TextView tv_content;
79         ImageView img_user;
80         LinearLayout ll_img_content;
81         LinearLayout ll_comments;
82     }

2)设置item之间的分隔线,可以设置颜色,宽度,或者设置透明:

        android:dividerHeight="10dp"//宽度
        android:divider="@color/colorAccent"    //颜色
     android:divider="@null" //透明

3) 取消滚动条的显示:

android:scrollbars="none"

4)设置ListView显示在第几项(从0开始):

lv_tweet.setSelection(4);//第5项要显示出来

5) 动态修改listView:

adapter.notifyDataSetChanged();//这个操作是基于listView的adapter的

6)遍历ListView的item

for(int i=0;i<listView.getChildCount();i++){
   View v = listView.getChildAt(i);      
}

7) 处理空ListView,当数据为空时,用一个View暂时顶替ListView的显示,防止数据加载过程中的界面假死.

listView.setEmpty(v);

8) 增加滑动监听事件(这个onTouchEvent是 ListView 的父类 AbsListView 里的):

   @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN://按下
                downY = ev.getY();// 按下的时候的Y轴坐标
                break;
            case MotionEvent.ACTION_MOVE://按住的过程中移动

                break;
            case MotionEvent.ACTION_UP://松开
                doSomething();
                break;
            default:
                break;
        }

        return super.onTouchEvent(ev);
    }

9) 增加滚动监听事件:

   /********************************重写父类的方法******************************/
    /**
     * 当滚动状态改变时的回调
     *
     * @param view
     * @param scrollState
     */
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        switch (scrollState) {
            case OnScrollListener.SCROLL_STATE_IDLE://当list处于静止时
                Log.d(TAG, "SCROLL_STATE_IDLE");
                break;
            case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL://当手指按住list,并且在滑动时
                Log.d(TAG, "SCROLL_STATE_TOUCH_SCROLL");
                break;
            case OnScrollListener.SCROLL_STATE_FLING://当手指松开之后,list还在滑动时
                Log.d(TAG, "SCROLL_STATE_FLING");
                break;
        }
    }


    /**
     * 这个方法将会在滚动动作完成之后被的回调
     * <p>
     * 经过测试,这个方法,在list初始化之后就会首先被调用一次
     *
     * @param view
     * @param firstVisibleItem 显示在列表第一个的item的index
     * @param visibleItemCount 当前列表中可见的item的总个数(显示出一半也算在内)
     * @param totalItemCount   列表中所有item的总个数
     */
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if (totalItemCount == 0)//由于list初始化的时候,这个onScroll也会被调用一次,而打印出来的3个参数都是0,所以这种情况必须排除.只需要判断total是否为0即可,如果为0,下面的都不需要执行了
            return;
        // 判断当前list的状态(滑动到了顶部,或者底部,或者在中间)
        if (firstVisibleItem + visibleItemCount == totalItemCount) {// 通过观察规律,前两者之和等于后者,就是到了底部//到了底部,如果还继续往上滑
        } else if (firstVisibleItem == 0) {
            Log.d(TAG + "-onScroll", "to the top");//那就 加载数据,并且刷新list
        } else {
            Log.d(TAG + "-onScroll", "at mid");
        }
    }

然后

setOnScrollListener(this);//设置滑动回调

10)

 

以上是关于android-ListView使用技巧以及优化的主要内容,如果未能解决你的问题,请参考以下文章

jvm之年轻代(新生代)老年代永久代以及GC原理详解GC优化

我的Android进阶之旅关于Android使用bindService()绑定服务,onServiceConnected()方法是异步回调的问题以及借鉴NotificationManager来优化(代

我的Android进阶之旅关于Android使用bindService()绑定服务,onServiceConnected()方法是异步回调的问题以及借鉴NotificationManager来优化(代

android-ListView空间的使用

13.Android-ListView使用BaseAdapter/ArrayAdapter/SimpleAdapter适配器使用

Android-listview控件