<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_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_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/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_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_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_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_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/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/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_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/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_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/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.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/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/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_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_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_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_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_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/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/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/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/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/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_fullscreen.xml" value="0.10144927536231885" /> | ||||
<entry key="..\:/Work/XKL/XklLocal/app/src/main/res/layout/activity_main.xml" value="0.1" /> | <entry key="..\:/Work/XKL/XklLocal/app/src/main/res/layout/activity_main.xml" value="0.1" /> | ||||
</map> | </map> | ||||
</option> | </option> | ||||
</component> | </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"> | <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" /> | <output url="file://$PROJECT_DIR$/build/classes" /> | ||||
</component> | </component> |
项目中遇到的错误: 使用jectPack时的依赖: · androidx.appcompat: appcompat | 项目中遇到的错误: 使用jectPack时的依赖: · androidx.appcompat: appcompat | ||||
依赖中已经带了livedata、viewmodel等,可以不用再依赖,再依赖时出现了异常编译错误,未解决 且 依赖版本不同,对应的livedata等版本也不同 | 依赖中已经带了livedata、viewmodel等,可以不用再依赖,再依赖时出现了异常编译错误,未解决 且 依赖版本不同,对应的livedata等版本也不同 | ||||
使用sqlcipher 需要在application 实现 : SQLiteDatabase.loadLibs(this) | |||||
kotlin 泛型 单例实现 扩展实现 导航实现 | kotlin 泛型 单例实现 扩展实现 导航实现 | ||||
svg | svg | ||||
android:fillColor="#000000"//填充颜色 | android:fillColor="#000000"//填充颜色 | ||||
android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" /> //路径的数据 | android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" /> //路径的数据 | ||||
</group> | </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 |
,"http://ws.stream.qqmusic.qq.com/C400001lePVO36SNSo.m4a?guid=748677131&vkey=A6077E31CB4A1C8A4F3D5D2BEDE8D3E16AB69FF2FEE95D9D48288EAD048AB89DC53B6846D184F842257617AF9714D97C864FB183BE05CC66&uin=&fromtag=66" | ,"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") | ,"http://ws.stream.qqmusic.qq.com/C400000ZFBf22vBvrf.m4a?guid=921100582&vkey=C9EA33972404E2AC1C80A479614AD44A9BF132A16F9B307D9515F595B41901AE53F6681E28BDA799FC047F30D624F8A19AE9C4856ADBEF4B&uin=&fromtag=66") | ||||
image包: 实现了图片加载的封装 | 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 | Kotlin的单例模式: https://developer.aliyun.com/article/642618 | ||||
饿汉式(直接获取实例)、懒汉式(一次判空获取实例)、双重校验锁 | 饿汉式(直接获取实例)、懒汉式(一次判空获取实例)、双重校验锁 | ||||
object 声明: 为饿汉式单例,即直接实例 | object 声明: 为饿汉式单例,即直接实例 | ||||
数字精度格式: https://www.jianshu.com/p/5286a44ed9b1 |
implementation project(path: ':lib:common') | implementation project(path: ':lib:common') | ||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' | implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' | ||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-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} | rootProject.ext.dependencies_required.each{ k, v -> implementation v} | ||||
testImplementation rootProject.ext.dependencies_testImplementation.junit | testImplementation rootProject.ext.dependencies_testImplementation.junit | ||||
rootProject.ext.dependencies_androidTestImplementation.each{ k,v -> androidTestImplementation v} | 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 | |||||
} | } |
package="com.xkl.cdl"> | package="com.xkl.cdl"> | ||||
<application | <application | ||||
android:name=".module.XKLApplication" | |||||
android:allowBackup="true" | android:allowBackup="true" | ||||
android:icon="@mipmap/ic_launcher" | android:icon="@mipmap/ic_launcher" | ||||
android:label="@string/app_name" | android:label="@string/app_name" | ||||
android:supportsRtl="true" | android:supportsRtl="true" | ||||
android:theme="@style/Theme.XklLocal" | android:theme="@style/Theme.XklLocal" | ||||
tools:ignore="LockedOrientationActivity"> | tools:ignore="LockedOrientationActivity"> | ||||
<activity | |||||
android:name=".module.m_center_learn.CourseMainActivity" | |||||
android:exported="true" /> | |||||
<activity | <activity | ||||
android:name=".module.splash.SplashActivity" | android:name=".module.splash.SplashActivity" | ||||
android:configChanges="orientation|keyboardHidden|screenSize" | android:configChanges="orientation|keyboardHidden|screenSize" | ||||
android:theme="@style/Theme.Splash"> | android:theme="@style/Theme.Splash"> | ||||
<intent-filter> | <intent-filter> | ||||
<action android:name="android.intent.action.MAIN" /> | <action android:name="android.intent.action.MAIN" /> | ||||
<category android:name="android.intent.category.LAUNCHER" /> | <category android:name="android.intent.category.LAUNCHER" /> | ||||
</intent-filter> | </intent-filter> | ||||
</activity> | </activity> |
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 | |||||
} | |||||
} | |||||
} |
* create 2022/3/18 9:34 | * create 2022/3/18 9:34 | ||||
* Describe: | * 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 //课外练习 | |||||
} | } |
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 | |||||
} |
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%" | |||||
} | |||||
} | |||||
} |
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 //课程复习数 | |||||
} |
import androidx.databinding.BaseObservable | import androidx.databinding.BaseObservable | ||||
import androidx.databinding.Bindable | import androidx.databinding.Bindable | ||||
import com.xkl.cdl.BR | |||||
/** | /** | ||||
* author suliang | * author suliang | ||||
* create 2022/3/17 18:12 | * 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 | |||||
} | |||||
} |
package com.xkl.cdl.data.manager | 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 com.xkl.cdl.data.course.CoursePack | ||||
import java.io.File | |||||
/** | /** | ||||
* author suliang | * author suliang | ||||
*/ | */ | ||||
object CourseManager { | 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) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | } |
package com.xkl.cdl.data.manager.db | |||||
/** | |||||
* author suliang | |||||
* create 2022/3/21 16:10 | |||||
* Describe: 作文数据库 | |||||
*/ | |||||
object CompositionDbManager : IDB { | |||||
} |
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 | |||||
} | |||||
} | |||||
} |
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 | |||||
} | |||||
} |
package com.xkl.cdl.data.manager.db | |||||
/** | |||||
* author suliang | |||||
* create 2022/3/21 16:09 | |||||
* Describe: 英语数据库 | |||||
*/ | |||||
object EnglishDbManager : IDB { | |||||
} |
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>) | |||||
} |
package com.xkl.cdl.module | package com.xkl.cdl.module | ||||
import android.app.Application | import android.app.Application | ||||
import com.suliang.common.util.LogUtil | |||||
import net.sqlcipher.database.SQLiteDatabase | |||||
import java.util.* | |||||
/** | /** | ||||
* author suliang | * author suliang | ||||
override fun onCreate() { | override fun onCreate() { | ||||
super.onCreate() | super.onCreate() | ||||
SQLiteDatabase.loadLibs(this) | |||||
// ImageLoader.mStrategy = GlideLoaderStrategy() | // ImageLoader.mStrategy = GlideLoaderStrategy() | ||||
LogUtil.e(UUID.randomUUID().toString().replace("-","")) | |||||
} | } |
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) | |||||
} | |||||
} |
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() { | |||||
} |
package com.xkl.cdl.module.m_center_learn | package com.xkl.cdl.module.m_center_learn | ||||
import android.os.Bundle | import android.os.Bundle | ||||
import androidx.core.content.res.ResourcesCompat | |||||
import androidx.lifecycle.Lifecycle | |||||
import androidx.lifecycle.ViewModel | import androidx.lifecycle.ViewModel | ||||
import androidx.lifecycle.ViewModelProvider | import androidx.lifecycle.ViewModelProvider | ||||
import androidx.recyclerview.widget.DividerItemDecoration | |||||
import androidx.recyclerview.widget.LinearLayoutManager | |||||
import com.suliang.common.Config | import com.suliang.common.Config | ||||
import com.suliang.common.base.fragment.BaseFragmentVM | import com.suliang.common.base.fragment.BaseFragmentVM | ||||
import com.suliang.common.util.LogUtil | 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.databinding.FragmentCoursePackBinding | ||||
import com.xkl.cdl.module.main.MainActivityViewModel | import com.xkl.cdl.module.main.MainActivityViewModel | ||||
*/ | */ | ||||
class CoursePackFragment : BaseFragmentVM<FragmentCoursePackBinding,CoursePackFragmentViewModel>() { | class CoursePackFragment : BaseFragmentVM<FragmentCoursePackBinding,CoursePackFragmentViewModel>() { | ||||
companion object { | companion object { | ||||
@JvmStatic | @JvmStatic | ||||
fun newInstance(projectId: Long) = CoursePackFragment().apply { | |||||
fun newInstance(projectId: Int) = CoursePackFragment().apply { | |||||
arguments = Bundle().apply { | arguments = Bundle().apply { | ||||
putLong(Config.INTENT_1,projectId) | |||||
putInt(Config.INTENT_1,projectId) | |||||
} | } | ||||
} | } | ||||
} | } | ||||
override fun initViewModel(): CoursePackFragmentViewModel { | 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 { | return ViewModelProvider(this,ViewModelFactory(projectId)).get(CoursePackFragmentViewModel::class.java).apply { | ||||
this.mainActivityViewModel = ViewModelProvider(requireActivity()).get(MainActivityViewModel::class.java) | this.mainActivityViewModel = ViewModelProvider(requireActivity()).get(MainActivityViewModel::class.java) | ||||
this.learnCenterFragmentViewModel = ViewModelProvider(requireParentFragment()).get(LearnCenterFragmentViewModel::class.java) | this.learnCenterFragmentViewModel = ViewModelProvider(requireParentFragment()).get(LearnCenterFragmentViewModel::class.java) | ||||
} | } | ||||
} | } | ||||
override fun initFragment() { | 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() { | 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 工厂类,提供传参 */ | /** ViewModle 工厂类,提供传参 */ | ||||
inner class ViewModelFactory(private val projectId:Long): ViewModelProvider.Factory{ | |||||
inner class ViewModelFactory(private val projectId:Int): ViewModelProvider.Factory{ | |||||
@Suppress("UNCHECKED_CAST") | @Suppress("UNCHECKED_CAST") | ||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T { | override fun <T : ViewModel?> create(modelClass: Class<T>): T { | ||||
return CoursePackFragmentViewModel(projectId) as T | return CoursePackFragmentViewModel(projectId) as T |
package com.xkl.cdl.module.m_center_learn | package com.xkl.cdl.module.m_center_learn | ||||
import androidx.lifecycle.MutableLiveData | |||||
import com.suliang.common.base.viewmodel.BaseViewModel | 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 | 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) | |||||
} | |||||
} | } |
import android.graphics.Typeface | import android.graphics.Typeface | ||||
import android.util.TypedValue | import android.util.TypedValue | ||||
import android.view.LayoutInflater | |||||
import android.widget.TextView | import android.widget.TextView | ||||
import androidx.core.content.ContextCompat | import androidx.core.content.ContextCompat | ||||
import androidx.lifecycle.ViewModelProvider | import androidx.lifecycle.ViewModelProvider | ||||
override fun initFragment() { | override fun initFragment() { | ||||
//初始化项目子tab和标题 | //初始化项目子tab和标题 | ||||
vm.initLearnCenterFragment() | vm.initLearnCenterFragment() | ||||
binding.viewModel = vm | |||||
//必须先进行ViewPager2的适配器 设置 | //必须先进行ViewPager2的适配器 设置 | ||||
binding.viewPager2.adapter = ViewPagerAdapter(this, vm.mProjectFragments) | binding.viewPager2.adapter = ViewPagerAdapter(this, vm.mProjectFragments) | ||||
} | } | ||||
//选中项恢复搜索框的数据 | //选中项恢复搜索框的数据 | ||||
tab?.run { | tab?.run { | ||||
vm.mCoursePackSearchValueMap[position]?.let { | |||||
binding.editTextSearch.apply { | |||||
setText(it) | |||||
setSelection(it.length) | |||||
} | |||||
}?:let { //为空,如果输入框也为空,则不操作,否则设置为null | |||||
if (!binding.editTextSearch.text.isNullOrEmpty()) { | |||||
binding.editTextSearch.setText("") | |||||
} | |||||
} | |||||
} | } | ||||
} | } | ||||
}.attach() | }.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 -> | // binding.editTextSearch.setOnEditorActionListener { v, actionId, event -> |
var mProjectFragments = mutableListOf<Fragment>() | var mProjectFragments = mutableListOf<Fragment>() | ||||
/** 学习中心 子 tab title **/ | /** 学习中心 子 tab title **/ | ||||
var mProjectTitles = mutableListOf<Int>() | var mProjectTitles = mutableListOf<Int>() | ||||
/** 搜索框的双向数据 */ | |||||
/** 搜索框数据的双向绑定 */ | |||||
val mCoursePackSearchLiveData = MutableLiveData<String>() | val mCoursePackSearchLiveData = MutableLiveData<String>() | ||||
//保存对应子fragment的搜索内容 | |||||
val mCoursePackSearchValueMap = hashMapOf<Int,String>() | |||||
/** 实例化项目课程包Fragment和显示Title */ | /** 实例化项目课程包Fragment和显示Title */ |
import android.annotation.SuppressLint | import android.annotation.SuppressLint | ||||
import android.os.Bundle | 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.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.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.databinding.ActivitySplashBinding | ||||
import com.xkl.cdl.module.main.MainActivity | 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 | import java.util.concurrent.TimeUnit | ||||
@SuppressLint("CustomSplashScreen") | @SuppressLint("CustomSplashScreen") | ||||
} | } | ||||
override fun loadData() { | 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) | |||||
} | |||||
} | } | ||||
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(); | |||||
} | |||||
} |
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 | |||||
} | |||||
} |
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 -> {} | |||||
} | |||||
} | |||||
} |
<?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> |
<?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> |
<?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> |
<?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> |
android:layout_height="match_parent" | android:layout_height="match_parent" | ||||
tools:context=".module.m_center_learn.CoursePackFragment"> | 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_width="match_parent" | ||||
android:layout_height="match_parent" | android:layout_height="match_parent" | ||||
android:text="@string/hello_blank_fragment" /> | |||||
android:layout_marginTop="8dp"/> | |||||
</FrameLayout> | </FrameLayout> |
<data> | <data> | ||||
<variable | <variable | ||||
name="viewmodel" | |||||
name="viewModel" | |||||
type="com.xkl.cdl.module.m_center_learn.LearnCenterFragmentViewModel" /> | type="com.xkl.cdl.module.m_center_learn.LearnCenterFragmentViewModel" /> | ||||
</data> | </data> | ||||
<com.google.android.material.tabs.TabLayout | <com.google.android.material.tabs.TabLayout | ||||
android:id="@+id/tab_layout" | android:id="@+id/tab_layout" | ||||
android:layout_width="match_parent" | android:layout_width="match_parent" | ||||
android:layout_height="@dimen/title_bar_height" | |||||
android:layout_height="48dp" | |||||
android:layout_marginStart="16dp" | android:layout_marginStart="16dp" | ||||
app:tabContentStart="4dp" | app:tabContentStart="4dp" | ||||
app:tabIndicatorHeight="0dp" | app:tabIndicatorHeight="0dp" | ||||
android:paddingRight="16dp" | android:paddingRight="16dp" | ||||
android:textColor="@color/main_text_color" | android:textColor="@color/main_text_color" | ||||
android:textSize="@dimen/normalSize" | android:textSize="@dimen/normalSize" | ||||
android:focusable="true" | |||||
android:focusableInTouchMode="true" | |||||
android:text="@={viewModel.MCoursePackSearchLiveData}" | |||||
/> | /> | ||||
<?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> | |||||
<?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> |
<?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> |
<?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> |
<?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> |
<?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> |
<?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>--> |
<resources> | <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> | </resources> |
<color name="gray_2">#8A8A99</color> | <color name="gray_2">#8A8A99</color> | ||||
<color name="white">#FFFFFFFF</color> | <color name="white">#FFFFFFFF</color> | ||||
<color name="white_1">#FFF5F5F5</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> | </resources> |
<dimen name="normalSize">16dp</dimen> | <dimen name="normalSize">16dp</dimen> | ||||
<dimen name="bigSize">18dp</dimen> | <dimen name="bigSize">18dp</dimen> | ||||
<dimen name="biggerSize">20dp</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> | </resources> |
<string name="project_english">英语</string> | <string name="project_english">英语</string> | ||||
<string name="project_chinese">语文</string> | <string name="project_chinese">语文</string> | ||||
<string name="search_hint_input_course_name">输入课程内容...</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> | </resources> |
<resources> | <resources> | ||||
<!--https://juejin.cn/post/6971647089503633438 玩转ShapeableImageView --> | |||||
<!--圆角图片--> | |||||
<style name="roundedCornerStyle"> | |||||
<item name="cornerFamily">rounded</item> | |||||
<item name="cornerSize">8dp</item> | |||||
</style> | |||||
</resources> | </resources> |
<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> |
<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> |
<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> |
<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> |
<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> |
<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> |
appcompat_version: "1.4.1", | appcompat_version: "1.4.1", | ||||
material_version : "1.3.0", | material_version : "1.3.0", | ||||
lifecycle_version: "2.5.0-alpha02", | lifecycle_version: "2.5.0-alpha02", | ||||
glide_version : "4.11.0", | |||||
glide_version : "4.11.0", | |||||
] | ] | ||||
//必须依赖 | //必须依赖 | ||||
dependencies_required = [ | dependencies_required = [ | ||||
//为属于Android框架的通用库提供扩展程序 | //为属于Android框架的通用库提供扩展程序 | ||||
core_ktx : "androidx.core:core-ktx:${versions.core_ktx_version}", | |||||
core_ktx : "androidx.core:core-ktx:${versions.core_ktx_version}", | |||||
//Androidx 依赖 | //Androidx 依赖 | ||||
appcompat : "androidx.appcompat:appcompat:${versions.appcompat_version}", | |||||
appcompat : "androidx.appcompat:appcompat:${versions.appcompat_version}", | |||||
//material_design | //material_design | ||||
material : "com.google.android.material:material:${versions.material_version}", | |||||
material : "com.google.android.material:material:${versions.material_version}", | |||||
//constraintlayout | //constraintlayout | ||||
constraintlayout : "androidx.constraintlayout:constraintlayout:2.0.4", | |||||
constraintlayout: "androidx.constraintlayout:constraintlayout:2.0.4", | |||||
//lifecycle | //lifecycle | ||||
// lifecycle : "androidx.lifecycle:lifecycle-runtime-ktx:${versions.lifecycle_version}", | // lifecycle : "androidx.lifecycle:lifecycle-runtime-ktx:${versions.lifecycle_version}", | ||||
// //lifecycle saved state module for viewmodel | // //lifecycle saved state module for viewmodel | ||||
//依赖项 | //依赖项 | ||||
dependencies_custom = [ | dependencies_custom = [ | ||||
//设置状态栏和导航栏的框架 https://github.com/Zackratos/UltimateBarX | //设置状态栏和导航栏的框架 https://github.com/Zackratos/UltimateBarX | ||||
UltimateBarX: "com.gitee.zackratos:UltimateBarX:0.8.0", | |||||
UltimateBarX : "com.gitee.zackratos:UltimateBarX:0.8.0", | |||||
//Glide 依赖和配置 | //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 | //Glide translation | ||||
GlideTranslation: "jp.wasabeef:glide-transformations:4.3.0", | GlideTranslation: "jp.wasabeef:glide-transformations:4.3.0", | ||||
//Activity_ktx Activity扩展 | //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" | |||||
] | ] | ||||
def customDependencies = rootProject.ext.dependencies_custom | def customDependencies = rootProject.ext.dependencies_custom | ||||
//Activity-ktx 扩展 | |||||
api customDependencies.Activity_ktx | |||||
//状态栏 | //状态栏 | ||||
api customDependencies.UltimateBarX | api customDependencies.UltimateBarX | ||||
//glide | //glide | ||||
//glide translation | //glide translation | ||||
api customDependencies.GlideTranslation | api customDependencies.GlideTranslation | ||||
//Activity-ktx 扩展 | |||||
api customDependencies.Activity_ktx | |||||
# hide the original source file name. | # hide the original source file name. | ||||
#-renamesourcefileattribute SourceFile | #-renamesourcefileattribute SourceFile | ||||
#Glide | |||||
-keep public class * implements com.bumptech.glide.module.GlideModule | -keep public class * implements com.bumptech.glide.module.GlideModule | ||||
-keep public class * extends com.bumptech.glide.module.AppGlideModule | -keep public class * extends com.bumptech.glide.module.AppGlideModule | ||||
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** { | -keep public enum com.bumptech.glide.load.ImageHeaderParser$** { | ||||
**[] $VALUES; | **[] $VALUES; | ||||
public *; | public *; | ||||
} | |||||
} | |||||
#SqlCiper | |||||
-keep,includedescriptorclasses class net.sqlcipher.** { *; } | |||||
-keep,includedescriptorclasses interface net.sqlcipher.** { *; } |
} | } | ||||
} | } | ||||
/** 单独封装一个方法,用于进行无参数的activity的跳转 */ | |||||
override fun startActivity(clazz: Class<*>) { | override fun startActivity(clazz: Class<*>) { | ||||
startActivity(Intent(this,clazz)) | 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 | // 反射实现 ViewBinding | ||||
// @SuppressWarnings("unchecked") | // @SuppressWarnings("unchecked") |
package com.suliang.common.base.adapter | package com.suliang.common.base.adapter | ||||
import android.annotation.SuppressLint | |||||
import android.view.LayoutInflater | |||||
import android.view.View | |||||
import android.view.ViewGroup | import android.view.ViewGroup | ||||
import androidx.databinding.DataBindingUtil | |||||
import androidx.recyclerview.widget.RecyclerView | import androidx.recyclerview.widget.RecyclerView | ||||
import androidx.viewbinding.ViewBinding | |||||
abstract class BaseAdapter<T> : | abstract class BaseAdapter<T> : | ||||
RecyclerView.Adapter<BaseAdapterViewHolder>() { | RecyclerView.Adapter<BaseAdapterViewHolder>() { | ||||
companion object{ | 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 | var needShowEmptyView = false | ||||
abstract fun onBindVH(holder: BaseAdapterViewHolder,position: Int) | 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>? | * @param data List<T>? | ||||
*/ | */ | ||||
@SuppressLint("NotifyDataSetChanged") | |||||
fun setData(data: MutableList<T>?) { | fun setData(data: MutableList<T>?) { | ||||
mData.clear() | |||||
data?.let { it -> //不为空 | data?.let { it -> //不为空 | ||||
mData = it | |||||
mData.addAll(data) | |||||
} ?: let { //为空 | } ?: let { //为空 | ||||
mData = mutableListOf() | |||||
mData.clear() | |||||
} | } | ||||
notifyItemRangeChanged(0, mData.size) | |||||
notifyDataSetChanged() | |||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } |
import androidx.lifecycle.ViewModel | import androidx.lifecycle.ViewModel | ||||
abstract class BaseAdapterVM<T,VM:ViewModel> : BaseAdapter<T>() { | |||||
abstract class BaseAdapterVM<T,VM:ViewModel>(val vm:VM) : BaseAdapter<T>() { | |||||
} | } |
import androidx.databinding.ViewDataBinding | import androidx.databinding.ViewDataBinding | ||||
import androidx.recyclerview.widget.RecyclerView | 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) |
package com.suliang.common.base.adapter | |||||
import android.view.View | |||||
/** | |||||
* author suliang | |||||
* create 2022/3/23 18:04 | |||||
* Describe: | |||||
*/ | |||||
interface IItemClickListener: View.OnClickListener { | |||||
} |
override fun onCreateView( | override fun onCreateView( | ||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? | inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? | ||||
): View? { | ): View? { | ||||
_binding = initBinding(inflater, container) | |||||
initCustomBinding(inflater,container) | |||||
return binding.root | return binding.root | ||||
} | } | ||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||||
super.onViewCreated(view, savedInstanceState) | |||||
initFirst() | initFirst() | ||||
initFragment() | initFragment() | ||||
super.onViewCreated(view, savedInstanceState) | |||||
} | } | ||||
override fun onResume() { | override fun onResume() { | ||||
_binding = null | _binding = null | ||||
} | } | ||||
open fun initCustomBinding(inflater: LayoutInflater,container: ViewGroup?){ | |||||
_binding = initBinding(inflater, container) | |||||
} | |||||
/** | /** | ||||
* onViewCreated中进行的初始:多数时候可以是空实现,主要在使用VM时进行vm的初始 | * onViewCreated中进行的初始:多数时候可以是空实现,主要在使用VM时进行vm的初始 | ||||
*/ | */ |
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 | |||||
} | |||||
} |
* 用于获取全局Applicaiton | * 用于获取全局Applicaiton | ||||
*/ | */ | ||||
object AppGlobals { | object AppGlobals { | ||||
val application by lazy { | |||||
val application : Application by lazy { | |||||
try { | 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){ | }catch (e:Exception){ | ||||
e.printStackTrace() | |||||
throw RuntimeException("AppGlobals获取Application异常") | throw RuntimeException("AppGlobals获取Application异常") | ||||
} | } | ||||
} | } |
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 | |||||
} | |||||
} | |||||
} |
* author suliang | * author suliang | ||||
* create 2022/3/7 10:55 | * create 2022/3/7 10:55 | ||||
* Describe: Log日志打印框架 | * Describe: Log日志打印框架 | ||||
* https://blog.csdn.net/qq5201314259/article/details/50749438 | |||||
*/ | */ | ||||
object LogUtil { | object LogUtil { | ||||
private val LogEnable = | private val LogEnable = | ||||
BuildConfig.LOG_ENABLE // 根据BuildConfig中的field LOG_ENABLE配置的值,确定是否打印 | 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) { | 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) { | 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) { | 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) { | 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) { | fun w(message: String) { | ||||
log("w",message) | |||||
} | |||||
private fun log(logType: String, message: String) { | |||||
if (!LogEnable) return | 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) | |||||
} | |||||
} | } | ||||
} | } |
private fun initTranslation(option: ImageLoaderOption): MultiTransformation<Bitmap> { | private fun initTranslation(option: ImageLoaderOption): MultiTransformation<Bitmap> { | ||||
val list: MutableList<Transformation<Bitmap>> = ArrayList() | val list: MutableList<Transformation<Bitmap>> = ArrayList() | ||||
list.add(BlurTransformation(25, 3)) | |||||
//gravity 模式 | //gravity 模式 | ||||
if (option.centerInsideEnable) { | if (option.centerInsideEnable) { | ||||
list.add(CenterInside()) | list.add(CenterInside()) |
}) | }) | ||||
} | } | ||||
fun loadImage(view: ImageView, imageByteArray: ByteArray) { | |||||
loadImage(ImageLoaderOption().apply { | |||||
this.byteArray = imageByteArray | |||||
targetView = view | |||||
}) | |||||
} | |||||
// override fun clearMemoryCache() { | // override fun clearMemoryCache() { | ||||
// mStrategyBase.clearMemoryCache() | // mStrategyBase.clearMemoryCache() |
val diskIO: ExecutorService = Executors.newSingleThreadExecutor() | val diskIO: ExecutorService = Executors.newSingleThreadExecutor() | ||||
/** 网络线程池 网络请求、异步任务等适用此线程 不建议在此线程 sleep 或者 wait */ | /** 网络线程池 网络请求、异步任务等适用此线程 不建议在此线程 sleep 或者 wait */ | ||||
val netWork: ExecutorService = Executors.newCachedThreadPool() | |||||
val io: ExecutorService = Executors.newCachedThreadPool() | |||||
/**主线程*/ | /**主线程*/ | ||||
val mainThread: Executor = MainThreadExecutor() | val mainThread: Executor = MainThreadExecutor() |
import android.view.MotionEvent | import android.view.MotionEvent | ||||
import android.view.inputmethod.EditorInfo | import android.view.inputmethod.EditorInfo | ||||
import androidx.appcompat.widget.AppCompatEditText | import androidx.appcompat.widget.AppCompatEditText | ||||
import androidx.core.content.res.ResourcesCompat | |||||
import com.suliang.common.R | import com.suliang.common.R | ||||
import com.suliang.common.util.LogUtil | |||||
import com.suliang.common.util.os.KeyboardUtil | import com.suliang.common.util.os.KeyboardUtil | ||||
/** | /** | ||||
*/ | */ | ||||
@SuppressLint("UseCompatLoadingForDrawables") | @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 drawableStart: Drawable? = null | ||||
private var drawableEnd: Drawable? = null | private var drawableEnd: Drawable? = null | ||||
init { | init { | ||||
attrs?.let { | attrs?.let { | ||||
context.obtainStyledAttributes(attrs, R.styleable.InputSearchEditText, defStyleAttr, 0).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() | 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) | 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?) { | override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) { | ||||
super.onFocusChanged(focused, direction, previouslyFocusedRect) | 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) { | override fun onTextChanged(text: CharSequence?, start: Int, lengthBefore: Int, lengthAfter: Int) { | ||||
super.onTextChanged(text, start, lengthBefore, lengthAfter) | 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) | |||||
// } | |||||
} | } |
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); | |||||
} | |||||
} |
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) | |||||
} | |||||
} | |||||
} |
<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> |
<?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>--> |
<attr name="ic_delete_src" format="reference"/> | <attr name="ic_delete_src" format="reference"/> | ||||
<attr name="ic_delete_color" format="color"/> | <attr name="ic_delete_color" format="color"/> | ||||
</declare-styleable> | </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> | </resources> |