ListView优化技巧 ,ListView分页

 
      

说明:】手机屏幕中一次展示10条数据,所以第一次加载时,新建了10个ViewHolder对象。这10个对象的id从截图中可以看到:@411e8a18、@413ab880等等。
当屏幕滑动,有些item滑出屏幕,又有新的item从底端进入屏幕。可以看到新item进入屏幕时只有一个是新建ViewHolder,其他的都是重复使用convertView。


一、ListView优化中的细节问题
1、android:layout_height属性:
        必须将ListView的布局高度属性设置为非“wrap_content”(可以是“match_parent /  fill_parent  /  400dp等绝对数值”),如果ListView的布局高度为“wrap_content”,那么getView()就会重复调用。一般来说,一个item会被调用四次左右。


2、ViewHolder
        利用ViewHolder内部类,将item布局文件中需要展示的控件定义为属性(其实ViewHolder就是一个自定义的模型类)。这样就把item中散在的多个控件合成一个整体,这样可以有效地避免图片错位


3、convertView
        ListView的加载是一个item一个item的加载,这样就会每次都inflate一个item布局,然后findViewById一遍该布局上的所有控件。当数据量大的时候,是不可想象的。而利用Recycle回收利用就可以解决问题。所以要善于重复利用convertView,这样可以减少填充布局的过程,减少ViewHolder对象实例化的次数。减少内存开销,提高性能。


4、convertView的setTag():
        利用setTag()方法将ViewHolder对象作为标签附加到convertView上,当convertView被重复利用的时候,因为上面有ViewHolder对象,所以convertView就具有了ViewHolder中的几个属性,这样就节省了findViewById()这个过程。如果一个item有三个控件,如果有100条item,那么在加载数据过程中,就就相当于节省了几百次findViewById(),节约了执行findViewById()的时间,提升了加载速度,节省了性能的开销。


5、LayoutInflater对象的inflate()方法:        
  •     inflate()方法一般接收两个参数,第一个参数就是要加载的布局id,第二个参数是指给该布局的外部再嵌套一层父布局,如果不需要就直接传null
  •     inflate()方法还有个接收三个参数的方法重载
  • 1. 如果root为null,attachToRoot将失去作用,设置任何值都没有意义。
  • 2. 如果root不为null,attachToRoot设为true,则会在加载的布局文件的最外层再嵌套一层root布局。
  • 3. 如果root不为null,attachToRoot设为false,则root参数失去作用。
  • 4. 在不设置attachToRoot参数的情况下,如果root不为null,attachToRoot参数默认为true。
        所以在使用LayoutInflater填充布局的时候,要注意inflate()方法的参数。如果是两个参数,则第二个参数可以采用null;如果使用三个参数的方法,则要注意参数之间的搭配。
【备注:】
获取填充器的三种方法:
  • LayoutInflater inflater = getLayoutInflater(); 
  • LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
  • LayoutInflater inflater = LayoutInflater.from(MainActivity.this);


6、监听屏幕的滚动状态的变动情况:
    ListView对象有OnScrollListener监听器。其回调方法onScrollStateChanged(AbsListView view, int scrollState)的第二个参数就是屏幕滚动状态。
  •   scrollState = SCROLL_STATE_TOUCH_SCROLL(1):表示正在滚动。当屏幕滚动且用户使用的触碰或手指还在屏幕上时为1
  •   scrollState =SCROLL_STATE_FLING(2) :表示手指做了抛的动作(手指离开屏幕前,用力滑了一下,屏幕产生惯性滑动)。 
  •   scrollState =SCROLL_STATE_IDLE(0) :表示屏幕已停止。屏幕停止滚动时为0。
    在以上三种屏幕滚动状态中,如果还处于SCROLL_STATE_FLING状态,则说明屏幕还处于惯性滑动状态,此时可以不进行异步加载图片。这样可以节省不必要的性能开销。


7、item中如果包含button,则事件会发生冲突。如何解决控件之间的事件冲突,将在Android事件分发机制(touch event)中讲解。


ListView分页

一、ListView分页:
(一)、目的:
         Android 应用开发中,采用ListView组件来展示数据是很常用的功能,当一个应用要展现很多的数据时,一般情况下都不会把所有的数据一次就展示出来,而是通过 分页的形式来展示数据,这样会有更好的用户体验。因此,很多应用都是采用分批次加载的形式来获取用户所需的数据。例如:微博客户端可能会在用户滑 动至列表底端时自动加载下一页数据,也可能在底部放置一个"查看更多"按钮,用户点击后,加载下一页数据。
(二)、核心技术点:
  1.   借助 ListView组件的OnScrollListener监听事件,去判断何时该加载新数据;
  2.   往服务器get传递表示页码的参数:page。而该page会每加载一屏数据后自动加一;
  3.   利用addAll()方法不断往list集合末端添加新数据,使得适配器的数据源每新加载一屏数据就发生变化;
  4.   利用适配器对象的notifyDataSetChanged()方法。该方法的作用是通知适配器自己及与该数据有关的view,数据已经发生变动,要刷新自己、更新数据。

(三)、 OnScrollListener监听事件 :
1、该监听器中有两个需要实现的方法:
  •   onScrollStateChanged(AbsListView view, int scrollState):监听屏幕的滚动状态的变动情况
  •   onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount):监听屏幕滚动的item的数量
2、 scrollState 回调顺序如下: 
  •   第1次:scrollState = SCROLL_STATE_TOUCH_SCROLL(1):表示正在滚动。当屏幕滚动且用户使用的触碰或手指还在屏幕上时为1
  •   第2次:scrollState =SCROLL_STATE_FLING(2) :表示手指做了抛的动作(手指离开屏幕前,用力滑了一下,屏幕产生惯性滑动)。 
  •   第3次:scrollState =SCROLL_STATE_IDLE(0) :表示屏幕已停止。屏幕停止滚动时为0
3、 onScroll中参数讲解:
  • firstVisibleItem:当前窗口中能看见的第一个列表项ID(从0开始)    
  • visibleItemCount:当前窗口中能看见的列表项的个数(小半个也算)    
  • totalItemCount:列表项的总数

4、思路:
  • 当滚到最后一条,加载新数据
  • 适配器的数据源要进行累加:totalList.addAll(list);
  • 数据发生变化,适配器通知:adapter.notifyDataSetChanged();【牢记】
  • 判断是否滚到最后一行。  
示例代码:
 
               
if (firstVisibleItem visibleItemCount == totalItemCount ) {    
isBottom = true; 
}
 
(四)、页面效果:

【要求:】
当屏幕滑动到最后一条时,显示“点击加载数据”的提示。点击后可以加载新的数据。当向上滑动或者没有到最后一条时,不显示“点击加载数据”的提示。

(五)、核心代码:

1、布局文件的核心代码:
 
         

<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent">


   <ListView

       android:id="@+id/listView_main"

       android:layout_below="@+id/button_main_init"

        android:layout_width="match_parent"

        android:layout_height="match_parent">

   ListView>


   <LinearLayout

       android:id="@+id/layout_main_nextpage"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:layout_alignParentBottom="true"

        android:background="#000"

        android:visibility="invisible"

        android:gravity="center"

        android:onClick="clickButton"

        android:padding="5dp">


       <ProgressBar

           android:id="@+id/progressBar_main"

           style="?android:attr/progressBarStyleSmall"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"/>

        

        <TextView

       android:id="@+id/text_main_nextpage"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_marginLeft="10dp"

        android:textSize="18sp"

        android:onClick="clickButton"

        android:textColor="#fff"

        android:text="点击加载更多数据"/>

   LinearLayout>


RelativeLayout> 


2、Activity页面核心代码:
 
               

publicclass MainActivity extends Activity {

privateStringTAG= "MainActivity";

privateListView listView_main;

privateLinearLayout layout_main_nextpage;


private MySQLiteDatabaseHelper dbHelper = null;


// 用于分页显示数据的属性

privateintpageSize= 30;// 每页显示的条数

privateintcurPage= 1;

privateintrowCount= 0;

privateintpageCount= 0;// 总页数


privatebooleanisBottom=false;// 判断是否滚动到数据最后一条

private ListtotalList = null;// 加载到适配器中的数据源

private SimpleAdapter adapter = null;


@Override

protectedvoid onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);


listView_main = (ListView) findViewById(R.id.listView_main);

layout_main_nextpage = (LinearLayout) findViewById(R.id.layout_main_nextpage);


// 实例化访问数据库帮助类

dbHelper = new MySQLiteDatabaseHelper();

// 获取数据表一共有多少条,从而计算共有多少页

rowCount=dbHelper.selectCount("select id from android_basic",null);

// 计算总页码数

pageCount = (int) Math.ceil(rowCount / (floatpageSize);


// 如果当前页为第一页,则数据源集合中就是第一页的内容

if (curPage == 1) {

totalList = getCurpageList(1);

}

adapter = new SimpleAdapter(thistotalList,

R.layout.item_listview_mainnew String[] { "_id""title" },

newint[] { R.id.text_item_listview_id,

R.id.text_item_listview_title});

listView_main.setAdapter(adapter);


// 给ListView对象设置滚动监听器,以此来判断是否已经滚动到最后一条,从而决定是否加载新数据

listView_main.setOnScrollListener(new OnScrollListener() {

@Override

publicvoid onScrollStateChanged(AbsListView view, int scrollState) {

if (isBottom) {

// 如果滚到最后一条数据(即:屏幕最底端),则显示:“加载更多新数据”

if(curPage < pageCount) {

layout_main_nextpage.setVisibility(View.VISIBLE);

}

else {

layout_main_nextpage.setVisibility(View.GONE);

}

}


@Override

publicvoid onScroll(AbsListView view, int firstVisibleItem,

int visibleItemCount, int totalItemCount) {

// Log.i(TAG, "==" + firstVisibleItem + ":::" + visibleItemCount

// + ":::" + totalItemCount);

// 判断是否已经滚动到了最后一条,从而决定是否提示加载新数据

isBottom = (firstVisibleItem + visibleItemCount == totalItemCount);

}

});

}


publicvoid clickButton(View view) {

switch (view.getId()) {

caseR.id.layout_main_nextpage:

// Log.i(TAG, "==" + curPage + ":::" + pageCount);

// 如果不是最后一页,则让当前页码累加,让数据源累加新数据,并通知适配器信息发生变化

if(curPage < pageCount) {

curPage++;

totalList.addAll(getCurpageList(curPage));

adapter.notifyDataSetChanged();

}

// 只要点击了提示“加载新数据”的信息,就让其隐藏

layout_main_nextpage.setVisibility(View.GONE);

break;

default:

break;

}

}


// 获取每一页的数据,返回List集合

private List> getCurpageList(int currentPage) {

int offset = (currentPage - 1) * pageSize;

String sql = "select id _id ,title from android_basic limit ? , ?";

returndbHelper.selectData(sql, new String[] { offset + "",

pageSize + "" });

}

 
}  

【备注】:ListView的置顶
listView_info.setSelection(int position);
listView_info. setSelectionFromTop(0, 0);

setSelectionFromTop()的作用是设置ListView选中的位置,同时在Y轴设置一个偏移量。
setSelection()内部就是调用了setSelectionFromTop(),只不过是Y轴的偏移量是0而已。





你可能感兴趣的:(Android)