目录
2.1 自定义Adapter 继承RecyclerView.Adapter
小编将讲述如何开发一个基于Litepal数据库的日记APP,为什么选择Litepal数据库呢?日记APP不是很简单的嘛这个可以做出啥新的点呢?... 为了解决大家的这些疑惑,本文没有附上很详细的代码,主要体现的是整个开发流程以及提出开发过程中容易遇到的坑,有啥问题大家可以及时指正相互学习~~~(文末已附上源码供大家参考)
一、项目概述
1. 研究背景
如今的人们工作忙碌学习繁重,往往每一天都无法有一个清晰的计划和安排,也会因为经常忘了一些事情而会徒增烦恼,所以需要借助一些工具来帮助自己安排和计划日常事务,让生活显得更加有条理。同时有时候人们会突然想起一些重要的事情、会开会听到重要的安排、会有日常生活的购物、支付等记录……这些时刻都需要立刻记下来,所以目前有许多APP以及上线,有专门记账的、专门记录运动记录的等等,都是为了方便大家记录生活的点点滴滴,让自己看到自己的成长和变化。
2. 研究意义
本APP是为了满足大众生活需求而研发出来的,简洁易上手,是各个年龄层都可以使用的日程APP。它不仅可以记录文字,还可以上传图片、拍照、录音分享给微信好友或QQ好友等,日记的内容可以在任何地方插入图片或者录音,同时为了用户的隐私,本日记APP可以对想要设置密码锁的记录进行设置,极大的保护了广大使用者的隐私,最后为了方便用户的使用,用户还可以设置日程提醒的时间,到点本APP将会振铃提醒用户。所做的一切都是站在用户的角度来看的。
二、系统设计
1. 关键技术
技术名称 | 技术概述 |
Litepal | LitePal是一款开源的Android数据库框架,采用对象关系映射(ORM)模式,将常用的数据库功能进行封装,可以不用写一行SQL语句就可以完成创建表、增删改查的操作。并且很轻量级,jar包不到100k,几乎零配置。 |
Recyclerview | RecyclerView是一个强大的滑动组件,与经典的ListView相比,同样拥有item回收复用的功能,这一点从它的名字Recyclerview即回收view也可以看出。 |
SpannableString | 图文混排可以使用Spannable进行多样式显示 |
MediaPlayerMediaRecorder | 通过语音操作对象进行录音操作 |
2. 系统设计
字段名 | 数据类型 | 是否主键 | 描述 |
id | Integer | Yes | 主键自增id |
title | String | no | 日记标题 |
context | String | no | 日记内容 |
time | String | no | 日记记录时间 |
dataType | boolean | no
| 标志是否设置到时提醒 false 无提醒 true 有提醒 |
dataTime | String | no | 提醒时间 |
lockType | boolean | no | 标志是否设置了密码锁 false 无密码锁 true 有密码锁 |
lock | String | no | 密码锁密码 |
三、系统实现
1. Litepal数据库使用步骤
LitePal是一款开源的Android数据库框架,它采用了对象关系映射的模式,并将我们平时最常用的一些数据库功能进行了封装,使得不用编写一行SQL语句就可以完成各个建表和增删改查的操作。
1.1 添加litepal数据库依赖
compile 'org.litepal.android:core:1.6.1'
1.2 新建assets目录
创建litepal.xml资源文件用于创建数据库名称、版本、表名以及存放的位置
<?xml version="1.0" encoding="utf-8"?>
<litepal>
<!--数据库名-->
<dbname value="DiaryUp"> </dbname>
<!--数据库版本号 若修改了model类要修改版本号,这样之前数据库中的数据也能得到保存-->
<!--每次只要数据库发生变动 版本号version都必须加1-->
<version value="1"> </version>
<!--映射模型,注意一定要使用完整的类名-->
<list>
<mapping class="com.example.diaryup.bean.Diary"></mapping>
</list>
</litepal>
1.3 Application配置
android:name="org.litepal.LitePalApplication"
1.4 增删改查操作
步骤 | 实现过程 |
添加 | diary.save(); |
删除 | DataSupport.delete(DiaryUp.class,id); DataSupport.deleteAll(DiaryUp.class, "条件=?",""); |
修改 | diary.update(id); |
查询 | DataSupport.find(DiaryUp.class,id); DataSupport.findAll(DiaryUp.class); |
2. 主界面实现(重点讲述Recyclerview)
本项目的所有列表界面都是采用的recyclerview实现的,有两个具体步骤(在adapter配置适配器,在activity中通过定义适配器对象来进行列表显示操作,自定义点击监听接口进行单击或长按操作)如下:
2.1 自定义Adapter 继承RecyclerView.Adapter
(1) 根据item中的字段重写ViewHolder
public class ViewHolder extends RecyclerView.ViewHolder
(2) 设置监听器接口后在ViewHolder中设置监听事件-单击长按
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
position = getAdapterPosition();
if(onItemClickListener!=null){
onItemLongClickListener.OnItemLongClick(v,diaryList.get(getAdapterPosition()));}
return true;
}
});
(3) 重写onBindViewHolder用于填充设置控件的值
(4) 根据item的位置删除数据或修改数据
public void removeData() {// 删除数据
diaryList.remove(position);
//删除动画
notifyItemRemoved(position);
notifyDataSetChanged();}
public void changeData(int diaryId) {// 修改数据
diary= DataSupport.find(DiaryUp.class, diaryId);
diaryList.set(position, diary);
notifyItemChanged(position);
notifyDataSetChanged();
}
2.2 列表中初始化列表集合初始化recyclerview
(1) 获取recyclerview创建adapter
(2) 给recyclerview设置adapter
(3) 设置layoutmanager(设置显示效果现行布局)
(4) 设置item分割线
(5) 调用适配器定义好的监听事件在里面写相应的逻辑代码
2.3 recyclerView的调用设置
(1) 获取所有日记列表 clubList = DataSupport.findAll(Club.class);
(2) 详情页面长按修改日记信息:注意当有锁时要先解锁才能进入编辑页面
(3) 长按删除日记:在初始化recyclerview的点击事件中进行删除(当有锁时要先解锁才能进行编辑或者删除操作)
// 编辑
case 0:
if (!diary.isLockType()) {//判断是否添加了秘密锁 false没有
Intent intent = new Intent(MainActivity.this,
AddActivity.class);//跳转到添加日记页
intent.putExtra("editModel", "update");//传递编辑信息
intent.putExtra("id", diary.getId());//传递id信息
startActivity(intent);//开始跳转
} else {//有秘密锁
// 弹出输入密码框
inputTitleDialog(diary.getLock(), 0, diary.getId());}
break;
// 删除
case 1:
if (!diary.isLockType()) {// 判断是否是加密日记 false没有
DataSupport.delete(Diary.class,diary.getId());
myRecyclerViewAdapter.removeData();
} else {//有秘密锁
// 弹出输入密码框
inputTitleDialog(diary.getLock(), 1, diary.getId());}
break;
(4) 调用adapter的removeData()提示列表更新刷新列表
3. 新增/修改记事界面实现
3.1 设置或取消密码锁
设置密码锁有相关的两个属性lockType和lock,一个用于判断是否加锁,一个用于存储加锁时的锁密码。在主界面且未加锁时直接进行编辑或者删除操作,否则先调用开锁函数判断密码是否正确,正确才可以进行下一步操作。而添加页面中可以设置或取消密码锁。
(1) 主界面中:确认输入密码是否正确 正确则打开编辑界面或者删除该条记录
(2) 设置锁
String inputName = inputServer.getText().toString();
if ("".equals(inputName)) {//判断输入框内容是否为空
Toast.makeText(AddActivity.this, "密码不能为空 请重新输入!",
Toast.LENGTH_LONG).show();
} else {//输入框内容不为空
lock = inputName;//密码
lockType = true;//添加了密码锁
ib_lk.setBackgroundResource(R.drawable.locky);//设置添加锁图案
Toast.makeText(AddActivity.this, "密码设置成功!",
Toast.LENGTH_LONG).show();
}
(3) 取消锁
//取消密码
private void inputUnlockDialog() {
//创建弹出框
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("是否取消密码")
.setNegativeButton("取消", null);//在弹窗上设置标题设置取消按钮
.setPositiveButton("确认", new DialogInterface.OnClickListener() {
//设置确认按钮
public void onClick(DialogInterface dialog, int which) {
lockType = false;//设置没有设置密码
lock = "0";//设置密码
ib_lk.setBackgroundResource(R.drawable.un_locky);
Toast.makeText(AddActivity.this, "密码已取消",Toast.LENGTH_LONG).show();
}
});
//弹出取消密码弹窗
builder.show();
}
3.2 分享
3.2.1 实现分享的功能会遇到一个bug,其实不仅是调用相机和相册,只要是访问文件,都会出现这个错误,其原因是Android 7.0 做了一些系统权限更改,为了提高私有文件的安全性,面向 Android 7.0 或更高版本的应用私有目录被限制访问,此设置可防止私有文件的元数据泄漏,如它们的大小或存在性。而此权限更改有多重副作用,其中之一就是当传递软件包网域外的 file://URI 可能给接收器留下无法访问的路径。
因此尝试传递 file://URI 会触发 FileUriExposedException。分享私有文件内容的推荐方法是使用 FileProvider。
(1) 配置AndroidManifest
也就是在application内加了一个provider,其中,name是固定的,android:authorities是你的应用报名+“.fileprovider”,其实这里不一定要写fileprovider,可以随便写,只是要与后面FileProvider.getUriForFile()这个方法中的第二个参数authority对应起来即可。android:grantUriPermissions固定true,表示uri访问授权,android:exported固定的false,android:resource表示我们app要共享文件的路径的资源文件。
(2) 接着就要在res目录下新建一个xml文件夹,然后新建文件filepaths.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<external-path
name="gallery_photos"
path=""/>
</paths>
</resources>
(3) 使用时用fileprovider获取URI
Uri imageUri = FileProvider.getUriForFile(getApplicationContext(), "com.example.fileprovider", file);//file即为所要共享的文件的file
3.2.2 分享的操作步骤
(1) Layout.xml绑定onclick事件 点击按钮就会进入该函数中
以分享的系统时间命名分享的照片名称 - 先截取scrollview的屏幕 - 获取到图片后保存图片 - 根据图片路径获取图片uri - 最后调用系统intent(注意要授予权限)
//创建意图
intent shareIntent = new Intent();
//授予临时权限别忘了
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//过滤条件允许分享的
shareIntent.setAction(Intent.ACTION_SEND);
//分享图片
shareIntent.putExtra(Intent.EXTRA_STREAM, imageUri);
//设置类型
shareIntent.setType("image/*");
//默认跳转类型
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(Intent.createChooser(shareIntent, "分享到:"));
(2) 截取scrollview屏幕:返回的数据类型是bitmap,主要是获取scrollview的实际高度,还可以设置scrollview的背景图片,最后使用canvas画布绘制图片并返回
// 截取scrollview的屏幕
public static Bitmap getBitmapByView(ScrollView scrollView) {
int h = 0;//设置高度0
Bitmap bitmap = null;//设置空的图片
// 获取scrollview实际高度
for (int i = 0; i < scrollView.getChildCount(); i++) {
// 计算scrollView高度
h += scrollView.getChildAt(i).getHeight();
scrollView.getChildAt(i).setBackgroundColor(Color.parseColor("#FFFFFF"));//设置scrollView背景颜色
}
// 创建scrollView大小的bitmap
bitmap = Bitmap.createBitmap(scrollView.getWidth(), h, Bitmap.Config.RGB_565);
// 绘制图片
final Canvas canvas = new Canvas(bitmap);
// 绘制scrollView
scrollView.draw(canvas);
// 返回图片
return bitmap;
}
(3) 保存图片:主要是要创建图片文件、创建文件流、实例化文件流后将图片保存到文件,最后文件写入操作结束后关闭文件流。
3.3 设置提示时间
3.3.1 设置提示时间提醒需要自行设定年、月、日、时间,就会用到自定义控件时间选择器DateTimePickerDialog,同时选择日期和时间,不用分开单独选择日期和时间该控件继承AlertDialog提示框。
(1) 初始化 / 设置提醒setReminder:未设置过则显示系统时间 - 设置过时间则显示之前设置的时间 - 设置监听器以自己设置的时间作为dataTime,dataType置为true更新数据库。
(2) 取消闹钟
data_rl.setVisibility(View.GONE);
dataType = false;// 判断是否开启记录开启了提醒功能
dataTime = "0";
(3) 修改闹钟提醒时间:直接调用setReminder即可
3.3.2 时间设置好了需要在该时间提示用户
(1) 修改AndroidManifest配置。
<receiver
android:name="com.example.diaryup.data.CallAlarm"
android:process=":remote" />
<activity
android:name="com.example.diaryup.data.AlarmAlert"
android:label="@string/remindsetting_name">
<intent-filter>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
(2) 消息管理者获取服务后使用PendingIntent获取广播并设置重复闹钟第一个参数表示闹钟类型,第二个参数表示闹钟首次执行时间,第三个参数表示闹钟两次执行的间隔时间,第三个参数表示闹钟响应动作。
if (am == null) {
am = (AlarmManager) getSystemService(ALARM_SERVICE);}
try {
Intent intent = new Intent(MainActivity.this, CallAlarm.class);
PendingIntent sender = PendingIntent.getBroadcast(
MainActivity.this, 0, intent, 0);
am.setRepeating(AlarmManager.RTC_WAKEUP, 0, 60 , sender);
} catch (Exception e) {
e.printStackTrace();
}
(3) 发送广播到CallAlarm获取当前时间,搜索数据库中全部数据,判断当系统时间与数据库中提醒时间相同则更新数据库,并调用AlarmAlert开始震动或者响铃。
if (diary.isDataType() && str.equals(diary.getDataTime())) { //设置了提醒 并且 现在到了提醒时间
diary.setToDefault("dataType");
diary.setDataTime("0");
diary.update(diary.getId());
}
3.4 照片和拍照
拍照和从相册取得照片都是需要对照片进行处理,并且要像上面处理分享时一样使用fileprovider,不同的是拍照和从相册获得照片的intent不一样。
3.4.1 相同点一:调用相册和拍照都要使用fileprovider获取权限
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" >
</meta-data>
</provider>
3.4.2 相同点二:都使用SpannableString处理图片
(1) 获取到照片的uri,根据uri经处理得到路径(相册的可以直接从系统中获取而拍的照片要通过文件流自行命名自定义路径存储起来)
并将对象存入bitmap
(2) 最后插入图片,插入之前先等比例缩放到合适的大小并添加在EditText中
(3) 日记内容区域将新增添加的图片
3.4.3 相同点三:加载数据
(1) 定义正则表达式用于匹配路径"/([^\\.]*)\\.\\w{3}"
(2) 每次Matcher m,m.find()后先取出路径前面的文字再取出照片路径及后缀
(3) 当后缀为图片时取出图片缩放图片,通过ImageSpan来setSpan最后append到控件内容上。最后再将最后一个图片的文字添加到textView中
3.4.4 相同点四:EditText点击事件(放大图片)的监听
(1) 通过ImageSpan找到图片
(2) 找到图片后判断图片后三位如果是图片则跳转到查看图片的界面(调用系统图库查看图片)
3.4.5 不同点:
//点击图片的话会打开相册、如果相册没有图片会自动打开相机
intent = new Intent(Intent.ACTION_PICK, null);
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
"image/*");
// 选中相片后返回本Activity
startActivityForResult(intent, 1);
// 调用系统拍照界面
intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// 区分选择相片
startActivityForResult(intent, 2);
3.5 录音
当点击菜单中的录音选项时,直接跳转至录音界面activity_record.xml,这里往往会遗漏多操作,不仅仅只是像常规思维一样根据设计步骤来,而需要考虑许多种操作,比如试听未完成就点击录音或录音未完成就保存需要做什么样的处理……
点击录音按钮 | 点击麦克风 | ||
isRecording==0 点击后开始录音 | 若正试听,则停止录音、通话、mTimer、至时间显示初始化 | isPlaying==0 点击后开始试听 | isPlaying==1 初始化计时器重新计时 播放器根据录音保存的FilePath开始试听并播放动画 |
若没有试听,则以系统时间命名创建文件保存至该路径,将麦克设为不可点击,时间初始化重新计时,播放动画 设置好mRecorder录制 | |||
isRecording==1 点击后停止录音 | 将按钮换成可录音 将麦克设为可点击 停止动画 | isPlaying==1 点击后结束试听 | 播放器停止并释放 停止动画
|
点击保存按钮 | 点击取消按钮 | ||
FilePath为空 | Toast没有录音保存 | 正在录音 | 先停止录音 再退出 |
无录音、试听正进行 | 创建intent将文件路径及音频时长回传到AddActivity | 正在试听 | 先停止播放 再退出 |
正在录音 | Toast录音完毕后才可保存 |
|
|
正在试听 | 停止试听再保存 |
|
|
回传后,在回调函数中创建接收器后接收返回的信息并将图片转化成bitmap并插入录音图标,之后的数据加载也类似于照片的处理但录音只需要设置特定的录音icon图标。并且在EditText点击事件中点击录音recorder_icon的图标后跳转到收听录音的ShowRecord中仅仅只是播放录音。
4. 项目演示
4.1 打开APP主页
小伙伴们下载安装好之后,手机里点击APP即可打开进入其主页,如图3所示:
4.2 新增记事
点击主页下面的绿色添加按钮即可进入新增记事界面,如图4所示:
4.3 单击编辑记事
点击主页中任何一条想要修改的记事(若该记事需要解锁则输入解锁密码),成功点击后则会进入编辑记事界面,如图5所示:
4.4 设置密码锁
在主界面且未加锁时直接进行编辑或者删除操作,否则先判断密码是否正确,正确才可以进行下一步操作,而添加页面中可以设置或取消密码锁。如图6-9所示:
4.5 分享日记
在新增或编辑记事内会有一个分享图标,用户点击该图标后可以自行选择传送方式,是将该日记界面的日记内容截图分享给QQ好友、微信、朋友圈还是蓝牙传送…… 如图10所示:
4.6 日记录音
在新增或编辑记事内的菜单中有录音选项,在当点击菜单中的录音选项时,直接跳转至录音界面,如图11-12所示:
4.7 日记调用相册
在新增或编辑记事内的菜单中有进入相册选项,在当点击菜单中的相册选项时,直接跳转至系统相册,选择完照片后缩小保存至编辑区域,再次点击图片将打开本地图库看大图,如图13-15所示:
4.8 日记拍照记录
在新增或编辑记事内的菜单中有进入拍照选项,在当点击菜单中的拍照选项时,将打开相机,拍照完毕后将照片缩小并存入编辑区域内,再次点击图片将打开本地图库看大图,如图16-17所示:
4.9 设置提醒时间
新增或编辑记事内的菜单中有进入设置提醒时间选项,在当点击菜单中的设置提醒时间选项时,用户需要自行设定年、月、日、时间,设置成功将显示出来便于之后用户进行修改,到达设定提醒时间后将通过手机振铃提醒用户。如图18-19:
4.10 搜索记事
主页点击按内容搜索图标将进入相应界面,在搜索框中输入文字将对所有记事的标题和文字内容进行搜索,若包括则列表显示。如图20所示:
Github 项目地址
https://github.com/Arielxyx/DiaryUp
欢迎大家积极提出有问题的地方,相互学习~~~