概述
综合ListView、SQLite、网络操作来完成一下需求:
- 程序第一次启动使用GET方式获取JSON数据并展示到ListView控件中,链接:http://www.imooc.com/api/teacher?type=2&page=1
- 程序第一次启动除了获取展示数据外,还需要创建数据表book,建立列id、learner、name,并将数据插入到book表中
- 程序每次启动,先从本地数据表book中获取数据,如果有数据则展示数据表book中的数据,如果没有则从网络获取数据展示并更新本地数据表book
要求:
- 使用SQLiteOpenHelper实现book表的创建、增、删、查操作
- 使用AsyncTask从网络获取json数据,构造书籍集合并更新数据表book
- 在MainActivity类的onCreate()方法中,查询book表中有无数据,如果有数据则绑定ListView并显示,如果没有则从网络获取并显示
运行效果
分析
需要涉及到网络请求,先完成网络权限的配置。功能分为以下几块来实现:
Book: 书籍类,实现序列化接口
BookDao: 书籍数据表,实现书籍数据表的创建和存储,同时提供对外的增删改查功能的接口
Book_Async: 异步类,从网络获取JSON数据,并设置相应适配器
两个Activity
MainActivity: 程序主界面,实现基本UI布局、数据加载、控件点击事件
BookActivity: 添加和修改数据的界面,实现书籍数据表的更新
代码实现
JSON
要获取的是data
里的JSON数组,通过遍历该数组来获取每一项的数据,数组长度是10,同时只需要id、name、learner三个属性
Book
书籍表类实现了序列化接口,同时声明了三个属性:_id,name,learner
这里一定要有_id!这里一定要有_id!这里一定要有_id!
id前面不能没有 _ !不能没有 _ !不能没有 _!
数据库操作需要使用到游标对象,游标对象的查询是基于_id来查询的,如果没有会报错,所以创建对象的时候要么带有_id属性,要么在后续的数据库操作中选择一个属性来代替_id作为主键
可以在创建数据库的时候将_id设置为自动增长,这样在添加新的Book
数据的时候就不用管这个_id属性了
public class Book implements Serializable {
private String _id, name,learner;
public Book() {
}
public Book(String _id, String name, String learner) {
this._id = _id;
this.learner = learner;
this.name = name;
}
public String get_id() {
return _id;
}
public void set_id(String _id) {
this._id = _id;
}
public String getLearner() {
return learner;
}
public void setLearner(String learner) {
this.learner = learner;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
BookDao
将数据库存放在内部存储中,并实例化一个SQLiteOpenHelper
辅助来来创建书籍数据库,并创建共有的增删改查方法
查询方法返回的就是一个游标对象,通过游标对象来定位表中对应的数据行
private SQLiteDatabase db;
public BookDao(Context context) {
// 数据库放置路径,这里放置在内部存储中
String path = context.getFilesDir().getAbsolutePath() + "/book.db";
// 实例化辅助类
// 参数1:上下文 参数2:数据库路径 参数3:游标工厂,null 参数4:版本,是否调用升级方法
SQLiteOpenHelper helper = new SQLiteOpenHelper(context, path, null, 1) {
// 创建
@Override
public void onCreate(SQLiteDatabase db) {
// 创建book表
String sql = "create table book (" +
"_id integer primary key autoincrement," +
"name varchar(50)," +
"learner varchar(10))";
db.execSQL(sql);
}
// 升级
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
};
// 获取数据库对象
/// 如果数据库不存在,则先创建,再打开(第一次运行)
/// 如果数据库存在,但是版本没变化,则直接打开
/// 如果数据库存在,但是版本号升高了,则调用升级方法,再打开
db = helper.getReadableDatabase();
}
// 增
public void add(Book book) {
String sql = "insert into book (name ,learner) values(?,?)";
Object[] args = {book.getName(), book.getLearner()};
db.execSQL(sql, args);// 无返回值
}
// 删
public void delete(String learner) {
String sql = "delete from book where learner = " + learner;
db.execSQL(sql);
}
// 改
public void update(Book book) {
String sql = "update book set learner = ?," +
"name = ? where _id = ?";
Object[] args = {book.getLearner(), book.getName(), book.get_id()};
db.execSQL(sql, args);
}
// 查
public Cursor getAllBooks() {
String sql = "select * from book";
Cursor c = db.rawQuery(sql, null);// 游标对象
return c;
}
Book_Async
该类继承了AsyncTask父类,因为不涉及结果和过程的传参,后两个参数设置成Void
类型
这里设置的是从外部传入网络链接,所以第一个参数设置成String
类型,也可以在doInBackground
方法里面写链接,这样三个参数都可以设置成Void
类型
在doInBackground()
方法中完成从网络获取数据,将所需数据放入Book
对象中,并加入数据表
private Book book;
private String[] from = {"learner", "name"};
private int[] to = {R.id.learner, R.id.name};
@Override
protected Void doInBackground(String... strings) {
try {
// 从网络获取JSON数据
URL url = new URL(strings[0]);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(6000);
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
InputStream is = conn.getInputStream();
byte[] b = new byte[1024];
int len = 0;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((len = is.read(b)) > -1) {
baos.write(b, 0, len);
}
String str = new String(baos.toByteArray());
// 通过JSONObject来解析JSON数据
// 参数:满足JSON格式要求的字符串
JSONObject jo = new JSONObject(str);
// 获取JSON对象数组
JSONArray ary = jo.getJSONArray("data");
for (int i = 0; i < ary.length(); i++) {
// 获取单个JSON对象
JSONObject obj = ary.getJSONObject(i);
String id = obj.getString("id");
String name = obj.getString("name");
String learner = obj.getString("learner");
// 向book表添加数据
book = new Book(id, name, learner);
// 创建book表,添加数据
MainActivity.dao.add(book);
}
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
在onPostExecute()
方法中创建简单游标适配器,更新主界面中的ListView的布局
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
// 设置游标适配器
MainActivity.c = MainActivity.dao.getAllBooks();
MainActivity.adapter = new SimpleCursorAdapter(MainActivity.context, R.layout.item, MainActivity.c, from, to, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
MainActivity.books.setAdapter(MainActivity.adapter);
}
MainActivity
为了代码整洁,在onCreate()
方法中分别实现了初始化数据和初始化事件两个方法:
public static ListView books;
public static BookDao dao;
public static Map<String, Book> map;
public static Context context;
public static Cursor c;
public static SimpleCursorAdapter adapter;
private String learner;
private Book_Async ba;
private Book book;
private int lastPostion = -1;
private String[] from = {"learner", "name"};
private int[] to = {R.id.learner, R.id.name};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
initEvent();
}
initData()
完成基本的初始化,同时完成数据库的判断,如果book.db
不存在则调用异步类从网络获取数据并创建数据库,如果book.db
存在,则直接加载本地数据
private void initData() {
books = findViewById(R.id.books);
context = this;
map = new HashMap<>();
// 判断数据库是否存在,如果存在则不再通过异步类获取网络数据
File f = new File(getFilesDir(),"book.db");
if (!f.exists()) {
dao = new BookDao(this);
ba = new Book_Async();
ba.execute("http://www.imooc.com/api/teacher?type=2&page=1");
}else{
dao = new BookDao(this);
}
}
initEvent()
设置ListView控件的点击事件,用户选中子项时,该子项背景色变为灰色,并保存子项信息,提供给其它方法使用
private void initEvent() {
// 为ListView子项添加点击事件
books.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// 将游标移动到指定的book数据处
c.moveToPosition(position);
learner = c.getString(2);
// 获得选中对象
book = new Book(c.getString(0), c.getString(1), c.getString(2));
// 设置选中子项背景色为灰色
view.setBackgroundColor(Color.GRAY);
// 将上一次选择的子项颜色恢复为白色
if (lastPostion != -1 && lastPostion != position) {
books.getChildAt(lastPostion).setBackgroundColor(Color.WHITE);
}
lastPostion = position;
}
});
}
要实现当用户在修改添加界面完成操作后,返回主界面时主界面数据的更新,所以要把ListView的布局放在MainActivity的onResume()
方法中
@Override
protected void onResume() {
super.onResume();
// 设置游标适配器
c = dao.getAllBooks();
adapter = new SimpleCursorAdapter(this, R.layout.item, c, from, to, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
books.setAdapter(adapter);
}
最后就是底部添加、删除、修改三个按钮的点击事件
**添加:**点击后直接跳转至添加界面BookActivity
**删除:**获取ListView子项点击事件所记录下的learner
属性,通过该属性调用BookDao中的删除方法删除数据,并更新游标对象,显示相应提示
**修改:**当用户选中子项后获取对应的Book
对象,通过实例化一个意图对象,将该对象传输至修改界面,并实现界面跳转,显示相应提示
// 按钮点击事件
public void myClick(View v) {
switch (v.getId()) {
case R.id.add:
startActivity(new Intent(this, BookActivity.class));
book = null;
break;
case R.id.delete:
dao.delete(learner);
// 更新游标对象
c.requery();
lastPostion = -1;
book = null;
break;
case R.id.change:
if (book != null) {
Intent it = new Intent(this, BookActivity.class);
it.putExtra("book", book);
startActivity(it);
book = null;
} else {
Toast.makeText(this, "请选择要修改的信息", Toast.LENGTH_SHORT).show();
}
break;
}
}
BookActivity
因为控件不多,所以直接在该界面的onCreate()
方法中进行初始化,同时接受上一个界面传过来的Book
对象,如果该对象为空,则说明用户是通过添加按钮进入,若对象不为空,这说明用户是通过修改按钮进入
private EditText learnerEdit, nameEdit;
private Book b;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book);
learnerEdit = findViewById(R.id.learner_edit);
nameEdit = findViewById(R.id.name_edit);
// 获取从上一个界面传过来的对象
b = (Book) getIntent().getSerializableExtra("book");
if (b != null) {
nameEdit.setText(b.getName());
learnerEdit.setText(b.getLearner());
}
}
commit()
在该方法中完成提交按钮的单击事件,同样通过Book
对象是否为空来判断是添加还是修改操作。添加直接调用添加方法,在原有的数据库的最后直接添加数据,修改需要获取当前传入Book
对象的id,再调用修改方法修改对应id的数据。完成后均弹出相应提示
public void commit(View v) {
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String learner = learnerEdit.getText().toString();
String name = nameEdit.getText().toString();
Book book = new Book("0", name, learner);
if (b == null) {
// 添加操作
dao.add(book);
Toast.makeText(BookActivity.this, "添加成功", Toast.LENGTH_SHORT).show();
} else {
// 修改操作
book.set_id(b.get_id());
dao.update(book);
Toast.makeText(BookActivity.this, "修改成功", Toast.LENGTH_SHORT).show();
}
learnerEdit.setText("");
nameEdit.setText("");
}
});
}