实验目的
- 学习SQLite数据库的使用。
- 学习ContentProvider的使用。
- 复习Android界面编程。
这次大概是做一个有登录、注册、评论、点赞等功能的小型APP
效果如下:(图片比较大)

登录注册页面的切换

两个按钮用RadioButton就可以实现了
1 | <RadioGroup |
但是,如何实现两种情况下显示不同布局,并且两个按钮都刚好在填写的信息底下呢
这里可以用设置控件的Visibility实现的!
我把按钮上方的所有东西放在一个View里面(也就是登录界面的两个输入框,注册页面的三个输入框,一个头像显示框)
通过ConstraintLayout的特点让两个按钮固定在这个大的View里面,登录状态或者注册状态让头像框和确认密框隐藏就好了
其实还有另一种办法:
只需要用四个控件(复用两个输入框)
登录页面的头像框隐藏,确认密码栏隐藏, 然后在注册页面时显示
但是会有挺多问题(感觉是),因为注册和登录用同样的输入框会让后期事件处理以及显示
HINT的操作有点繁琐
这就是那个大的View,包含了6个控件
1 | <android.support.constraint.ConstraintLayout |
这是底下的两个按钮
1 | <Button |
然后在JAVA代码里切换VISIBILITY属性
1 | final RadioGroup radioGroup = findViewById(R.id.radio_group); |
注意:这里设置为
GONE而不是INVISIBLE,因为INVISIBLE虽然不显示但是占用空间,而GONE不会占用空间
SQLite的简单使用
这里创建一个SQLiteHelper用来存储用户名,密码,和头像
1 | package com.example.myapplication; |
在Activity的onCreate方法中new一个SQLiteHelper的实例
1 | userSQLiteHelper = new UserSQLiteHelper(this); |
然后在注册页面的button_ok的监听事件里调用添加用户的方法就实现了注册功能
1 | if(userSQLiteHelper.addUser(register_username.getText().toString(), register_password.getText().toString(), default_image)){ |
在登录页面的button_ok的监听事件里调用验证用户的方法就实现了登录功能
1 | switch (userSQLiteHelper.loginVerify(login_username.getText().toString(), login_password.getText().toString())){ |
调用系统相册选择图片

这里的用户头像其实是一个ImageButton,设置监听事件为
1 | String default_image = "android.resource://com.example.myapplication/" + R.mipmap.me; |
这样通过调用Intent打开了系统的图片查看器,然后还是用onActivityResult方法接收点击的图片
SQLite中保存图片
Android数据库中存取图片通常使用两种方式
保存图片所在路径
将图片以二进制的形式存储(
sqlite3支持BLOB数据类型)
从我之前的代码中可以看出我是通过存储图片的Uri存储头像,以后显示该头像就用该Uri做参数就好了
Android中的URI和Uri是不一样的,从import的包就可以看出来import java.net.URI;
import android.net.Uri;
1 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { |
注:这里的
default_image保存的是默认用户头像,如果选择了自定义的用户头像它也会改成新的头像,因为最后添加头像的时候是调用它
但是!!!这样是有问题的
就是刚添加的时候图片能成功显示,但是关闭APP重新启动或者放在后台又返回的话图片就显示不了了
会出现如下警告
1 | W/ImageView: Unable to open content: content://com.google.android.apps.photos.contentprovider/-1/1/content%3A%2F%2Fmedia%2Fexternal%2Fimages%2Fmedia%2F6217/NO_TRANSFORM/1633586863 |
可以看到是因为没有权限访问这个图片,应该是打开选择图片窗口的Intent是一次性的,即使存储了它的路径,以后想得到它操作系统也不允许,为了解决这个问题,而且又不想学习BLOB的使用,最简单的办法
把图片存到自己的地盘!
就是使用InternalStorage把图片复制一份到本应用的files文件夹中:参考教程
所以修改onActivityResult
1 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { |
以后再也不用担心别人不要我读取这个文件了,因为这个文件已经属于我自己了!
SQLite存储评论
想了又想,还是把评论新建一个表吧,不然每个用户发表很多评论,每个评论又有很多人点赞,还要点赞别人的评论……(想想就恐怖)
修改UserSQLiteHelper
这里我用的是评论发送时间作为该表的主键,因为时间精确到秒,理论上不会冲突
1 | package com.example.myapplication; |
还要新建一个存储评论的实体类CommentInfo.java
1 | package com.example.myapplication; |
这其中涉及到评论点赞的问题,一个评论可能会有很多用户点赞,如何记录下这些用户,因为用户数不是固定的,甚至会无限增长,于是想到把存储点赞用户的数组合并到一个列里,以后再提取出来
最简单的办法就是使用分隔符分隔用户名
新建一个转换类Convert.java
1 | package com.example.myapplication; |
ListView的使用
效果:

Item项布局
1 |
|
这个里面很关键的一句
1 | android:descendantFocusability="blocksDescendants" |
是为了后面点击事件的处理,这个属性值表示子有子的焦点,父有父的焦点
如果没有这个的话,整个Item项和内部的Button将不会同时可以点击
属性
android:descendantFocusability的值有三种:
beforeDescendants:viewgroup会优先其子类控件而获取到焦点afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点,也就是各有各的焦点
评论页面布局
直接贴代码吧,毕竟很简单
1 |
|
Adapter的使用
有这三种常用的Adapter
ArrayAdapter:支持泛型操作,最简单的一个Adapter,只能展现一行文字
SimpleAdapter:同样具有良好扩展性的一个Adapter,可以自定义多种效果
BaseAdapter:抽象类,实际开发中我们会继承这个类并且重写相关方法,用得最多的一个Adapter
因为ListView中设计的元素比较多,还是自己继承BaseAdapter写一个Adapter吧,
新建CommentAdapter.java
1 | package com.example.myapplication; |
Click点击事件的添加
1 | commentAdapter = new CommentAdapter(this, username); |
发送评论
1 | Button button = findViewById(R.id.comment_submit); |
获取本机的通讯录
这里实现的是点击评论出现评论的用户名以及其电话号码的对话框

首先需要静态申请权限,在AndroidManifest.xml里添加
1 |
|
但是还是需要动态申请权限
在listview的点击事件中添加
1 | //动态申请权限 |
但是出现了一些状况,就是运行到这一句的时候
1 | Cursor cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " = \"" + commentAdapter.getItem(position).getUsername() + "\"", null, null); |
申请权限的对话框还没有弹出来,也就是说此时权限还没有申请到,但是却执行了搜索通讯录的操作,所以会出现以下报错

解决办法:确保申请到了权限再显示电话号码
修改单击事件
1 | listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { |
并添加申请结果的处理方法
1 |
|
这样子对话框在申请到了权限之后才会显示,就不会出错了!
笔记
添加图片到资源文件夹引用的时候又一次出现错误
1 | java.lang.RuntimeException: Unable to start activity |
解决办法:
把图片从mipmap-anydpi-v26放到mipmap-hdpi
项目结构
文件结构

数据结构
可以看到确实有一个数据库文件,有几个用户头像的副本

打开数据库查看


验证数据没有什么问题
完整代码
1 | package com.example.myapplication; |
1 | package com.example.myapplication; |