如果数据库特别大,存储的数据特别多,我们把它加载到适配器控件中,很容易出现内存溢出的情况,并且数据加载的速度也会受到影响。所以我们在加载数据的时候,为了解决这个问题,让用户体验更好,我们可以采用分页的形式,每次加载10条,或者20条,当用户去滑动,有查看下一页的需求时,再次查询数据库,进行数据的展示。
要进行数据库分页,我们要用到的就是 limit 子句,如下:
select * from person limit ?,?;
limit 用到两个参数,前者是当前页首数据的下标,后者则是每页显示的数据。
这里我有一个SQLite 数据库文件,有张 PERSON 表,表中数据如下:
我们把它放到模拟器 sdcard 目录下,让我们可以找到。
在讲代码前,我们要先知道实现数据库分页需要什么,总条目(数据库有多少条数据),每页显示条数,总页数,当前页码,有了这些我们就能准确的从数据库中取到数据了。
用 ListView 加载数据先展示第一页的数据,当 ListView 加载完本页数据后再加载下一页。
布局文件
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
item.xml:
<?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">
<TextView
android:id="@+id/tv_id"
android:layout_weight="1"
android:layout_width="0dp"
android:textSize="20sp"
android:textColor="#aa0000"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="_id"/>
<TextView
android:id="@+id/tv_name"
android:layout_weight="1"
android:layout_width="0dp"
android:textSize="20sp"
android:textColor="#00aa00"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="name"/>
<TextView
android:id="@+id/tv_age"
android:layout_weight="1"
android:layout_width="0dp"
android:textSize="20sp"
android:textColor="#0000aa"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="age"/>
</LinearLayout>
这里我要创建一个常量类,一个实体类,一个数据库的工具类,用来实现对数据库的加工操作。
Constant.java:
public class Constant {
public static final String DATABASE_NAME = "info.db"; //数据库名称
public static final int DATABASE_VERSION = 1; //数据库版本
public static final String TABLE_NAME = "person"; //表名
public static final String _ID = "_id";
public static final String NAME = "name";
public static final String AGE = "age";
}
Person.java:
public class Person {
private int _id;
private String name;
private int age;
public Person(int _id, String name, int age) {
this._id = _id;
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"_id=" + _id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
public int get_id() {
return _id;
}
public void set_id(int _id) {
this._id = _id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
DbManager.java:
public class DbManager {
public static List<Person> cursorTolist(Cursor cursor) {
List<Person> list = new ArrayList<Person>();
while (cursor.moveToNext()) {
int columnIndex = cursor.getColumnIndex(Constant._ID);
int _id = cursor.getInt(columnIndex);
String name = cursor.getString(cursor.getColumnIndex(Constant.NAME));
int age = cursor.getInt(cursor.getColumnIndex(Constant.AGE));
Person person = new Person(_id, name, age);
list.add(person);
}
return list;
}
/**
* 根据数据库以及数据表名称获取表中数据总条目
*/
public static int getDataCount(SQLiteDatabase db, String tableName) {
int count = 0;
if (db != null) {
Cursor cursor = db.rawQuery("select * from " + tableName, null);
count = cursor.getCount(); //获取cursor中的数据总数
}
return count;
}
/**
* 根据页码获取对应的数据源
* @param db 数据库对象
* @param tableName 表名
* @param currentPage 当前页码
* @param pageSize 每页总条目
* @return 当前页对应的集合
*/
public static List<Person> getListByCurrentPage(SQLiteDatabase db, String tableName, int currentPage, int pageSize) {
int index = (currentPage - 1) * pageSize; //获取当前页码第一条数据的下标
Cursor cursor = null;
if (db != null) {
String sql = "select * from " + tableName + " limit ?,?";
cursor = db.rawQuery(sql, new String[]{index + "", pageSize + ""});
}
return cursorTolist(cursor);
}
}
这里面一共有三个方法,cursorTolist 顾名思义就是把 Cursor 游标对象转换为 List 集合,方便我们对数据做加载更新操作,这个看过我的Android中SQLite的基本使用(二)博客的朋友应该都能理解。
getDataCount() 就是获取数据总条数的,代码很容易读懂。
getListByCurrentPage() 是获取 List集合的,从我们前面的讲解可以知道获取第一个数据的下标 index 是至关重要的,如何获得我们根据 pageSize 和 currentPage 就可以了。
index = (currentPage - 1) * pageSize
如果 pageSize = 20,currentPage = 1,那么下标就等于 0,如果 currentPage = 2,下标就为 20,很明显是符合我们要求的。
后面就没什么了,关于查询 sql 语句和占位符的使用相信大家很容易就能读懂。
既然我们用得是 ListView 这个适配器控件,自然需要我们自定义一个适配器,这里我继承 BaseAdapter。
MyBaseAdapter.java:
public class MyBaseAdapter extends BaseAdapter {
private Context context;
private List<Person> list;
public MyBaseAdapter(List<Person> list, Context context) {
this.list = list;
this.context = context;
}
public void setContext(Context context) {
this.context = context;
}
public void setList(List<Person> list) {
this.list = list;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.list_item, null);
holder = new ViewHolder();
holder.tv_id = (TextView) convertView.findViewById(R.id.tv_id);
holder.tv_age = (TextView) convertView.findViewById(R.id.tv_age);
holder.tv_name = (TextView) convertView.findViewById(R.id.tv_name);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.tv_id.setText(list.get(position).get_id() + "");
holder.tv_age.setText(list.get(position).getAge() + "");
holder.tv_name.setText(list.get(position).getName());
return convertView;
}
static class ViewHolder {
TextView tv_id, tv_name, tv_age;
}
}
适配器是我们经常写的,相信大家对 ViewHolder 这种格式并不陌生,所以我就不多做讲解。
MainActivity.java:
public class MainActivity extends AppCompatActivity {
private ListView lv;
private SQLiteDatabase db;
private int totalNum; //表示当前控件加载数据的总条目
private int pageSize = 20; //每页展示数据的条目
private int pageNum; //表示总页码
private int currentPage = 1; //当前页码
private List<Person> totalList; //表示每页的数据源
private MyBaseAdapter adapter;
private boolean isDiv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv = (ListView) findViewById(R.id.lv);
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "info.db";
db = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY);
//获取数据表数据总条目
totalNum = DbManager.getDataCount(db, Constant.TABLE_NAME);
//获取总页码 向上取整
pageNum = (int) Math.ceil(totalNum / (double)pageSize);
if (currentPage == 1) {
totalList = DbManager.getListByCurrentPage(db, Constant.TABLE_NAME, currentPage, pageSize);
}
adapter = new MyBaseAdapter(totalList, this);
lv.setAdapter(adapter);
lv.setOnScrollListener(new AbsListView.OnScrollListener(){
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (isDiv && AbsListView.OnScrollListener.SCROLL_STATE_IDLE == scrollState) {
if (currentPage < pageNum) {
currentPage ++;
//根据最新的页码加载获取集合存储到数据源中
totalList.addAll(DbManager.getListByCurrentPage(db, Constant.TABLE_NAME, currentPage, pageSize));
adapter.notifyDataSetChanged();
}
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
isDiv = ((firstVisibleItem+visibleItemCount)==totalItemCount);
}
});
}
}
相信到绑定适配器为止大家都能很容易的看懂,这里就不提了。需要注意的就是当数据滑动到底的时候去加载数据,这要用到 ListView 的 onScrollListener 监听器。
onScrollListener 会让我们重写两个方法,分别是 :
onScrollStateChanged(AbsListView view, int scrollState)
onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
在这里我设置了一个boolean类型的变量 isDiv 判断是否到最后一个数据。分页需要满足 isDiv = true 和 不再滚动两个条件。
firstVisibleItem 是顶部 item 的下标,visibleItemCount 是显示的item 总数,totalItemCount 是当前加载的 item 总数。
isDiv = ((firstVisibleItem+visibleItemCount)==totalItemCount)
举例说明,在我的模拟器上能显示20条数据,所以 visibleItemCount = 20,如果顶部 item id = 1,下标为0,则是在第一页,加载了20条数据,所以 0 + 20 = 20,数据已经显示完了。
如果顶部 item id = 11,则下标为10,已加载了第二页,加载了40条数据,所以 10 + 20 != 40,数据还没有显示完,isDiv = false。
滚动状态可以由 scrollState 得到,如果条件都满足且当前加载的页数小于总页数,就从数据库取出接下来的20条数据,加进 List 集合里,更新适配器,这样我们就能实现下滑更新数据啦。
一切OK,当然对本地数据库的操作是需要读写权限的,别忘了添加权限。
结束语:本文仅用来学习记录,参考查阅。