Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

Android--SQLite数据库分页

$
0
0

如果数据库特别大,存储的数据特别多,我们把它加载到适配器控件中,很容易出现内存溢出的情况,并且数据加载的速度也会受到影响。所以我们在加载数据的时候,为了解决这个问题,让用户体验更好,我们可以采用分页的形式,每次加载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,当然对本地数据库的操作是需要读写权限的,别忘了添加权限。

结束语:本文仅用来学习记录,参考查阅。

作者:HardWorkingAnt 发表于2017/4/28 17:40:20 原文链接
阅读:52 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>