ListView:android里面经典的列表view,一般我们都会去自定义adapter来达到业务需求,必要的时候还会继承ListView,重写其中某些方法形成 我特有的ListView。本文总结一些ListView的使用技巧,以及性能优化的方法。
使用技巧:
这里,有一个知识背景需要强调,用过自定义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)