1. 首先思考一个问题ListView如何才能提高效率 ?
当convertView为空时候,用setTag()方法为每个View绑定一个存放控件的ViewHolder对象。当convertView不为空,重复利用已经创建的View的时候,使用getTag()方法获取绑定的ViewHolder对象,这样就避免了findViewById对控件的层层查询,而是快速定位到控件。
鉴于上面分析,可以通过如下方法对ListView进行性能优化:
(1)复用convertView,使用历史的View,提高效率200%
(2)自定义静态类ViewHolder,减少findViewById的次数,提高效率50%
(3)异步加载数据,分页加载数据
(4)使用WeakRefrence 引用ImageView对象(采用WeakRefrence (弱引用),防止在不断刷新当前界面View时候产生内存泄露)
2. ListView的性能优化之 使用convertView和ViewHolder:
(1)自定义ListView的Adapter时候,里面有个实现方法为getView(),这个方法专门用来加载View的,优化getView()方法,内容如下:
1 static class ViewHolder { 2 TextView text; 3 ImageView icon; 4 } 5 public View getView(int position, View convertView, ViewGroup parent) 6 { 7 ViewHolder holder; 8 if (convertView == null) { 9 convertView = mInflater.inflate(R.layout.list_item_icon_text,parent, false); 10 holder = new ViewHolder(); 11 holder.text = (TextView) convertView.findViewById(R.id.text); 12 holder.icon = (ImageView) convertView.findViewById(R.id.icon); 13 convertView.setTag(holder); 14 } else { 15 holder = (ViewHolder) convertView.getTag(); 16 } 17 18 holder.text.setText(DATA[position]); 19 holder.icon.setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2); 20 return convertView; 21 }
先讲下ListView的原理:ListView中的每一个Item显示都需要Adapter调用一次getView的方法,这个方法会传入一个convertView的参数,返回的View就是这个Item显示的View。如果当Item的数量足够大,再为每一个Item都创建一个View对象,必将占用很多内存,创建View对象(mInflater.inflate(R.layout.lv_item, null);从xml中生成View,这是属于IO操作)也是耗时操作,所以必将影响性能。Android提供了一个叫做Recycler(反复循环器)的构件,就是当ListView的Item从上方滚出屏幕视角之外,对应Item的View会被缓存到Recycler中,相应的会从下方生成一个Item,而此时调用的getView中的convertView参数就是滚出屏幕的Item的View,所以说如果能重用这个convertView,就会大大改善性能。
我们都知道在getView方法中的操作是这样的:先从xml中创建view对象(inflate操作,我们采用了重用convertView方法优化),然后在这个view去findViewById,找到每一个子View,如:一个TextView等。这里的findViewById操作是一个树查找过程,也是一个耗时的操作,所以这里也需要优化,就是使用viewHolder,把每一个子View都放在Holder中,当第一次创建convertView对象时,把这些子view找出来。然后用convertView的setTag将viewHolder设置到Tag中,以便系统第二次绘制ListView时从Tag中取出。当第二次重用convertView时,只需从convertView中getTag取出来就可以。
3. ListView的性能优化之 使用异步加载:
4. ListView性能优化之 使用分页加载数据:
通常来说,一个应用在展现大量数据时,不会将全部的可用数据都呈现给用户,因为这不管对于服务端还是客户端来说都是不小的压力,因此,很多应用都是采用分批次加载的形式来获取用户所需的数据。比如:微博客户端可能会在用户滑动至列表底端时自动加载下一页数据,也可能在底部放置一个“加载更多”按钮,用户点击后,加载下一页数据。
我们今天就结合实例来演示一下使用ListView获取数据的过程,当然你使用GridView也是类似的。
(1)新建一个"ListView分页加载"工程,看一下 结构图 和 最终效果图:
结构图:
最终效果图:
上面结构图之中,包含了三个布局文件,一个Adapter和一个Activity
(2)首先我们来到主布局main.xml,它包含一个ListView组件,代码如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="fill_parent" 4 android:layout_height="fill_parent" 5 android:orientation="vertical" 6 android:paddingLeft="3dp" 7 android:paddingRight="3dp" > 8 9 <ListView 10 android:id="@id/android:list" 11 android:layout_width="fill_parent" 12 android:layout_height="wrap_content" /> 13 14 </LinearLayout>
这里我们引用了Android内置的名为list的id,因为我们后面要使用到ListActivity,我们的MainActivity继承于它。
然后是list_item.xml,它是ListView中单个列表项的布局文件,从效果图中可以看到,这里只使用一个TextView组件:
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="fill_parent" 4 android:layout_height="fill_parent" 5 android:orientation="vertical" > 6 7 <TextView 8 android:id="@+id/list_item_text" 9 android:layout_width="fill_parent" 10 android:layout_height="fill_parent" 11 android:gravity="center" 12 android:paddingBottom="10dp" 13 android:paddingTop="10dp" 14 android:textSize="20sp" /> 15 16 </LinearLayout>
我们注意到在右图中列表底部有一个按钮不同于其他的列表项,这是什么情况?事实上这个按钮是我们在ListView底部添加的一个视图。ListView组件提供了两个很实用的功能,那就是可以在顶部和底部添加自定义的视图。我们在此处ListView的底部添加了一个视图用来加载更多数据,这个视图对应着load_more.xml布局文件,代码如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="fill_parent" 4 android:layout_height="wrap_content" 5 android:orientation="vertical" > 6 7 <Button 8 android:id="@+id/loadMoreButton" 9 android:layout_width="fill_parent" 10 android:layout_height="wrap_content" 11 android:onClick="loadMore" 12 android:text="load more" /> 13 14 </LinearLayout>
(3)接下来我们了解一下我们的Adapter,ListViewAdapter代码如下:
1 package com.himi.listviewload; 2 3 import java.util.List; 4 5 import android.content.Context; 6 import android.view.LayoutInflater; 7 import android.view.View; 8 import android.view.ViewGroup; 9 import android.widget.BaseAdapter; 10 import android.widget.TextView; 11 12 public class ListViewAdapter extends BaseAdapter { 13 private List<String> items; 14 private LayoutInflater inflater; 15 16 public ListViewAdapter(Context context, List<String> items) { 17 this.items = items; 18 inflater = (LayoutInflater) context.getSystemService(Context 19 .LAYOUT_INFLATER_SERVICE); 20 } 21 22 @Override 23 public int getCount() { 24 return items.size(); 25 } 26 27 @Override 28 public Object getItem(int position) { 29 return items.get(position); 30 } 31 32 @Override 33 public long getItemId(int position) { 34 return position; 35 } 36 37 @Override 38 public View getView(int position, View convertView, ViewGroup parent) { 39 if (convertView == null) { 40 convertView = inflater.inflate(R.layout.list_item, null); 41 } 42 TextView text = (TextView) convertView.findViewById(R.id.list_item_text); 43 text.setText(items.get(position)); 44 return view; 45 } 46 47 /** 48 * 添加列表项 49 * @param item 50 */ 51 public void addItem(String item) { 52 items.add(item); 53 } 54 }
这个ListViewAdapter是我们自定义适配器,它继承自BaseAdapter,实例化此适配器需要一个Context对象来获取LayoutInflater实例和一个集合对象来充当适配器的数据集;在getView方法中我们填充list_item.xml布局文件,完成列表每一项的数据显示;addItem方法用来在加载数据时向数据集中添加新数据。
(4)最后我们看看一个MainActivity,如下:
1 package com.himi.listviewload; 2 import java.util.ArrayList; 3 4 import android.app.ListActivity; 5 import android.os.Bundle; 6 import android.os.Handler; 7 import android.util.Log; 8 import android.view.View; 9 import android.widget.AbsListView; 10 import android.widget.AbsListView.OnScrollListener; 11 import android.widget.Button; 12 import android.widget.ListView; 13 14 public class MainActivity extends ListActivity implements OnScrollListener { 15 private ListView listView; 16 private int visibleLastIndex = 0; //最后的可视项索引 17 private int visibleItemCount; //当前窗口可见项总数 18 private ListViewAdapter adapter; 19 private View loadMoreView; 20 private Button loadMoreButton; 21 private Handler handler = new Handler(); 22 23 @Override 24 public void onCreate(Bundle savedInstanceState) { 25 super.onCreate(savedInstanceState); 26 setContentView(R.layout.main); 27 28 loadMoreView = getLayoutInflater().inflate(R.layout.load_more, null); 29 loadMoreButton = (Button) loadMoreView.findViewById(R.id.loadMoreButton); 30 31 listView = getListView(); //获取id是list的ListView 32 33 listView.addFooterView(loadMoreView); //设置列表底部视图 34 35 initAdapter(); 36 37 setListAdapter(adapter); //自动为id是list的ListView设置适配器 38 39 listView.setOnScrollListener(this); //添加滑动监听 40 } 41 42 /** 43 * 初始化适配器 44 */ 45 private void initAdapter() { 46 ArrayList<String> items = new ArrayList<String>(); 47 for (int i = 0; i < 10; i++) { 48 items.add(String.valueOf(i + 1)); 49 } 50 adapter = new ListViewAdapter(this, items); 51 } 52 53 /** 54 * 滑动时被调用 55 */ 56 @Override 57 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { 58 this.visibleItemCount = visibleItemCount; 59 visibleLastIndex = firstVisibleItem + visibleItemCount - 1; 60 61 //System.out.println("onScroll:"+visibleLastIndex); 62 } 63 64 /** 65 * 滑动状态改变时被调用 66 */ 67 @Override 68 public void onScrollStateChanged(AbsListView view, int scrollState) { 69 int itemsLastIndex = adapter.getCount() - 1; //数据集最后一项的索引 70 int lastIndex = itemsLastIndex + 1; //加上底部的loadMoreView项 71 if (scrollState == OnScrollListener.SCROLL_STATE_IDLE && visibleLastIndex == lastIndex) { 72 //如果是自动加载,可以在这里放置异步加载数据的代码 73 74 Log.e("LOADMORE", "loading..."); 75 } 76 } 77 78 /** 79 * 点击按钮事件 80 * @param view 81 */ 82 public void loadMore(View view) { 83 loadMoreButton.setText("loading..."); //设置按钮文字loading 84 handler.postDelayed(new Runnable() { 85 @Override 86 public void run() { 87 88 loadData(); 89 90 adapter.notifyDataSetChanged(); //数据集变化后,通知adapter 91 listView.setSelection(visibleLastIndex - visibleItemCount + 2); //设置选中项:ListView第一个可视Item 92 System.out.println("loadMore(visibleLastIndex):"+visibleLastIndex); 93 loadMoreButton.setText("load more"); //恢复按钮文字 94 } 95 }, 2000); 96 } 97 98 /** 99 * 模拟加载数据 100 */ 101 private void loadData() { 102 int count = adapter.getCount(); 103 for (int i = count; i < count + 10; i++) { 104 adapter.addItem(String.valueOf(i + 1)); 105 } 106 } 107 }
如代码所示,我们在onCreate方法被调用时获取listView组件,设置其底部视图为loadMoreView,它包含一个按钮,点击时会触发loadMore方法调用,另外在为listView设置完适配器时,又为其设置了滑动事件监听器,滑动列表时onScroll会被调用,滑动状态改变时onScrollStateChanged会被调用。
上面设置ListView的滚动监听器:setOnScrollListener(new OnScrollListener{……})
在上面的监听器中有两个方法:滚动状态发生变化的方法(onScrollStateChanged)和 ListView被滚动的时候调用的方法(onScroll)。
滚动状态发生改变的方法(onScrollStateChanged)之中,有三种状态:
> 手指按下移动的状态:SCROLL_STATE_TOUCH_SCROLL(触摸滑动)
> 惯性滚动:SCROLL_STATE_FLING(滑翔)
> 静止状态:SCROLL_STATE_IDLE(静止)
分页(分批)加载数据,我们只关心静止状态(SCROLL_STATE_IDLE),只关心最后一个可见的条目,如果最后一个可见条目就是数据适配器(集合)里面的最后一个数据,此时可加载更多的数据。在每次加载的时候,计算滚动的数量,当滚动的数量大于等于总数量的时候,可以提示用户没有更多的数据了。
(5)部署程序到手机上,演示一下:
如图,当点击完按钮后,出现加载动作,加载完之后如右图所示,新数据紧接在原数据之后。然后我们滑动到底部,加载按钮仍可工作:
最后,我们测试一下滑动列表到底部,然后松开,控制台打印如下:
我们看到onScrollStateChanged方法里的if语句里代码执行了,所以如果我们希望自动加载的话,可以把加载代码放于此处。
来源:https://www.cnblogs.com/hebao0514/p/5198261.html