使用Kotlin+JetPack 从零开发自己的日记App

Posted Jinxed.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Kotlin+JetPack 从零开发自己的日记App相关的知识,希望对你有一定的参考价值。

前言

本人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的图库功能
  • 日记的收藏和设置私密
感谢

以上是关于使用Kotlin+JetPack 从零开发自己的日记App的主要内容,如果未能解决你的问题,请参考以下文章

Android Kotlin Jetpack Compose 使用

安卓开发: Jetpack compose + kotlin 实现 俄罗斯方块游戏

Kotlin基础从入门到进阶系列讲解(进阶篇)Jetpack,(更新中)

Kotlin基础从入门到进阶系列讲解(进阶篇)Jetpack,(更新中)

Android Jetpack 是不是需要使用 Kotlin 语言?

Kotlin 和 Jetpack 视频合集 | MAD Skills