ListView是每个app中都要使用的,所以今天我来总结下ListView的使用和一些简单的优化。
先看下运行效果:
一、创建数据库
为了模拟数据,这里将数据保存数据库中,顺便复习一下SQLite的知识,将数据保存到数据库的好处就是很容易模拟网络请求的延迟。
1.创建数据库打开帮助类BlackNumberDBOpenHelper,它继承自SQLiteOpenHelper
package com.yzx.listviewdemo.db;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class BlackNumberDBOpenHelper extends SQLiteOpenHelper {
public BlackNumberDBOpenHelper(Context context) {
super(context, "blacknumber.db", null, 1);
}
//数据库第一次创建的时候调用的方法。 适合做数据库表结构的初始化
@Override
public void onCreate(SQLiteDatabase db) {
//创建数据库的表结构 主键_id 自增长 number黑名单号码 mode拦截模式 1电话拦截 2短信拦截 3全部拦截。
db.execSQL("create table blacknumber (_id integer primary key autoincrement , number varchar(20), mode varchar(2))");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
2.创建类BlackNumberDao 在里面实现增删改查
package com.yzx.listviewdemo.db.dao;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import java.util.ArrayList;
import java.util.List;
import com.yzx.listviewdemo.db.BlackNumberDBOpenHelper;
import com.yzx.listviewdemo.domain.BlackNumberInfo;
/**
* 数据库增删改查的工具类
*
*/
public class BlackNumberDao {
private BlackNumberDBOpenHelper helper;
/**
* 构造方法中完成数据库打开帮助类的初始化
* @param context
*/
public BlackNumberDao(Context context) {
helper = new BlackNumberDBOpenHelper(context);
}
/**
* 添加一条黑名单号码
* @param number 黑名单号码
* @param mode 拦截模式 1电话拦截 2短信拦截 3全部拦截。
*/
public void add(String number,String mode){
SQLiteDatabase db = helper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("number", number);
values.put("mode", mode);
db.insert("blacknumber", null, values);
db.close();
}
/**
* 删除一条黑名单号码
* @param number 黑名单号码
*/
public void delete(String number){
SQLiteDatabase db = helper.getWritableDatabase();
db.delete("blacknumber", "number=?", new String[]{number});
db.close();
}
/**
* 更改一条黑名单号码的拦截模式
* @param number 要修改的黑名单号码
* @param newmode 新的拦截模式
*/
public void update(String number, String newmode){
SQLiteDatabase db = helper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("mode", newmode);
db.update("blacknumber", values, "number=?", new String[]{number});
db.close();
}
/**
* 获取全部的黑名单号码信息。
* @return
*/
public List<BlackNumberInfo> findAll(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
SQLiteDatabase db = helper.getReadableDatabase();
Cursor cursor = db.query("blacknumber", new String[]{"number","mode"}, null, null, null, null, null);
List<BlackNumberInfo> infos = new ArrayList<BlackNumberInfo>();
while(cursor.moveToNext()){
BlackNumberInfo info = new BlackNumberInfo();
String number = cursor.getString(0);
String mode = cursor.getString(1);
info.setMode(mode);
info.setNumber(number);
infos.add(info);
}
cursor.close();
db.close();
return infos;
}
/**
* 分页获取部分的黑名单号码信息。
* @param startIndex 查询的开始位置
* @return
*/
public List<BlackNumberInfo> findPart(int startIndex){
try {
Thread.sleep(600);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
SQLiteDatabase db = helper.getReadableDatabase();
Cursor cursor = db.rawQuery("select number,mode from blacknumber order by _id desc limit 20 offset ?", new String[]{String.valueOf(startIndex)});
List<BlackNumberInfo> infos = new ArrayList<BlackNumberInfo>();
while(cursor.moveToNext()){
BlackNumberInfo info = new BlackNumberInfo();
String number = cursor.getString(0);
String mode = cursor.getString(1);
info.setMode(mode);
info.setNumber(number);
infos.add(info);
}
cursor.close();
db.close();
return infos;
}
/**
* 获取数据库一共有多少条记录
* @return int 总条目个数
*/
public int getTotalCount(){
SQLiteDatabase db = helper.getReadableDatabase();
Cursor cursor = db.rawQuery("select count(*) from blacknumber ",null);
cursor.moveToNext();
int count = cursor.getInt(0);
cursor.close();
db.close();
return count;
}
/**
* 查询黑名单号码是否存在
* @param number
* @return
*/
public boolean find(String number){
boolean result = false;
SQLiteDatabase db = helper.getReadableDatabase();
Cursor cursor = db.query("blacknumber", null, "number=?", new String[]{number}, null, null, null);
if(cursor.moveToNext()){
result = true;
}
cursor.close();
db.close();
return result;
}
/**
* 查询黑名单号码的拦截模式
* @param number
* @return null代表不存在 1电话 2短信 3全部
*/
public String findMode(String number){
String mode = null;
SQLiteDatabase db = helper.getReadableDatabase();
Cursor cursor = db.query("blacknumber", new String[]{"mode"}, "number=?", new String[]{number}, null, null, null);
if(cursor.moveToNext()){
mode = cursor.getString(0);
}
cursor.close();
db.close();
return mode;
}
}
3.创建BlackNumberInfo实体
package com.yzx.listviewdemo.domain;
/**
* Created by yzx on 2016/7/5.
*/
public class BlackNumberInfo {
private String number;
private String mode;
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getMode() {
return mode;
}
public void setMode(String mode) {
this.mode = mode;
}
@Override
public String toString() {
return "BlackNumberInfo [number=" + number + ", mode=" + mode + "]";
}
}
里面的Thread.sleep(600)和Thread.sleep(3000)是用来模拟请求网络时的延迟。
二、在使用ListView展示列表
1.便于展示先添加100条数据
Random random = new Random();
//13512340001
for(int i = 0 ;i< 100;i++ ){
dao.add("1351234000"+i,String.valueOf(random.nextInt(3)+1));
}
2.列表的展示
在onCreate中初始化数据和布局文件,只有自定义继承自BaseAdapter的Adapter,在其getView加在每个item的布局文件。这样就能展示数据了,但是存在很多的问题,下面我们就来简单的优化。
三、ListView的简单优化
1.使用历史缓存的显示数据
View view;
if(convertView != null){
view = convertView;
Log.i(TAG, "使用历史缓存的显示数据"+position);
}else{
Log.i(TAG, "创建新的View的显示数据"+position);
view=View.inflate(CallSmsSafeActivity.this,
R.layout.list_callsmssafe_item, null);
}
2.使用ViewHolder优化
因为寻找孩子的过程是一个比较耗时,消耗资源的操作,进一步的优化。没有必要每一次都去查看每个孩子的特征,根据id得到孩子的引用。只需要在孩子出生的时候,找到特征,把特征给存起来。这里可以使用ViewHolder作为容器来保存孩子的引用。
private class CallSmsSafeAdapter extends BaseAdapter {
private static final String TAG = "CallSmsSafeAdapter";
@Override
public int getCount() {
return infos.size();
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
// 为了减少view对象创建的个数 使用历史缓存的view对象 convertview
View view;
ViewHolder holder;
if (convertView != null) {
view = convertView;
Log.i(TAG, "使用历史缓存的view对象" + position);
holder = (ViewHolder) view.getTag();
} else {
Log.i(TAG, "创建新的view对象" + position);
view = View.inflate(getApplicationContext(),
R.layout.list_callsmssafe_item, null);
// 寻找孩子的过程 是一个比较耗时 消耗资源的操作 进一步的优化。
// 没有必要每一次都去查看每个孩子的特征 根据id得到孩子的引用。
// 只需要在孩子出生的时候 , 找到特征 ,把特征给存起来
holder = new ViewHolder();// 买了一个记事本 记录孩子的信息。
holder.tv_number = (TextView) view.findViewById(R.id.tv_number);
holder.tv_mode = (TextView) view.findViewById(R.id.tv_mode);
holder.iv_delete = (ImageView) view.findViewById(R.id.iv_delete);
view.setTag(holder); // 把孩子的引用 记事本 放在口袋里
}
BlackNumberInfo info = infos.get(position);
String mode = info.getMode();
if ("1".equals(mode)) {
holder.tv_mode.setText("电话拦截");
} else if ("2".equals(mode)) {
holder.tv_mode.setText("短信拦截");
} else if ("3".equals(mode)) {
holder.tv_mode.setText("电话+短信拦截");
}
holder.tv_number.setText(info.getNumber());
holder.iv_delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
BlackNumberInfo info = infos.get(position);
String number = info.getNumber();
dao.delete(number);//删除数据库里面的记录。
infos.remove(info);//删除当前界面对应的集合的数据。
adapter.notifyDataSetChanged();//通知界面刷新。
totalCount--;
}
});
return view;
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return null;
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return 0;
}
}
class ViewHolder {
TextView tv_number;
TextView tv_mode;
ImageView iv_delete;
}
四、ListView的UI效果优化
因为我们我们在app中使用ListView展示数据一般都是请求网络获取数据的,所以曾在延迟,我们应该给用户显示一个ProgressBar来增加用户的等待欲。当数据很多时,我们不仅要使用ProgressBar,还要分批加载数据。
1.创建一个线程去读取数据
new Thread() {
public void run() {
list = dao.findAll();//查询数据库得到数据
handler.sendEmptyMessage(0);//发消息给主线程更新数据
};
}.start();
//更新数据
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
lv_call_sms_safe.setAdapter(new CallSmsSafeAdapter());
};
};
2.没数据的时候加一个提醒效果
在布局文件中添加相应控件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ListView
android:id="@+id/lv_call_sms_safe"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView>
<LinearLayout
android:id="@+id/ll_loading"
android:visibility="invisible"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical" >
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="玩命加载中。。。" />
</LinearLayout>
</FrameLayout>
</LinearLayout>
//代码配合处理
ll_loading.setVisibility(View.VISIBLE);//没数据的时候显示
ll_loading.setVisibility(View.INVISIBLE);//数据加载好了隐藏
3.分批加载的好处:不用等待太久、节约流量、慢慢引导用户看感兴趣内容
/**
* 分页获取部分的黑名单号码信息。
* @param startIndex 查询的开始位置
* @return
*/
public List<BlackNumberInfo> findPart(int startIndex){
try {
Thread.sleep(600);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
SQLiteDatabase db = helper.getReadableDatabase();
Cursor cursor = db.rawQuery("select number,mode from blacknumber order by _id desc limit 20 offset ?", new String[]{String.valueOf(startIndex)});
List<BlackNumberInfo> infos = new ArrayList<BlackNumberInfo>();
while(cursor.moveToNext()){
BlackNumberInfo info = new BlackNumberInfo();
String number = cursor.getString(0);
String mode = cursor.getString(1);
info.setMode(mode);
info.setNumber(number);
infos.add(info);
}
cursor.close();
db.close();
return infos;
}
4.监听拖动到末尾
lv_call_sms_safe.setOnScrollListener(new AbsListView.OnScrollListener() {
// 滚动状态变化了。
// 静止-->滚动
// 滚动-->静止
// 手指触摸滚动-->惯性滑动
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:// 静止状态
// 判断界面是否已经滑动到了最后面。
// 获取最后一个可见条目的位置
int position = lv_call_sms_safe.getLastVisiblePosition(); // 19
int total = infos.size(); // 20
if (isloading) {
Toast.makeText(getApplicationContext(),
"正在加载数据,不要重复刷新。", Toast.LENGTH_SHORT).show();
return;
}
if (position >= (total - 5)) {
// 指定新的获取数据的开始位置
startIndex += 20;
if (startIndex >= totalCount) {
Toast.makeText(getApplicationContext(),
"没有更多数据了,别再拖了", Toast.LENGTH_SHORT).show();
return;
}
Toast.makeText(getApplicationContext(),
"拖动到最下面了。加载更多数据", Toast.LENGTH_SHORT).show();
fillData();
}
break;
case AbsListView.OnScrollListener.SCROLL_STATE_FLING:// 惯性滑动
break;
case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:// 触摸滚动
break;
}
}
// 滚动的时候执行的方法
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
}
});
private void fillData() {
ll_loading.setVisibility(View.VISIBLE);
isloading = true;
new Thread() {
public void run() {
if (infos == null) {
infos = dao.findPart(startIndex);
} else {// 原来集合里面已经有数据了。
infos.addAll(dao.findPart(startIndex));
}
// 发送一个消息 更新界面。
handler.sendEmptyMessage(0);
};
}.start();
}
5.让数据继续停留在当前位置
两种实现方式
第一种:lv_call_sms_safe.setSelection(startIndex);//不推荐
第二种:重复利用适配器,数据变化通知一下就行了
private CallSmsSafeAdapter adapter;
Handler里面修改成:
if(adapter == null){
adapter = new CallSmsSafeAdapter();
lv_call_sms_safe.setAdapter(adapter);
}else{
//通知数据适配器更新一下界面
adapter.notifyDataSetChanged();
}
6.防止重复加载
定义成员变量
private boolean isLoading = false;
在加载的过程中
if(isLoading){
return;
}
isLoading = true;
加载好后 handler里面处理
isLoading = false;
7.处理拖动到所有数据的最后一条时的处理
得到数据库一共有多少条数据
/**
* 得到数据的总数
* @return
*/
public int getTotalCount(){
int count = 0;
SQLiteDatabase database = helper.getWritableDatabase();
Cursor cursor = database.rawQuery("select count(*) from blacknumber", null);
if(cursor.moveToNext()){
count = cursor.getInt(0);
}
cursor.close();
database.close();
return count;
}
代码处理
if(startIndex >= total){
Toast.makeText(getApplicationContext(), "已经最后一条了", 0).show();
return;
}
到此就已经实现了ListView的使用和简单优化。