安卓开发笔记(六)—— SQLite数据库与ContentProvider的使用

中山大学数据科学与计算机学院本科生实验报告

(2018年秋季学期)


实验代码:传送门:https://github.com/dick20/Android


一、实验题目

第十一周任务 数据存储(二)


二、实现内容

实验目的

  1. 学习SQLite数据库的使用。
  2. 学习ContentProvider的使用。
  3. 复习Android界面编程。

实验内容

实现一个评论应用,本次实验虽然号称是(二),但是和(一)无法合并到同一个项目当中,因此本实验应当新建一个项目,而不是在(一)的基础上继续开发。

要求

点击Login切换到登录界面 图1.2 若Username为空,则发出Toast提示
图1.3 若Password为空,则发出Toast提示 图1.4 若Username不存在,则发出Toast提示
图1.5 若密码不正确,则发出Toast提示
图2.1 点击Register切换到注册页面 图2.2 若Username为空,则发出Toast提示
图2.3 若New Password为空,则发出Toast提示 图2.4 若New Password与Confirm Password不匹配,则发出Toast提示
图2.5 若Username已经存在,则发出Toast提示
图3.1 评论页面 图3.2 若EditText为空,则发出Toast提示
图3.3 短按评论:弹出对话框,显示该评论的用户以及通讯录中该用户的电话号码 图3.4 短按评论:弹出对话框,显示该评论的用户以及通讯录中该用户的电话号码
图3.5 弹出是否删除的对话框 图3.6 弹出是否举报的对话框
图4.1 进入手机图库进行图片选择 图4.2 ImageView显示本次选择的图片
图4.3 在评论页面,每条Item应当正确显示用户的头像
  • 技术要求:

    1. 使用SQLite数据库保存用户的相关信息和评论的相关信息,使得每次运行程序都可以使用数据库进行用户的登陆与注册,以及显示数据库中的评论;
    2. 使用ContentProvider来获取对应用户的电话号码;
  • 功能要求:

    1. 如图1至图8所示,本次实验演示应包含2个Activity。
    2. 首页Activity包含登录功能和注册功能,通过radioButton在两个页面进行切换,在登陆界面输入正确的用户名和密码后跳转到评论页面。
    3. 评论Activity,界面由ListView、EditText和Button组成,ListView中展示数据库中保存的评论信息,在EditText写评论,点击Send按钮发送评论。
    4. 首页Activity:
      • 应用启动时,界面初始化为登录界面,通过Login和Register两个RadioButton进行登录与注册之间的切换。
      • 点击Login切换到登录界面(见图1.1),可以保留注册界面时的Username,但不保存密码:
        • OK按钮点击后:
          • 若Username为空,则发出Toast提示。见图1.2.
          • 若Password为空,则发出Toast提示。见图1.3.
          • 若Username不存在,则发出Toast提示。见图1.4.
          • 若密码不正确,则发出Toast提示。见图1.5.
        • CLEAR按钮点击后:清除两个输入框的内容。
      • 点击Register切换到注册页面(见图2.1),可以保留登录界面时的Username,但不保存密码,在输入框和RadioButto之间存在一个头像ImageView,水平居中:
        • OK按钮点击后:
          • 若Username为空,则发出Toast提示。见图2.2.
          • 若New Password为空,则发出Toast提示。见图2.3.
          • 若New Password与Confirm Password不匹配,则发出Toast提示。见图2.4.
          • 若Username已经存在,则发出Toast提示。见图2.5.
        • CLEAR按钮点击后:清除三个输入框的内容。
    5. 评论页面:
      • 界面底部有一个EditText和一个按钮,高度一致,EditText占据按钮左边的全部空间。上方的全部剩余空间由一个ListView占据(保留margin)。见图3.1.
      • ListView中的每条Item,包含了头像、点赞按钮这两个ImageView和用户名、评论时间、评论内容、点赞数这4个TextView。
        • 用户名、评论时间、评论内容在头像的右边。
        • 点赞按钮在Item的最右边,而且在用户名+评论时间的总高度上处于竖直方向上居中,注意:总高度不包括评论占据的高度
        • 点赞数在点赞按钮的左边,竖直方向居中要求同点赞按钮。
        • 以下样式供参考,不做强制要求,但要求至少美观:
          • Item整体margin:10dp,
          • 头像width、hight:40sp,
          • 用户名textColor:#7a7a7a、textSize:20sp
          • 评论时间textColor:#7a7a7a、textSize:10sp
          • 评论textColor:#3e3e3e、textSize:20sp
          • 点赞数textSize:15sp
      • 点击EditText写评论
      • 点击Send按钮发送评论
        • 若EditText为空,则发出Toast提示。如图3.2.
        • 若EditText不为空,则发送评论,在数据库和ListView中添加新评论。
      • ListView中的Item点击事件:
        • 短按评论:弹出对话框,显示该评论的用户以及通讯录中该用户的电话号码。如图3.3和图3.4.
        • 长按评论:
          • 若该评论为当前用户发送的,弹出是否删除的对话框,若选择了Yes,则删除该条评论并更新数据库和ListView。如图3.5.
          • 若该评论不为当前用户发送的,弹出是否举报的对话框,若选择了Yes,则弹出Toast提示,不需做任何数据库和ListView的更改。如图3.6.

    附加内容(加分项,本次实验与(一)合计100分,加分项每项占10分)

    1. 头像
      在用户注册页面可以选择用户头像,ImageView初始化为图add,如图2.1。点击ImageView,进入手机图库进行图片选择。如图4.1.
      • 如果正确选择了一张图片,则ImageView显示本次选择的图片。如图4.2.
      • 如果没有正确选择图片(如在图片选择页面点击了取消或按了手机的BACK键),则ImageView保留本次点击事件发生前的状态,如初始的+号图片,如图4.1,或者是上一个被正确选择的图像。如图4.2.
        在评论页面,每条Item应当正确显示用户的头像,如果用户没有在注册页面选择头像,则使用默认头像。如图4.3.
    2. 点赞
      在评论界面,点赞按钮可以被点击,点赞数可以正常统计,用户点赞状态可以被正常记录,白色的未点赞状态经用户点击后变为红色的点赞状态,点赞数加1;再次点击点赞按钮可取消点赞,点赞数减1.要求用数据库记录点赞的信息,使得应用重启后用户的点赞状态,评论的点赞数可以正常显示,注意:用户的对每条评论的点赞状态是唯一的,即不同用户对每条评论的点赞状态应当分开记录,同一用户对不同评论的点赞状态也应当分开记录。同理,每条评论的点赞数也应当分开记录。请参考demo自行体会。

    对附加内容的补充(不想做加分项的看这里)

    1. 头像
      在用户注册页面的ImageView显示为默认头像,且不需要添加任何的点击事件监听器,在评论页面的用户头像也使用默认头像。

    2. 点赞
      不需要为点赞按钮添加点击事件监听器,关于点赞状态和点赞数使用随机数进行生成即可,也不要求用数据库记录点赞状态和点赞数。

    3. 虽然点击事件的逻辑可以不做,但是界面的样式是必须按照前文做的!


三、实验结果

(1)实验截图

1.点击Login切换到登录界面

安卓开发笔记(六)—— SQLite数据库与ContentProvider的使用_第1张图片

2.若Username为空,则发出Toast提示

安卓开发笔记(六)—— SQLite数据库与ContentProvider的使用_第2张图片

3.若Password为空,则发出Toast提示

安卓开发笔记(六)—— SQLite数据库与ContentProvider的使用_第3张图片

4.若Username不存在,则发出Toast提示

安卓开发笔记(六)—— SQLite数据库与ContentProvider的使用_第4张图片

5.若密码不正确,则发出Toast提示

安卓开发笔记(六)—— SQLite数据库与ContentProvider的使用_第5张图片

6.点击Register切换到注册页面

安卓开发笔记(六)—— SQLite数据库与ContentProvider的使用_第6张图片

7.若Username为空,则发出Toast提示

安卓开发笔记(六)—— SQLite数据库与ContentProvider的使用_第7张图片

8.若New Password为空,则发出Toast提示

安卓开发笔记(六)—— SQLite数据库与ContentProvider的使用_第8张图片

9.若New Password与Confirm Password不匹配,则发出Toast提示

安卓开发笔记(六)—— SQLite数据库与ContentProvider的使用_第9张图片

10.若Username已经存在,则发出Toast提示

安卓开发笔记(六)—— SQLite数据库与ContentProvider的使用_第10张图片

11.评论页面

安卓开发笔记(六)—— SQLite数据库与ContentProvider的使用_第11张图片

12.若EditText为空,则发出Toast提示

安卓开发笔记(六)—— SQLite数据库与ContentProvider的使用_第12张图片

13.短按评论:弹出对话框,显示该评论的用户以及通讯录中该用户的电话号码

安卓开发笔记(六)—— SQLite数据库与ContentProvider的使用_第13张图片

14.短按评论:弹出对话框,电话号码不存在

安卓开发笔记(六)—— SQLite数据库与ContentProvider的使用_第14张图片

15.弹出是否删除的对话框

安卓开发笔记(六)—— SQLite数据库与ContentProvider的使用_第15张图片

16.弹出是否举报的对话框

安卓开发笔记(六)—— SQLite数据库与ContentProvider的使用_第16张图片

17.进入手机图库进行图片选择

安卓开发笔记(六)—— SQLite数据库与ContentProvider的使用_第17张图片

18.ImageView显示本次选择的图片

安卓开发笔记(六)—— SQLite数据库与ContentProvider的使用_第18张图片

(2)实验步骤以及关键代码

a.实现登陆注册页面的UI

这一部分只是在复习之前的内容,包括三个EditText以及两个Button,一个ImageView,一组RadioGroup.组件的属性就不再详述,根据ConstraintLayout来控制组件的位置。

选择显示是通过RadioGroup的切换来进行判断,来将一些内容隐藏或者显示

		radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener(){
            @Override
            //选择变化时,弹出toast提示信息
            public void onCheckedChanged(RadioGroup group, int checkedID){
                RadioButton register = findViewById(R.id.register);
                // 判断是注册页面是登陆页面
                if(register.getId() == checkedID){
                    img_button.setVisibility(View.VISIBLE);
                    confirm.setVisibility(View.VISIBLE);
                    password.setText("");
                    is_register = true;
                }
                else {
                    password.setText("");
                    confirm.setText("");
                    img_button.setVisibility(View.GONE);
                    confirm.setVisibility(View.GONE);
                    is_register = false;
                }
            }
        });

b.登陆注册页面的业务逻辑设计

对于clear按钮以及ok按钮来设置监听器,这一部分在项目的前半部分已经完成,这里也只是简单的叙述不同之处。

这次的登陆页面要结合数据库来进行判断,所以在ok按钮监听器必须预留功能,检测用户是否存在,密码是否匹配,用户名是否正确等等。

跳转逻辑前必须将新建的用户插入数据库的用户表中。

ok按钮需要根据是登陆界面还是注册页面来进行改变功能,下面代码是根据is_register变量来进行判断。

 			// 检查注册页面的账户密码情况
                if (is_register){
                    User user = mydb.queryUserByUsername(str_username);
                    //两次密码匹配
                    if(str_confirm.equals(str_password)) {
                        // 数据库已存在该用户
                        if (user != null) {
                            Toast.makeText(MainActivity.this, "Username already existed.", Toast.LENGTH_SHORT).show();
                        }
                        else {
                            int num = mydb.getAllUserCount()+1;
                            // 临时从resources获取头像,后要改成读取图库
                            Bitmap head = ((BitmapDrawable) getResources().getDrawable(R.mipmap.me)).getBitmap();
                            ContentResolver resolver = getContentResolver();
                            //使用ContentProvider通过URI获取原始图片
                            if(uri != null){
                                try {
                                    head = MediaStore.Images.Media.getBitmap(resolver, uri);
                                } catch (IOException ignored) {
                                }
                            }
                            ArrayList<Integer> like_comment = new ArrayList<>();
                            User new_user = new User(num,str_username,head,str_password,like_comment);
                            mydb.insertUser(new_user);
                            // 跳转
                            Intent intent = new Intent();
                            Bundle bundle = new Bundle();
                            bundle.putString("user",new_user.getUsername());
                            intent.putExtras(bundle);
                            intent.setClass(MainActivity.this, CommentActivity.class);
                            startActivity(intent);
                        }
                    }
                    //两次密码不匹配
                    else{
                        //弹出 Toast
                        Toast.makeText(MainActivity.this, "Password Mismatch.", Toast.LENGTH_SHORT).show();
                    }
                }
                // 检查登陆页面的账户密码情况
                else{
                    User user = mydb.queryUserByUsername(str_username);
                    if (user == null){
                        Toast.makeText(MainActivity.this, "Username not existed.", Toast.LENGTH_SHORT).show();
                    }
                    else{
                        if(str_password.equals(user.getPassword())){
                            // 跳转
                            Intent intent = new Intent();
                            Bundle bundle = new Bundle();
                            bundle.putString("user",user.getUsername());
                            intent.putExtras(bundle);
                            intent.setClass(MainActivity.this, CommentActivity.class);
                            startActivity(intent);
                        }
                        else{
                            Toast.makeText(MainActivity.this, "Invalid Password.", Toast.LENGTH_SHORT).show();
                        }
                    }
                }
            }
        });

c. 实现评论页面的UI设计

评论页面只包括一个EditText,一个Button,一个ListView列表。

关于ListView的显示,我使用了自定义的Adapter。一个item包括头像,用户名,时间戳,内容,点赞数等元素。

	<ListView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/listview"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="2dp"
        app:layout_constraintBottom_toTopOf="@id/comment"
        android:layout_marginBottom="8dp"
        android:background="#000000"
        />

    <EditText
        android:id="@+id/comment"
        android:hint="Comment"
        app:layout_constraintRight_toLeftOf="@id/send"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        android:layout_width="0dp"
        android:layout_height="wrap_content"

        />

    <Button
        android:id="@+id/send"
        android:text="Send"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_marginRight="5dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

d.实现用户User与评论Comment的两个基类设计

这两个类是用作与数据库交互的,插入数据库的正是user对象的信息或者comment对象的信息。故必须在设置数据库之前编写。

这两个类很简单,只包含一些属性,已经他们的get与set函数,构造函数。

以下是User类的成员变量,其中的like_comment来储存点赞过的comment的cid。用以判断该评论是否被点赞过。

public class User {
    private int uid;
    private String username;
    private Bitmap head;
    private String password;
    private ArrayList<Integer> like_comment;
    ···
}

以下是Comment类的成员变量 。其中like_num是统计该评论的点赞总数。

public class Comment {
    private int cid;
    private String username;
    private Bitmap head;
    private String timestamp;
    private String content;
    private int like_num;
    ···
}

e.设计数据库的表格

该次数据库包含两个两个表,分别是User与Comment。在创建数据库的时候来建立,若还未存在的话。

以下是两个表的元素

 	public void onCreate(SQLiteDatabase sqLiteDatabase) {
        String CREATE_TABLE_USER = "CREATE TABLE if not exists "
                + TABLE_NAME_USER
                + " (uid INTEGER PRIMARY KEY, username TEXT, head BLOB, password TEXT, like_comment TEXT)";
        sqLiteDatabase.execSQL(CREATE_TABLE_USER);
        String CREATE_TABLE_COMMENT = "CREATE TABLE if not exists "
                + TABLE_NAME_COMMENT
                + " (cid INTEGER PRIMARY KEY, username TEXT, head BLOB, timestamp TEXT, content TEXT, like_num INTEGER, is_liked INTEGER)";
        sqLiteDatabase.execSQL(CREATE_TABLE_COMMENT);
    }

然后,我根据需要来实现一些增删查改的函数。

以下是对user的插入函数,这里唯一的难点是在于如何存储图片,将bmp转化成byte数组存入数据库。

参考链接:如何在数据库存储图片

public long insertUser(User user){
        SQLiteDatabase db = getWritableDatabase();
        ContentValues values = new ContentValues();
        // 将图片转换成字节,插入数据库
        Bitmap bmp = user.getHead();
        byte[] img;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bmp.compress(Bitmap.CompressFormat.PNG, 100, baos);
        img = baos.toByteArray();

        values.put("uid",user.getUid());
        values.put("head",img);
        values.put("username",user.getUsername());
        values.put("password",user.getPassword());
        String like = "";
        values.put("like_comment",like);
        long rid = db.insert(TABLE_NAME_USER,null,values);
        db.close();
        return rid;
    }

插入Comment也类似,不再重复扔代码。这里说说查询的方法。以下是通过用户名字来查询user。

这里的难点在于将存在数据库中的text转化成一个ArrayList<>。然后将查询到的user新建一个对象传出即可。查询语句为selection,查询对象为selectionArgs.

public User queryUserByUsername(String username){
        User user = null;
        SQLiteDatabase db = getReadableDatabase();
        String selection = "username = ?";
        String[] selectionArgs = { username};
        Cursor c = db.query(TABLE_NAME_USER,null,selection,selectionArgs,null,null,null);
        if(c.getCount()!= 0 && c.moveToNext()){
            String temp = c.getString(4);
            String[] like_comment_str;
            like_comment_str = temp.split(",");
            ArrayList<Integer> like_comment = new ArrayList<>();
            for (String aLike_comment_str : like_comment_str) {
                Log.i("like_comment_str",aLike_comment_str);
                if(!aLike_comment_str.equals(""))
                    like_comment.add(Integer.parseInt(aLike_comment_str));
            }
            byte[] img = c.getBlob(2);
            Bitmap bmpout = BitmapFactory.decodeByteArray(img, 0, img.length);

            user = new User(c.getInt(0),c.getString(1),bmpout,c.getString(3), like_comment);
        }
        c.close();
        db.close();
        return user;
    }

删除函数,这里举根据Comment的cid来删除该评论在数据库的条目。

	public void deleteCommentByCid(int cid){
        SQLiteDatabase db = getWritableDatabase();
        String whereClause = "cid = ?";
        String[] whereArgs = { cid+"" };
        db.delete(TABLE_NAME_COMMENT, whereClause, whereArgs);
        db.close();
    }

更新函数,用于点赞后改变用户的状态,这时不应该新增条目,而是在原有的用户条目上改变参数即可。更新comment而主要来刷新点赞的总数。

 	public void updateComment(Comment comment){
        SQLiteDatabase db = getWritableDatabase();
        String whereClause = "cid = ?";
        String[] whereArgs = {comment.getCid()+""};
        // 将图片转换成字节,插入数据库
        Bitmap bmp = comment.getHead();
        byte[] img;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bmp.compress(Bitmap.CompressFormat.PNG, 100, baos);
        img = baos.toByteArray();

        ContentValues values = new ContentValues();
        values.put("cid",comment.getCid());
        values.put("head",img);
        values.put("username",comment.getUsername());
        values.put("timestamp",comment.getTimestamp());
        values.put("content",comment.getContent());
        values.put("like_num",comment.getLike_num());
        db.update(TABLE_NAME_COMMENT, values, whereClause, whereArgs);
        db.close();
    }

以下是拿到所有评论的函数。主要是用于页面一开始的加载,快速的加载出所有评论,而不用一条一条的读取。除此以外,可以拿到评论的总数,用于新建评论时候的cid设置,避免cid重复的情况,导致数据库插入错误。

	public ArrayList<Comment> getAllComment(){
        ArrayList<Comment> commentArrayList = new ArrayList<>();
        SQLiteDatabase db = getReadableDatabase();
        Cursor c = db.query(TABLE_NAME_COMMENT,null,null,null,null,null,null);
        if (c.getCount() == 0 || !c.moveToFirst()) return null;
        do{
            byte[] img = c.getBlob(2);
            Bitmap bmpout = BitmapFactory.decodeByteArray(img, 0, img.length);
            Boolean is_liked = c.getInt(6) == 1;
            Comment comment = new Comment(c.getInt(0),c.getString(1),bmpout,c.getString(3),c.getString(4),
            c.getInt(5),is_liked);
            commentArrayList.add(comment);
        }while (c.moveToNext());
        Log.i("getAllComment",commentArrayList.size()+"");
        return commentArrayList;
    }

f.登陆注册界面的数据库应用过程

注册过程:
首先查找这个用户名是否已经存在,若存在直接返回Toast。然后根据用户总数获取一个新的cid,作为primary key,待会插入表中。图片转化部分是通过图库的uri来进行转化,如果未访问图库则用默认用户头像。

最后只需要将用户id,用户名字,密码等创建新的User对象,调用数据库之前写好的接口插入即可。

					User user = mydb.queryUserByUsername(str_username);
                    //两次密码匹配
                    if(str_confirm.equals(str_password)) {
                        // 数据库已存在该用户
                        if (user != null) {
                            Toast.makeText(MainActivity.this, "Username already existed.", Toast.LENGTH_SHORT).show();
                        }
                        else {
                            int num = mydb.getAllUserCount()+1;
                            // 临时从resources获取头像,后要改成读取图库
                            Bitmap head = ((BitmapDrawable) getResources().getDrawable(R.mipmap.me)).getBitmap();
                            ContentResolver resolver = getContentResolver();
                            //使用ContentProvider通过URI获取原始图片
                            if(uri != null){
                                try {
                                    head = MediaStore.Images.Media.getBitmap(resolver, uri);
                                } catch (IOException ignored) {
                                }
                            }
                            ArrayList<Integer> like_comment = new ArrayList<>();
                            User new_user = new User(num,str_username,head,str_password,like_comment);
                            mydb.insertUser(new_user);

登陆过程:
登陆的数据库调用比注册更加简单,只需要判断用户名是否存在,用户名是否与密码匹配即可

					User user = mydb.queryUserByUsername(str_username);
                    if (user == null){
                        Toast.makeText(MainActivity.this, "Username not existed.", Toast.LENGTH_SHORT).show();
                    }
                    else{
                        if(str_password.equals(user.getPassword())){
                            // 跳转
                            Intent intent = new Intent();
                            Bundle bundle = new Bundle();
                            bundle.putString("user",user.getUsername());
                            intent.putExtras(bundle);
                            intent.setClass(MainActivity.this, CommentActivity.class);
                            startActivity(intent);
                        }
                        else{
                            Toast.makeText(MainActivity.this, "Invalid Password.", Toast.LENGTH_SHORT).show();
                        }
                    }

g.评论页面的数据库应用过程

发表评论事件:
首先要拿到跳转过来的用户

		Bundle bundle=this.getIntent().getExtras();
        String str_username = bundle.getString("user");
        user = mydb.queryUserByUsername(str_username);

加载出所有之前的评论,这通过数据库中的接口getAllComment来获取。

		mList = new ArrayList<>();
        if(mydb.getAllComment() != null){
            mList = mydb.getAllComment();
        }
        ListView listView = findViewById(R.id.listview);
        final MyAdapter adapter = new MyAdapter(CommentActivity.this, mList, user);
        listView.setAdapter(adapter);

这里来解释下我的Adapter类,传入当前的上下文,所有Comment,已经当前用户。
下面是最关键的一个函数getView(), 通过findViewById来获取组件的绑定,根据mList中的信息来给他们设置内容,对于like_img的操作是判断是否点赞过,对于图片的处理,这个在实验思考再详述。

	@Override
    public View getView(final int i, View view, ViewGroup viewGroup) {
        ViewHolder viewHolder = null;
        if (view == null) {
            viewHolder = new ViewHolder();
            view = LayoutInflater.from(mContext).inflate(R.layout.item, null);
            viewHolder.head = view.findViewById(R.id.head);
            viewHolder.name = view.findViewById(R.id.name);
            viewHolder.time = view.findViewById(R.id.time);
            viewHolder.content = view.findViewById(R.id.content);
            viewHolder.num = view.findViewById(R.id.num);
            viewHolder.like_img = view.findViewById(R.id.like_img);
            view.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) view.getTag();
        }
        viewHolder.head.setImageBitmap(mList.get(i).getHead());
        viewHolder.name.setText(mList.get(i).getUsername());
        viewHolder.time.setText(mList.get(i).getTimestamp());
        viewHolder.content.setText(mList.get(i).getContent());
        viewHolder.num.setText(mList.get(i).getLike_num()+"");
        ArrayList<Integer> arrayList = mUser.getLike_comment();
        viewHolder.like_img.setImageResource(R.mipmap.white);

        // Log.i("已收藏",arrayList.size()+"");
        for(int j = 0; j < arrayList.size(); j++){
            if(arrayList.get(j) == mList.get(i).getCid()){
                viewHolder.like_img.setImageResource(R.mipmap.red);
            }
        }
        viewHolder.like_img.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mOnItemLikeListener.onLikeClick(i);
            }
        });
        return view;
    }

发送评论,需要设置send的监听器,内容就是EditText的内容,而评论id我是根据历史的评论总数来创建新的id,时间戳则是用了Date类,只需要设置格式即可。最后新建Comment对象,调用数据库api插入表,通知adapter改变list的显示。

// 发送评论
        send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String content = comment.getText().toString();
                if (content.isEmpty()){
                    Toast.makeText(CommentActivity.this, "Comment cannot be empty.", Toast.LENGTH_SHORT).show();
                    return;
                }
                int cid = history_comment_num+2;
                history_comment_num++;
                // 临时从resources获取头像,后要改成读取图库
                // Bitmap head =  ((BitmapDrawable) getResources().getDrawable(R.mipmap.me)).getBitmap();
                Bitmap head = user.getHead();
                Date now = new Date( );
                SimpleDateFormat ft = new SimpleDateFormat ("yyyy.MM.dd hh:mm:ss");
                Log.i("timestamp",ft.format(now));
                Comment new_comment = new Comment(cid,user.getUsername(),head,ft.format(now),content,0,false);
                mydb.insertComment(new_comment);
                mList.add(new_comment);
                adapter.notifyDataSetChanged();
                comment.setText("");
            }
        });

h.对于评论的点击事件处理

这里的点击分为长按以及短按,短按显示通讯录的信息。这里必须根据用户名来向手机获取通讯录的电话号码。 getContentResolver().query()获取到cursor上,注意要判断该cursor是否为空,若为空则证明没有该电话信息,在dialog直接显示未找到即可。若找到了则要不断移进cursor来读取完整的电话号码。

		listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                AlertDialog.Builder dialog = new AlertDialog.Builder(CommentActivity.this);
                dialog.setTitle("info");
                //获取电话号码
                String username = ((Comment)adapter.getItem(position)).getUsername();
                Cursor cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,
                        ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " = \"" + username + "\"", null, null);
                Boolean is_finded = false;
                if(cursor.moveToFirst() && cursor.getCount() != 0) {
                    is_finded = true;
                }
                if(is_finded){
                    cursor.moveToFirst();
                    String number = "\nPhone: ";
                    do {
                        number += cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)) + "";
                    } while (cursor.moveToNext());

                    dialog.setMessage("Username: " + username + number);
                }
                else {
                    dialog.setMessage("Username: " + username + "\nPhone number not exist");
                }
                dialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                    }
                });
                dialog.show();
            }
        });

长按事件:有两种情况,分别是删除或者举报弹框。区分这两种情况是通过点击条目的用户名是否与登陆进去的用户名匹配情况,若匹配则可以进行删除操作,调用数据库的删除接口即可通过cid来删除该评论,最后通知adapter来改变listview。而举报仅需要简单的输出Toast即可,没有复杂的点击事件触发。

listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView<?> parent, View view, final int position, long id) {
                if (((Comment)adapter.getItem(position)).getUsername().equals(user.getUsername())){
                    AlertDialog.Builder dialog = new AlertDialog.Builder(CommentActivity.this);
                    dialog.setMessage("Delete or not?");
                    dialog.setPositiveButton("YES", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {
                            mydb.deleteCommentByCid(((Comment)adapter.getItem(position)).getCid());
                            mList.remove(position);
                            adapter.notifyDataSetChanged();
                        }
                    });
                    dialog.setNegativeButton("NO", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {
                        }
                    });
                    dialog.show();
                }
                else{
                    AlertDialog.Builder dialog = new AlertDialog.Builder(CommentActivity.this);
                    dialog.setMessage("Report or not?");
                    dialog.setPositiveButton("YES", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {
                            Toast.makeText(CommentActivity.this, "You have reported this comment.", Toast.LENGTH_SHORT).show();
                        }
                    });
                    dialog.setNegativeButton("NO", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {
                        }
                    });
                    dialog.show();
                }
                return true;
            }
        });

(3)实验遇到的困难以及解决思路

1.通讯录读取的权限问题

根据教程在Manifest中申请通讯录的读取权限,然后利用ContentProvider来获取即可,但是我在写完上述内容仍显示报错,报错信息显示没有该权限。

<uses-permission android:name="android.permission.READ_CONTACTS"/>

解决办法:根据网上的问答信息,我找到了错误的原因,我手机模拟器并没有打开该通讯录的权限,必须在设置中找到该软件的信息,对于该权限设置允许,再次运行就可以正常运行。

2.如何将图片uri转化为bmp

根据教程只提供了如何通过手机图库获取,并没有告诉如何将uri转化成bmp存入数据库。由于我对于图片在user中的存取是通过bmp来进行操作的。

而在数据库中的存取是将bmp转化成byte[]数组来存储。

以下语句首先获得图片的uri,然后我再对这个uri来进行处理。

	img_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 激活系统图库,选择一张图片
                Intent intent = new Intent();
                intent.setAction(Intent.ACTION_PICK);
                intent.setType("image/*");
                startActivityForResult(intent, 0);
            }
        });
	@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (data != null) {
            // 得到图片的全路径
            uri = data.getData();
            this.img_button.setImageURI(uri);
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

解决办法:根据网上的资料,将uri转化为bmp是通过ContentResolver来进行的。
MediaStore.Images.Media.getBitmap(resolver, uri);就可以转化成功,注意uri为空的时候会报错,所以要进行一定的处理,保证应用的正常的进行

						ContentResolver resolver = getContentResolver();
                            //使用ContentProvider通过URI获取原始图片
                            if(uri != null){
                                try {
                                    head = MediaStore.Images.Media.getBitmap(resolver, uri);
                                } catch (IOException ignored) {
                                }
                            }

3.对于字符串与ArrrayList<>之间的转化问题

就因为这个转化问题,我在最后完成点赞功能时候,进行了长达两小时的debug过程。我将ArrrayList<>里的数据分别拿出来连接成字符串再插入数据库,结果该字符串为空,导致拿出来的时候数据也是错误的,点赞功能无法正常进行。

以下是字符串转ArrrayList,利用了split函数来划分,以及Integer.parseInt来转化回整数。

		String temp = c.getString(4);
            String[] like_comment_str;
            like_comment_str = temp.split(",");
            ArrayList<Integer> like_comment = new ArrayList<>();
            for (String aLike_comment_str : like_comment_str) {
                Log.i("like_comment_str",aLike_comment_str);
                if(!aLike_comment_str.equals(""))
                    like_comment.add(Integer.parseInt(aLike_comment_str));
            }

以下是ArrayList转字符串,将其中元素分别取出,利用Integer.toString转回字符串直接连接,并用逗号分割

 		ArrayList<Integer> like_comment = user.getLike_comment();
        String like = "";
        for (int i = 0; i < like_comment.size(); i++){
            String str = Integer.toString(like_comment.get(i));
            like += str;
            if(i != like_comment.size()-1){
                like += ",";
            }
        }

四、实验思考及感想

1.加分项

a.数据库存取头像

我的用户头像是通过访问手机的本地图库来获取,通过uri来转化成bmp,上面的困难与解决方法的部分已经叙述,这里不再重复。

而数据库存图像我用的是BLOB,利用ByteArrayOutputStream转化为字符数组存入数据库。我的bmp是通过user的基类函数get到,然后在数据库的插入函数再进行处理。

		// 将图片转换成字节,插入数据库
        Bitmap bmp = user.getHead();
        byte[] img;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bmp.compress(Bitmap.CompressFormat.PNG, 100, baos);
        img = baos.toByteArray();

数据库取头像,传出的也是bmp,故也要类似进行处理,将byte[]数组转化为bmp。这里使用的是BitmapFactory的decodeByteArray函数,将byte数组转化为bmp。

			byte[] img = c.getBlob(2);
            Bitmap bmpout = BitmapFactory.decodeByteArray(img, 0, img.length);

b.设计评论与用户之间点赞关系的关联

关于评论与用户的点赞关系,我采用的是在user表中新建一个项ArrayList,将该用户的点赞过的评论id全部存入ArrayList中,读取用户加载评论列表的时候就可以获取到用户的点赞信息。
但是对于ArrayList的存取必须转化成String来进行,所有就有了我上面困难与解决办法的相互转换方法。

初始化用户的时候可以将该属性设置为空,即没有点赞过任何评论。

			ArrayList<Integer> like_comment = new ArrayList<>();
            for (String aLike_comment_str : like_comment_str) {
                Log.i("like_comment_str",aLike_comment_str);
                if(!aLike_comment_str.equals(""))
                    like_comment.add(Integer.parseInt(aLike_comment_str));
            }

c.关于点赞事件,点赞状态和点赞数是如何获取的

关于点赞事件,先要绑定在点赞的图片上,这里要对listview来进行处理。
对于item.xml中的点赞加上下面的一句,才可以在同一个listview有两个点击事件的判断,点击主体是弹出电话号码,点击点赞图片是执行点赞事件

	android:focusable="false"

我在Adapter类中写了点赞的接口函数,在具体使用的时候实现即可。并为该组件绑定了该接口的点击函数。

		viewHolder.like_img.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mOnItemLikeListener.onLikeClick(i);
            }
        });
        ······
        
	/**
     * 点赞按钮的监听接口
     */
    public interface onItemLikeListener {
        void onLikeClick(int i);
    }

    private onItemLikeListener mOnItemLikeListener;

    public void setOnItemLikeClickListener(onItemLikeListener mOnItemLikeListener) {
        this.mOnItemLikeListener = mOnItemLikeListener;
    }

下面对ListView的adapter点赞按钮事件来重构onLikeClick函数。

现在的问题是如何获取点赞的信息呢?根据我储存在数据库中user的点赞评论编号。
like_comment = user.getLike_comment();然后逐项拿出,判断是否已经点过赞的。若已经点过赞就进入取消赞的逻辑,若没有点过赞进入加入赞的逻辑。

关于点赞数的获取,点赞数我是储存在评论对象中,
int old_num = mList.get(i).getLike_num();可以获得该评论的点赞总数。

注意当改变点赞状态时要更新like_comment列表的信息,保持同步

	 //ListView item 中的点赞按钮的点击事件
        adapter.setOnItemLikeClickListener(new MyAdapter.onItemLikeListener() {
            @Override
            public void onLikeClick(int i) {
                Boolean is_liked = false;
                int item = -1;
                // 获取点赞的信息
                like_comment = user.getLike_comment();
                for(int ii = like_comment.size() - 1; ii >= 0; ii--){
                    item = like_comment.get(ii);
                    if(mList.get(i).getCid() == item){
                        is_liked = true;
                    }
                }
                int old_num = mList.get(i).getLike_num();
                // 判断是否已经点赞过
                if(is_liked) {
                    mList.get(i).setLike_num(old_num - 1);
                    mydb.updateComment(mList.get(i));
                    for(int ii = like_comment.size() - 1; ii >= 0; ii--){
                        item = like_comment.get(ii);
                        if(mList.get(i).getCid() == item){
                            like_comment.remove(ii);
                        }
                    }
                    user.setLike_comment(like_comment);
                    mydb.updateUser(user);
                }
                // 点赞数加一
                else{
                    mList.get(i).setLike_num(old_num + 1);
                    mydb.updateComment(mList.get(i));
                    like_comment.add(mList.get(i).getCid());
                    user.setLike_comment(like_comment);
                    mydb.updateUser(user);
                }
                adapter.notifyDataSetChanged();
            }
        });

2.感想

这次作业的内容有点多,一共做了三四天。这次项目既要完成后台数据库的设计,增删查改函数,也要对前端的ui设计以及业务逻辑进行处理。我分了很多个阶段来完成该项目,尤为重要的是对数据库表格的设计,这影响到后面我如何调用这些api来进行业务逻辑处理。如果在后面再修改数据库的结构,重构的代价会非常高,所以我在开始着手写这次项目时,先对数据库的设计思考了较长的时间。做项目的过程也遇到不少的问题,例如存储图片,读取本地图片,对于图片的格式转化等,这些都需要自学了解。最后,实现点赞功能的逻辑设计也想了不少时间,如何更新数据库的数据也是一大难点。既然能顺利完成,当然还是挺开心的,可以给自己一个Like!


你可能感兴趣的:(Android)