Browse Source

词典功能完成

master
suliang 2 years ago
parent
commit
19ae0067ea
27 changed files with 918 additions and 481 deletions
  1. 1
    1
      .idea/misc.xml
  2. 14
    4
      app/build.gradle
  3. BIN
      app/src/main/assets/dictionary.db
  4. BIN
      app/src/main/assets/vocabulary.db
  5. 190
    41
      app/src/main/java/com/xkl/cdl/adapter/DictionaryAdapter.kt
  6. 1
    1
      app/src/main/java/com/xkl/cdl/data/bean/DictionaryBean.kt
  7. 4
    2
      app/src/main/java/com/xkl/cdl/data/bean/DictionaryItem.kt
  8. 16
    2
      app/src/main/java/com/xkl/cdl/data/exam_record/AppDatabase.kt
  9. 15
    1
      app/src/main/java/com/xkl/cdl/data/manager/FilePathManager.kt
  10. 59
    8
      app/src/main/java/com/xkl/cdl/data/manager/db/DictionaryManager.kt
  11. 1
    1
      app/src/main/java/com/xkl/cdl/data/manager/db/VocabularyManager.kt
  12. 98
    68
      app/src/main/java/com/xkl/cdl/data/repository/AudioCache.kt
  13. 3
    3
      app/src/main/java/com/xkl/cdl/module/learn/LearnExamActivity.kt
  14. 1
    4
      app/src/main/java/com/xkl/cdl/module/learn/LearnWordActivity.kt
  15. 1
    4
      app/src/main/java/com/xkl/cdl/module/m_memo/MemoListDetailActivity.kt
  16. 112
    79
      app/src/main/java/com/xkl/cdl/module/m_service_center/DictionaryActivity.kt
  17. 107
    30
      app/src/main/java/com/xkl/cdl/module/m_service_center/DictionaryViewModel.kt
  18. 5
    3
      app/src/main/java/com/xkl/cdl/module/splash/SplashActivity.kt
  19. 5
    4
      app/src/main/java/com/xkl/cdl/util/ViewUtil.kt
  20. 17
    8
      app/src/main/res/layout/activity_dictionary.xml
  21. 47
    41
      app/src/main/res/layout/item_dic_detail_chinese.xml
  22. 57
    51
      app/src/main/res/layout/item_dic_detail_chinese_for_english_item.xml
  23. 85
    78
      app/src/main/res/layout/item_dic_detail_english.xml
  24. 45
    39
      app/src/main/res/layout/item_dic_search.xml
  25. 2
    1
      lib/common/src/main/java/com/suliang/common/util/LogUtil.kt
  26. 22
    6
      lib/common/src/main/java/com/suliang/common/util/StringUtil.kt
  27. 10
    1
      lib/common/src/main/java/com/suliang/common/util/file/FileUtil.kt

+ 1
- 1
.idea/misc.xml View File

<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_checkbox.xml" value="1.0" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_checkbox.xml" value="1.0" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_course_lesson.xml" value="0.4785615491009682" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_course_lesson.xml" value="0.4785615491009682" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_dic_detail_chinese.xml" value="0.33242753623188404" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_dic_detail_chinese.xml" value="0.33242753623188404" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_dic_detail_chinese_for_english_item.xml" value="0.75" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_dic_detail_chinese_for_english_item.xml" value="0.6032138442521632" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_dic_detail_english.xml" value="0.5" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_dic_detail_english.xml" value="0.5" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_dic_history_chinese.xml" value="0.23632218844984804" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_dic_history_chinese.xml" value="0.23632218844984804" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_dic_history_english.xml" value="0.23632218844984804" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_dic_history_english.xml" value="0.23632218844984804" />

+ 14
- 4
app/build.gradle View File

versionName androidConfig.version_name versionName androidConfig.version_name
multiDexEnabled true //解决64k 分包限制 multiDexEnabled true //解决64k 分包限制
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
//java环境roomSchemaLocation配置
// javaCompileOptions {
// annotationProcessorOptions {
// arguments = [
// "room.schemaLocation" : "$projectDir/schemas".toString(),
// "room.incremental" : "true",
// "room.expandProjection" :"true"
// ]
// }
// }
//Kotlin环境RoomSchemaLocation配置
kapt{
arguments{
arg("room.schemaLocation","$projectDir/schemas")
} }
} }



BIN
app/src/main/assets/dictionary.db View File


BIN
app/src/main/assets/vocabulary.db View File


+ 190
- 41
app/src/main/java/com/xkl/cdl/adapter/DictionaryAdapter.kt View File

package com.xkl.cdl.adapter package com.xkl.cdl.adapter


import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import com.suliang.common.base.adapter.BaseAdapterViewHolder import com.suliang.common.base.adapter.BaseAdapterViewHolder
import com.suliang.common.base.adapter.BaseRVAdapterVM import com.suliang.common.base.adapter.BaseRVAdapterVM
import com.suliang.common.databinding.ItemEmptyBinding import com.suliang.common.databinding.ItemEmptyBinding
import com.suliang.common.extension.click
import com.suliang.common.util.StringUtil
import com.xkl.cdl.R import com.xkl.cdl.R
import com.xkl.cdl.data.AppConstants
import com.xkl.cdl.data.bean.DictionaryItem import com.xkl.cdl.data.bean.DictionaryItem
import com.xkl.cdl.data.manager.UserInfoManager
import com.xkl.cdl.data.repository.AudioCache
import com.xkl.cdl.databinding.ItemDicDetailChineseBinding
import com.xkl.cdl.databinding.ItemDicDetailChineseForEnglishItemBinding
import com.xkl.cdl.databinding.ItemDicDetailEnglishBinding
import com.xkl.cdl.databinding.ItemDicSearchBinding
import com.xkl.cdl.initValue
import com.xkl.cdl.module.m_service_center.DictionaryViewModel import com.xkl.cdl.module.m_service_center.DictionaryViewModel


/** /**
* create 2022/8/4 10:41 * create 2022/8/4 10:41
* Describe: * Describe:
*/ */
class DictionaryAdapter(vm:DictionaryViewModel) : BaseRVAdapterVM<DictionaryItem,DictionaryViewModel>(vm) {
class DictionaryAdapter(vm : DictionaryViewModel) : BaseRVAdapterVM<DictionaryItem, DictionaryViewModel>(vm) {
override fun getItemCount() : Int {
//历史记录,不显示空状态
return if (vm.detailState == 1) {
//大于页数显示的总数量,则显示指定数量,否则显示全部
val showCount = vm.histroyShowPage * vm.historyPageSize
if (getData().size >= showCount){
showCount
}else{
getData().size
/**
* 获取显示 viewType
* 1 如果允许显示空 则显示空
* 2 历史记录列表和关键字搜索列表,展示type为 3
* 3 详情 item type 为item的form 1英文 2中文
* @param position Int
* @return Int
*/
override fun getItemViewType(position : Int) : Int {
return when {
enableEmptyPosition(position) -> TYPE_EMPTY
else -> when(vm.detailState){
//历史记录和关键字搜索结果列表
DictionaryViewModel.STATE_HISTROY,DictionaryViewModel.STATE_SEARCH -> {
3
}
//单条记录结果
else -> getItem(position).form //详情展示
} }
}else if (needShowEmptyView && getData().isEmpty()) 1 else getData().size
}
} }
override fun onBindEmptyViewHolder(holder : BaseAdapterViewHolder) {
(holder.binding as ItemEmptyBinding).run {
//根据监听,显示具体的内容
vm.etSearchLiveData.value?.let {
if (it.isNotEmpty()) {
imgEmpty.setImageResource(R.mipmap.empty_nothing_search)
tvContent.text = "没有搜索到任何内容"
} else {
imgEmpty.setImageResource(R.mipmap.empty_nothing)
tvContent.text = "没有数据"
/**
* 空状态为 1
* 列表状态根据可显示数量,进行显示
* 详情就直接显示数量
* @return Int
*/
override fun getItemCount() : Int {
return when {
//允许空显示,且数据为空时,显示空界面
needShowEmptyView && getData().isEmpty() -> {
1
}
else -> when (vm.detailState) {
DictionaryViewModel.STATE_HISTROY -> {
val showCount = vm.histroyShowPage * vm.pageShowCount
if (getData().size >= showCount) {
showCount
} else {
getData().size
}
}
DictionaryViewModel.STATE_SEARCH -> {
val showCount = vm.searchShowPage * vm.pageShowCount
if (getData().size >= showCount) {
showCount
} else {
getData().size
}
}
//单条记录结果
else -> {
getData().size
} }
} }
} }
} }
override fun getItemViewType(position : Int) : Int {
return when{
enableEmptyPosition(position) -> TYPE_EMPTY
//单条详情
vm.searchItemDetailAble -> {
val item = getItem(position)
item.form //1英文 2 中文
}
//多条记录
else -> 3
/**
* 空状态显示
* @param holder BaseAdapterViewHolder
*/
override fun onBindEmptyViewHolder(holder : BaseAdapterViewHolder) {
(holder.binding as ItemEmptyBinding).run {
imgEmpty.setImageResource(R.mipmap.empty_nothing_search)
tvContent.text = "没有搜索到任何内容"
} }
} }

override fun coverViewHolder(parent : ViewGroup, viewType : Int) : BaseAdapterViewHolder { override fun coverViewHolder(parent : ViewGroup, viewType : Int) : BaseAdapterViewHolder {
return when(viewType){
1 -> BaseAdapterViewHolder(inflateBinding(parent,R.layout.item_dic_detail_english)) //英文搜索结果详情
2 -> BaseAdapterViewHolder(inflateBinding(parent,R.layout.item_dic_detail_chinese)) //中文搜索结果详情
else -> BaseAdapterViewHolder(inflateBinding(parent,R.layout.item_dic_search)) //记录列表 历史和搜索记录列表
return when (viewType) {
1 -> BaseAdapterViewHolder(inflateBinding(parent, R.layout.item_dic_detail_english)) //英文搜索结果详情
2 -> BaseAdapterViewHolder(inflateBinding(parent, R.layout.item_dic_detail_chinese)) //中文搜索结果详情
else -> BaseAdapterViewHolder(inflateBinding(parent, R.layout.item_dic_search)) //记录列表 历史和搜索记录列表
} }
} }
override fun onBindVH(holder : BaseAdapterViewHolder, position : Int) { override fun onBindVH(holder : BaseAdapterViewHolder, position : Int) {
val itemViewType = getItemViewType(position) val itemViewType = getItemViewType(position)
when(itemViewType){
when (itemViewType) {
//英文
1 -> { 1 -> {
getItem(position).let {
(holder.binding as ItemDicDetailEnglishBinding).run {
tvWord.text = it.word
when {
it.phonetic_uk.isNullOrEmpty() -> tvPhoneticUk.visibility = View.GONE
else -> tvPhoneticUk.apply {
visibility = View.VISIBLE
text = "英 ${it.phonetic_uk}"
click { v -> //发音
AudioCache.getDictionaryVoice(it.id,AppConstants.SOUND_TYPE_UK)
}
}
}
when {
it.phonectic_us.isNullOrEmpty() -> tvPhoneticUs.visibility = View.GONE
else -> tvPhoneticUs.apply {
visibility = View.VISIBLE
text = "美 ${it.phonectic_us}"
click { v -> //发音
AudioCache.getDictionaryVoice(it.id,AppConstants.SOUND_TYPE_US)
}
}
}
when {
it.basic_explaination.isNullOrEmpty() -> tvExplain.visibility = View.GONE
else -> tvExplain.apply {
visibility = View.VISIBLE
text = it.basic_explaination
}
}
when {
it.all_explaination.isNullOrEmpty() -> tvExpandExplain.visibility = View.GONE
else -> tvExpandExplain.apply {
visibility = View.VISIBLE
text = "扩展释义: ${it.all_explaination}"
}
}
incWordDetail.initValue(it.phrase, it.example, it.reference)
}
}
} }
//中文
2 -> { 2 -> {
getItem(position).let {
(holder.binding as ItemDicDetailChineseBinding).run {
tvWord.text = it.word
when {
it.phonectic_cn.isNullOrEmpty() -> tvPhonetic.visibility = View.GONE
else -> tvPhonetic.apply {
visibility = View.VISIBLE
text = it.phonectic_cn
}
}
recyclerView.apply {
layoutManager = LinearLayoutManager(context,LinearLayoutManager.VERTICAL,false)
adapter = adapterChinese
}
//查询数据后更新显示
vm.queryChineseForEnglishList(it)
}
}
} }
//多条记录 搜索结果与历史记录
3 -> { 3 -> {
val binding = holder.binding as ItemDicSearchBinding
getItem(position).let {
binding.apply {
iv.setImageResource(if (it.inHistory) R.drawable.ic_clock else R.drawable.ic_search)
tvValue.text = it.basic_explaination
tvWord.text = when (vm.detailState) {
DictionaryViewModel.STATE_HISTROY -> it.word
DictionaryViewModel.STATE_SEARCH -> StringUtil.highString(it.word,
vm.keyWord,
ContextCompat.getColor(context,
R.color.theme_color))
else -> ""
}
//点击,进入详情
binding.root.click { v ->
vm.insertAndUpdateHistory(it, position)
}
}
}
} }
} }
} }
val adapterChinese = DicDetailChinese(vm)
class DicDetailChinese(_vm:DictionaryViewModel) : BaseRVAdapterVM<DictionaryItem,DictionaryViewModel>(_vm){
override fun coverViewHolder(parent : ViewGroup, viewType : Int) : BaseAdapterViewHolder {
return BaseAdapterViewHolder(inflateBinding(parent,R.layout.item_dic_detail_chinese_for_english_item))
}
override fun onBindVH(holder : BaseAdapterViewHolder, position : Int) {
getItem(position).let {
(holder.binding as ItemDicDetailChineseForEnglishItemBinding).apply {
tvValue.text = it.basic_explaination
tvWord.text = it.word
ivHoruns.click { v -> //发音
AudioCache.getDictionaryVoice(it.id,UserInfoManager.getDefaultSoundWay())
}
//点击,进入详情
root.click { v ->
//更改或添加进入数据库,更新插入时间,更新历史记录列表位置
vm.insertAndUpdateHistory(it, position)
}
}
}
}
}
} }

+ 1
- 1
app/src/main/java/com/xkl/cdl/data/bean/DictionaryBean.kt View File

*/ */
open class DictionaryBean() { open class DictionaryBean() {
var id : Long = 0 var id : Long = 0
var word : String? = null
var word : String = ""
var form : Int = 0 // 1 英文 2中文 var form : Int = 0 // 1 英文 2中文
var pre_index : String? = null //前缀 var pre_index : String? = null //前缀
var phonetic_uk : String? = null var phonetic_uk : String? = null

+ 4
- 2
app/src/main/java/com/xkl/cdl/data/bean/DictionaryItem.kt View File

//查询时间 //查询时间
var queryTime: Long = 0 var queryTime: Long = 0
//词典联想结果判断其是否在历史记录中,如果在历史记录中,则进行时间重排序
//词典联想结果判断其是否在历史记录中,如果在历史记录中,则进行时间重排序,
//历史记录表中查询,默认是true
//词典查询时,需要设置为false
@Ignore @Ignore
var inHistory : Boolean = false
var inHistory : Boolean = true
} }

+ 16
- 2
app/src/main/java/com/xkl/cdl/data/exam_record/AppDatabase.kt View File

import androidx.room.Database import androidx.room.Database
import androidx.room.Room import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.suliang.common.util.file.FileUtil import com.suliang.common.util.file.FileUtil
import com.xkl.cdl.data.bean.DictionaryBean
import com.xkl.cdl.data.bean.DictionaryItem
import com.xkl.cdl.module.XKLApplication import com.xkl.cdl.module.XKLApplication
import mqComsumerV1.Struct import mqComsumerV1.Struct
import java.io.File import java.io.File
* create 2022/7/7 16:50 * create 2022/7/7 16:50
* Describe: * Describe:
*/ */
@Database(entities = [Exam::class,ExamItem::class],version = 1,exportSchema = true)
@Database(entities = [Exam::class,ExamItem::class,DictionaryItem::class], version = 2, exportSchema = true)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
abstract fun examDao() : ExamDao abstract fun examDao() : ExamDao
abstract fun examItemDao(): ExamItemDao abstract fun examItemDao(): ExamItemDao
companion object{ companion object{
private var DATABASE_NAME :String = "${FileUtil.getSaveDirPath("db")}${File.separator}app.db" private var DATABASE_NAME :String = "${FileUtil.getSaveDirPath("db")}${File.separator}app.db"
val MIGRATION_1_2 = object :Migration(1,2){
override fun migrate(database : SupportSQLiteDatabase) {
//版本升级策略,增加一个dictionary表
database.execSQL("CREATE TABLE IF NOT EXISTS `dic_history` (`courseId` INTEGER NOT NULL, `queryTime` INTEGER NOT NULL, `id` INTEGER NOT NULL, `word` TEXT NOT NULL, `form` INTEGER NOT NULL, `pre_index` TEXT, `phonetic_uk` TEXT, `phonectic_us` TEXT, `phonectic_cn` TEXT, `basic_explaination` TEXT, `all_explaination` TEXT, `phrase` TEXT, `example` TEXT, `reference` TEXT, PRIMARY KEY(`word`))")
}
}
val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
Room.databaseBuilder(XKLApplication.instance(), AppDatabase::class.java, DATABASE_NAME).build()
Room.databaseBuilder(XKLApplication.instance(), AppDatabase::class.java, DATABASE_NAME)
.addMigrations(MIGRATION_1_2).build()
} }
@JvmStatic @JvmStatic

+ 15
- 1
app/src/main/java/com/xkl/cdl/data/manager/FilePathManager.kt View File

package com.xkl.cdl.data.manager package com.xkl.cdl.data.manager


import com.suliang.common.AppConfig
import com.suliang.common.util.file.FileUtil import com.suliang.common.util.file.FileUtil
import com.xkl.cdl.data.manager.db.DbControlBase import com.xkl.cdl.data.manager.db.DbControlBase
import java.io.File import java.io.File
fun getMp4File(subjectId : Int,coursePackId : Long,courseId : Long, fileName:String):File{ fun getMp4File(subjectId : Int,coursePackId : Long,courseId : Long, fileName:String):File{
return File(getMp4RootPath(), "$subjectId/$coursePackId/$courseId/$fileName") return File(getMp4RootPath(), "$subjectId/$coursePackId/$courseId/$fileName")
} }

/** 存储音频地址的父文件 */
fun getVoiceParent() : File {
return FileUtil.getSaveDirFile(AppConfig.VOICE)
}
/** 词典存储音频地址的父文件 */
fun getDicVoiceParent() : File {
return File(getVoiceParent(),"dic")
}
/** vocabulary存储音频地址的父文件 */
fun getVocVoiceParent() : File {
return File(getVoiceParent(),"voc")
}
} }


} }

+ 59
- 8
app/src/main/java/com/xkl/cdl/data/manager/db/DictionaryManager.kt View File

package com.xkl.cdl.data.manager.db package com.xkl.cdl.data.manager.db


import android.widget.Toast import android.widget.Toast
import com.google.common.base.Joiner
import androidx.core.database.getBlobOrNull
import com.suliang.common.AppConfig
import com.suliang.common.util.AppGlobals import com.suliang.common.util.AppGlobals
import com.suliang.common.util.LogUtil import com.suliang.common.util.LogUtil
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.data.AppConstants
import com.xkl.cdl.data.bean.DictionaryItem import com.xkl.cdl.data.bean.DictionaryItem
import com.xkl.cdl.data.manager.FilePathManager import com.xkl.cdl.data.manager.FilePathManager
import net.sqlcipher.database.SQLiteDatabase import net.sqlcipher.database.SQLiteDatabase
import java.io.File
import java.lang.StringBuilder import java.lang.StringBuilder
import java.util.* import java.util.*


private var mDataBase : SQLiteDatabase? = null private var mDataBase : SQLiteDatabase? = null
private var pwd : String = "XKL_XECD_DICT_DATA_KEY" private var pwd : String = "XKL_XECD_DICT_DATA_KEY"
private const val DATA_DICT = "dict"
private const val DATA_DICT = "dict"
private const val MAX_COUNT = 50 private const val MAX_COUNT = 50
private fun open() { private fun open() {
val titleMap : MutableMap<String, Int> = mutableMapOf() val titleMap : MutableMap<String, Int> = mutableMapOf()
var sql = "" var sql = ""
//1: wor //1: wor
sql = "SELECT word FROM $DATA_DICT WHERE word = $keyWord"
sql = "SELECT word FROM $DATA_DICT WHERE word = '$keyWord'"
query(sql, titleMap) query(sql, titleMap)
//2: wor% %wor% //2: wor% %wor%
sql = "SELECT word FROM $DATA_DICT WHERE word LIKE $keyWord || '%' LIMIT $MAX_COUNT" //完全匹配搜索:以关键字开头的搜索
sql = "SELECT word FROM $DATA_DICT WHERE word LIKE '$keyWord' || '%' LIMIT $MAX_COUNT" //完全匹配搜索:以关键字开头的搜索
query(sql, titleMap) query(sql, titleMap)
if (titleMap.size < MAX_COUNT) { if (titleMap.size < MAX_COUNT) {
sql = "SELECT word FROM $DATA_DICT WHERE word LIKE '%' || $keyWord || '%' LIMIT $MAX_COUNT" //查找任意位置包含keyword的值
sql = "SELECT word FROM $DATA_DICT WHERE word LIKE '%' || '$keyWord' || '%' LIMIT $MAX_COUNT" //查找任意位置包含keyword的值
query(sql, titleMap) query(sql, titleMap)
if (titleMap.size < MAX_COUNT) { if (titleMap.size < MAX_COUNT) {
//3:% w % o % r % //3:% w % o % r %
var newKeyWord : String = insertPercent(keyWord) var newKeyWord : String = insertPercent(keyWord)
val length = keyWord.length
val length = newKeyWord.length
for (i in 0 until length - 1) { for (i in 0 until length - 1) {
val max : Int = MAX_COUNT - titleMap.size val max : Int = MAX_COUNT - titleMap.size
newKeyWord = substringEnd(newKeyWord) newKeyWord = substringEnd(newKeyWord)
* @param hashMap * @param hashMap
*/ */
private fun query(sql : String, hashMap : MutableMap<String, Int>) { private fun query(sql : String, hashMap : MutableMap<String, Int>) {
LogUtil.e(sql)
mDataBase?.rawQuery(sql, null)?.let { mDataBase?.rawQuery(sql, null)?.let {
while (it.moveToNext()) { while (it.moveToNext()) {
if (hashMap.size == MAX_COUNT) break //数量15则打断 if (hashMap.size == MAX_COUNT) break //数量15则打断
example = it.getString(13) example = it.getString(13)
reference = it.getString(14) reference = it.getString(14)
// setVersion(it.getInt(15)) //version版本 // setVersion(it.getInt(15)) //version版本
inHistory = false
} }
} }
it.close() it.close()
* @param courseId 课程查询的courseId * @param courseId 课程查询的courseId
* @return * @return
*/ */
fun querys(words : List<String>,courseId : Long = 0 ) : List<DictionaryItem> {
fun querys(words : String ,courseId : Long = 0 ) : MutableList<DictionaryItem> {
val list : MutableList<DictionaryItem> = ArrayList<DictionaryItem>() val list : MutableList<DictionaryItem> = ArrayList<DictionaryItem>()
val sql = "SELECT * FROM " + DATA_DICT + " WHERE word IN (" + Joiner.on(",").join(words) + ")"
// val sql = "SELECT * FROM " + DATA_DICT + " WHERE word IN (" + Joiner.on(",").join(words) + ")"
val sql = "SELECT * FROM $DATA_DICT WHERE word IN ( $words)"
open() open()
mDataBase?.rawQuery(sql, null)?.let { mDataBase?.rawQuery(sql, null)?.let {
while (it.moveToNext()) { while (it.moveToNext()) {
phrase = it.getString(12) phrase = it.getString(12)
example = it.getString(13) example = it.getString(13)
reference = it.getString(14) reference = it.getString(14)
inHistory = false
} }
list.add(mDictionaryItem) list.add(mDictionaryItem)
} }
//去重 排序 //去重 排序
return list.distinctBy { it.word }.sortedBy { return list.distinctBy { it.word }.sortedBy {
words.indexOf(it.word) words.indexOf(it.word)
}.toMutableList()
}
/**
* 查询发音音频,并写入文件,返回地址
* 查询到内容,直接保存为对应发音文件,如果有一个为空,则保存为另一个,如果都为空,则都不保存
* @param id Long
* @param soundWay 发音方式
* @return 对应发音的路劲
*/
fun queryAudio(id:Long, soundWay : Int): String?{
open()
val sql = "SELECT audio_uk,audio_us FROM $DATA_DICT WHERE id = $id"
var result: String? = null
mDataBase?.rawQuery(sql,null)?.let {
while (it.moveToNext()){
val audioUk = it.getBlobOrNull(0)
val audioUs = it.getBlobOrNull(0)
val parentFile = FilePathManager.getDicVoiceParent()
audioUk?.let {
val writeFile = File(parentFile,"${id}_${AppConstants.SOUND_TYPE_UK}")
FileUtil.writeBytesToFile(writeFile,it)
}?:audioUs?.let {
val writeFile = File(parentFile,"${id}_${AppConstants.SOUND_TYPE_UK}")
FileUtil.writeBytesToFile(writeFile,it)
}
audioUs?.let {
val writeFile = File(parentFile,"${id}_${AppConstants.SOUND_TYPE_US}")
FileUtil.writeBytesToFile(writeFile,it)
}?:audioUk?.let {
val writeFile = File(parentFile,"${id}_${AppConstants.SOUND_TYPE_US}")
FileUtil.writeBytesToFile(writeFile,it)
}
if (audioUk != null || audioUs != null){
result = File(parentFile,"${id}_$soundWay").path
}
}
it.close()
} }
return result
} }
} }

+ 1
- 1
app/src/main/java/com/xkl/cdl/data/manager/db/VocabularyManager.kt View File

it.close() it.close()
} }
//不为空,写入本身,如果为空,用另外的发音方式写入 //不为空,写入本身,如果为空,用另外的发音方式写入
val parentPath = FileUtil.getSaveDirPath(AppConfig.VOICE)
val parentPath = FilePathManager.getVocVoiceParent()
val audio_us_file_path = audio_us?.let { val audio_us_file_path = audio_us?.let {
val audioFileNameUS = "${wordId}_${AppConstants.SOUND_TYPE_US}" val audioFileNameUS = "${wordId}_${AppConstants.SOUND_TYPE_US}"
val file = File(parentPath, audioFileNameUS) val file = File(parentPath, audioFileNameUS)

+ 98
- 68
app/src/main/java/com/xkl/cdl/data/repository/AudioCache.kt View File

package com.xkl.cdl.data.repository package com.xkl.cdl.data.repository


import android.net.IpPrefix
import android.util.LruCache import android.util.LruCache
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.suliang.common.AppConfig import com.suliang.common.AppConfig
import com.suliang.common.extension.diskIo2Main import com.suliang.common.extension.diskIo2Main
import com.suliang.common.util.file.FileUtil import com.suliang.common.util.file.FileUtil
import com.xkl.cdl.data.AppConstants import com.xkl.cdl.data.AppConstants
import com.xkl.cdl.data.manager.FilePathManager
import com.xkl.cdl.data.manager.db.DBCourseManager import com.xkl.cdl.data.manager.db.DBCourseManager
import com.xkl.cdl.data.manager.db.DbControlBase import com.xkl.cdl.data.manager.db.DbControlBase
import com.xkl.cdl.data.manager.db.DictionaryManager
import com.xkl.cdl.data.manager.db.VocabularyManager import com.xkl.cdl.data.manager.db.VocabularyManager
import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Observable
import java.io.File import java.io.File
* 1缓存 2文件 3数据库 * 1缓存 2文件 3数据库
* @return String 文件路径 * @return String 文件路径
*/ */
fun get(dbControlBase : DbControlBase, wordId : Long, soundWay : Int) {
fun getLearnVoice(dbControlBase : DbControlBase, wordId : Long, soundWay : Int) {
//父文件路劲 //父文件路劲
val parentPath = FileUtil.getSaveDirPath(AppConfig.VOICE)
//先查对应的文件路径 : 项目id_课程包id_课程id_单词id_发音方式
val defaultKey = "${dbControlBase.subjectId}_${dbControlBase.coursePackId}_${dbControlBase.courseId}_${wordId}_$soundWay"
val parentPath = FilePathManager.getVoiceParent()
//先查对应的文件路径 : 项目id_课程包id_课程id_单词id 发音方式在内部拼接
val fileName = "${dbControlBase.subjectId}_${dbControlBase.coursePackId}_${dbControlBase.courseId}_${wordId}"
getVoicePath(parentPath,fileName,"",soundWay) {DBCourseManager.queryAudio(dbControlBase, wordId, soundWay) }
}
/**
* 获取词汇量测试发音音频
* @param wordId Long
* @param soundWay Int
*/
fun getVocabularyVoice(wordId : Long, soundWay : Int){
//父文件路劲
val parentPath = FilePathManager.getVocVoiceParent()
getVoicePath(parentPath,"$wordId","v",soundWay) { VocabularyManager.queryAudio(wordId, soundWay) }
}
/**
* 获取词典音频
* @param wordId Long
* @param soundWay Int
*/
fun getDictionaryVoice(wordId : Long,soundWay : Int){
//父文件路劲
val parentPath = FilePathManager.getDicVoiceParent()
getVoicePath(parentPath,"$wordId","d",soundWay) { DictionaryManager.queryAudio(wordId, soundWay) }
}
/**
* 获取对应发音文件,使用audioLiveData发送回去
* 查询缓存 -> 查询文件 -> 查询数据库
* @param parentPath File 父文件
* @param fileName String 文件名称,没有加发音格式,需要评价发音格式,用于存key的时候,需要添加前缀
* @param prefix String 存key时用的前缀,课程、词汇等的时候,为空, 词汇量测试的时候为 "v" , 词典的时候为"d"
* @param soundWay Int 发音方式,拼接在发音文件后面
* @param {} -> String
*/
private fun getVoicePath(parentPath:File,fileName:String, prefix : String, soundWay : Int, queryMethod : ()-> String?){
val defaultKey = "${prefix}_${fileName}_${soundWay}"
Observable.fromCallable { Observable.fromCallable {
//不为空,直接取值
if (lruCache.get(defaultKey) == null){
//为空 查文件
val file = File(parentPath, defaultKey)
when {
file.exists() -> lruCache.put(defaultKey, file.path) //文件存在,则表示以前进行过数据库查询,且进行了保存文件,英美发音本地都有文件
else -> { //文件不存在,则查询数据库进行查询,返回的为保存的文件地址
val filePath = DBCourseManager.queryAudio(dbControlBase, wordId, soundWay)
filePath?.also {
when(soundWay){
AppConstants.SOUND_TYPE_UK -> {
lruCache.put(defaultKey, it) //保存路径
val audioFileNameUS = "${dbControlBase.subjectId}_${dbControlBase.coursePackId}_${dbControlBase.courseId}_${wordId}_${AppConstants.SOUND_TYPE_US}"
lruCache.put(audioFileNameUS, File(parentPath,audioFileNameUS).path) //保存路径
}
AppConstants.SOUND_TYPE_US -> {
lruCache.put(defaultKey, it) //保存路径
val audioFileNameUS = "${dbControlBase.subjectId}_${dbControlBase.coursePackId}_${dbControlBase.courseId}_${wordId}_${AppConstants.SOUND_TYPE_UK}"
lruCache.put(audioFileNameUS, File(parentPath,audioFileNameUS).path) //保存路径
}
else -> lruCache.put(defaultKey, it) //保存路径
return@fromCallable lruCache[defaultKey]?:let {
val file = File(parentPath,"${fileName}_$soundWay") //存储的文件
if (file.exists()) {
lruCache.put(defaultKey,file.path)
}else{
queryMethod.invoke()?.let {
when(soundWay){
AppConstants.SOUND_TYPE_US -> {
val audioUkKey = "${prefix}_${fileName}_${AppConstants.SOUND_TYPE_UK}"
val audioUk = File(parentPath,"${fileName}_${AppConstants.SOUND_TYPE_UK}").path
lruCache.put(audioUkKey,audioUk)
}
AppConstants.SOUND_TYPE_UK -> {
val audioUsKey = "${prefix}_${fileName}_${AppConstants.SOUND_TYPE_US}"
val audioUs = File(parentPath,"${fileName}_${AppConstants.SOUND_TYPE_US}").path
lruCache.put(audioUsKey,audioUs)
} }
} //如果查询返回路径为空,则说明,没有发音内容
}
lruCache.put(defaultKey,it)
} }
} }
lruCache[defaultKey]?:""
} }
return@fromCallable lruCache.get(defaultKey) ?: ""
}.compose(diskIo2Main()).subscribe { }.compose(diskIo2Main()).subscribe {
if (this::audioLiveData.isInitialized) if (this::audioLiveData.isInitialized)
audioLiveData.value = it
audioLiveData.value = it
} }
} }
/** /**
* 口语总测获取发音文件 * 口语总测获取发音文件
* @param fileName String 发音文件名称 * @param fileName String 发音文件名称
* @return String 发音文件路径 * @return String 发音文件路径
*/ */
fun get(fileName : String):String {
fun getSpokenVoice(fileName : String):String {
val parentPath = FileUtil.getSaveDirPath(AppConfig.VOICE) val parentPath = FileUtil.getSaveDirPath(AppConfig.VOICE)
if (lruCache[fileName] == null){ if (lruCache[fileName] == null){
lruCache.put(fileName,File(parentPath,fileName).path) lruCache.put(fileName,File(parentPath,fileName).path)
} }
return lruCache[fileName] return lruCache[fileName]
} }
fun getVocabulary(wordId : Long,soundWay : Int){
//父文件路劲
val parentPath = FileUtil.getSaveDirPath(AppConfig.VOICE)
//先查对应的文件路径 : 项目id_课程包id_课程id_单词id_发音方式
val defaultKey = "${wordId}_$soundWay"
Observable.fromCallable {
//不为空,直接取值
if (lruCache.get(defaultKey) == null){
//为空 查文件
val file = File(parentPath, defaultKey)
when {
file.exists() -> lruCache.put(defaultKey, file.path) //文件存在,则表示以前进行过数据库查询,且进行了保存文件,英美发音本地都有文件
else -> { //文件不存在,则查询数据库进行查询,返回的为保存的文件地址
val filePath = VocabularyManager.queryAudio(wordId, soundWay)
filePath?.also {
when(soundWay){
AppConstants.SOUND_TYPE_UK -> {
lruCache.put(defaultKey, it) //保存路径
val audioFileNameUS = "${wordId}_${AppConstants.SOUND_TYPE_US}"
lruCache.put(audioFileNameUS, File(parentPath,audioFileNameUS).path) //保存路径
}
AppConstants.SOUND_TYPE_US -> {
lruCache.put(defaultKey, it) //保存路径
val audioFileNameUS = "${wordId}_${AppConstants.SOUND_TYPE_UK}"
lruCache.put(audioFileNameUS, File(parentPath,audioFileNameUS).path) //保存路径
}
else -> lruCache.put(defaultKey, it) //保存路径
/*
Observable.fromCallable {
//不为空,直接取值
if (lruCache.get(defaultKey) == null){
//为空 查文件
val file = File(parentPath, defaultKey)
when {
file.exists() -> lruCache.put(defaultKey, file.path) //文件存在,则表示以前进行过数据库查询,且进行了保存文件,英美发音本地都有文件
else -> { //文件不存在,则查询数据库进行查询,返回的为保存的文件地址
val filePath = VocabularyManager.queryAudio(wordId, soundWay)
filePath?.also {
when(soundWay){
AppConstants.SOUND_TYPE_UK -> {
lruCache.put(defaultKey, it) //保存路径
val audioFileNameUS = "${wordId}_${AppConstants.SOUND_TYPE_US}"
lruCache.put(audioFileNameUS, File(parentPath,audioFileNameUS).path) //保存路径
} }
} //如果查询返回路径为空,则说明,没有发音内容
}
AppConstants.SOUND_TYPE_US -> {
lruCache.put(defaultKey, it) //保存路径
val audioFileNameUS = "${wordId}_${AppConstants.SOUND_TYPE_UK}"
lruCache.put(audioFileNameUS, File(parentPath,audioFileNameUS).path) //保存路径
}
else -> lruCache.put(defaultKey, it) //保存路径
}
} //如果查询返回路径为空,则说明,没有发音内容
} }
} }
return@fromCallable lruCache.get(defaultKey) ?: ""
}.compose(diskIo2Main()).subscribe {
if (this::audioLiveData.isInitialized)
audioLiveData.value = it
} }
return@fromCallable lruCache.get(defaultKey) ?: ""
}.compose(diskIo2Main()).subscribe {
if (this::audioLiveData.isInitialized)
audioLiveData.value = it
}*/
}
} }

+ 3
- 3
app/src/main/java/com/xkl/cdl/module/learn/LearnExamActivity.kt View File

if (view is LottieAnimationView && view.visibility == View.VISIBLE) currentPlayView = view if (view is LottieAnimationView && view.visibility == View.VISIBLE) currentPlayView = view
when{ when{
//词汇量测试 //词汇量测试
vm.intentData.examType == AppConstants.TEST_TYPE_NORMAL -> AudioCache.getVocabulary(it.word_id,vm.defaultSoundWay)
vm.intentData.examType == AppConstants.TEST_TYPE_NORMAL -> AudioCache.getVocabularyVoice(it.word_id, vm.defaultSoundWay)
//口语 //口语
isSpokenTotalTest() -> AudioCache.get(view.tag as String)
isSpokenTotalTest() -> AudioCache.getSpokenVoice(view.tag as String)
//测试 //测试
else -> AudioCache.get(vm.dbBaseControl, it.word_id, vm.defaultSoundWay)
else -> AudioCache.getLearnVoice(vm.dbBaseControl, it.word_id, vm.defaultSoundWay)
} }
} }
} }

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



import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.graphics.Color import android.graphics.Color
import android.opengl.Visibility
import android.os.Bundle import android.os.Bundle
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
when (vm.learnData.lesson.coursePackType) { when (vm.learnData.lesson.coursePackType) {
AppConstants.COURSEPACK_TYPE_ENGLISH_WORD, AppConstants.COURSEPACK_TYPE_ENGLISH_SOUNDMARK, AppConstants.COURSEPACK_TYPE_ENGLISH_SPOKEN, AppConstants.COURSEPACK_TYPE_CHINESE_LITERACY, AppConstants.COURSEPACK_TYPE_CHINESE_PINYIN -> { AppConstants.COURSEPACK_TYPE_ENGLISH_WORD, AppConstants.COURSEPACK_TYPE_ENGLISH_SOUNDMARK, AppConstants.COURSEPACK_TYPE_ENGLISH_SPOKEN, AppConstants.COURSEPACK_TYPE_CHINESE_LITERACY, AppConstants.COURSEPACK_TYPE_CHINESE_PINYIN -> {
val valueWord = vm.currentLearnWord.value!! val valueWord = vm.currentLearnWord.value!!
AudioCache.get(vm.dbControlBase, valueWord.wordId, vm.defaultSoundWay)
AudioCache.getLearnVoice(vm.dbControlBase, valueWord.wordId, vm.defaultSoundWay)
} }
} }
} }

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

package com.xkl.cdl.module.m_memo package com.xkl.cdl.module.m_memo


import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.TimePickerDialog
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.TimePicker
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.haibin.calendarview.Calendar import com.haibin.calendarview.Calendar
import com.xkl.cdl.data.repository.AudioCache import com.xkl.cdl.data.repository.AudioCache
import com.xkl.cdl.databinding.ActivityMemoListDetailBinding import com.xkl.cdl.databinding.ActivityMemoListDetailBinding
import com.xkl.cdl.module.learn.LearnWordActivity import com.xkl.cdl.module.learn.LearnWordActivity
import com.xkl.cdl.module.m_center_learn.coursechildren.CourseMainFragment
import java.util.* import java.util.*
import java.util.Calendar.* import java.util.Calendar.*


needShowEmptyView = true needShowEmptyView = true
//处理发音事件 //处理发音事件
soundListener = { wordId, soundWay -> soundListener = { wordId, soundWay ->
AudioCache.get(vm.dbControlBase, wordId, soundWay)
AudioCache.getLearnVoice(vm.dbControlBase, wordId, soundWay)
} }
} }
} }

+ 112
- 79
app/src/main/java/com/xkl/cdl/module/m_service_center/DictionaryActivity.kt View File

import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.suliang.common.AppConfig import com.suliang.common.AppConfig
import com.suliang.common.base.activity.BaseActivityVM import com.suliang.common.base.activity.BaseActivityVM
import com.suliang.common.extension.click import com.suliang.common.extension.click
import com.suliang.common.util.LogUtil
import com.suliang.common.util.media.MPManager
import com.suliang.common.util.os.KeyboardUtil
import com.xkl.cdl.R import com.xkl.cdl.R
import com.xkl.cdl.data.repository.AudioCache
import com.xkl.cdl.databinding.ActivityDictionaryBinding import com.xkl.cdl.databinding.ActivityDictionaryBinding
import com.xkl.cdl.dialog.CommonDialog import com.xkl.cdl.dialog.CommonDialog
import com.xkl.cdl.dialog.CommonDialogBean import com.xkl.cdl.dialog.CommonDialogBean
import com.xkl.cdl.module.m_service_center.DictionaryViewModel.Companion.STATE_HISTROY
import com.xkl.cdl.module.m_service_center.DictionaryViewModel.Companion.STATE_SEARCH

/**
* author suliang
* create 2022/8/9 14:53
* Describe: 词典
* 历史记录: 查询数据库 历史记录点击进入详情并更新记录
* 词典搜索 :1、输入联想搜索 2、联想结果点击详情显示(显示结果,更新记录) 3、联想结果中文详情点击英文item进入详情
*/
class DictionaryActivity : BaseActivityVM<ActivityDictionaryBinding, DictionaryViewModel>() { class DictionaryActivity : BaseActivityVM<ActivityDictionaryBinding, DictionaryViewModel>() {
companion object { companion object {
override fun initActivity(savedInstanceState : Bundle?) { override fun initActivity(savedInstanceState : Bundle?) {
vm.courseId = intent.getLongExtra(AppConfig.INTENT_1, 0) vm.courseId = intent.getLongExtra(AppConfig.INTENT_1, 0)
binding.titleBar.onBackClick = {finish()}
initHistoryRecyclerView() initHistoryRecyclerView()
//点击 查看更多 //点击 查看更多
binding.tvSeeMore.click { binding.tvSeeMore.click {
when (vm.detailState) { when (vm.detailState) {
STATE_HISTROY -> {
DictionaryViewModel.STATE_HISTROY -> {
//显示页面加1, 列表更新 //显示页面加1, 列表更新
vm.histroyShowPage++ vm.histroyShowPage++
vm.adapter.notifyDataSetChanged() vm.adapter.notifyDataSetChanged()
//根据数量判断,是否还要显示 //根据数量判断,是否还要显示
binding.tvSeeMore.visibility = if (vm.historyList.size > vm.histroyShowPage * vm.historyPageSize) View.VISIBLE else View.GONE
binding.tvSeeMore.visibility = if (vm.historyList.size > vm.histroyShowPage * vm.pageShowCount) View.VISIBLE else View.GONE
binding.rv.smoothScrollToPosition(vm.histroyShowPage * vm.pageShowCount - 1)
} }
STATE_SEARCH -> {
DictionaryViewModel.STATE_SEARCH -> {
//显示页面加1, 列表更新 //显示页面加1, 列表更新
vm.searchShowPage++ vm.searchShowPage++
vm.adapter.notifyDataSetChanged() vm.adapter.notifyDataSetChanged()
//根据数量判断,是否还要显示 //根据数量判断,是否还要显示
binding.tvSeeMore.visibility = if (vm.searchDetailList.size > vm.searchShowPage * vm.historyPageSize) View.VISIBLE else View.GONE
binding.tvSeeMore.visibility = if (vm.searchAssociateList.size > vm.searchShowPage * vm.pageShowCount) View.VISIBLE else View.GONE
binding.rv.smoothScrollToPosition(vm.searchShowPage * vm.pageShowCount - 1)
} }
} }
} }
} }
}.show(supportFragmentManager, javaClass.name) }.show(supportFragmentManager, javaClass.name)
} }
}
private fun initHistoryRecyclerView() {
binding.rv.apply {
layoutManager = LinearLayoutManager(this@DictionaryActivity, LinearLayoutManager.VERTICAL, false)
adapter = vm.adapter
}
}
@SuppressLint("NotifyDataSetChanged")
override fun loadData() {
//更新历史记录适配器
vm.updateHistoryAdapterLiveData.observe(this) {
//监听EditText变化
// 1 点清空或没有内容时 显示历史记录
// 2 关键字搜索 显示搜索记录
// 3 点击历史记录或者点击搜索记录,更新显示详情
// 4 单独定义一个值,定义为点击了历史记录或搜索记录
binding.etSearch.addTextChangedListener{
vm.handler.removeCallbacksAndMessages(null)
when { when {
//历史记录
!it -> vm.adapter.apply {
//如果非历史列表记录,则更新列表布局的状态
if (vm.detailState != STATE_HISTROY) {
vm.detailState = STATE_HISTROY
binding.apply {
tvHistoryTitle.visibility = View.VISIBLE
ivDelete.visibility = View.VISIBLE
}
}
binding.layoutDetail.visibility = if (vm.historyList.isEmpty()) View.GONE else View.VISIBLE
binding.tvSeeMore.visibility = if (vm.historyList.size > vm.histroyShowPage * vm.historyPageSize) View.VISIBLE else View.GONE
//更新值
needShowEmptyView = false
setData(vm.historyList)
it.isNullOrEmpty() -> { //内容为空,显示为历史记录
LogUtil.e("EditText输入数据为空: 空数据,显示历史记录")
vm.detailState = DictionaryViewModel.STATE_HISTROY
vm.updateHistoryAdapterLiveData.value = vm.detailState
} }
//搜索结果
else -> vm.adapter.apply {
if (vm.detailState != STATE_SEARCH) {
vm.detailState = STATE_SEARCH
binding.apply {
tvHistoryTitle.visibility = View.GONE
ivDelete.visibility = View.GONE
else -> { //不为空
when(vm.detailState){
DictionaryViewModel.STATE_DETAIL -> { //点击显示详情了, 更新详情显示
LogUtil.e("EditText输入数据 : 点击item显示详情")
KeyboardUtil.hideKeyboard(binding.etSearch) //关闭键盘
vm.updateHistoryAdapterLiveData.value = vm.detailState
}
else -> { //关键字搜索
LogUtil.e("EditText输入数据 : 进行关键字搜索")
vm.detailState = DictionaryViewModel.STATE_SEARCH
vm.searchKeyWord(it.toString())
} }
} }
binding.layoutDetail.visibility = View.VISIBLE
binding.tvSeeMore.visibility = if (vm.searchDetailList.size > vm.searchShowPage * vm.historyPageSize) View.VISIBLE else View.GONE
needShowEmptyView = vm.searchDetailList.size == 0
setData(vm.searchDetailList)
} }
} }
} }
/**搜索监听*/ /**搜索监听*/
vm.etSearchLiveData.observe(this) {
when {
//直接历史记录更新
it.isNullOrEmpty() -> vm.updateHistoryAdapterLiveData.value = false
//历史记录的详情
vm.searchItemDetailAble -> {
vm.etSearchLiveData.observe(this){
binding.etSearch.setText(it)
binding.etSearch.setSelection(it.length)
}
//更新历史记录适配器
// true 跟新搜索结果
// false 更新列表
vm.updateHistoryAdapterLiveData.observe(this) {
LogUtil.e("Adapter 进行数据更新 $it")
when(it){
DictionaryViewModel.STATE_HISTROY -> { //历史记录
//布局显示处理
binding.run {
tvHistoryTitle.visibility = View.VISIBLE
ivDelete.visibility = View.VISIBLE
vLine.visibility = View.VISIBLE
layoutDetail.visibility = if (vm.historyList.isEmpty()) View.GONE else View.VISIBLE
}
binding.tvSeeMore.visibility = if (vm.historyList.size > vm.histroyShowPage * vm.pageShowCount) View.VISIBLE else View.GONE
vm.adapter.run {
needShowEmptyView = false
setData(vm.historyList)
}
}
DictionaryViewModel.STATE_SEARCH -> { //关键字搜索记录
binding.run {
vLine.visibility = View.GONE
tvHistoryTitle.visibility = View.GONE
ivDelete.visibility = View.GONE
layoutDetail.visibility = View.VISIBLE
tvSeeMore.visibility = if (vm.searchAssociateList.size > vm.searchShowPage * vm.pageShowCount) View.VISIBLE else View.GONE
}
vm.searchShowPage = 1
vm.adapter.run {
needShowEmptyView = true
setData(vm.searchAssociateList)
}
}
DictionaryViewModel.STATE_DETAIL -> { //item详情
binding.run {
vLine.visibility = View.GONE
tvHistoryTitle.visibility = View.GONE
ivDelete.visibility = View.GONE
layoutDetail.visibility = View.VISIBLE
tvSeeMore.visibility = View.GONE
}
vm.adapter.run {
needShowEmptyView = false
setData(vm.detailItemList)
}
} }
//关键字搜索
else -> vm.searchKeyWord(it)
} }
} }
//查询历史记录
vm.loadHistory()
/**发音监听*/
AudioCache.initAudioLiveData().observe(this){
it?.let {
MPManager.play(it)
}?: showToast("未找到发音文件")
}
} }
/**
* 详情中的状态
* @param state Int 1历史记录 2搜索详情
*/
private fun initLayoutDetailState(state : Int) {
when (state) {
//历史布局
STATE_HISTROY -> binding.apply {
tvHistoryTitle.visibility = View.VISIBLE
ivDelete.visibility = View.VISIBLE
}
//查询详情
STATE_SEARCH -> binding.apply {
tvHistoryTitle.visibility = View.GONE
ivDelete.visibility = View.GONE
}
private fun initHistoryRecyclerView() {
binding.rv.apply {
layoutManager = LinearLayoutManager(this@DictionaryActivity, LinearLayoutManager.VERTICAL, false)
adapter = vm.adapter
} }
binding.tvSeeMore.visibility = View.GONE
} }
@SuppressLint("NotifyDataSetChanged")
override fun loadData() {
//查询历史记录
vm.loadHistory()
}
} }

+ 107
- 30
app/src/main/java/com/xkl/cdl/module/m_service_center/DictionaryViewModel.kt View File

import android.os.Looper import android.os.Looper
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.suliang.common.base.viewmodel.BaseViewModel import com.suliang.common.base.viewmodel.BaseViewModel
import com.suliang.common.extension.io2Main
import com.suliang.common.util.thread.AppExecutors import com.suliang.common.util.thread.AppExecutors
import com.xkl.cdl.adapter.DictionaryAdapter import com.xkl.cdl.adapter.DictionaryAdapter
import com.xkl.cdl.data.bean.DictionaryItem import com.xkl.cdl.data.bean.DictionaryItem
//记录状态 //记录状态
companion object { companion object {
const val STATE_HISTROY = 1
const val STATE_SEARCH = 2
const val STATE_HISTROY = 1 //历史记录
const val STATE_SEARCH = 2 //关键字搜索结果
const val STATE_DETAIL = 3 //详情展示
} }
//状态 : 默认历史记录
var detailState = STATE_HISTROY
//适配器 //适配器
val adapter = DictionaryAdapter(this) val adapter = DictionaryAdapter(this)
//输入框监听
//手动改变editText Value
val etSearchLiveData = MutableLiveData<String>() val etSearchLiveData = MutableLiveData<String>()
//搜索关键子
var keyWord : String? = null
//状态 : 默认历史记录
var detailState = STATE_HISTROY
//历史记录集合 //历史记录集合
val historyList = mutableListOf<DictionaryItem>() val historyList = mutableListOf<DictionaryItem>()
//搜索结果 可为关键字搜索结果
var searchAssociateList = mutableListOf<DictionaryItem>()
//详情结果 固定一个item的大小,即显示详情
var detailItemList: MutableList<DictionaryItem>? = null
//更新详情适配器 false:历史记录 true:联想搜索结果
val updateHistoryAdapterLiveData = MutableLiveData<Boolean>()
//更新详情适配器 STATE_HISTROY历史记录 STATE_SEARCH 搜索结果 STATE_DETAIL 详情
val updateHistoryAdapterLiveData = MutableLiveData<Int>()
//一页显示的历史记录个数,最多历史记录显示30个 搜索结果50个
val pageShowCount = 15
//历史记录,显示的页数 //历史记录,显示的页数
var histroyShowPage = 1 var histroyShowPage = 1
var searchShowPage = 1 var searchShowPage = 1
//一页显示的历史记录个数,最多历史记录显示30个
val historyPageSize = 10
//搜索结果,做多匹配显示50个
//是否显示搜索单词的详情 true:显示单条记录的详情 false:搜索显示的为搜索结果的列表详情
var searchItemDetailAble = false
//搜索结果 多条为联想结果 单条基本为搜索的详情结果
var searchDetailList = mutableListOf<DictionaryItem>()
val handler : Handler = Handler(Looper.getMainLooper()) val handler : Handler = Handler(Looper.getMainLooper())
//记录联想结果中为历史记录的位置 //记录联想结果中为历史记录的位置
val searchListForHistoryPositionMap = mutableMapOf<Int,Int>()
private val searchListForHistoryPositionMap = mutableMapOf<Int, Int>()
/** /**
Observable.fromCallable { Observable.fromCallable {
val history = AppDatabase.instance.dictionaryDao().query(courseId) val history = AppDatabase.instance.dictionaryDao().query(courseId)
historyList.addAll(history) historyList.addAll(history)
updateHistoryAdapterLiveData.postValue(false)
}.observeOn(Schedulers.from(AppExecutors.io)).subscribe()
if (detailState == STATE_HISTROY)
updateHistoryAdapterLiveData.postValue(detailState)
}.subscribeOn(Schedulers.from(AppExecutors.io)).subscribe()
} }
/**清空历史记录*/ /**清空历史记录*/
fun clearHistory() { fun clearHistory() {
Observable.fromCallable { Observable.fromCallable {
historyList.clear() historyList.clear()
updateHistoryAdapterLiveData.postValue(false)
if (detailState == STATE_HISTROY)
updateHistoryAdapterLiveData.postValue(detailState)
AppDatabase.instance.dictionaryDao().clear(courseId) AppDatabase.instance.dictionaryDao().clear(courseId)
}.observeOn(Schedulers.from(AppExecutors.io)).subscribe()
}.subscribeOn(Schedulers.from(AppExecutors.io)).subscribe()
} }
/** /**
* @param keyWord String * @param keyWord String
*/ */
fun searchKeyWord(keyWord : String) { fun searchKeyWord(keyWord : String) {
handler.removeCallbacksAndMessages(null)
this.keyWord = keyWord
handler.postDelayed({ handler.postDelayed({
Observable.fromCallable { Observable.fromCallable {
//清空位置对应信息 //清空位置对应信息
searchListForHistoryPositionMap.clear() searchListForHistoryPositionMap.clear()
//联想查询结果 //联想查询结果
searchDetailList = DictionaryManager.queryKeyWordLikeMatchSimple(keyWord)
searchAssociateList = DictionaryManager.queryKeyWordLikeMatchSimple(keyWord)
//搜索记录中获取历史记录位置 需要判断是否有历史记录 //搜索记录中获取历史记录位置 需要判断是否有历史记录
if (historyList.isNotEmpty() && searchDetailList.isNotEmpty()) {
if (historyList.isNotEmpty() && searchAssociateList.isNotEmpty()) {
historyList.forEachIndexed { historyIndex, historyItem -> historyList.forEachIndexed { historyIndex, historyItem ->
searchDetailList.forEachIndexed { searchIndex, searchItem ->
searchAssociateList.forEachIndexed { searchIndex, searchItem ->
if (historyItem.word == searchItem.word) { if (historyItem.word == searchItem.word) {
searchItem.inHistory = true searchItem.inHistory = true
searchListForHistoryPositionMap.put(searchIndex, historyIndex) searchListForHistoryPositionMap.put(searchIndex, historyIndex)
} }
} }
} }
//更新搜索结果
updateHistoryAdapterLiveData.postValue(true)
}.observeOn(Schedulers.from(AppExecutors.io)).subscribe()
}, 300)
//更新搜索结果 : 避免搜索结束后,显示状态已改变,发送出去消息后,造成更新错乱
if (detailState == STATE_SEARCH) {
updateHistoryAdapterLiveData.postValue(detailState)
}
}.subscribeOn(Schedulers.from(AppExecutors.io)).subscribe()
}, 200)
} }
/**
* 查询中文item下的多个英文item集合
* @param it DictionaryItem
* @return MutableList<DictionaryItem>?
*/
fun queryChineseForEnglishList(chineseItem : DictionaryItem) {
Observable.fromCallable {
//清空位置对应信息
searchListForHistoryPositionMap.clear()
//精准查询结果
return@fromCallable chineseItem.basic_explaination?.let {
val replace = it.replace(";", "\',\'")
val querys = DictionaryManager.querys("\'$replace\'")
//搜索记录中获取历史记录位置 需要判断是否有历史记录
if (historyList.isNotEmpty() && querys.isNotEmpty()) {
historyList.forEachIndexed { historyIndex, historyItem ->
querys.forEachIndexed { searchIndex, searchItem ->
if (historyItem.word == searchItem.word) {
searchItem.inHistory = true
searchListForHistoryPositionMap.put(searchIndex, historyIndex)
}
}
}
}
querys
} ?: mutableListOf<DictionaryItem>()
}.compose(io2Main()).subscribe {
if (detailState == STATE_DETAIL)
adapter.adapterChinese.setData(it)
}
}
/**
* 添加历史记录中
* @param item DictionaryItem
* @param position Int 搜索列表中的位置
*/
fun insertAndUpdateHistory(item : DictionaryItem, position : Int) {
detailState = STATE_DETAIL
Observable.fromCallable {
item.queryTime = System.currentTimeMillis()
//添加到历史记录第一个
if (!item.inHistory) {
item.inHistory = true
if (historyList.isNotEmpty() && historyList.size >= 30) {
historyList.removeLast() //移除最后一个
}
} else if(detailState == STATE_SEARCH){ // 搜索结果的item点击,需要移除历史记录中对应的位置
searchListForHistoryPositionMap.get(position)?.let {
historyList.removeAt(it) //移除其在历史记录中的位置
}
}else if (detailState == STATE_HISTROY){ //历史记录的item点击,移除该条历史记录
historyList.removeAt(position)
}
//添加到历史记录第一个
historyList.add(0, item)
//重新赋值需要用于刷新的集合
detailItemList = mutableListOf(item)
//更改inputEditView的值,重新赋值列表集合
etSearchLiveData.postValue(item.word)
//更新或插入历史搜索记录中
AppDatabase.instance.dictionaryDao().insert(item)
}.subscribeOn(Schedulers.from(AppExecutors.io)).subscribe()
}
} }

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

//复制词典 和 词汇量测试 //复制词典 和 词汇量测试
val dictionary = FilePathManager.getDictionaryDbPath() val dictionary = FilePathManager.getDictionaryDbPath()
if (!dictionary.exists()) {
FileUtil.copyAsset("dictionary.db", dictionary)
if (!dictionary.exists() || dictionary.length() != FileUtil.getAssetFileSize("dictionary.db")) {
LogUtil.e("复制dictionary.db")
FileUtil.copyAsset("dictionary.db", dictionary)
} }
val vocabulary = FilePathManager.getVocabularyDbPath() val vocabulary = FilePathManager.getVocabularyDbPath()
if (!vocabulary.exists()) {
if (!vocabulary.exists() || vocabulary.length() != FileUtil.getAssetFileSize("vocabulary.db")) {
LogUtil.e("复制vocabulary.db")
FileUtil.copyAsset("vocabulary.db", vocabulary) FileUtil.copyAsset("vocabulary.db", vocabulary)
} }

+ 5
- 4
app/src/main/java/com/xkl/cdl/util/ViewUtil.kt View File

package com.xkl.cdl.util package com.xkl.cdl.util


import android.text.Html
import android.text.Spanned
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import android.widget.TextView import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.suliang.common.util.AppGlobals import com.suliang.common.util.AppGlobals
import com.suliang.common.util.ColorUtil import com.suliang.common.util.ColorUtil
import com.suliang.common.util.os.ScreenUtil import com.suliang.common.util.os.ScreenUtil
import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern


/** /**
m.appendTail(buffer) m.appendTail(buffer)
return buffer.toString() return buffer.toString()
} }
} }
} }

+ 17
- 8
app/src/main/res/layout/activity_dictionary.xml View File

xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">


<data> <data>
<variable
name="vm"
type="com.xkl.cdl.module.m_service_center.DictionaryViewModel" />
</data> </data>


<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="@dimen/global_spacing" android:layout_marginStart="@dimen/global_spacing"
android:layout_marginEnd="@dimen/global_spacing" android:layout_marginEnd="@dimen/global_spacing"
android:background="@drawable/shape_rounder_8_stroke_gray1" android:background="@drawable/shape_rounder_8_stroke_gray1"
android:imeOptions="actionDone"
android:paddingStart="@dimen/global_spacing" android:paddingStart="@dimen/global_spacing"
android:paddingEnd="@dimen/global_spacing" android:paddingEnd="@dimen/global_spacing"
android:drawablePadding="4dp" android:drawablePadding="4dp"
android:textSize="@dimen/smallSize" android:textSize="@dimen/smallSize"
android:gravity="center_vertical" android:gravity="center_vertical"
android:hint="输入你要搜索的内容…"
android:text="@{vm.etSearchLiveData}"/>
android:singleLine="true"
android:hint="输入你要搜索的内容…"/>


<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_detail" android:id="@+id/layout_detail"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/et_search" app:layout_constraintTop_toBottomOf="@+id/et_search"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0"
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
android:layout_marginBottom="34dp"
android:background="@drawable/shape_rounder_8_solid_green1" android:background="@drawable/shape_rounder_8_solid_green1"
android:backgroundTint="@color/gray_3" android:backgroundTint="@color/gray_3"
android:layout_marginStart="@dimen/global_spacing" android:layout_marginStart="@dimen/global_spacing"
android:layout_marginEnd="@dimen/global_spacing">
android:layout_marginEnd="@dimen/global_spacing"
app:layout_constrainedHeight="true">


<View <View
android:id="@+id/v_line" android:id="@+id/v_line"
android:id="@+id/rv" android:id="@+id/rv"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/v_line" />
app:layout_constraintTop_toBottomOf="@+id/v_line"
app:layout_constraintBottom_toTopOf="@+id/tv_see_more"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintVertical_bias="0"
app:layout_constrainedHeight="true"
tools:itemCount="10"/>


<TextView <TextView
android:id="@+id/tv_see_more" android:id="@+id/tv_see_more"
app:layout_constraintTop_toBottomOf="@+id/rv" app:layout_constraintTop_toBottomOf="@+id/rv"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0"
android:text="查看更多" android:text="查看更多"
android:drawableEnd="@drawable/ic_down" android:drawableEnd="@drawable/ic_down"
android:drawableTint="@color/gray_2" android:drawableTint="@color/gray_2"

+ 47
- 41
app/src/main/res/layout/item_dic_detail_chinese.xml View File

<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">


<TextView
android:id="@+id/tv_word"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/global_spacing"
android:layout_marginTop="16dp"
android:layout_marginEnd="@dimen/global_spacing"
android:text="jaapjaapjaap"
android:textColor="@color/main_text_color"
android:textSize="22dp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />


<TextView
android:id="@+id/tv_phonetic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:textColor="@color/gray_2"
android:textSize="@dimen/smallerSize"
app:layout_constraintEnd_toEndOf="@+id/tv_word"
app:layout_constraintStart_toStartOf="@+id/tv_word"
app:layout_constraintTop_toBottomOf="@+id/tv_word"
tools:text="[gud:bai] "
app:layout_constraintHorizontal_bias="0"
android:layout_marginTop="8dp"/>

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
<data>

</data>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_phonetic"
/>
android:layout_height="wrap_content">

<TextView
android:id="@+id/tv_word"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/global_spacing"
android:layout_marginTop="16dp"
android:layout_marginEnd="@dimen/global_spacing"
android:text="jaapjaapjaap"
android:textColor="@color/main_text_color"
android:textSize="22dp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />


<TextView
android:id="@+id/tv_phonetic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:textColor="@color/gray_2"
android:textSize="@dimen/smallerSize"
app:layout_constraintEnd_toEndOf="@+id/tv_word"
app:layout_constraintStart_toStartOf="@+id/tv_word"
app:layout_constraintTop_toBottomOf="@+id/tv_word"
tools:text="[gud:bai] "
app:layout_constraintHorizontal_bias="0"
android:layout_marginTop="8dp" />

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_phonetic" />


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

+ 57
- 51
app/src/main/res/layout/item_dic_detail_chinese_for_english_item.xml View File

<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:gravity="center_vertical">
xmlns:app="http://schemas.android.com/apk/res-auto">


<TextView
android:id="@+id/tv_word"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
tools:text="introduction"
android:textSize="@dimen/normalSize"
android:textColor="@color/main_text_color"
android:layout_marginTop="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/iv_horuns"
android:gravity="center_vertical"
/>

<ImageView
android:id="@+id/iv_horuns"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:src="@drawable/ic_horuns"
android:layout_marginStart="@dimen/global_spacing"
android:layout_marginEnd="8dp"
app:layout_constraintTop_toTopOf="@+id/tv_word"
app:layout_constraintBottom_toBottomOf="@+id/tv_word"
app:layout_constraintStart_toStartOf="parent"
android:paddingRight="4dp"/>


<TextView
android:id="@+id/tv_value"
android:layout_width="wrap_content"
<data>

</data>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/global_spacing"
android:layout_marginStart="@dimen/global_spacing"
android:textSize="@dimen/smallerSize"
android:textColor="@color/gray_2"
tools:text="简介"
app:layout_constrainedWidth="true"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_word"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="8dp"
android:layout_marginTop="4dp"/>

</androidx.constraintlayout.widget.ConstraintLayout>
android:orientation="horizontal"
android:gravity="center_vertical">

<TextView
android:id="@+id/tv_word"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
tools:text="introduction"
android:textSize="@dimen/normalSize"
android:textColor="@color/main_text_color"
android:layout_marginTop="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/iv_horuns"
android:gravity="center_vertical" />

<ImageView
android:id="@+id/iv_horuns"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_horuns"
android:layout_marginStart="@dimen/global_spacing"
android:layout_marginEnd="8dp"
app:layout_constraintTop_toTopOf="@+id/tv_word"
app:layout_constraintBottom_toBottomOf="@+id/tv_word"
app:layout_constraintStart_toStartOf="parent"
android:paddingRight="4dp" />


<TextView
android:id="@+id/tv_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/global_spacing"
android:layout_marginStart="@dimen/global_spacing"
android:textSize="@dimen/smallerSize"
android:textColor="@color/gray_2"
tools:text="简介"
app:layout_constrainedWidth="true"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_word"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="8dp"
android:layout_marginTop="4dp" />

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

+ 85
- 78
app/src/main/res/layout/item_dic_detail_english.xml View File

<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">


<TextView
android:id="@+id/tv_word"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/global_spacing"
android:layout_marginTop="16dp"
android:layout_marginEnd="@dimen/global_spacing"
android:text="jaapjaapjaap"
android:textColor="@color/main_text_color"
android:textSize="22dp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<data>


<com.suliang.common.widget.TagLinearLayout
android:id="@+id/layout_phonetic"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:item_horizontal_space="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/tv_word"
app:layout_constraintTop_toBottomOf="@+id/tv_word"
android:layout_marginTop="8dp"
android:layout_marginEnd="@dimen/global_spacing">
</data>

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


<TextView <TextView
android:id="@+id/tv_phonetic_uk"
android:layout_width="wrap_content"
android:id="@+id/tv_word"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:drawableEnd="@drawable/ic_horuns"
android:gravity="center"
android:textColor="@color/gray_2"
android:textSize="@dimen/smallerSize"
tools:text="英 [gud:bai] " />
android:layout_marginStart="@dimen/global_spacing"
android:layout_marginTop="16dp"
android:layout_marginEnd="@dimen/global_spacing"
android:text="jaapjaapjaap"
android:textColor="@color/main_text_color"
android:textSize="22dp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />


<TextView
android:id="@+id/tv_phonetic_us"
android:layout_width="wrap_content"
<com.suliang.common.widget.TagLinearLayout
android:id="@+id/layout_phonetic"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:drawableEnd="@drawable/ic_horuns"
android:gravity="center"
android:textColor="@color/gray_2"
android:textSize="@dimen/smallerSize"
tools:text="美 [gud:bai] " />
app:item_horizontal_space="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/tv_word"
app:layout_constraintTop_toBottomOf="@+id/tv_word"
android:layout_marginTop="8dp"
android:layout_marginEnd="@dimen/global_spacing">

<TextView
android:id="@+id/tv_phonetic_uk"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableEnd="@drawable/ic_horuns"
android:gravity="center"
android:textColor="@color/gray_2"
android:textSize="@dimen/smallerSize"
tools:text="英 [gud:bai] " />

<TextView
android:id="@+id/tv_phonetic_us"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableEnd="@drawable/ic_horuns"
android:gravity="center"
android:textColor="@color/gray_2"
android:textSize="@dimen/smallerSize"
tools:text="美 [gud:bai] " />


</com.suliang.common.widget.TagLinearLayout>
</com.suliang.common.widget.TagLinearLayout>


<TextView
android:id="@+id/tv_explain"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginEnd="@dimen/global_spacing"
android:textColor="@color/main_text_color"
android:textSize="@dimen/smallSize"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/tv_word"
app:layout_constraintTop_toBottomOf="@+id/layout_phonetic"
app:layout_constraintHorizontal_bias="0"
android:textStyle="bold"
tools:text="基本释义"/>
<TextView
android:id="@+id/tv_explain"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginEnd="@dimen/global_spacing"
android:textColor="@color/main_text_color"
android:textSize="@dimen/smallSize"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/tv_word"
app:layout_constraintTop_toBottomOf="@+id/layout_phonetic"
app:layout_constraintHorizontal_bias="0"
android:textStyle="bold"
tools:text="基本释义" />


<TextView
android:id="@+id/tv_expand_explain"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textColor="@color/gray_2"
android:textSize="@dimen/smallSize"
app:layout_constraintEnd_toEndOf="@+id/tv_word"
app:layout_constraintStart_toStartOf="@+id/tv_word"
app:layout_constraintTop_toBottomOf="@+id/tv_explain"
tools:text="扩展释义>"/>
<TextView
android:id="@+id/tv_expand_explain"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textColor="@color/gray_2"
android:textSize="@dimen/smallSize"
app:layout_constraintEnd_toEndOf="@+id/tv_word"
app:layout_constraintStart_toStartOf="@+id/tv_word"
app:layout_constraintTop_toBottomOf="@+id/tv_explain"
tools:text="扩展释义>" />




<include
android:id="@+id/inc_word_detail"
android:layout_width="0dp"
android:layout_height="wrap_content"
layout="@layout/inc_word_detail"
app:layout_constraintTop_toBottomOf="@+id/tv_expand_explain"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<include
android:id="@+id/inc_word_detail"
android:layout_width="0dp"
android:layout_height="wrap_content"
layout="@layout/inc_word_detail"
app:layout_constraintTop_toBottomOf="@+id/tv_expand_explain"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />


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

+ 45
- 39
app/src/main/res/layout/item_dic_search.xml View File

<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="32dp"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:gravity="center_vertical">

<ImageView
android:id="@+id/iv"
android:layout_width="16dp"
android:layout_height="16dp"
tools:src="@drawable/ic_clock"
android:layout_marginStart="@dimen/global_spacing"
android:layout_marginEnd="8dp"/>

<TextView
android:id="@+id/tv_word"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
tools:text="jaap"
android:textSize="@dimen/smallSize"
android:textColor="@color/main_text_color"
/>

<TextView
android:id="@+id/tv_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="end"
android:layout_marginEnd="@dimen/global_spacing"
android:layout_marginStart="@dimen/global_spacing"
android:textSize="@dimen/smallSize"
android:textColor="@color/main_text_color"
tools:text="治疗"/>


</LinearLayout>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<data>

</data>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="32dp"
android:orientation="horizontal"
android:gravity="center_vertical">

<ImageView
android:id="@+id/iv"
android:layout_width="16dp"
android:layout_height="16dp"
tools:src="@drawable/ic_clock"
android:layout_marginStart="@dimen/global_spacing"
android:layout_marginEnd="8dp" />

<TextView
android:id="@+id/tv_word"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
tools:text="jaap"
android:textSize="@dimen/smallSize"
android:textColor="@color/main_text_color" />

<TextView
android:id="@+id/tv_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="end"
android:layout_marginEnd="@dimen/global_spacing"
android:layout_marginStart="@dimen/global_spacing"
android:textSize="@dimen/smallSize"
android:textColor="@color/main_text_color"
tools:text="治疗" />


</LinearLayout>
</layout>

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

if (!LogEnable) return if (!LogEnable) return
val stackTraceElement = Thread.currentThread().stackTrace[5] val stackTraceElement = Thread.currentThread().stackTrace[5]
val className = stackTraceElement.className.substringAfterLast(".") val className = stackTraceElement.className.substringAfterLast(".")
val out = "==> \n${className}_${stackTraceElement.methodName.substringAfterLast(".")}_${stackTraceElement.lineNumber} \n$message"
// val out = "==> \n${className}_${stackTraceElement.methodName.substringAfterLast(".")}_${stackTraceElement.lineNumber} \n$message"
val out = "==> $className \n$message"
when(logType){ when(logType){
"v" -> Log.v(className,out) "v" -> Log.v(className,out)
"d" -> Log.d(className,out) "d" -> Log.d(className,out)

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

package com.suliang.common.util package com.suliang.common.util


import android.text.Html
import android.text.Spanned
import androidx.annotation.ColorInt
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import java.util.regex.Pattern import java.util.regex.Pattern


/** /**
companion object { companion object {
/**
* 匹配指定字符串,并对匹配的字符串每个字符都进行匹配,进行高亮
* @param word String
* @param needHighStr String
* @param color Int
*/
fun highString(word : String, needHighStr : String?, color : Int) : SpannableStringBuilder {
val result = SpannableStringBuilder(word)
if (!needHighStr.isNullOrEmpty()){
val compile = Pattern.compile("[${needHighStr.lowercase()}]") // 匹配needHighStr中的所有匹配字符
val matcher = compile.matcher(word.lowercase())
while (matcher.find()) {
val start = matcher.start() //开始位置
val end = matcher.end() //结束位置
result.setSpan(ForegroundColorSpan(color), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
return result
}
} }
} }

+ 10
- 1
lib/common/src/main/java/com/suliang/common/util/file/FileUtil.kt View File

import com.suliang.common.util.AppGlobals import com.suliang.common.util.AppGlobals
import com.suliang.common.util.LogUtil import com.suliang.common.util.LogUtil
import java.io.* import java.io.*
import java.lang.Exception
import java.text.DecimalFormat import java.text.DecimalFormat
import java.util.zip.GZIPInputStream import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
} }
return result return result
} }
fun getAssetFileSize(assetName:String):Long{
try {
val open = AppGlobals.application.assets.open(assetName)
return open.available().toLong()
}catch (e:Exception){
e.printStackTrace()
}
return 0
}


/* private fun getFileSize(file: File) : Int{ /* private fun getFileSize(file: File) : Int{
if (file.exists()){ if (file.exists()){

Loading…
Cancel
Save