zoukankan      html  css  js  c++  java
  • 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)让ListView具有弹性

    (如果滑动到了最上面,或者最下面,android里面没有什么友好的提示,android5.0只增加了一个半月形的灰色动画)

    这里的红色数字,100,其实就是 弹性的最大距离list的滑动效果,

     

    @Override
        protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
            return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, 100, isTouchEvent);
        }

     

    具体效果,参照IOS上:滑到顶端之后,它会弹一下,比较友好。

     

     关于ListView的优化:

    优化方式1) 

    如果getView中有大量对象的创建,那么,必须将创建的代码放置在viewHolder创建的代码附近,保证创建动作只执行一次。(此方案,基于 1)ViewHolder 模式提高效率 )
    优化方式2)

    如果有大量数据需要展示,可以采用分段加载。常用的技术手段是,上拉加载,下拉刷新(懂的都懂,不解释了)。每一次滑到底部之后上拉,就多加载几条。
    优化方式3)

    优化方式2,在一定情况下可以应付 数据量过大的问题,那么数据量如果继续增大,比如listView中容纳一万个item,那么它就算是采用了分段加载,也是吃不消的,

    因为生成的item就算没有在界面上显示,也是存在于内存中的,占用内存空间的,量太大了,还是有可能OOM,内存溢出。

    这种情况下,就不必保留原有的数据List,直接将原有的List清空,然后加入新的数据,再调用notifyDataSetChanged进行list刷新。
  • 相关阅读:
    mysql 查看表注解
    oracle 相关
    sql version control
    ccna
    msql 清库
    mybatisplus,application.properties 配置数据库密码加密
    驱动开发print无法输出问题
    bochs帮助
    以虚御虚用虚拟机调试vt程式
    ssm整合
  • 原文地址:https://www.cnblogs.com/hankzhouAndroid/p/8976396.html
Copyright © 2011-2022 走看看