前言
本人Android小菜鸡一枚,开发该app的主要目的是为了巩固kotlin语法,学习使用JetPack进行一个完整App的开发,不得不说,Kotlin+JetPack开发起来真是无敌丝滑(末尾附上项目地址)
介绍
😄时刻是一个使用纯kotlin和Jetpack实现的具有简单增删改查功能的日记App
效果图
使用的相关技术
- DataBinding :省去了繁琐的findViewById工作,而且通过和ViewMode配合,可以直接把ViewModel的数据渲染到界面上
- Paging:分页加载库
郭神的博客 - Room:封装了sqlite,将表映射成Java或Kotlin对象,我觉得相比于之前使用的greendao,room最大的优势是支持了kotlin的flow,通过和paging配合,实现自动的刷新数据链接
- dataStore:用来代替SP的数据持久化方案,最大的优势是支持异步获取保存数据,通过一个PreferencesKey保存对应类型泛型的方法保证了类型安全
- ViewModel:用于保存actvity的数据中转
- Glide:图片加载,这个太牛皮了就不介绍了
- liveData:代替EventBus用于进行事件分发
部分代码展示
- 日记的主题颜色更改
/**
* 设置日记编辑的背景主题
* 判断颜色是否是亮色,设置对应主题字体
*
* todo 自定义背景功能
*/
private fun setThemeBackGround(@ColorRes colorRes: Int) {
val color: Int = ContextCompat.getColor(this, colorRes)
mBinding.clEditDairy.setBackgroundResource(colorRes)
setStatusBarColor(color)
if (ColorUtils.calculateLuminance(color) > 0.6) {
// 亮色
setAndroidNativeLightStatusBar(this, true)
} else {
// 暗色
setAndroidNativeLightStatusBar(this, false)
}
val shape = GradientDrawable()
shape.color = ColorStateList.valueOf(addColorDepth(color))
shape.cornerRadius = dp2px(baseContext, 5f)
ripperDrawable =
RippleDrawable(ColorStateList.valueOf(ContextCompat.getColor(this, R.color.DairyEditHintText)), shape, null)
}
/**
* 将原有颜色的R,G,B值依次减去20得到一个更深的颜色
*/
private fun addColorDepth(color: Int): Int {
var red = color and 0xff0000 shr 16
var green = color and 0x00ff00 shr 8
var blue = color and 0x0000ff
red = if (red - 25 > 0) red - 25 else 0
green = if (green - 25 > 0) green - 25 else 0
blue = if (blue - 25 > 0) blue - 25 else 0
return Color.rgb(red, green, blue)
}
- 日记意外退出恢复功能
/**
这里把保存操作写在onPause里,因为不管是手动退出app还是意外终止app都一定会走到该方法,然后增加一个标记表示是意外中止
**/
override fun onPause() {
super.onPause()
if (!TextUtils.isEmpty(viewModel.dairyContent.value) && isNeedToSaved) {
DataStoreUtils.saveSyncStringData(RECOVER_CONTENT, viewModel.dairyContent.value!!)
DataStoreUtils.saveSyncStringData(RECOVER_TITLE, mBinding.appBar.getTitle())
}
}
/**
恢复操作,开始一个协程,异步获取
**/
private fun tryToRecoverDairy() {
lifecycleScope.launch(Dispatchers.Main) {
DataStoreUtils.readStringFlow(RECOVER_CONTENT).first {
if (it.isNotEmpty()) {
recoveredContent = it
}
true
}
DataStoreUtils.readStringFlow(RECOVER_TITLE).first {
if (it.isNotEmpty()) {
recoveredTitle = it
}
true
}
}
- 主界面获取日记列表的操作
/**
Activity层
Activity里调用到viewModel
**/
private fun getDairyData() {
lifecycleScope.launch(Dispatchers.Main) {
viewModel.getAllDairy().collect {
dairyAdapter.submitData(it)
}
}
}
/**
ViewModel层
ViewModel当作中转站,通过持有的仓库对象repository访问到数据库
**/
fun getAllDairy(): Flow<PagingData<DairyItem>> {
return repository.getAllDairyData().cachedIn(viewModelScope)
}
/**
repository层
查找表获得所有日记
**/
fun getAllDairyData(): Flow<PagingData<DairyItem>> {
return Pager(
config = PagingConfig(PAGE_SIZE, maxSize = 150),
pagingSourceFactory = dairyDao.getAllDairy().asPagingSourceFactory()
).flow
}
/**
* Dao层 这里通过和Paging的factory绑定,数据库变动后会自动的刷新到paging中
* 用DataSource把数据绑定到Paging 响应式
* 根据时间降序查找
*/
@Query("SELECT * FROM DairyEntity order by createTime desc")
fun getAllDairy(): DataSource.Factory<Int, DairyItem>
- 调起手机的相册和相机
/**
这里我使用了新版的启动器方式来调起,只需要写一个启动器和一个启动协议
* 初始化相册启动器
*/
private val toAlbumLauncher =
registerForActivityResult(ToSystemAlbumResultContract()) {
if (it != null) {
viewModel.pictureList.add(it)
viewModel.isChanged.value = true
// 只需要刷新新增的一个和尾部,也是就itemCount为2
pictureSelectAdapter.notifyItemRangeChanged(viewModel.pictureList.size, 2)
}
}
/**
* 初始化相机启动器
*/
private val toCameraLauncher =
registerForActivityResult(ActivityResultContracts.TakePicture()) {
if (it) {
viewModel.pictureList.add(currentUri)
viewModel.isChanged.value = true
pictureSelectAdapter.notifyItemRangeChanged(viewModel.pictureList.size, 2)
galleryAddPic()
}
}
/**
* 跳转到系统相册
* 传入参数 ArrayList<Bitmap> 图片列表
* 返回参数 Int 标识哪一张图片被删除
*/
inner class ToSystemAlbumResultContract : ActivityResultContract<Unit, Uri?>() {
override fun createIntent(context: Context, input: Unit?): Intent {
val intent = Intent(
Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
)
intent.type = "image/*"
return intent
}
override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
return intent?.data
}
}
最后
源码中代码的注释非常详细,有兴趣的大佬可以进去看看呐
Kotlin的扩展函数用起来贼爽
github地址:LongerRelationShip
有兴趣的大佬可以下载提提建议
时刻下载地址
后期开发功能
- 记账功能
- 录音笔记功能
- App的图库功能
- 日记的收藏和设置私密
感谢
- 界面设计很大部分参考了该设计
美贴 记事原型 - 代码的学习参考
Eyepetizer
官方的代码示例
Sunflower