<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" /> |
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") | |||||
} | } | ||||
} | } | ||||
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) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | } |
*/ | */ | ||||
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 |
//查询时间 | //查询时间 | ||||
var queryTime: Long = 0 | var queryTime: Long = 0 | ||||
//词典联想结果判断其是否在历史记录中,如果在历史记录中,则进行时间重排序 | |||||
//词典联想结果判断其是否在历史记录中,如果在历史记录中,则进行时间重排序, | |||||
//历史记录表中查询,默认是true | |||||
//词典查询时,需要设置为false | |||||
@Ignore | @Ignore | ||||
var inHistory : Boolean = false | |||||
var inHistory : Boolean = true | |||||
} | } |
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 |
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") | |||||
} | |||||
} | } | ||||
} | } |
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 | |||||
} | } | ||||
} | } |
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) |
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 | |||||
}*/ | |||||
} | |||||
} | } |
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) | |||||
} | } | ||||
} | } | ||||
} | } |
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) | |||||
} | } | ||||
} | } | ||||
} | } |
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) | |||||
} | } | ||||
} | } | ||||
} | } |
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() | |||||
} | |||||
} | } |
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() | |||||
} | |||||
} | } |
//复制词典 和 词汇量测试 | //复制词典 和 词汇量测试 | ||||
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) | ||||
} | } | ||||
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() | ||||
} | } | ||||
} | } | ||||
} | } |
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" |
<?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> |
<?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> |
<?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> |
<?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> |
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) |
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 | |||||
} | |||||
} | } | ||||
} | } |
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()){ |