Browse Source

mian learncenter coursemain

master
suliang 2 years ago
parent
commit
f7ab414fd9
82 changed files with 2305 additions and 242 deletions
  1. 28
    0
      .idea/misc.xml
  2. 27
    1
      ProjectErrors.md
  3. 11
    1
      README.md
  4. 14
    31
      app/build.gradle
  5. 5
    0
      app/src/main/AndroidManifest.xml
  6. BIN
      app/src/main/assets/20479f95-32bc-44e6-8398-75e1646c84a0.db
  7. BIN
      app/src/main/assets/6fa25e64-e68d-4fd0-b57d-f20ee0f3b31d.db
  8. BIN
      app/src/main/assets/be6e9d63-d9aa-4684-b1a6-c6f304b06701.db
  9. BIN
      app/src/main/assets/course-pack.db
  10. 1
    0
      app/src/main/assets/lottie_loading.json
  11. 96
    0
      app/src/main/java/com/xkl/cdl/adapter/AdapterCoursePackWithLearCenter.kt
  12. 50
    49
      app/src/main/java/com/xkl/cdl/data/Constants.kt
  13. 19
    0
      app/src/main/java/com/xkl/cdl/data/bean/Lesson.kt
  14. 89
    0
      app/src/main/java/com/xkl/cdl/data/binding/BindingAdapter.kt
  15. 32
    0
      app/src/main/java/com/xkl/cdl/data/course/Course.kt
  16. 60
    2
      app/src/main/java/com/xkl/cdl/data/course/CoursePack.kt
  17. 76
    2
      app/src/main/java/com/xkl/cdl/data/manager/CourseManager.kt
  18. 10
    0
      app/src/main/java/com/xkl/cdl/data/manager/db/CompositionDbManager.kt
  19. 27
    0
      app/src/main/java/com/xkl/cdl/data/manager/db/DBCourseManager.kt
  20. 82
    0
      app/src/main/java/com/xkl/cdl/data/manager/db/DbCoursePackManager.kt
  21. 11
    0
      app/src/main/java/com/xkl/cdl/data/manager/db/EnglishDbManager.kt
  22. 32
    0
      app/src/main/java/com/xkl/cdl/data/manager/db/IDB.kt
  23. 5
    1
      app/src/main/java/com/xkl/cdl/module/XKLApplication.kt
  24. 15
    0
      app/src/main/java/com/xkl/cdl/module/m_center_learn/CourseMainActivity.kt
  25. 13
    0
      app/src/main/java/com/xkl/cdl/module/m_center_learn/CourseMainActivityViewModel.kt
  26. 46
    12
      app/src/main/java/com/xkl/cdl/module/m_center_learn/CoursePackFragment.kt
  27. 27
    3
      app/src/main/java/com/xkl/cdl/module/m_center_learn/CoursePackFragmentViewModel.kt
  28. 24
    2
      app/src/main/java/com/xkl/cdl/module/m_center_learn/LearnCenterFragment.kt
  29. 4
    1
      app/src/main/java/com/xkl/cdl/module/m_center_learn/LearnCenterFragmentViewModel.kt
  30. 24
    21
      app/src/main/java/com/xkl/cdl/module/splash/SplashActivity.kt
  31. 134
    0
      app/src/main/java/com/xkl/cdl/widget/MyRankingView.java
  32. 39
    0
      app/src/main/java/com/xkl/cdl/widget/smartRefresh/BaseRefreshHeader.kt
  33. 78
    0
      app/src/main/java/com/xkl/cdl/widget/smartRefresh/XkLRefreshHeader.kt
  34. 6
    0
      app/src/main/res/drawable/item_decoration_only_maring_8.xml
  35. 17
    0
      app/src/main/res/drawable/progress_center.xml
  36. 6
    0
      app/src/main/res/drawable/shape_rounder_toplr_24_white.xml
  37. 142
    0
      app/src/main/res/layout/activity_course_main.xml
  38. 5
    3
      app/src/main/res/layout/fragment_course_pack.xml
  39. 3
    4
      app/src/main/res/layout/fragment_learn_center.xml
  40. 52
    0
      app/src/main/res/layout/include_main_learn_center_course_progress.xml
  41. 85
    0
      app/src/main/res/layout/include_main_learn_center_course_type_title.xml
  42. 20
    0
      app/src/main/res/layout/include_recyclerview_smart_refresh_layout.xml
  43. 22
    0
      app/src/main/res/layout/include_title_bar.xml
  44. 37
    0
      app/src/main/res/layout/item_empty.xml
  45. 139
    0
      app/src/main/res/layout/main_item_coursepack.xml
  46. 32
    0
      app/src/main/res/layout/smart_refresh_header.xml
  47. BIN
      app/src/main/res/mipmap-xxhdpi/empty_nothing.png
  48. BIN
      app/src/main/res/mipmap-xxhdpi/empty_nothing_search.png
  49. BIN
      app/src/main/res/mipmap-xxhdpi/img_default.png
  50. 9
    0
      app/src/main/res/values/attrs.xml
  51. 3
    0
      app/src/main/res/values/colors.xml
  52. 2
    1
      app/src/main/res/values/dimens.xml
  53. 7
    2
      app/src/main/res/values/strings.xml
  54. 9
    0
      app/src/main/res/values/styles.xml
  55. 12
    0
      app/svg/drawable/ic_arrow_right.xml
  56. 12
    0
      app/svg/drawable/ic_discern.xml
  57. 12
    0
      app/svg/drawable/ic_more.xml
  58. 12
    0
      app/svg/drawable/ic_spell.xml
  59. 10
    0
      app/svg/drawable/ic_spoken.xml
  60. 12
    0
      app/svg/drawable/ic_voice.xml
  61. 16
    9
      build.gradle
  62. 5
    2
      lib/common/build.gradle
  63. 5
    2
      lib/common/proguard-rules.pro
  64. 32
    0
      lib/common/src/main/java/com/suliang/common/base/activity/BaseActivity.kt
  65. 29
    5
      lib/common/src/main/java/com/suliang/common/base/adapter/BaseAdapter.kt
  66. 1
    1
      lib/common/src/main/java/com/suliang/common/base/adapter/BaseAdapterVM.kt
  67. 2
    1
      lib/common/src/main/java/com/suliang/common/base/adapter/BaseAdapterViewHolder.kt
  68. 12
    0
      lib/common/src/main/java/com/suliang/common/base/adapter/IItemClickListener.kt
  69. 7
    2
      lib/common/src/main/java/com/suliang/common/base/fragment/BaseFragment.kt
  70. 40
    0
      lib/common/src/main/java/com/suliang/common/extension/ViewClickExtension.kt
  71. 6
    3
      lib/common/src/main/java/com/suliang/common/util/AppGlobals.kt
  72. 27
    0
      lib/common/src/main/java/com/suliang/common/util/DrawableUti.kt
  73. 19
    26
      lib/common/src/main/java/com/suliang/common/util/LogUtil.kt
  74. 0
    1
      lib/common/src/main/java/com/suliang/common/util/image/GlideLoaderStrategy.kt
  75. 7
    0
      lib/common/src/main/java/com/suliang/common/util/image/ImageLoader.kt
  76. 1
    1
      lib/common/src/main/java/com/suliang/common/util/thread/AppExecutors.kt
  77. 91
    53
      lib/common/src/main/java/com/suliang/common/widget/InputSearchEditText.kt
  78. 57
    0
      lib/common/src/main/java/com/suliang/common/widget/TextViewCenterDrawable.java
  79. 116
    0
      lib/common/src/main/java/com/suliang/common/widget/TitleBar.kt
  80. 12
    0
      lib/common/src/main/res/drawable/ic_back.xml
  81. 56
    0
      lib/common/src/main/res/layout/public_title_bar.xml
  82. 19
    0
      lib/common/src/main/res/values/attrs.xml

+ 28
- 0
.idea/misc.xml View File

@@ -15,28 +15,51 @@
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/ic_search.xml" value="0.30520833333333336" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/ic_spell.xml" value="0.30520833333333336" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/ic_voice.xml" value="0.30520833333333336" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/item_decoration_only_maring_8.xml" value="0.375" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/nav_learn_center.xml" value="0.30520833333333336" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/nav_memo.xml" value="0.30520833333333336" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/nav_my.xml" value="0.30520833333333336" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/nav_service.xml" value="0.30520833333333336" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/nav_statistics.xml" value="0.1" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/progress_center.xml" value="0.287962962962963" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/shape_rounder_toplr_24_white.xml" value="0.5061538461538462" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/theme_splash_bg.xml" value="0.30520833333333336" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout-v23/include_main_learn_center_course_type_title.xml" value="0.4963768115942029" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_course_main.xml" value="0.33" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_main.xml" value="0.5" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_splash.xml" value="0.4921875" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_course_pack.xml" value="0.34427083333333336" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_learn_center.xml" value="0.25" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_my.xml" value="0.28229166666666666" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/include_main_learn_center_course_type_title.xml" value="0.67" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/include_recyclerview_smart_refresh_layout.xml" value="0.4979166666666667" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/include_title_bar.xml" value="0.25052083333333336" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_empty.xml" value="0.4979166666666667" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/main_item_course_progress.xml" value="0.25" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/main_item_coursepack.xml" value="0.43500866551126516" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/merge_recyclerview_smart_refresh_layout.xml" value="0.34427083333333336" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/smart_refresh_header.xml" value="0.34427083333333336" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/textview.xml" value="0.49773550724637683" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/textview_only.xml" value="0.49773550724637683" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/statedrawable/drawable/ic_nav_learn_center.xml" value="0.44166666666666665" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_discern.xml" value="0.5061538461538462" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_icon___.xml" value="0.287962962962963" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_nav_learn_center.xml" value="0.44166666666666665" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_nav_memo.xml" value="0.21574074074074073" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_nav_my.xml" value="0.44166666666666665" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_nav_service.xml" value="0.21574074074074073" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_nav_statistics.xml" value="0.44166666666666665" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_spell.xml" value="0.5061538461538462" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_spoken.xml" value="0.5061538461538462" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_voice.xml" value="0.5061538461538462" />
<entry key="..\:/Work/XKL/XKL/XklLocal/lib/common/src/main/res/drawable/ic_back.xml" value="0.4148148148148148" />
<entry key="..\:/Work/XKL/XKL/XklLocal/lib/common/src/main/res/drawable/ic_search.xml" value="0.19814814814814816" />
<entry key="..\:/Work/XKL/XKL/XklLocal/lib/common/src/main/res/drawable/loading_bg.xml" value="0.5061538461538462" />
<entry key="..\:/Work/XKL/XKL/XklLocal/lib/common/src/main/res/layout/lib_dialog_loading.xml" value="0.4979166666666667" />
<entry key="..\:/Work/XKL/XKL/XklLocal/lib/common/src/main/res/layout/lib_layout_custom_title.xml" value="0.4979166666666667" />
<entry key="..\:/Work/XKL/XKL/XklLocal/lib/common/src/main/res/layout/loadding_fragment.xml" value="0.4817708333333333" />
<entry key="..\:/Work/XKL/XKL/XklLocal/lib/common/src/main/res/layout/loading_fragment.xml" value="0.4979166666666667" />
<entry key="..\:/Work/XKL/XKL/XklLocal/lib/common/src/main/res/layout/public_title_bar.xml" value="0.33" />
<entry key="..\:/Work/XKL/XklLocal/app/src/main/res/drawable/theme_splash_bg.xml" value="0.22" />
<entry key="..\:/Work/XKL/XklLocal/app/src/main/res/layout/activity_fullscreen.xml" value="0.10144927536231885" />
<entry key="..\:/Work/XKL/XklLocal/app/src/main/res/layout/activity_main.xml" value="0.1" />
@@ -44,6 +67,11 @@
</map>
</option>
</component>
<component name="EntryPointsManager">
<list size="1">
<item index="0" class="java.lang.String" itemvalue="androidx.databinding.BindingAdapter" />
</list>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>

+ 27
- 1
ProjectErrors.md View File

@@ -1,6 +1,8 @@
项目中遇到的错误: 使用jectPack时的依赖: · androidx.appcompat: appcompat
依赖中已经带了livedata、viewmodel等,可以不用再依赖,再依赖时出现了异常编译错误,未解决 且 依赖版本不同,对应的livedata等版本也不同

使用sqlcipher 需要在application 实现 : SQLiteDatabase.loadLibs(this)

kotlin 泛型 单例实现 扩展实现 导航实现

svg
@@ -19,4 +21,28 @@ svg
android:fillColor="#000000"//填充颜色
android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" /> //路径的数据
</group>
</vector>
</vector>

apk唯一数据源:
设备唯一 : UUID 存值
第一次启动时间
课程包信息通过合成进行加解密,获取用户的课程数据

数据复制方案:
1: 使用两个apk方式:
A.apk中自带数据库文件 包含功能: 复制数据文件到共有目录(document)
B.apk中不包含课程数据: 运行时检查本地外置存储是否有需要的课程,如果没有,则检查共有目录(document)中是否存在有对应文件,有则复制到本地,没有则提示资源加载失败,应用无法使用
2: 只有一个apk方式
动态打包: 将需要的课程资源动态复制到asset中,再使用命令进行apk打包,这样打出的apk包只有固定的资源
3: 一个apk,包含所有课程资源... 体积超大

用户记录保存:
读取唯一数据源

google 给与的唯一标识符最佳做法:
1、避免使用硬件标识符(权限限制,硬件定制)
2、只针对用户剖析或广告用例使用广告 ID
3、尽一切可能针对防欺诈支付和电话以外的所有其他用例使用实例 ID 或私密存储的 GUID。对于绝大多数非广告用例,使用实例 ID 或 GUID 应已足够。
4、使用适合您的用例的 API 以尽量降低隐私权风险

如何实现防快速点击 aspectJ

+ 11
- 1
README.md View File

@@ -26,10 +26,20 @@ media包: 实现音视频的播放封装
,"http://ws.stream.qqmusic.qq.com/C400001lePVO36SNSo.m4a?guid=748677131&vkey=A6077E31CB4A1C8A4F3D5D2BEDE8D3E16AB69FF2FEE95D9D48288EAD048AB89DC53B6846D184F842257617AF9714D97C864FB183BE05CC66&uin=&fromtag=66"
,"http://ws.stream.qqmusic.qq.com/C400000ZFBf22vBvrf.m4a?guid=921100582&vkey=C9EA33972404E2AC1C80A479614AD44A9BF132A16F9B307D9515F595B41901AE53F6681E28BDA799FC047F30D624F8A19AE9C4856ADBEF4B&uin=&fromtag=66")


image包: 实现了图片加载的封装


防快速点击和跳转限制:
1 扩展点击事件和效果 -> ViewClickExtension.kt 类
https://www.cnblogs.com/zh2314372037/p/13965371.html
2 BaseActivity覆写 startActivityForResult(intent: Intent, requestCode: Int, options: Bundle?),检查目标和间隔时间,判断是否可以跳转新的Activity
https://blog.csdn.net/ziyexiaoxiao/article/details/85292729

注意include为merge的坑,viewbinding中需动态绑定 https://juejin.cn/post/6844904065655111693



Kotlin的单例模式: https://developer.aliyun.com/article/642618
饿汉式(直接获取实例)、懒汉式(一次判空获取实例)、双重校验锁
object 声明: 为饿汉式单例,即直接实例
数字精度格式: https://www.jianshu.com/p/5286a44ed9b1

+ 14
- 31
app/build.gradle View File

@@ -72,39 +72,22 @@ dependencies {
implementation project(path: ':lib:common')
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
rootProject.ext.dependencies_required.each{ k, v -> implementation v}
testImplementation rootProject.ext.dependencies_testImplementation.junit
rootProject.ext.dependencies_androidTestImplementation.each{ k,v -> androidTestImplementation v}
// kapt rootProject.ext.dependencies_kapt.lifecycle_compiler
def lifecycle_version = "2.5.0-alpha02"
def arch_version = "2.1.0"
//
// // ViewModel
// implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// // ViewModel utilities for Compose
// implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
// // LiveData
// implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// // Lifecycles only (without ViewModel or LiveData)
// implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
//
// // Saved statedrawable module for ViewModel
// implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
//
// // Annotation processor
//// kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
// // alternately - if using Java8, use the following instead of lifecycle-compiler
// implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
// // optional - helpers for implementing LifecycleOwner in a Service
// implementation "androidx.lifecycle:lifecycle-service:$lifecycle_version"
//
// // optional - ProcessLifecycleOwner provides a lifecycle for the whole application process
// implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
//
// // optional - ReactiveStreams support for LiveData
// implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version"
//
// // optional - Test helpers for LiveData
// testImplementation "androidx.arch.core:core-testing:$arch_version"

def customDependencies = rootProject.ext.dependencies_custom
//SmartRefreshLayout
implementation customDependencies.SmartRefreshLayout
implementation 'io.github.scwang90:refresh-header-classics:2.0.5' //经典刷新头
//SqlCipher
implementation customDependencies.SqlCipher
//androidx-sqlite
implementation customDependencies.Sqlite
//Lottie
implementation customDependencies.Lottie

}

+ 5
- 0
app/src/main/AndroidManifest.xml View File

@@ -4,6 +4,7 @@
package="com.xkl.cdl">

<application
android:name=".module.XKLApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
@@ -11,6 +12,9 @@
android:supportsRtl="true"
android:theme="@style/Theme.XklLocal"
tools:ignore="LockedOrientationActivity">
<activity
android:name=".module.m_center_learn.CourseMainActivity"
android:exported="true" />
<activity
android:name=".module.splash.SplashActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
@@ -19,6 +23,7 @@
android:theme="@style/Theme.Splash">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

BIN
app/src/main/assets/20479f95-32bc-44e6-8398-75e1646c84a0.db View File


BIN
app/src/main/assets/6fa25e64-e68d-4fd0-b57d-f20ee0f3b31d.db View File


BIN
app/src/main/assets/be6e9d63-d9aa-4684-b1a6-c6f304b06701.db View File


BIN
app/src/main/assets/course-pack.db View File


+ 1
- 0
app/src/main/assets/lottie_loading.json
File diff suppressed because it is too large
View File


+ 96
- 0
app/src/main/java/com/xkl/cdl/adapter/AdapterCoursePackWithLearCenter.kt View File

@@ -0,0 +1,96 @@
package com.xkl.cdl.adapter

import android.opengl.Visibility
import android.view.View
import android.view.ViewGroup
import com.suliang.common.base.adapter.BaseAdapterVM
import com.suliang.common.base.adapter.BaseAdapterViewHolder
import com.suliang.common.extension.click
import com.xkl.cdl.R
import com.xkl.cdl.data.Constants
import com.xkl.cdl.data.course.CoursePack
import com.xkl.cdl.databinding.ItemEmptyBinding
import com.xkl.cdl.databinding.MainItemCoursepackBinding
import com.xkl.cdl.module.m_center_learn.CoursePackFragmentViewModel

/**
* author suliang
* create 2022/3/23 17:05
* Describe: 学习中心课程包适配器
*/
class AdapterCoursePackWithLearCenter(vm: CoursePackFragmentViewModel) :
BaseAdapterVM<CoursePack, CoursePackFragmentViewModel>(vm) {


override fun coverEmptyViewHolder(parent: ViewGroup): BaseAdapterViewHolder {
return BaseAdapterViewHolder(inflateBinding(parent, R.layout.item_empty))
}

override fun onBindEmptyViewHolder(holder: BaseAdapterViewHolder) {
(holder.binding as ItemEmptyBinding).apply {
val nullOrEmpty = vm.learnCenterFragmentViewModel.mCoursePackSearchValueMap[vm.projectId].isNullOrEmpty()
imgEmpty.setImageResource(if (nullOrEmpty) R.mipmap.empty_nothing else R.mipmap.empty_nothing_search)
tvContent.setText(if (nullOrEmpty) R.string.empty_nothing else R.string.empty_nothing_search)
}
}

override fun coverViewHolder(parent: ViewGroup, viewType: Int): BaseAdapterViewHolder {
return BaseAdapterViewHolder(inflateBinding(parent, R.layout.main_item_coursepack))
}

override fun onBindVH(holder: BaseAdapterViewHolder, position: Int) {
(holder.binding as MainItemCoursepackBinding).apply {
//图标默认全部隐藏
imageViewTypeDiscern.visibility = View.GONE
imageViewTypeSpell.visibility = View.GONE
imageViewTypeVoice.visibility = View.GONE
coursePack = getItem(position).also { cp ->
when (vm.projectId) {
Constants.SUBJECT_ENGLISH -> initEnglish(this, cp, position)
Constants.SUBJECT_CHINESE -> initChinese(this, cp, position)
}
root.click {
onItemClick.invoke(it, position, cp)
}
}
executePendingBindings()
}
}

//英语item初始化
private fun initEnglish(binding: MainItemCoursepackBinding, coursePack: CoursePack, position: Int) {
binding.apply {
//进度
myRankingView.visibility = View.GONE
//图标
coursePack.childrenCourses.forEach {
when(it.courseType){
Constants.COURSE_TYPE_ENGLISH_DISCERN -> imageViewTypeDiscern.visibility = View.VISIBLE
Constants.COURSE_TYPE_ENGLISH_SPELL -> imageViewTypeSpell.visibility = View.VISIBLE
Constants.COURSE_TYPE_ENGLISH_VOICE -> {
imageViewTypeVoice.run {
visibility = View.VISIBLE
setImageResource(R.drawable.ic_voice)
}
}
Constants.COURSE_TYPE_ENGLISH_SPOKEN -> {
imageViewTypeVoice.run{
visibility = View.VISIBLE
setImageResource(R.drawable.ic_spoken)
}
}
}
}
}
}

//作文item初始化
private fun initChinese(binding: MainItemCoursepackBinding, coursePack: CoursePack, position: Int) {
binding.apply {
//进度
myRankingView.visibility = View.VISIBLE
}
}


}

+ 50
- 49
app/src/main/java/com/xkl/cdl/data/Constants.kt View File

@@ -5,60 +5,61 @@ package com.xkl.cdl.data
* create 2022/3/18 9:34
* Describe:
*/
object Constants{
/** 项目: 英语 */
const val SUBJECT_ENGLISH = 3L
/** 项目: 语文*/
const val SUBJECT_CHINESE = 1L
object Constants {
/** 项目: 英语 */
const val SUBJECT_ENGLISH = 3
/** 项目: 语文*/
const val SUBJECT_CHINESE = 1

/**课程包类型: categoryId -> 英语单词课程 */
const val COURSEPACK_TYPE_ENGLISH_WORD = 1
/**课程包类型: categoryId -> 英语音标课程 */
const val COURSEPACK_TYPE_ENGLISH_SOUNDMARK= 4
/**课程包类型: categoryId -> 英语口语 */
const val COURSEPACK_TYPE_ENGLISH_SPOKEN = 13
/**课程包类型: categoryId -> 语文作文 */
const val COURSEPACK_TYPE_CHINESE_COMPOSITION = 2
/**课程包类型: categoryId -> 语文识字 */
const val COURSEPACK_TYPE_CHINESE_LITERACY = 3
/**课程包类型: categoryId -> 语文拼音 */
const val COURSEPACK_TYPE_CHINESE_PINYIN = 1
/**课程包类型: categoryId -> 英语单词课程 */
const val COURSEPACK_TYPE_ENGLISH_WORD = 1
/**课程包类型: categoryId -> 英语音标课程 */
const val COURSEPACK_TYPE_ENGLISH_SOUNDMARK = 4
/**课程包类型: categoryId -> 英语口语 */
const val COURSEPACK_TYPE_ENGLISH_SPOKEN = 13
/**课程包类型: categoryId -> 语文作文 */
const val COURSEPACK_TYPE_CHINESE_COMPOSITION = 2
/**课程包类型: categoryId -> 语文识字 */
const val COURSEPACK_TYPE_CHINESE_LITERACY = 3
/**课程包类型: categoryId -> 语文拼音 */
const val COURSEPACK_TYPE_CHINESE_PINYIN = 1


/**课程类型: typeId 英语认读*/
const val COURSE_TYPE_ENGLISH_DISCERN = 1
/**课程类型: typeId 英语辨音*/
const val COURSE_TYPE_ENGLISH_VOICE= 2
/**课程类型: typeId 英语拼写*/
const val COURSE_TYPE_ENGLISH_SPELL = 3
/**课程类型: typeId 英语音标*/
const val COURSE_TYPE_ENGLISH_SOUNDMARK = 6
/**课程类型: typeId 英语口语*/
const val COURSE_TYPE_ENGLISH_SPOKEN = 8
/**课程类型: typeId 语文作文*/
const val COURSE_TYPE_CHINESE_COMPOSITION = 4
/**课程类型: typeId 语文识字*/
const val COURSE_TYPE_CHINESE_LITERACY = 5
/**课程类型: typeId 语文拼音*/
const val COURSE_TYPE_CHINESE_PINYIN = 7
/**课程类型: typeId 英语认读*/
const val COURSE_TYPE_ENGLISH_DISCERN = 1
/**课程类型: typeId 英语辨音*/
const val COURSE_TYPE_ENGLISH_VOICE = 2
/**课程类型: typeId 英语拼写*/
const val COURSE_TYPE_ENGLISH_SPELL = 3
/**课程类型: typeId 英语音标*/
const val COURSE_TYPE_ENGLISH_SOUNDMARK = 6
/**课程类型: typeId 英语口语*/
const val COURSE_TYPE_ENGLISH_SPOKEN = 8
/**课程类型: typeId 语文作文*/
const val COURSE_TYPE_CHINESE_COMPOSITION = 4
/**课程类型: typeId 语文识字*/
const val COURSE_TYPE_CHINESE_LITERACY = 5
/**课程类型: typeId 语文拼音*/
const val COURSE_TYPE_CHINESE_PINYIN = 7


/**课时类型: 作文视频*/
const val COMPOSITION_TYPE_VIDEO = 1 //视频
/**课时类型: 作文知识点学习*/
const val COMPOSITION_TYPE_KNOWLEDGE = 2 //知识点学习
/**课时类型: 作文知识点测试*/
const val COMPOSITION_TYPE_EXAM = 3 //知识点测试
/**课时类型: 作文课堂练习*/
const val COMPOSITION_TYPE_READING = 4 //课堂练习
/**课时类型: 作文课外练习*/
const val COMPOSITION_TYPE_TASK = 5 //课外练习

/**课时类型: 口语词汇*/
const val SPOKEN_LESSON_TYPE_WORD = 1
/**课时类型: 口语句型*/
const val SPOKEN_LESSON_TYPE_SENTENCE = 2
/**课时类型: 口语对话*/
const val SPOKEN_LESSON_TYPE_DIALOGUE = 3
/**课时类型: 词汇, 口语词汇*/
const val LESSON_TYPE_WORD = 1
/**课时类型: 口语句型*/
const val LESSON_TYPE_SENTENCE = 2
/**课时类型: 口语对话*/
const val LESSON_TYPE_DIALOGUE = 3

/**课时类型: 作文视频*/
const val COMPOSITION_TYPE_VIDEO = 4 //视频
/**课时类型: 作文知识点学习*/
const val COMPOSITION_TYPE_KNOWLEDGE = 5 //知识点学习
/**课时类型: 作文知识点测试*/
const val COMPOSITION_TYPE_EXAM = 6 //知识点测试
/**课时类型: 作文课堂练习*/
const val COMPOSITION_TYPE_READING = 7 //课堂练习
/**课时类型: 作文课外练习*/
const val COMPOSITION_TYPE_TASK = 8 //课外练习

}

+ 19
- 0
app/src/main/java/com/xkl/cdl/data/bean/Lesson.kt View File

@@ -0,0 +1,19 @@
package com.xkl.cdl.data.bean

/**
* author suliang
* create 2022/3/21 17:10
* Describe:
*/
data class Lesson(
val subjectId: Long,
val coursePackId:Long,
val courseId:Long,
val chapterId:Long,
val chapterName: String,
val lessonId:Long,
val lessonName:String
) {
/** 课时类型 */
var lessonType : Int = 0
}

+ 89
- 0
app/src/main/java/com/xkl/cdl/data/binding/BindingAdapter.kt View File

@@ -0,0 +1,89 @@
package com.xkl.cdl.data.binding

import android.graphics.BitmapFactory
import android.graphics.drawable.Drawable
import android.widget.ImageView
import android.widget.TextView
import androidx.core.graphics.drawable.DrawableCompat
import androidx.databinding.BindingAdapter
import com.suliang.common.util.DrawableUti
import com.suliang.common.util.image.ImageLoader
import com.suliang.common.util.image.ImageLoaderOption
import com.xkl.cdl.R
import kotlinx.coroutines.NonCancellable.start
import org.w3c.dom.Text
import java.math.BigDecimal
import java.math.RoundingMode
import java.text.DecimalFormat

/**
* author suliang
* create 2022/3/23 11:43
* Describe:
* 如果您希望在设置了任意属性时调用适配器,则可以将适配器的可选 requireAll 标志设置为 false
*/
object BindingAdapter {
/**
* 设置图片
* @param view ImageView
* @param imageByteArray ByteArray 字节数组
*/
//https://stackoverflow.com/questions/60264081/in-android-how-databinding-with-byte-array
@BindingAdapter("imageByteArray")
@JvmStatic
fun imageByteArray(view:ImageView,imageByteArray:ByteArray){
ImageLoader.loadImage(view,imageByteArray)
// view.setImageBitmap(BitmapFactory.decodeByteArray(imageByteArray, 0, imageByteArray.size))
}

@BindingAdapter(value = ["imgByteArray","blur"])
@JvmStatic
fun imageByteArray(view:ImageView,imgByteArray:ByteArray,blur:Boolean){
ImageLoader.loadImage(ImageLoaderOption().apply {
targetView = view
byteArray = imgByteArray
blurEanble = blur
})
}

/**
* 改变drawable颜色,只能有一个方向的才可以,多个方向的未验证
* @param view TextView
* @param color Int
*/
@BindingAdapter("svgColor")
@JvmStatic
fun loadVectorDrawable(view:TextView,svgColor:Int){
view.compoundDrawables.forEachIndexed{ index: Int, drawable: Drawable? ->
when(index){
0 -> drawable?.let { view.setCompoundDrawablesWithIntrinsicBounds(DrawableUti.tintDrawable(it,svgColor),null,null,null) }
1 -> drawable?.let { view.setCompoundDrawablesWithIntrinsicBounds(null,DrawableUti.tintDrawable(it,svgColor),null,null) }
2 -> drawable?.let { view.setCompoundDrawablesWithIntrinsicBounds(null,null,DrawableUti.tintDrawable(it,svgColor),null) }
3 -> drawable?.let { view.setCompoundDrawablesWithIntrinsicBounds(null,null,null,DrawableUti.tintDrawable(it,svgColor),) }
}
}
}

/**
* 课程包显示时的进度格式化
* @param view TextView
* @param learnProgress Double
*/
@BindingAdapter("coursePackLearnProgressFormat")
@JvmStatic
fun coursePackLearnProgressFormat(view: TextView,learnProgress:Double){
view.text = when{
learnProgress == 0.0 -> view.resources.getString(R.string.start)
learnProgress < 0.1 -> "0.1%"
learnProgress < 100 -> {
DecimalFormat("#.#%").apply {
roundingMode = RoundingMode.DOWN
}.format(BigDecimal(learnProgress))
}
else -> "100%"
}
}



}

+ 32
- 0
app/src/main/java/com/xkl/cdl/data/course/Course.kt View File

@@ -0,0 +1,32 @@
package com.xkl.cdl.data.course

import com.xkl.cdl.data.bean.Lesson

/**
* author suliang
* create 2022/3/22 10:08
* Describe: 课程类
* @property subjectId Int 项目类型Id
* @property coursePackId Long 课程包Id
* @property coursePackType Int 课程包的类型
* @property courseId Long 课程id
* @property sort Int 排序
* @property courseType Int 课程类型
* @property totalWords Int 总词量
* @property dbPathName String 数据库的名称
* @property allLesson ArrayList<Lesson>? 所有课时
* @constructor
*/
data class Course(
val subjectId: Int,
val coursePackId: Long,
val coursePackType: Int,
val courseId: Long,
val courseType: Int,
val totalWords: Int,
val dbPathName: String
) {
var allLesson: ArrayList<Lesson>? = null
var courseLearnProgress : Double = 0.0 //课程学习进度
var courseReviewNumber : Int = 0 //课程复习数
}

+ 60
- 2
app/src/main/java/com/xkl/cdl/data/course/CoursePack.kt View File

@@ -2,10 +2,68 @@ package com.xkl.cdl.data.course

import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
import com.xkl.cdl.BR

/**
* author suliang
* create 2022/3/17 18:12
* Describe:
* Describe: 课程包
* @property coursePackId Long 课程包id
* @property coursePackName String 课程包名称
* @property cover ByteArray 课程包图片
* @property summary String 简介
* @property subjectId Int 项目Id
* @property coursePackType Int 课程包类型
* @property childrenCourses mutableListOf<Course>() 所有子课程包
* @constructor
*/
data class CoursePack(@Bindable var id : Long) : BaseObservable()
data class CoursePack(
val coursePackId: Long,
val coursePackName: String,
val cover: ByteArray,
val summary: String,
val subjectId: Int,
val coursePackType: Int,
) : BaseObservable() {

var childrenCourses = mutableListOf<Course>()

//学习进度(主要针对与语文课程显示,英语课程不显示)
@get:Bindable
var learnProgress: Double = 0.0
set(value) {
field = value
notifyPropertyChanged(BR.learnProgress)
}


override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as CoursePack

if (coursePackId != other.coursePackId) return false
if (coursePackName != other.coursePackName) return false
if (!cover.contentEquals(other.cover)) return false
if (summary != other.summary) return false
if (subjectId != other.subjectId) return false
if (coursePackType != other.coursePackType) return false
if (childrenCourses != other.childrenCourses) return false

return true
}

override fun hashCode(): Int {
var result = coursePackId.hashCode()
result = 31 * result + coursePackName.hashCode()
result = 31 * result + cover.contentHashCode()
result = 31 * result + summary.hashCode()
result = 31 * result + subjectId
result = 31 * result + coursePackType
result = 31 * result + childrenCourses.hashCode()
return result
}


}

+ 76
- 2
app/src/main/java/com/xkl/cdl/data/manager/CourseManager.kt View File

@@ -1,6 +1,9 @@
package com.xkl.cdl.data.manager

import com.suliang.common.util.file.FileUtil
import com.xkl.cdl.data.course.Course
import com.xkl.cdl.data.course.CoursePack
import java.io.File

/**
* author suliang
@@ -9,10 +12,81 @@ import com.xkl.cdl.data.course.CoursePack
*/
object CourseManager {

//所有课程
/** 项目对应的课程包 */
val subjectWithCoursePackMap = hashMapOf<Int, List<CoursePack>>()

/**
* 获取对应项目的课程数量
* @param subjectId Int 项目
* @return Int 课程数量
*/
fun getSubjectForCourseSize(subjectId: Int): Int {
return subjectWithCoursePackMap[subjectId]?.let {
var count = 0
it.forEach { coursePack ->
count += coursePack.childrenCourses?.size ?: 0
}
count
} ?: 0
}

/**
* 搜索项目下的课程包
* @param subjectId Int 项目id
* @param searchValue String? 搜索内容,如果为空,则为全搜索
* @return MutableList<CoursePack>? 结果值
*/
@Synchronized
fun filterSearchCoursePack(subjectId: Int, searchValue: String?): MutableList<CoursePack>? {
return if (searchValue.isNullOrEmpty() || searchValue.trim().isEmpty()) {
subjectWithCoursePackMap[subjectId]?.toMutableList()
} else {
subjectWithCoursePackMap[subjectId]?.let {
val result = mutableListOf<CoursePack>()
it.forEach { coursePack ->
if (coursePack.coursePackName.contains(searchValue)) {
result.add(coursePack)
}
}
result
}
}
}

private var allCoursePack : HashMap<Long, CoursePack> = HashMap()
/**
* 根据subjectId,courseId 获取CoursePack
* @param subjectId Int 项目Id
* @param coursePackId Long 课程包Id
* @return CoursePack? 课程包
*/
@Synchronized
fun getCoursePackWithCoursePackId(subjectId: Int, coursePackId: Long): CoursePack? {
return subjectWithCoursePackMap[subjectId]?.let {
//返回很符合条件的第一个元素
it.firstOrNull {
it.coursePackId == coursePackId
}
}
}

/***
* 检查课程包下课程的db数据文件是否存在,不存在,则复制过去
*/
fun checkCourseDb() {
subjectWithCoursePackMap.forEach{entry ->
entry.value.forEach{ coursePack ->
coursePack.childrenCourses.forEach {
val file = File(
FileUtil.getSaveDirPath("db"),
"${entry.key}/${coursePack.coursePackId}/${it.courseId}/${it.dbPathName}"
)
//不存在,复制,存在则不操作
if (!file.exists()){
FileUtil.copyAsset(it.dbPathName,file)
}
}
}
}

}
}

+ 10
- 0
app/src/main/java/com/xkl/cdl/data/manager/db/CompositionDbManager.kt View File

@@ -0,0 +1,10 @@
package com.xkl.cdl.data.manager.db

/**
* author suliang
* create 2022/3/21 16:10
* Describe: 作文数据库
*/
object CompositionDbManager : IDB {

}

+ 27
- 0
app/src/main/java/com/xkl/cdl/data/manager/db/DBCourseManager.kt View File

@@ -0,0 +1,27 @@
package com.xkl.cdl.data.manager.db

import com.xkl.cdl.data.Constants
import com.xkl.cdl.data.bean.Lesson

/**
* author suliang
* create 2022/3/21 10:41
* Describe: 课程数据库管理类,主要用于读取数据
*/
object DBCourseManager {

fun getDb(subjectId:Int,coursePackId:Long,courseId:Long):IDB{
return if (subjectId == Constants.SUBJECT_ENGLISH){
EnglishDbManager
}else{
CompositionDbManager
}
}







}

+ 82
- 0
app/src/main/java/com/xkl/cdl/data/manager/db/DbCoursePackManager.kt View File

@@ -0,0 +1,82 @@
package com.xkl.cdl.data.manager.db

import com.suliang.common.util.file.FileUtil
import com.xkl.cdl.data.Constants
import com.xkl.cdl.data.course.Course
import com.xkl.cdl.data.course.CoursePack
import com.xkl.cdl.data.manager.CourseManager
import net.sqlcipher.database.SQLiteDatabase
import net.sqlcipher.database.SQLiteDatabase.OPEN_READONLY
import java.io.File

/**
* author suliang
* create 2022/3/22 9:51
* Describe: 课程包数据库管理类
* 本来想用单例的,但感觉这个类只会在启动的时候使用一次,没有必要使用全局单例
*/
class DbCoursePackManager {

private val database: SQLiteDatabase

init {
val coursePackDbPath = FileUtil.getSaveDirPath("db") + File.separator + "course-pack.db"
database = SQLiteDatabase.openDatabase(coursePackDbPath, "XUEKAOLE_COURSE_LIST_KEY", null, OPEN_READONLY)
}

/***
* 查询用户绑定的课程包
* @param coursePackIds String 课程包id集合如: 1,2,3,4,5 最后不能有逗号
* @return Boolean 查询是否成功
*/
fun queryBindingCoursePack(coursePackIds: String): Boolean {
//英语课程包
val englishCoursePack = mutableListOf<CoursePack>()
//语文课程包
val chineseCoursePack = mutableListOf<CoursePack>()

//查询所有课程包
val coursePackSql = "SELECT * FROM course_pack WHERE id in ($coursePackIds)"
val coursePackCursor = database.rawQuery(coursePackSql, null)
coursePackCursor.let {
while (it.moveToNext()) {
val subjectId = it.getInt(4)
val coursePackId = it.getLong(0)
val coursePackType = it.getInt(12)
val coursePack = CoursePack(
coursePackId, it.getString(1), it.getBlob(2), it.getString(3), subjectId, coursePackType
).apply {
//查询对应的课程
val courseSql =
"SELECT * FROM course_basic WHERE id in (SELECT course_id FROM course_pack_item WHERE course_pack_id = $coursePackId) ORDER by sort ASC"
database.rawQuery(courseSql, null).let { cursor ->
while (cursor.moveToNext()) {
val course = Course(
subjectId,
coursePackId,
coursePackType,
cursor.getLong(0),
cursor.getInt(8),
cursor.getInt(9),
cursor.getString(10)
)
//课程包添加课程
childrenCourses.add(course)
}
cursor.close()
}
}
if (subjectId == Constants.SUBJECT_ENGLISH) {
englishCoursePack.add(coursePack)
} else {
chineseCoursePack.add(coursePack)
}
}
it.close()
}
CourseManager.subjectWithCoursePackMap[Constants.SUBJECT_ENGLISH] = englishCoursePack.toList()
CourseManager.subjectWithCoursePackMap[Constants.SUBJECT_CHINESE] = chineseCoursePack.toList()
database.close()
return true
}
}

+ 11
- 0
app/src/main/java/com/xkl/cdl/data/manager/db/EnglishDbManager.kt View File

@@ -0,0 +1,11 @@
package com.xkl.cdl.data.manager.db

/**
* author suliang
* create 2022/3/21 16:09
* Describe: 英语数据库
*/
object EnglishDbManager : IDB {


}

+ 32
- 0
app/src/main/java/com/xkl/cdl/data/manager/db/IDB.kt View File

@@ -0,0 +1,32 @@
package com.xkl.cdl.data.manager.db

/**
* author suliang
* create 2022/3/21 16:34
* Describe: 英语数据库接口
*/
interface IDB {


// fun open(subjectId:Long,coursePackId:Long,courseId:Long)
//
// /**获取该课程的所有课时*/
// fun queryAllLesson()
//
// /** 获取总测试的数据 */
// fun queryTotalTest(coursePackId: Long,courseId: Long)
// /** 获取课时测试数据 */
// fun queryLessonTest(coursePackId: Long,courseId: Long,chapterId:Long,lessonId:Long,courseType:Long)
// /** 获取指定课时(指定数据源wordIds)的随机个数测试数据 */
// fun queryLessonTestRandomSize(coursePackId: Long,courseId: Long,chapterId: Long,lessonId: Long,courseType: Long,limitSize:Int,wordIds:ArrayList<Long>? = null)
//
// /** 获取课时学习内容 */
// fun queryLessonLearnData(coursePackId: Long,courseId: Long,chapterId: Long,lessonId: Long,wordIds:ArrayList<Long>)
//
// /** 获取课程今日需要复习的数据 */
// fun queryCourseReviewDatas(coursePackId: Long,courseId: Long,wordIds: ArrayList<Long>)




}

+ 5
- 1
app/src/main/java/com/xkl/cdl/module/XKLApplication.kt View File

@@ -1,6 +1,9 @@
package com.xkl.cdl.module

import android.app.Application
import com.suliang.common.util.LogUtil
import net.sqlcipher.database.SQLiteDatabase
import java.util.*

/**
* author suliang
@@ -11,8 +14,9 @@ class XKLApplication : Application() {

override fun onCreate() {
super.onCreate()
SQLiteDatabase.loadLibs(this)
// ImageLoader.mStrategy = GlideLoaderStrategy()
LogUtil.e(UUID.randomUUID().toString().replace("-",""))


}

+ 15
- 0
app/src/main/java/com/xkl/cdl/module/m_center_learn/CourseMainActivity.kt View File

@@ -0,0 +1,15 @@
package com.xkl.cdl.module.m_center_learn

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.xkl.cdl.R

/**
* 课程中心
*/
class CourseMainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_course_main)
}
}

+ 13
- 0
app/src/main/java/com/xkl/cdl/module/m_center_learn/CourseMainActivityViewModel.kt View File

@@ -0,0 +1,13 @@
package com.xkl.cdl.module.m_center_learn

import com.suliang.common.base.viewmodel.BaseViewModel
import com.xkl.cdl.data.course.Course

/**
* author suliang
* create 2022/3/24 17:42
* Describe: 课程主页
*/
class CourseMainActivityViewModel(val course: Course) : BaseViewModel() {

}

+ 46
- 12
app/src/main/java/com/xkl/cdl/module/m_center_learn/CoursePackFragment.kt View File

@@ -1,11 +1,17 @@
package com.xkl.cdl.module.m_center_learn

import android.os.Bundle
import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.suliang.common.Config
import com.suliang.common.base.fragment.BaseFragmentVM
import com.suliang.common.util.LogUtil
import com.xkl.cdl.R
import com.xkl.cdl.data.course.CoursePack
import com.xkl.cdl.databinding.FragmentCoursePackBinding
import com.xkl.cdl.module.main.MainActivityViewModel

@@ -15,43 +21,71 @@ import com.xkl.cdl.module.main.MainActivityViewModel
*/
class CoursePackFragment : BaseFragmentVM<FragmentCoursePackBinding,CoursePackFragmentViewModel>() {



companion object {
@JvmStatic
fun newInstance(projectId: Long) = CoursePackFragment().apply {
fun newInstance(projectId: Int) = CoursePackFragment().apply {
arguments = Bundle().apply {
putLong(Config.INTENT_1,projectId)
putInt(Config.INTENT_1,projectId)
}
}
}

override fun initViewModel(): CoursePackFragmentViewModel {
val projectId = requireArguments().getLong(Config.INTENT_1)
val projectId = requireArguments().getInt(Config.INTENT_1)
return ViewModelProvider(this,ViewModelFactory(projectId)).get(CoursePackFragmentViewModel::class.java).apply {
this.mainActivityViewModel = ViewModelProvider(requireActivity()).get(MainActivityViewModel::class.java)
this.learnCenterFragmentViewModel = ViewModelProvider(requireParentFragment()).get(LearnCenterFragmentViewModel::class.java)
}
}


override fun initFragment() {

vm.learnCenterFragmentViewModel.mCoursePackSearchLiveData.observe(this){
LogUtil.e("课程包列表监听到搜索内容改变: ${vm.projectId}")
//禁止自动加载
binding.contentLayout.refreshLayout.setEnableLoadMore(false)
binding.contentLayout.refreshLayout.setOnRefreshListener{
//刷新调用搜索
search(vm.learnCenterFragmentViewModel.mCoursePackSearchValueMap[vm.projectId])
}
//recyclerView初始化
binding.contentLayout.recyclerView.apply {
layoutManager = LinearLayoutManager(requireContext(),LinearLayoutManager.VERTICAL,false)
adapter = vm.adpater.apply {
onItemClick = { v,position,courePack ->

}
}
//分割线
addItemDecoration(DividerItemDecoration(requireContext(),DividerItemDecoration.VERTICAL).apply {
setDrawable(ResourcesCompat.getDrawable(resources, R.drawable.item_decoration_only_maring_8,null)!!)
})
//动画
itemAnimator = null
}
//数据监听
vm.mDataLiveData.observe(this){
binding.contentLayout.refreshLayout.finishRefresh()
vm.adpater.needShowEmptyView = it == null || it.size == 0
vm.adpater.setData(it)
}
}

/**加载数据*/
override fun loadData() {

println("load ${vm.projectId} ")
binding.contentLayout.refreshLayout.autoRefresh()
}


/** 匹配搜索课程包 */
fun search(searchValue: String?) {
LogUtil.e("课程包列表监听到搜索内容改变: ${vm.projectId} $searchValue")
//如果不是resume 则直接返回
if (lifecycle.currentState != Lifecycle.State.RESUMED) return
vm.loadCoursePack(searchValue)
}


/** ViewModle 工厂类,提供传参 */
inner class ViewModelFactory(private val projectId:Long): ViewModelProvider.Factory{
inner class ViewModelFactory(private val projectId:Int): ViewModelProvider.Factory{
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return CoursePackFragmentViewModel(projectId) as T

+ 27
- 3
app/src/main/java/com/xkl/cdl/module/m_center_learn/CoursePackFragmentViewModel.kt View File

@@ -1,9 +1,33 @@
package com.xkl.cdl.module.m_center_learn

import androidx.lifecycle.MutableLiveData
import com.suliang.common.base.viewmodel.BaseViewModel
import com.xkl.cdl.adapter.AdapterCoursePackWithLearCenter
import com.xkl.cdl.data.course.CoursePack
import com.xkl.cdl.data.manager.CourseManager
import com.xkl.cdl.module.main.MainActivityViewModel

class CoursePackFragmentViewModel(val projectId : Long) : BaseViewModel() {
lateinit var mainActivityViewModel: MainActivityViewModel
lateinit var learnCenterFragmentViewModel: LearnCenterFragmentViewModel
/**
* 课程包的ViewModel包含父Fragmnet和Activity的ViewModel
* @property projectId Long
* @property mainActivityViewModel MainActivityViewModel
* @property learnCenterFragmentViewModel LearnCenterFragmentViewModel
* @constructor
*/
class CoursePackFragmentViewModel(val projectId: Int) : BaseViewModel() {
lateinit var mainActivityViewModel: MainActivityViewModel
lateinit var learnCenterFragmentViewModel: LearnCenterFragmentViewModel
//列表适配器
val adpater = AdapterCoursePackWithLearCenter(this)

//课程包
val mDataLiveData = MutableLiveData<MutableList<CoursePack>?>()

/**
* 加载对应的课程包资源
* @param searchValue String? 搜素的内容,没有内容,则为全部显示
*/
fun loadCoursePack(searchValue: String?) {
mDataLiveData.value = CourseManager.filterSearchCoursePack(projectId, searchValue)
}
}

+ 24
- 2
app/src/main/java/com/xkl/cdl/module/m_center_learn/LearnCenterFragment.kt View File

@@ -2,7 +2,6 @@ package com.xkl.cdl.module.m_center_learn

import android.graphics.Typeface
import android.util.TypedValue
import android.view.LayoutInflater
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModelProvider
@@ -34,6 +33,7 @@ class LearnCenterFragment : BaseFragmentVM<FragmentLearnCenterBinding, LearnCent
override fun initFragment() {
//初始化项目子tab和标题
vm.initLearnCenterFragment()
binding.viewModel = vm

//必须先进行ViewPager2的适配器 设置
binding.viewPager2.adapter = ViewPagerAdapter(this, vm.mProjectFragments)
@@ -51,6 +51,17 @@ class LearnCenterFragment : BaseFragmentVM<FragmentLearnCenterBinding, LearnCent
}
//选中项恢复搜索框的数据
tab?.run {
vm.mCoursePackSearchValueMap[position]?.let {
binding.editTextSearch.apply {
setText(it)
setSelection(it.length)
}

}?:let { //为空,如果输入框也为空,则不操作,否则设置为null
if (!binding.editTextSearch.text.isNullOrEmpty()) {
binding.editTextSearch.setText("")
}
}


}
@@ -80,7 +91,18 @@ class LearnCenterFragment : BaseFragmentVM<FragmentLearnCenterBinding, LearnCent
}
}.attach()


//监听到搜索框的数据变化
vm.mCoursePackSearchLiveData.observe(this){
binding.viewPager2.currentItem.let { currentItem ->
println("search change ${currentItem} $it ")
//如果取值不相等,则调用显示的Fragment进行搜索,并重新赋值
if (vm.mCoursePackSearchValueMap[currentItem] != it){
val coursePackFragment = (binding.viewPager2.adapter as ViewPagerAdapter).getItem(currentItem) as CoursePackFragment
coursePackFragment.search(it)
vm.mCoursePackSearchValueMap[currentItem] = it
}
}
}

// /** 搜索框 输入监听 */
// binding.editTextSearch.setOnEditorActionListener { v, actionId, event ->

+ 4
- 1
app/src/main/java/com/xkl/cdl/module/m_center_learn/LearnCenterFragmentViewModel.kt View File

@@ -20,8 +20,11 @@ class LearnCenterFragmentViewModel : BaseViewModel() {
var mProjectFragments = mutableListOf<Fragment>()
/** 学习中心 子 tab title **/
var mProjectTitles = mutableListOf<Int>()
/** 搜索框的双向数据 */

/** 搜索框数据的双向绑定 */
val mCoursePackSearchLiveData = MutableLiveData<String>()
//保存对应子fragment的搜索内容
val mCoursePackSearchValueMap = hashMapOf<Int,String>()


/** 实例化项目课程包Fragment和显示Title */

+ 24
- 21
app/src/main/java/com/xkl/cdl/module/splash/SplashActivity.kt View File

@@ -2,25 +2,14 @@ package com.xkl.cdl.module.splash

import android.annotation.SuppressLint
import android.os.Bundle
import android.os.Looper
import android.view.View
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.bumptech.glide.load.resource.bitmap.CircleCrop
import com.suliang.common.base.activity.BaseActivity
import com.suliang.common.eventbus.LiveDataBus
import com.suliang.common.eventbus.NonStickyMutableLiveData
import com.suliang.common.util.LogUtil
import com.suliang.common.util.image.ImageLoader
import com.suliang.common.util.image.ImageLoaderOption
import com.suliang.common.util.media.*
import com.suliang.common.util.file.FileUtil
import com.suliang.common.util.thread.AppExecutors
import com.xkl.cdl.R
import com.xkl.cdl.data.manager.CourseManager
import com.xkl.cdl.data.manager.db.DbCoursePackManager
import com.xkl.cdl.databinding.ActivitySplashBinding
import com.xkl.cdl.module.main.MainActivity
import com.xkl.cdl.module.main.MainActivityViewModel
import java.util.Calendar.SECOND
import java.io.File
import java.util.concurrent.TimeUnit

@SuppressLint("CustomSplashScreen")
@@ -39,13 +28,27 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>() {
}

override fun loadData() {
//定时跳跃到住主界面
AppExecutors.scheduledExecutor.schedule({
AppExecutors.mainThread.execute {
startActivity(MainActivity::class.java)
finish()
showHideLoading(true)
AppExecutors.io.execute {
//数据文件是否存在
val coursePackFile = File(FileUtil.getSaveDirPath("db"), "course-pack.db")
if (!coursePackFile.exists()) {
val copyAssetResult = FileUtil.copyAsset("course-pack.db", coursePackFile)
}
}, 2, TimeUnit.SECONDS)
//读取课程数据
// TODO: 2022/3/22 读取当前app绑定的课程数据,
DbCoursePackManager().queryBindingCoursePack("262,261,264")
//复制课程的数据库到对应位置
CourseManager.checkCourseDb()
//定时跳跃到住主界面
AppExecutors.scheduledExecutor.schedule({
AppExecutors.mainThread.execute {
showHideLoading(false)
startActivity(MainActivity::class.java)
finish()
}
}, 1, TimeUnit.SECONDS)
}
}



+ 134
- 0
app/src/main/java/com/xkl/cdl/widget/MyRankingView.java View File

@@ -0,0 +1,134 @@
package com.xkl.cdl.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.widget.LinearLayout;

import androidx.annotation.Nullable;

import com.suliang.common.util.os.ScreenUtil;
import com.xkl.cdl.R;


/**
* Description: 可控制的原型进度布局
*/
public class MyRankingView extends LinearLayout {

private Context mContext;
private Paint mPaint, mProgressPaint, backgroundPaint;
private int defaultSize = 0;
private float progressWidth = 8; //圆弧进度条宽度
private RectF mRectF; //绘制圆弧的矩形区域

private float progress; //进度
private int normalColor; //默认弧形颜色
private int progressColor; //进度弧形颜色
private int backgroundColor; //背景颜色


public MyRankingView(Context context) {
this(context, null);
}

public MyRankingView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}

public MyRankingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
TypedArray ta = mContext.obtainStyledAttributes(attrs, R.styleable.MyRankingView);
normalColor = ta.getColor(R.styleable.MyRankingView_normalColor, -2);
progressColor = ta.getColor(R.styleable.MyRankingView_progressColor, -2);
progressWidth = ta.getDimensionPixelOffset(R.styleable.MyRankingView_borderWidth, -2);
progress = ta.getInteger(R.styleable.MyRankingView_progressNumber, -2);
backgroundColor = ta.getColor(R.styleable.MyRankingView_backgroundColor, -2);
if (progress != -2) {
progress = progress / 100 * 360;
}
ta.recycle();
initView();
}


private void initView() {
defaultSize = ScreenUtil.INSTANCE.dp2px(122); //默认大小

mRectF = new RectF();
if (normalColor != -2 && progressWidth != -2) {
mPaint = getPaint(normalColor, Paint.Style.STROKE);
}
if (progressColor != -2 && progressWidth != -2) {
mProgressPaint = getPaint(progressColor, Paint.Style.STROKE);
}
if (backgroundColor != -2) {
backgroundPaint = getPaint(backgroundColor, Paint.Style.FILL);
}
}

private Paint getPaint(int color, Paint.Style style) {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStrokeWidth(progressWidth);
paint.setStyle(style);
paint.setColor(color);
paint.setAntiAlias(true);
return paint;
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = measureSize(heightMeasureSpec);
int width = measureSize(widthMeasureSpec);
int min = Math.min(height, width); //获取最短边
setMeasuredDimension(min, min); //修改大小
if (min >= progressWidth * 2) { //简单限制圆弧的最大宽度
mRectF.set(progressWidth / 2, progressWidth / 2, min - progressWidth / 2, min - progressWidth / 2);
}
}

private int measureSize(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) { //明确大小
result = specSize;
} else {
result = defaultSize;
}
return result;
}

@Override
protected void dispatchDraw(Canvas canvas) {
if (backgroundColor != -2) {
canvas.drawArc(mRectF, 0, 360, false, backgroundPaint);
}
/*startAngel: 0为12小时,3点钟的位置 sweepAngle:为扫描多少度*/
if (normalColor != -2 && progressWidth != -2) {
canvas.drawArc(mRectF, 0, 360, false, mPaint);
}
if (progressColor != -2 && progressWidth != -2 && progress > 0) {
canvas.drawArc(mRectF, -90, progress, false, mProgressPaint);
}
//放在super前是后景,相反是前景,前景会覆盖子布局
super.dispatchDraw(canvas);
}

/**
* 设置进度
*
* @param progresss 进度
*/
public void setProgressNumber(float progresss) {
this.progress = progresss / 100 * 360;
postInvalidate();
}


}

+ 39
- 0
app/src/main/java/com/xkl/cdl/widget/smartRefresh/BaseRefreshHeader.kt View File

@@ -0,0 +1,39 @@
package com.xkl.cdl.widget.smartRefresh

import com.scwang.smart.refresh.layout.api.RefreshHeader
import com.scwang.smart.refresh.layout.api.RefreshKernel
import com.scwang.smart.refresh.layout.api.RefreshLayout

/**
* author suliang
* create 2022/3/23 9:29
* Describe: 实现自定义刷新头部的基类
*/
interface BaseRefreshHeader : RefreshHeader {


override fun setPrimaryColors(vararg colors: Int) {

}

override fun onInitialized(kernel: RefreshKernel, height: Int, maxDragHeight: Int) {

}

override fun onMoving(isDragging: Boolean, percent: Float, offset: Int, height: Int, maxDragHeight: Int) {

}

override fun onReleased(refreshLayout: RefreshLayout, height: Int, maxDragHeight: Int) {

}


override fun onHorizontalDrag(percentX: Float, offsetX: Int, offsetMax: Int) {

}

override fun isSupportHorizontalDrag(): Boolean {
return false
}
}

+ 78
- 0
app/src/main/java/com/xkl/cdl/widget/smartRefresh/XkLRefreshHeader.kt View File

@@ -0,0 +1,78 @@
package com.xkl.cdl.widget.smartRefresh

import android.content.Context
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.view.setPadding
import com.airbnb.lottie.LottieAnimationView
import com.scwang.smart.refresh.layout.api.RefreshLayout
import com.scwang.smart.refresh.layout.constant.RefreshState
import com.scwang.smart.refresh.layout.constant.SpinnerStyle
import com.suliang.common.util.os.ScreenUtil
import com.xkl.cdl.R

/**
* author suliang
* create 2022/3/22 18:03
* Describe:
*/
class XkLRefreshHeader @JvmOverloads constructor(
context: Context, attributeSet: AttributeSet? = null, defaultStyle: Int = 0
) : LinearLayout(context, attributeSet, defaultStyle),BaseRefreshHeader {

init {
orientation = VERTICAL
gravity = Gravity.CENTER
setPadding(0,ScreenUtil.dp2px(8f),0,ScreenUtil.dp2px(8f))
LayoutInflater.from(context).inflate(R.layout.smart_refresh_header, this, true)
}


override fun getView(): View {
return this //真实的视图就是自己,不能返回null
}

override fun getSpinnerStyle(): SpinnerStyle {
return SpinnerStyle.Translate //指定为平移,不能null
}

//开始动画
override fun onStartAnimator(refreshLayout: RefreshLayout, height: Int, maxDragHeight: Int) {
view.findViewById<LottieAnimationView>(R.id.refreshImg).playAnimation()
}

override fun onFinish(refreshLayout: RefreshLayout, success: Boolean): Int {
view.findViewById<LottieAnimationView>(R.id.refreshImg).cancelAnimation()
view.findViewById<TextView>(R.id.refreshText).text = if (success) "刷新完成" else "刷新失败"
return 500 //延迟500毫秒之后再弹回
}


/**
* 状态控制
* @param refreshLayout RefreshLayout
* @param oldState RefreshState
* @param newState RefreshState
*/
override fun onStateChanged(refreshLayout: RefreshLayout, oldState: RefreshState, newState: RefreshState) {
when(newState){
RefreshState.PullDownToRefresh -> {
view.findViewById<TextView>(R.id.refreshText).text = "下拉开始刷新"
view.findViewById<LottieAnimationView>(R.id.refreshImg).pauseAnimation()
}
RefreshState.Refreshing -> {
view.findViewById<TextView>(R.id.refreshText).text = "正在刷新···"
}
RefreshState.ReleaseToRefresh -> {
view.findViewById<TextView>(R.id.refreshText).text = "释放立即刷新"
}
else -> {}
}
}


}

+ 6
- 0
app/src/main/res/drawable/item_decoration_only_maring_8.xml View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--item decoration 间距8 -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:height="8dp"/>
</shape>

+ 17
- 0
app/src/main/res/drawable/progress_center.xml View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape android:shape="rectangle">
<corners android:radius="2dp"/>
<solid android:color="@color/gray_1"/>
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape android:shape="rectangle">
<corners android:radius="2dp"/>
<solid android:color="@color/green_1"/>
</shape>
</clip>
</item>
</layer-list>

+ 6
- 0
app/src/main/res/drawable/shape_rounder_toplr_24_white.xml View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:topLeftRadius="24dp"
android:topRightRadius="24dp"/>
<solid android:color="@color/white"/>
</shape>

+ 142
- 0
app/src/main/res/layout/activity_course_main.xml View File

@@ -0,0 +1,142 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:bind="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<data>

<variable
name="coursePack"
type="com.xkl.cdl.data.course.CoursePack" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".module.m_center_learn.CourseMainActivity">

<!--模糊图-->
<ImageView
android:id="@+id/img_course_pack_cover_blur"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="matrix"
bind:imgByteArray="@{coursePack.cover}"
bind:blur= "@{false}"
app:layout_constraintBottom_toBottomOf="@+id/view_placeholder_1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:background="@color/gray_2" />

<!--状态栏-->
<View
android:id="@+id/status_bar_view"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<include
android:id="@+id/include_title_bar"
layout="@layout/include_title_bar"
app:layout_constraintTop_toBottomOf="@+id/status_bar_view"
bind:title="@string/course_detail" />

<View
android:id="@+id/view_placeholder_1"
android:layout_width="wrap_content"
android:layout_height="1dp"
android:layout_marginTop="124dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/include_title_bar" />

<!--全显图-->
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/img_course_pack_cover"
android:layout_width="75dp"
android:layout_height="100dp"
android:layout_marginStart="@dimen/global_spacing"
android:scaleType="fitXY"
app:imageByteArray="@{coursePack.cover}"
app:layout_constraintBottom_toTopOf="@+id/view_placeholder_1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/include_title_bar"
app:shapeAppearance="@style/roundedCornerStyle"
tools:background="@color/theme_color" />

<!--课程包名称-->
<TextView
android:id="@+id/text_view_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="@dimen/global_spacing"
android:text="@{coursePack.coursePackName}"
android:textColor="@color/main_text_color"
android:textSize="@dimen/normalSize"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/img_course_pack_cover"
app:layout_constraintTop_toTopOf="@+id/img_course_pack_cover"
tools:text="学考乐小学英语单词班" />

<!--简介-->
<TextView
android:id="@+id/text_view_introduction"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/global_spacing"
android:layout_marginBottom="12dp"
android:drawableRight="@drawable/ic_arrow_right"
android:drawablePadding="2dp"
android:gravity="center"
android:text="@string/introduction"
android:textColor="@color/gray_2"
android:textSize="@dimen/miniSize"
app:layout_constraintBottom_toBottomOf="@+id/img_course_pack_cover"
app:layout_constraintEnd_toEndOf="parent" />

<!--课程类型标题栏-->

<include
android:id="@+id/include_course_type_tab"
android:layout_width="0dp"
android:layout_height="48dp"
layout="@layout/include_main_learn_center_course_type_title"
app:layout_constraintTop_toBottomOf="@+id/view_placeholder_1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>


<!--课程进度-->
<include
android:id="@+id/include_courseProgress"
android:layout_width="0dp"
android:layout_height="28dp"
layout="@layout/include_main_learn_center_course_progress"
app:layout_constraintTop_toBottomOf="@+id/include_course_type_tab"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>


<!--课程内容-->
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager_2"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/include_courseProgress"
app:layout_constraintBottom_toBottomOf="parent"
android:background="@color/white"/>


</androidx.constraintlayout.widget.ConstraintLayout>

</layout>

+ 5
- 3
app/src/main/res/layout/fragment_course_pack.xml View File

@@ -5,10 +5,12 @@
android:layout_height="match_parent"
tools:context=".module.m_center_learn.CoursePackFragment">

<!-- TODO: Update blank fragment layout -->
<TextView
<include
android:id="@+id/content_layout"
layout="@layout/include_recyclerview_smart_refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment" />
android:layout_marginTop="8dp"/>


</FrameLayout>

+ 3
- 4
app/src/main/res/layout/fragment_learn_center.xml View File

@@ -5,7 +5,7 @@

<data>
<variable
name="viewmodel"
name="viewModel"
type="com.xkl.cdl.module.m_center_learn.LearnCenterFragmentViewModel" />
</data>

@@ -19,7 +19,7 @@
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="@dimen/title_bar_height"
android:layout_height="48dp"
android:layout_marginStart="16dp"
app:tabContentStart="4dp"
app:tabIndicatorHeight="0dp"
@@ -49,8 +49,7 @@
android:paddingRight="16dp"
android:textColor="@color/main_text_color"
android:textSize="@dimen/normalSize"
android:focusable="true"
android:focusableInTouchMode="true"
android:text="@={viewModel.MCoursePackSearchLiveData}"
/>



+ 52
- 0
app/src/main/res/layout/include_main_learn_center_course_progress.xml View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="28dp"
android:background="@color/gray_3"
android:paddingStart="@dimen/global_spacing"
android:paddingEnd="@dimen/global_spacing"
tools:showIn="@layout/activity_course_main">

<TextView
android:id="@+id/tv_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/theme_color"
android:textSize="@dimen/miniSize"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="%s学习进度: %d%" />


<ProgressBar
android:id="@+id/progressbar"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="0dp"
android:layout_height="4dp"
android:layout_marginStart="26dp"
android:layout_marginEnd="26dp"
android:layout_weight="1"
android:max="1000"
android:progressDrawable="@drawable/progress_center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/iv_more"
app:layout_constraintStart_toEndOf="@+id/tv_progress"
app:layout_constraintTop_toTopOf="parent" />

<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/iv_more"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_more"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/progressbar"
app:layout_constraintTop_toTopOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>


+ 85
- 0
app/src/main/res/layout/include_main_learn_center_course_type_title.xml View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:bind="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<data>

</data>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@drawable/shape_rounder_toplr_24_white"
tools:showIn="@layout/activity_course_main">

<com.suliang.common.widget.TextViewCenterDrawable
android:id="@+id/tab_discern"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:drawableStart="@drawable/ic_discern"
android:text="认读"
bind:svgColor="@{@color/theme_color}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/iv_arrow_1"
android:gravity="center"
android:drawablePadding="4dp" />

<ImageView
android:id="@+id/iv_arrow_1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/tab_spell"
app:layout_constraintStart_toEndOf="@+id/tab_discern"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible"
android:layout_width="14dp"
android:layout_height="14dp"
android:src="@drawable/ic_arrow_right"
android:visibility="gone" />


<com.suliang.common.widget.TextViewCenterDrawable
android:id="@+id/tab_spell"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:drawableStart="@drawable/ic_spell"
bind:svgColor="@{@color/theme_color}"
android:text="拼写"
app:layout_constraintStart_toEndOf="@+id/iv_arrow_1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/iv_arrow_2"
android:gravity="center"
android:drawablePadding="4dp" />

<ImageView
android:id="@+id/iv_arrow_2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/tab_voice"
app:layout_constraintStart_toEndOf="@+id/tab_spell"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible"
android:layout_width="14dp"
android:layout_height="14dp"
android:src="@drawable/ic_arrow_right"
android:visibility="gone" />

<com.suliang.common.widget.TextViewCenterDrawable
android:id="@+id/tab_voice"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:drawableStart="@drawable/ic_voice"
android:text="辨音"
bind:svgColor="@{@color/theme_color}"
app:layout_constraintStart_toEndOf="@+id/iv_arrow_2"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:gravity="center"
android:drawablePadding="4dp" />

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

+ 20
- 0
app/src/main/res/layout/include_recyclerview_smart_refresh_layout.xml View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<com.scwang.smart.refresh.layout.SmartRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">

<com.xkl.cdl.widget.smartRefresh.XkLRefreshHeader
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />

<com.scwang.smart.refresh.footer.ClassicsFooter
android:layout_width="match_parent"
android:layout_height="wrap_content" />

</com.scwang.smart.refresh.layout.SmartRefreshLayout>

+ 22
- 0
app/src/main/res/layout/include_title_bar.xml View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">

<data>
<variable
name="title"
type="java.lang.String" />
</data>

<com.suliang.common.widget.TitleBar
android:id="@+id/title_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/title_bar_height"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:titleTextValue="@{title}"
/>

</layout>

+ 37
- 0
app/src/main/res/layout/item_empty.xml View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<data>

</data>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/tv_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
tools:text="没有搜索到任何内容"
android:gravity="center"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />

<ImageView
android:id="@+id/img_empty"
android:layout_width="160dp"
android:layout_height="160dp"
app:layout_constraintBottom_toTopOf="@+id/tv_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:src="@mipmap/empty_nothing"
android:layout_marginBottom="25dp" />


</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

+ 139
- 0
app/src/main/res/layout/main_item_coursepack.xml View File

@@ -0,0 +1,139 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<data>

<variable
name="coursePack"
type="com.xkl.cdl.data.course.CoursePack" />

</data>

<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="8dp"
app:cardElevation="0dp">

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--https://juejin.cn/post/6971647089503633438 玩转ShapeableImageView -->
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/img_cover"
android:layout_width="75dp"
android:layout_height="match_parent"
android:scaleType="fitXY"
app:imageByteArray="@{coursePack.cover}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearance="@style/roundedCornerStyle"
tools:src="@mipmap/img_default" />

<com.google.android.material.textview.MaterialTextView
android:id="@+id/textview_coursePack"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="10dp"
android:ellipsize="end"
android:maxLines="2"
android:minHeight="48dp"
android:text="@{coursePack.coursePackName}"
android:textColor="@color/main_text_color"
android:textSize="@dimen/normalSize"
app:layout_constraintEnd_toStartOf="@+id/my_ranking_view"
app:layout_constraintStart_toEndOf="@+id/img_cover"
app:layout_constraintTop_toTopOf="parent"
tools:text="人教版·小学三年级" />


<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image_view_type_discern"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:padding="3dp"
android:src="@drawable/ic_discern"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@+id/image_view_type_spell"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="@+id/textview_coursePack"
app:layout_constraintTop_toBottomOf="@+id/textview_coursePack"
app:shapeAppearance="@style/roundedCornerStyle"
app:strokeColor="#1A5082E6"
app:strokeWidth="5dp"
tools:visibility="visible" />

<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image_view_type_spell"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:padding="3dp"
android:src="@drawable/ic_spell"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@+id/image_view_type_voice"
app:layout_constraintStart_toEndOf="@+id/image_view_type_discern"
app:layout_constraintTop_toBottomOf="@+id/textview_coursePack"
app:shapeAppearance="@style/roundedCornerStyle"
app:strokeColor="#1AF26255"
app:strokeWidth="5dp"
tools:visibility="visible" />

<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image_view_type_voice"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:padding="3dp"
android:src="@drawable/ic_voice"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/image_view_type_spell"
app:layout_constraintTop_toBottomOf="@+id/textview_coursePack"
app:shapeAppearance="@style/roundedCornerStyle"
app:strokeColor="#1A52CC52"
app:strokeWidth="8dp"
tools:visibility="visible" />

<!--进度-->
<com.xkl.cdl.widget.MyRankingView
android:id="@+id/my_ranking_view"
android:layout_width="50dp"
android:layout_height="50dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="16dp"
app:normalColor="@color/gray_1"
app:progressColor="@color/blue_1"
app:borderWidth="3dp"
app:progressNumber="@{(int)coursePack.learnProgress}"
>
<TextView
android:id="@+id/tv_learning_percent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textColor="@color/gray_2"
android:textSize="@dimen/miniSize"
app:coursePackLearnProgressFormat="@{coursePack.learnProgress}"/>
</com.xkl.cdl.widget.MyRankingView>


</androidx.constraintlayout.widget.ConstraintLayout>

</androidx.cardview.widget.CardView>
</layout>

+ 32
- 0
app/src/main/res/layout/smart_refresh_header.xml View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!--<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"-->
<!-- xmlns:app="http://schemas.android.com/apk/res-auto"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:gravity="center_horizontal"-->
<!-- android:orientation="vertical"-->
<!-- android:paddingTop="5dp"-->
<!-- android:paddingBottom="5dp">-->

<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/refreshImg"
android:layout_width="30dp"
android:layout_height="30dp"
app:lottie_autoPlay="true"
app:lottie_fileName="lottie_loading.json"
app:lottie_loop="true"
app:lottie_repeatMode="restart" />

<TextView
android:id="@+id/refreshText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="6dp"
android:text="加载中,请稍后..."
android:textColor="#ff8a8a99"
android:textSize="12sp" />
</merge>
<!--</LinearLayout>-->

BIN
app/src/main/res/mipmap-xxhdpi/empty_nothing.png View File


BIN
app/src/main/res/mipmap-xxhdpi/empty_nothing_search.png View File


BIN
app/src/main/res/mipmap-xxhdpi/img_default.png View File


+ 9
- 0
app/src/main/res/values/attrs.xml View File

@@ -1,3 +1,12 @@
<resources>

<!--背景带圆弧-->
<declare-styleable name="MyRankingView">
<attr name="normalColor" format="color"/> <!--正常颜色-->
<attr name="progressColor" format="color"/> <!--进度颜色-->
<attr name="borderWidth" format="dimension"/> <!--弧形边框大小-->
<attr name="progressNumber" format="integer"/> <!--弧形进度-->
<attr name="backgroundColor" format="color"/> <!--背景颜色-->
</declare-styleable>

</resources>

+ 3
- 0
app/src/main/res/values/colors.xml View File

@@ -21,4 +21,7 @@
<color name="gray_2">#8A8A99</color>
<color name="white">#FFFFFFFF</color>
<color name="white_1">#FFF5F5F5</color>
<color name="blue_1">#52CC52</color>
<color name="gray_3">#FAFAFA</color>
<color name="green_1">#40A540</color>
</resources>

+ 2
- 1
app/src/main/res/values/dimens.xml View File

@@ -8,7 +8,8 @@
<dimen name="normalSize">16dp</dimen>
<dimen name="bigSize">18dp</dimen>
<dimen name="biggerSize">20dp</dimen>
<dimen name="title_bar_height">48dp</dimen>
<dimen name="title_bar_height">44dp</dimen>
<dimen name="global_spacing">16dp</dimen>


</resources>

+ 7
- 2
app/src/main/res/values/strings.xml View File

@@ -11,8 +11,13 @@

<string name="project_english">英语</string>
<string name="project_chinese">语文</string>
<string name="search_hint_input_course_name">输入课程内容...</string>
<string name="start">开始</string>
<string name="empty_nothing">没有任何内容</string>
<string name="empty_nothing_search">没有搜索到任何内容</string>
<string name="course_detail">课程详情</string>
<string name="introduction">简介</string>

</resources>

+ 9
- 0
app/src/main/res/values/styles.xml View File

@@ -1,2 +1,11 @@
<resources>


<!--https://juejin.cn/post/6971647089503633438 玩转ShapeableImageView -->
<!--圆角图片-->
<style name="roundedCornerStyle">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">8dp</item>
</style>

</resources>

+ 12
- 0
app/svg/drawable/ic_arrow_right.xml View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="12"
android:viewportHeight="12">
<path
android:fillColor="#8A8A99"
android:fillType="evenOdd"
android:pathData="M3.8691,1.704C3.7056,1.5554 3.4526,1.5675 3.304,1.7309C3.1669,1.8818 3.1666,2.109 3.2959,2.2599L3.3309,2.296L7.406,6L3.3309,9.704C3.18,9.8412 3.1582,10.0673 3.2726,10.2298L3.304,10.2691C3.4412,10.42 3.6673,10.4418 3.8298,10.3274L3.8691,10.296L8.2691,6.296C8.4312,6.1486 8.4428,5.9014 8.3038,5.7397L8.2691,5.704L3.8691,1.704Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

+ 12
- 0
app/svg/drawable/ic_discern.xml View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="18"
android:viewportHeight="18">
<path
android:pathData="M2.7143,1.5714L15.2857,1.5714C15.9169,1.5714 16.4286,2.0831 16.4286,2.7143L16.4286,15.2857C16.4286,15.9169 15.9169,16.4286 15.2857,16.4286L2.7143,16.4286C2.0831,16.4286 1.5714,15.9169 1.5714,15.2857L1.5714,2.7143C1.5714,2.0831 2.0831,1.5714 2.7143,1.5714ZM9.0956,5C9.3117,5 9.4921,5.0413 9.6368,5.124C9.7814,5.2067 9.8982,5.3131 9.9871,5.4433C10.076,5.5734 10.1509,5.7133 10.212,5.8628L10.2666,5.999L10.2666,5.999L10.329,6.1584L10.4446,6.4617L12.4776,11.5284C12.6142,11.8701 12.6825,12.1214 12.6825,12.2823C12.6825,12.4617 12.6085,12.6262 12.4603,12.7757C12.3122,12.9252 12.1335,13 11.9244,13C11.8024,13 11.6978,12.978 11.6106,12.934C11.5235,12.8901 11.4503,12.8303 11.391,12.7546C11.3436,12.6941 11.2934,12.6077 11.2404,12.4954L11.2002,12.4063L11.0882,12.1445L11.025,11.9921L10.659,11.0211L7.5427,11.0211L7.1484,12.0889L7.0639,12.303C6.9684,12.5339 6.884,12.6985 6.8106,12.7968C6.7096,12.9323 6.544,13 6.3139,13C6.1187,13 5.9462,12.9279 5.7963,12.7836C5.6464,12.6394 5.5714,12.4758 5.5714,12.2929C5.5714,12.1873 5.5889,12.0783 5.6237,11.9657L5.6628,11.8479C5.6781,11.8043 5.6955,11.7565 5.7151,11.7045L7.757,6.4723L7.9584,5.9525C8.0368,5.7502 8.1204,5.5822 8.2093,5.4485C8.2982,5.3149 8.415,5.2067 8.5597,5.124C8.7043,5.0413 8.883,5 9.0956,5ZM9.1168,6.7778L8.0159,9.8889L10.2381,9.8889L9.1168,6.7778Z"
android:strokeWidth="1"
android:fillColor="#5082E6"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

+ 12
- 0
app/svg/drawable/ic_more.xml View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="21dp"
android:height="20dp"
android:viewportWidth="21"
android:viewportHeight="20">
<path
android:pathData="M5.5831,8.6667C6.3214,8.6667 6.92,9.2636 6.92,10C6.92,10.4764 6.6652,10.9165 6.2515,11.1547C5.8379,11.3929 5.3283,11.3929 4.9147,11.1547C4.501,10.9165 4.2462,10.4764 4.2462,10C4.2462,9.2636 4.8448,8.6667 5.5831,8.6667ZM10.9307,8.6667C11.669,8.6667 12.2675,9.2636 12.2675,10C12.2675,10.4764 12.0127,10.9165 11.5991,11.1547C11.1855,11.3929 10.6758,11.3929 10.2622,11.1547C9.8486,10.9165 9.5938,10.4764 9.5938,10C9.5938,9.2636 10.1923,8.6667 10.9307,8.6667ZM16.2782,8.6667C17.0166,8.6667 17.6151,9.2636 17.6151,10C17.6151,10.4764 17.3603,10.9165 16.9467,11.1547C16.533,11.3929 16.0234,11.3929 15.6098,11.1547C15.1961,10.9165 14.9413,10.4764 14.9413,10C14.9413,9.2636 15.5399,8.6667 16.2782,8.6667Z"
android:strokeWidth="1"
android:fillColor="#9A9EB3"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

+ 12
- 0
app/svg/drawable/ic_spell.xml View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="18"
android:viewportHeight="18">
<path
android:pathData="M6.5434,13.8354L3.1127,14.9789L4.2563,11.5482L6.5434,13.8354ZM13.706,6.6728L6.8445,13.5342L4.5574,11.2471L11.4188,4.3857L13.706,6.6728ZM14.9219,3.6271C15.4272,4.1324 15.4272,4.9516 14.9219,5.4568L14.0071,6.3717L11.72,4.0845L12.6348,3.1697C13.1401,2.6644 13.9593,2.6644 14.4645,3.1697L14.9219,3.6271Z"
android:strokeWidth="1"
android:fillColor="#F26255"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

+ 10
- 0
app/svg/drawable/ic_spoken.xml View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:tint="#40A540"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,15c1.66,0 2.99,-1.34 2.99,-3L15,6c0,-1.66 -1.34,-3 -3,-3S9,4.34 9,6v6c0,1.66 1.34,3 3,3zM17.3,12c0,3 -2.54,5.1 -5.3,5.1S6.7,15 6.7,12L5,12c0,3.42 2.72,6.23 6,6.72L11,22h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z" />
</vector>

+ 12
- 0
app/svg/drawable/ic_voice.xml View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="18"
android:viewportHeight="18">
<path
android:pathData="M4.5429,8.8328L4.5429,15.8571L2.1429,15.8571L2.1429,8.8328L4.5429,8.8328ZM8.3143,2.1429L8.3143,15.8571L5.9143,15.8571L5.9143,2.1429L8.3143,2.1429ZM12.0857,5.4878L12.0857,15.8571L9.6857,15.8571L9.6857,5.4878L12.0857,5.4878ZM15.8571,8.8328L15.8571,15.8571L13.4571,15.8571L13.4571,8.8328L15.8571,8.8328Z"
android:strokeWidth="1"
android:fillColor="#40A540"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

+ 16
- 9
build.gradle View File

@@ -35,18 +35,18 @@ ext {
appcompat_version: "1.4.1",
material_version : "1.3.0",
lifecycle_version: "2.5.0-alpha02",
glide_version : "4.11.0",
glide_version : "4.11.0",
]
//必须依赖
dependencies_required = [
//为属于Android框架的通用库提供扩展程序
core_ktx : "androidx.core:core-ktx:${versions.core_ktx_version}",
core_ktx : "androidx.core:core-ktx:${versions.core_ktx_version}",
//Androidx 依赖
appcompat : "androidx.appcompat:appcompat:${versions.appcompat_version}",
appcompat : "androidx.appcompat:appcompat:${versions.appcompat_version}",
//material_design
material : "com.google.android.material:material:${versions.material_version}",
material : "com.google.android.material:material:${versions.material_version}",
//constraintlayout
constraintlayout : "androidx.constraintlayout:constraintlayout:2.0.4",
constraintlayout: "androidx.constraintlayout:constraintlayout:2.0.4",
//lifecycle
// lifecycle : "androidx.lifecycle:lifecycle-runtime-ktx:${versions.lifecycle_version}",
// //lifecycle saved state module for viewmodel
@@ -68,14 +68,21 @@ ext {
//依赖项
dependencies_custom = [
//设置状态栏和导航栏的框架 https://github.com/Zackratos/UltimateBarX
UltimateBarX: "com.gitee.zackratos:UltimateBarX:0.8.0",
UltimateBarX : "com.gitee.zackratos:UltimateBarX:0.8.0",
//Glide 依赖和配置
Glide : "com.github.bumptech.glide:glide:${versions.glide_version}",
GlideCompiler : "com.github.bumptech.glide:compiler:${versions.glide_version}",
Glide : "com.github.bumptech.glide:glide:${versions.glide_version}",
GlideCompiler : "com.github.bumptech.glide:compiler:${versions.glide_version}",
//Glide translation
GlideTranslation: "jp.wasabeef:glide-transformations:4.3.0",
//Activity_ktx Activity扩展
Activity_ktx : "androidx.activity:activity-ktx:1.4.0",
Activity_ktx : "androidx.activity:activity-ktx:1.4.0",
//SqlCipher 需要在application 实现 : SQLiteDatabase.loadLibs(this)
SqlCipher : "net.zetetic:android-database-sqlcipher:4.5.0",
Sqlite : "androidx.sqlite:sqlite:2.0.1",
//SmartRefreshLayout
SmartRefreshLayout: "io.github.scwang90:refresh-layout-kernel:2.0.5",
//Lottie
Lottie : "com.airbnb.android:lottie:5.0.3"
]



+ 5
- 2
lib/common/build.gradle View File

@@ -50,6 +50,8 @@ dependencies {

def customDependencies = rootProject.ext.dependencies_custom

//Activity-ktx 扩展
api customDependencies.Activity_ktx
//状态栏
api customDependencies.UltimateBarX
//glide
@@ -58,8 +60,9 @@ dependencies {
//glide translation
api customDependencies.GlideTranslation

//Activity-ktx 扩展
api customDependencies.Activity_ktx







+ 5
- 2
lib/common/proguard-rules.pro View File

@@ -20,10 +20,13 @@
# hide the original source file name.
#-renamesourcefileattribute SourceFile

#Glide
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
}
#SqlCiper
-keep,includedescriptorclasses class net.sqlcipher.** { *; }
-keep,includedescriptorclasses interface net.sqlcipher.** { *; }

+ 32
- 0
lib/common/src/main/java/com/suliang/common/base/activity/BaseActivity.kt View File

@@ -117,10 +117,42 @@ abstract class BaseActivity<VB : ViewBinding> : LifecycleLogActivity(), ViewBeha
}
}

/** 单独封装一个方法,用于进行无参数的activity的跳转 */
override fun startActivity(clazz: Class<*>) {
startActivity(Intent(this,clazz))
}

//覆写该方法,避免多次跳转失败的问题
override fun startActivityForResult(intent: Intent, requestCode: Int, options: Bundle?) {
if (checkDoubleClick(intent)) {
super.startActivityForResult(intent, requestCode, options)
}else{
LogUtil.e("多次跳转无效操作--> $intent")
}
}

private var mActivityJumpTag : String = "" // activity跳转tag
private var clickTime = 0L //点击时间
//避免多次点击跳转到同一个activity 和 避免短时间内多次点击跳转到不同activity
private fun checkDoubleClick(intent: Intent): Boolean{
val currentTime = System.currentTimeMillis()
var result = true
var tag : String = ""
if (intent.component != null) { //显示跳转
tag = intent.component!!.className
}else if (intent.action != null){ //隐式跳转
tag = intent.action!!
}else{
return true
}

result = tag != mActivityJumpTag && currentTime - clickTime >= 500

mActivityJumpTag = tag
clickTime = currentTime

return result
}

// 反射实现 ViewBinding
// @SuppressWarnings("unchecked")

+ 29
- 5
lib/common/src/main/java/com/suliang/common/base/adapter/BaseAdapter.kt View File

@@ -1,17 +1,24 @@
package com.suliang.common.base.adapter

import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding

abstract class BaseAdapter<T> :
RecyclerView.Adapter<BaseAdapterViewHolder>() {

companion object{
val TYPE_EMPTY = -1
const val TYPE_EMPTY = -1
}
// item 点击事件
lateinit var onItemClick : (v: View, position: Int, item:T) -> Unit

//数据源
private var mData: MutableList<T> = mutableListOf<T>()
private var mData: MutableList<T> = mutableListOf()

/** 是否需要显示空布局 */
var needShowEmptyView = false
@@ -60,18 +67,31 @@ abstract class BaseAdapter<T> :
abstract fun onBindVH(holder: BaseAdapterViewHolder,position: Int)


/** 适配器获取 binding */
fun inflateBinding(parent:ViewGroup,layoutRes:Int):ViewBinding{
return DataBindingUtil.inflate(LayoutInflater.from(parent.context),layoutRes,parent,false)
}


@Synchronized
fun getItem(position: Int) : T {
return mData.get(position)
}


/**
* 设置数据,并更新列表
* @param data List<T>?
*/
@SuppressLint("NotifyDataSetChanged")
fun setData(data: MutableList<T>?) {
mData.clear()
data?.let { it -> //不为空
mData = it
mData.addAll(data)
} ?: let { //为空
mData = mutableListOf()
mData.clear()
}
notifyItemRangeChanged(0, mData.size)
notifyDataSetChanged()
}


@@ -126,4 +146,8 @@ abstract class BaseAdapter<T> :
}
}





}

+ 1
- 1
lib/common/src/main/java/com/suliang/common/base/adapter/BaseAdapterVM.kt View File

@@ -2,6 +2,6 @@ package com.suliang.common.base.adapter

import androidx.lifecycle.ViewModel

abstract class BaseAdapterVM<T,VM:ViewModel> : BaseAdapter<T>() {
abstract class BaseAdapterVM<T,VM:ViewModel>(val vm:VM) : BaseAdapter<T>() {

}

+ 2
- 1
lib/common/src/main/java/com/suliang/common/base/adapter/BaseAdapterViewHolder.kt View File

@@ -2,5 +2,6 @@ package com.suliang.common.base.adapter

import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding

class BaseAdapterViewHolder(binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root)
class BaseAdapterViewHolder(val binding: ViewBinding) : RecyclerView.ViewHolder(binding.root)

+ 12
- 0
lib/common/src/main/java/com/suliang/common/base/adapter/IItemClickListener.kt View File

@@ -0,0 +1,12 @@
package com.suliang.common.base.adapter

import android.view.View

/**
* author suliang
* create 2022/3/23 18:04
* Describe:
*/
interface IItemClickListener: View.OnClickListener {

}

+ 7
- 2
lib/common/src/main/java/com/suliang/common/base/fragment/BaseFragment.kt View File

@@ -40,14 +40,14 @@ abstract class BaseFragment<VB : ViewBinding> : Fragment(),ViewBehavior {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
_binding = initBinding(inflater, container)
initCustomBinding(inflater,container)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initFirst()
initFragment()
super.onViewCreated(view, savedInstanceState)
}

override fun onResume() {
@@ -63,6 +63,11 @@ abstract class BaseFragment<VB : ViewBinding> : Fragment(),ViewBehavior {
_binding = null
}

open fun initCustomBinding(inflater: LayoutInflater,container: ViewGroup?){
_binding = initBinding(inflater, container)
}


/**
* onViewCreated中进行的初始:多数时候可以是空实现,主要在使用VM时进行vm的初始
*/

+ 40
- 0
lib/common/src/main/java/com/suliang/common/extension/ViewClickExtension.kt View File

@@ -0,0 +1,40 @@
package com.suliang.common.extension

import android.annotation.SuppressLint
import android.view.MotionEvent
import android.view.View
import com.suliang.common.util.LogUtil
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

/**
* author suliang
* create 2022/3/23 18:16
* Describe: View防快速点击
*
* 利用Kotlin扩展函数实现任意View的点击处理(点击效果和防止快速点击)
* https://www.cnblogs.com/zh2314372037/p/13965371.html
*/

@SuppressLint("ClickableViewAccessibility")
fun View.click(clickListener: (view:View) -> Unit){
val minTime = 500L
var lastTime = 0L
this.setOnClickListener{
val tempTime = System.currentTimeMillis()
if (tempTime - lastTime > minTime){
lastTime = tempTime
clickListener.invoke(this)
}else{
LogUtil.e("点击过快,取消触发")
}
}
this.setOnTouchListener{ view , motionEvent ->
when(motionEvent.action){
MotionEvent.ACTION_DOWN -> view.alpha = 0.7f
MotionEvent.ACTION_UP -> view.alpha = 1f
else -> view.alpha = 1f
}
return@setOnTouchListener false
}
}

+ 6
- 3
lib/common/src/main/java/com/suliang/common/util/AppGlobals.kt View File

@@ -7,11 +7,14 @@ import java.lang.Exception
* 用于获取全局Applicaiton
*/
object AppGlobals {
val application by lazy {
val application : Application by lazy {
try {
val currentApplication = Class.forName("android.app.ActivityThread").getDeclaredMethod("currentApplication")
currentApplication.invoke(null, null) as Application
val atClass = Class.forName("android.app.ActivityThread")
val currentApplicationMethod = atClass.getDeclaredMethod("currentApplication")
currentApplicationMethod.isAccessible = true
currentApplicationMethod.invoke(null) as Application
}catch (e:Exception){
e.printStackTrace()
throw RuntimeException("AppGlobals获取Application异常")
}
}

+ 27
- 0
lib/common/src/main/java/com/suliang/common/util/DrawableUti.kt View File

@@ -0,0 +1,27 @@
package com.suliang.common.util

import android.graphics.drawable.Drawable
import androidx.annotation.NonNull
import androidx.core.graphics.drawable.DrawableCompat

/**
* author suliang
* create 2022/3/25 17:08
* Describe:
*/
class DrawableUti {
companion object{
/**
* 改变Drawable填充颜色
* @param drawable Drawable
* @param color Int 改变的颜色
* @return Drawable 改变颜色后的drawable
*/
@JvmStatic
fun tintDrawable(@NonNull drawable:Drawable , color:Int):Drawable{
val wrapperDrawable = DrawableCompat.wrap(drawable).mutate()
DrawableCompat.setTint(wrapperDrawable,color)
return wrapperDrawable
}
}
}

+ 19
- 26
lib/common/src/main/java/com/suliang/common/util/LogUtil.kt View File

@@ -7,6 +7,7 @@ import com.suliang.common.BuildConfig
* author suliang
* create 2022/3/7 10:55
* Describe: Log日志打印框架
* https://blog.csdn.net/qq5201314259/article/details/50749438
*/
object LogUtil {

@@ -14,45 +15,37 @@ object LogUtil {
private val LogEnable =
BuildConfig.LOG_ENABLE // 根据BuildConfig中的field LOG_ENABLE配置的值,确定是否打印

/**
* 创建输出内容
* @param element StackTraceElement
* @param message String
* @return String
*/
private fun createLog(element:StackTraceElement ,message : String) : String{
return StringBuffer().apply {
append("====${element.methodName}() : ${element.lineNumber} ==== $message ====" )
}.toString()
}

fun v(message: String) {
if (!LogEnable) return
val stackTrace = Throwable().stackTrace[1]
Log.v(stackTrace.className,createLog(stackTrace,message))
log("v",message)
}

fun d(message: String) {
if (!LogEnable) return
val stackTrace = Throwable().stackTrace[1]
Log.d(stackTrace.className,createLog(stackTrace,message))
log("d",message)
}

fun e(message: String) {
if (!LogEnable) return
val stackTrace = Throwable().stackTrace[1]
Log.e(stackTrace.className,createLog(stackTrace,message))
log("e",message)
}

fun i(message: String) {
if (!LogEnable) return
val stackTrace = Throwable().stackTrace[1]
Log.i(stackTrace.className,createLog(stackTrace,message))
log("i",message)
}

fun w(message: String) {
log("w",message)
}

private fun log(logType: String, message: String) {
if (!LogEnable) return
val stackTrace = Throwable().stackTrace[1]
Log.w(stackTrace.className,createLog(stackTrace,message))
val stackTraceElement = Thread.currentThread().stackTrace[5]
val out = "==${stackTraceElement.methodName} : ${stackTraceElement.lineNumber} --- $message ===== "
when(logType){
"v" -> Log.v(stackTraceElement.className,out)
"d" -> Log.d(stackTraceElement.className,out)
"e" -> Log.e(stackTraceElement.className,out)
"i" -> Log.i(stackTraceElement.className,out)
"w" -> Log.w(stackTraceElement.className,out)
}
}

}

+ 0
- 1
lib/common/src/main/java/com/suliang/common/util/image/GlideLoaderStrategy.kt View File

@@ -87,7 +87,6 @@ internal class GlideLoaderStrategy : IBaseLoaderStrategy {

private fun initTranslation(option: ImageLoaderOption): MultiTransformation<Bitmap> {
val list: MutableList<Transformation<Bitmap>> = ArrayList()
list.add(BlurTransformation(25, 3))
//gravity 模式
if (option.centerInsideEnable) {
list.add(CenterInside())

+ 7
- 0
lib/common/src/main/java/com/suliang/common/util/image/ImageLoader.kt View File

@@ -65,6 +65,13 @@ object ImageLoader {
})
}

fun loadImage(view: ImageView, imageByteArray: ByteArray) {
loadImage(ImageLoaderOption().apply {
this.byteArray = imageByteArray
targetView = view
})
}


// override fun clearMemoryCache() {
// mStrategyBase.clearMemoryCache()

+ 1
- 1
lib/common/src/main/java/com/suliang/common/util/thread/AppExecutors.kt View File

@@ -22,7 +22,7 @@ object AppExecutors {
val diskIO: ExecutorService = Executors.newSingleThreadExecutor()

/** 网络线程池 网络请求、异步任务等适用此线程 不建议在此线程 sleep 或者 wait */
val netWork: ExecutorService = Executors.newCachedThreadPool()
val io: ExecutorService = Executors.newCachedThreadPool()

/**主线程*/
val mainThread: Executor = MainThreadExecutor()

+ 91
- 53
lib/common/src/main/java/com/suliang/common/widget/InputSearchEditText.kt View File

@@ -9,8 +9,8 @@ import android.util.AttributeSet
import android.view.MotionEvent
import android.view.inputmethod.EditorInfo
import androidx.appcompat.widget.AppCompatEditText
import androidx.core.content.res.ResourcesCompat
import com.suliang.common.R
import com.suliang.common.util.LogUtil
import com.suliang.common.util.os.KeyboardUtil

/**
@@ -20,15 +20,15 @@ import com.suliang.common.util.os.KeyboardUtil
*/

@SuppressLint("UseCompatLoadingForDrawables")
class InputSearchEditText @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
defStyleAttr: Int = 0) :
AppCompatEditText(context, attrs, defStyleAttr) {
class InputSearchEditText @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatEditText(context, attrs, defStyleAttr) {

private val clearRect by lazy { Rect() }
private var ic_delete_src: Int = 0
private var ic_delete_color: Int = 0
private var ic_search_src: Int = 0
private var ic_search_color: Int = 0
// private val clearRect by lazy { Rect() }
private var icDeleteSrc: Int = R.drawable.ic_delete
private var icDeleteColor: Int = Color.parseColor("#8A8A8A")
private var icSearchSrc: Int = R.drawable.ic_search
private var icSearchColor: Int = Color.parseColor("#8A8A8A")

private var drawableStart: Drawable? = null
private var drawableEnd: Drawable? = null
@@ -36,75 +36,113 @@ class InputSearchEditText @JvmOverloads constructor(context: Context, attrs: Att
init {
attrs?.let {
context.obtainStyledAttributes(attrs, R.styleable.InputSearchEditText, defStyleAttr, 0).let {
ic_search_src = it.getResourceId(R.styleable.InputSearchEditText_ic_search_src, R.drawable.ic_search)
ic_search_color =
it.getColor(R.styleable.InputSearchEditText_ic_search_color, Color.parseColor("#8A8A8A"))
ic_delete_color =
it.getColor(R.styleable.InputSearchEditText_ic_delete_color, Color.parseColor("#8A8A8A"))
ic_delete_src = it.getColor(R.styleable.InputSearchEditText_ic_delete_src, R.drawable.ic_delete)
icSearchSrc = it.getResourceId(R.styleable.InputSearchEditText_ic_search_src, icSearchSrc)
icSearchColor =
it.getColor(R.styleable.InputSearchEditText_ic_search_color, icSearchColor)
icDeleteColor =
it.getColor(R.styleable.InputSearchEditText_ic_delete_color, icDeleteColor)
icDeleteSrc = it.getColor(R.styleable.InputSearchEditText_ic_delete_src, icDeleteSrc)
it.recycle()
drawableStart = resources.getDrawable(ic_search_src, null).apply {
setTint(ic_search_color)
drawableStart = resources.getDrawable(icSearchSrc, null).apply {
setTint(icSearchColor)
}
setCompoundDrawablesWithIntrinsicBounds(drawableStart, null, null, null)
drawableEnd = resources.getDrawable(ic_delete_src, null).apply {
setTint(ic_delete_color)
drawableEnd = resources.getDrawable(icDeleteSrc, null).apply {
setTint(icDeleteColor)
}
isFocusable = true
isFocusableInTouchMode = true
}
}
}

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
//计算删除按钮所在的矩形区域
clearRect.apply {
left = w - paddingRight - w.coerceAtMost(h)
top = paddingTop
right = w - paddingRight
bottom = w.coerceAtLeast(h)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}

// override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
// super.onSizeChanged(w, h, oldw, oldh)
// //计算删除按钮所在的矩形区域
// clearRect.apply {
// left = w - paddingRight - w.coerceAtMost(h)
// top = paddingTop
// right = w - paddingRight
// bottom = w.coerceAtLeast(h)
// }
// }

override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
super.onFocusChanged(focused, direction, previouslyFocusedRect)
checkEditFocus(focused)
}

/** 根据焦点改变,显示对应的图标内容
* @param focused Boolean 有焦点为true 否则false
*/
@SuppressLint("UseCompatLoadingForDrawables")
private fun checkEditFocus(focused: Boolean) {
if (text.isNullOrEmpty() || !focused) { //如果没有内容 或者没有焦点,则设置搜索图标
setCompoundDrawablesWithIntrinsicBounds(drawableStart, null, null, null)
} else {
setCompoundDrawablesRelativeWithIntrinsicBounds(drawableStart, null, drawableEnd, null)
}
// checkEditFocus(focused)
setClearIconVisible(hasFocus() && !text.isNullOrEmpty())
}

override fun onTextChanged(text: CharSequence?, start: Int, lengthBefore: Int, lengthAfter: Int) {
super.onTextChanged(text, start, lengthBefore, lengthAfter)
//时刻监听
checkEditFocus(true)
// checkEditFocus(true)
setClearIconVisible(hasFocus() && !text.isNullOrEmpty())
}

@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_DOWN && isFocused && clearRect.contains(event.x.toInt(),event.y.toInt()) && text.isNullOrEmpty()){
setText("")
return true
/** 设置清除图标显示 */
private fun setClearIconVisible(visible: Boolean) {
if (visible) {
setCompoundDrawablesRelativeWithIntrinsicBounds(drawableStart, null, drawableEnd, null)
}else{
setCompoundDrawablesWithIntrinsicBounds(drawableStart, null, null, null)
}
return super.onTouchEvent(event)
}

override fun onEditorAction(actionCode: Int) {
if (actionCode == EditorInfo.IME_ACTION_SEARCH){
KeyboardUtil.hideKeyboard(this)
//
// /** 根据焦点改变,显示对应的图标内容
// * @param focused Boolean 有焦点为true 否则false
// */
// @SuppressLint("UseCompatLoadingForDrawables")
// private fun checkEditFocus(focused: Boolean) {
// if (text.isNullOrEmpty() || !focused) { //如果没有内容 或者没有焦点,则设置搜索图标
// setCompoundDrawablesWithIntrinsicBounds(drawableStart, null, null, null)
// } else {
// setCompoundDrawablesRelativeWithIntrinsicBounds(drawableStart, null, drawableEnd, null)
// }
// }


@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_DOWN){
drawableEnd?.let {
//去除padding的宽度
val w1 = width - paddingRight
//去除图标的宽度
val w2 = w1 - it.bounds.width()
//点击的清除图标
if (event.x <= w1 && event.x >= w2 ){
setText("")
LogUtil.e("点击了清除图标")
return true
}
}
}
super.onEditorAction(actionCode)
}



// if (event.action == MotionEvent.ACTION_DOWN && isFocused && clearRect.contains(
// event.x.toInt(),
// event.y.toInt()
// ) && text.isNullOrEmpty()
// ) {
// setText("")
// return true
// }
return super.onTouchEvent(event)
}

// override fun onEditorAction(actionCode: Int) {
// if (actionCode == EditorInfo.IME_ACTION_SEARCH) {
// KeyboardUtil.hideKeyboard(this)
// }
// super.onEditorAction(actionCode)
// }


}

+ 57
- 0
lib/common/src/main/java/com/suliang/common/widget/TextViewCenterDrawable.java View File

@@ -0,0 +1,57 @@
package com.suliang.common.widget;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.TextView;

@SuppressLint("AppCompatCustomView")
public class TextViewCenterDrawable extends TextView {

public TextViewCenterDrawable(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}

public TextViewCenterDrawable(Context context, AttributeSet attrs) {
super(context, attrs);
}

public TextViewCenterDrawable(Context context) {
super(context);
}

@Override
protected void onDraw(Canvas canvas) {
Drawable[] drawables = getCompoundDrawables();
if (drawables != null) {
Drawable drawable = drawables[0];
if (drawable != null) {
// 当左边Drawable的不为空时,测量要绘制文本的宽度
float textWidth = getPaint().measureText(getText().toString());
int drawablePadding = getCompoundDrawablePadding();
int drawableWidth = drawable.getIntrinsicWidth();
// 计算总宽度(文本宽度 + drawablePadding + drawableWidth)
float bodyWidth = textWidth + drawablePadding + drawableWidth;
// 移动画布开始绘制的X轴
canvas.translate((getWidth() - bodyWidth) / 2, 0);
} else if ((drawable = drawables[1]) != null) {
// 否则如果上边的Drawable不为空时,获取文本的高度
Rect rect = new Rect();
getPaint().getTextBounds(getText().toString(), 0, getText().toString().length(), rect);
float textHeight = rect.height();
int drawablePadding = getCompoundDrawablePadding();
int drawableHeight = drawable.getIntrinsicHeight();
// 计算总高度(文本高度 + drawablePadding + drawableHeight)
float bodyHeight = textHeight + drawablePadding + drawableHeight;
// 移动画布开始绘制的Y轴
canvas.translate(0, (getHeight() - bodyHeight) / 2);
}
}

super.onDraw(canvas);
}
}

+ 116
- 0
lib/common/src/main/java/com/suliang/common/widget/TitleBar.kt View File

@@ -0,0 +1,116 @@
package com.suliang.common.widget

import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.util.TypedValue
import android.view.LayoutInflater
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import com.suliang.common.R
import com.suliang.common.extension.click
import com.suliang.common.util.os.ScreenUtil

/**
* author suliang
* create 2022/3/25 10:12
* Describe: 自定义标题栏
* 与databinding 结合时,自定义属性需要有set方法,否则编译不通过,所以,属性没有定义为private
*/
class TitleBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
ConstraintLayout(context, attrs, defStyleAttr) {


lateinit var onBackClick : (view:TextView) -> Unit
lateinit var onRightClick : (view:TextView) -> Unit

lateinit var textViewBackView: TextView
private set
lateinit var textViewTitle: TextView
private set
lateinit var textViewRight: TextView
private set

var barBackground: Int = 0 //背景
var titleTextValue: String? = null //标题文字-->
var titleTextSize = ScreenUtil.dp2px(18f).toFloat() //标题大小-->
var titleTextColor: Int = Color.parseColor("#323232") //标题颜色-->
var rightTextValue: String? = null //右侧文字
var rightTextSize = 0f //右侧大小-->
var rightTextColor = 0 //右侧颜色-->
var rightSrc = 0 //右侧图片-->
var backTextValue: String? = null //左侧文字-->
var backTextSize = 0f //左侧文字大小-->
var backTextColor = 0 //左侧大小-->
var backSrc = 0 //左侧图片-->
var drawPadding = 0 //图片drawPadding-->

init {
initAttrs(attrs)
initView()
attrsToView()
}
private fun initAttrs(attrs: AttributeSet?) {
context.obtainStyledAttributes(attrs, R.styleable.TitleBar).run {
barBackground = getColor(R.styleable.TitleBar_barBackground,barBackground)
titleTextValue = getString(R.styleable.TitleBar_titleTextValue)
titleTextSize = getDimension(R.styleable.TitleBar_titleTextSize,titleTextSize)
titleTextColor = getColor(R.styleable.TitleBar_titleTextColor,titleTextColor)
rightTextValue = getString(R.styleable.TitleBar_rightTextValue)
rightTextColor = getColor(R.styleable.TitleBar_rightTextColor,rightTextColor)
rightTextSize = getDimension(R.styleable.TitleBar_rightTextSize,rightTextSize)
rightSrc = getColor(R.styleable.TitleBar_rightSrc,rightSrc)
backTextSize = getDimension(R.styleable.TitleBar_backTextSize,backTextSize)
backTextColor = getColor(R.styleable.TitleBar_backTextColor,backTextColor)
backTextValue = getString(R.styleable.TitleBar_backTextValue)
backSrc = getColor(R.styleable.TitleBar_backSrc,backSrc)
drawPadding = getDimension(R.styleable.TitleBar_drawPadding,0f).toInt()
recycle()
}
}

private fun initView() {
LayoutInflater.from(context).inflate(R.layout.public_title_bar, this, true)
textViewTitle = findViewById(R.id.title_bar_title)
textViewBackView = findViewById(R.id.title_bar_back)
textViewRight = findViewById(R.id.title_bar_right)
}

private fun attrsToView() {
setBackgroundColor(barBackground)
textViewTitle.apply {
text = titleTextValue
setTextColor(titleTextColor)
setTextSize(TypedValue.COMPLEX_UNIT_PX,titleTextSize)
}
textViewBackView.apply {
text = titleTextValue
setTextColor(titleTextColor)
setTextSize(TypedValue.COMPLEX_UNIT_PX,titleTextSize)
if (backSrc != 0){
setCompoundDrawablesWithIntrinsicBounds(backSrc,0,0,0)
}
}
textViewRight.apply {
text = titleTextValue
setTextColor(titleTextColor)
setTextSize(TypedValue.COMPLEX_UNIT_PX,titleTextSize)
if (rightSrc != 0){
setCompoundDrawablesWithIntrinsicBounds(rightSrc,0,0,0)
if(drawPadding != 0){
compoundDrawablePadding = drawPadding
}
}
}

//事件
textViewBackView.click {
onBackClick(it as TextView)
}
textViewRight.click {
onRightClick(it as TextView)
}
}


}

+ 12
- 0
lib/common/src/main/res/drawable/ic_back.xml View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M15.4619,3.408C15.7888,3.1108 16.2947,3.1349 16.592,3.4619C16.8663,3.7636 16.8669,4.218 16.6082,4.5198L16.5381,4.592L8.388,12L16.5381,19.408C16.8399,19.6824 16.8837,20.1346 16.6548,20.4597L16.592,20.5381C16.3176,20.8399 15.8654,20.8837 15.5403,20.6548L15.4619,20.592L6.6619,12.592C6.3377,12.2972 6.3145,11.8027 6.5924,11.4794L6.6619,11.408L15.4619,3.408Z"
android:strokeWidth="1"
android:fillColor="#323233"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

+ 56
- 0
lib/common/src/main/res/layout/public_title_bar.xml View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?><!--<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">-->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:parentTag="com.suliang.common.widget.TitleBar">

<TextView
android:id="@+id/title_bar_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="60dp"
android:layout_marginEnd="60dp"
android:ellipsize="end"
android:gravity="center"
android:textColor="#323233"
android:textSize="18dp"
android:textStyle="bold"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="标题标题标题标题标题标" />


<TextView
android:id="@+id/title_bar_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:drawableStart="@drawable/ic_back"
tools:text="返回" />


<TextView
android:id="@+id/title_bar_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:drawableStart="@android:drawable/ic_menu"
tools:text="更多" />

</merge>

<!--</androidx.constraintlayout.widget.ConstraintLayout>-->

+ 19
- 0
lib/common/src/main/res/values/attrs.xml View File

@@ -9,4 +9,23 @@
<attr name="ic_delete_src" format="reference"/>
<attr name="ic_delete_color" format="color"/>
</declare-styleable>

<!--自定义的标题栏样式-->
<declare-styleable name="TitleBar">
<attr name="barBackground" format="color"/> <!--背景-->
<attr name="titleTextValue" format="string"/> <!--标题文字-->
<attr name="titleTextSize" format="dimension"/> <!--标题大小-->
<attr name="titleTextColor" format="color"/> <!--标题颜色-->
<attr name="rightTextValue" format="string"/> <!--右侧文字-->
<attr name="rightTextSize" format="dimension"/> <!--右侧大小-->
<attr name="rightTextColor" format="color"/> <!--右侧颜色-->
<attr name="rightSrc" format="reference|integer"/> <!--右侧图片-->
<attr name="backTextValue" format="string"/> <!--左侧文字-->
<attr name="backTextSize" format="dimension"/> <!--左侧文字大小-->
<attr name="backTextColor" format="color"/> <!--左侧大小-->
<attr name="backSrc" format="reference|integer"/> <!--左侧图片-->
<attr name="drawPadding" format="dimension"/> <!--图片drawPadding-->


</declare-styleable>
</resources>

Loading…
Cancel
Save