• Android进阶笔记11:ListView篇之ListView性能优化


    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的性能优化之 使用异步加载

    参见:Android中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语句里代码执行了,所以如果我们希望自动加载的话,可以把加载代码放于此处

  • 相关阅读:
    历数几款第三方的Oracle数据库安全及漏洞扫描软件
    Oracle Real Application Testing diagram
    Exadata实体图
    关于喝牛奶的几点注意事项 生活至上,美容至尚!
    夏季防暑降温小常识汇总 生活至上,美容至尚!
    geotools Point创建一个点
    CGAL Arrangements and Their Applications
    CGAL安装
    对一个或多个实体的验证失败。有关详细信息,请参阅“EntityValidationErrors” 属性。
    每个文件的大小必须大于或等于512 KB。
  • 原文地址:https://www.cnblogs.com/hebao0514/p/5198261.html
Copyright © 2020-2023  润新知