Merge branch 'master' of gitlab.navinfo.com:CollectVehicle/OneMapQS

This commit is contained in:
2023-05-04 11:07:46 +08:00
56 changed files with 1290 additions and 82 deletions

View File

@@ -37,6 +37,7 @@ public class CheckPermissionsActivity extends BaseActivity {
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO
};
private static final int PERMISSON_REQUESTCODE = 0;
@@ -51,6 +52,7 @@ public class CheckPermissionsActivity extends BaseActivity {
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO,
BACKGROUND_LOCATION_PERMISSION
};
}

View File

@@ -156,12 +156,17 @@ class LoginViewModel @Inject constructor(
* 创建用户目录
*/
private fun createUserFolder(context: Context, userId: String) {
Constant.IS_VIDEO_SPEED = false
Constant.USER_ID = userId
Constant.VERSION_ID = userId
Constant.USER_DATA_PATH = Constant.DATA_PATH + Constant.USER_ID + "/" + Constant.VERSION_ID
Constant.USER_DATA_ATTACHEMNT_PATH = Constant.USER_DATA_PATH + "/attachment/"
// 在SD卡创建用户目录解压资源等
val userFolder = File(Constant.USER_DATA_PATH)
if (!userFolder.exists()) userFolder.mkdirs()
//创建附件目录
val userAttachmentFolder = File(Constant.USER_DATA_ATTACHEMNT_PATH)
if (!userAttachmentFolder.exists()) userAttachmentFolder.mkdirs()
// 初始化Realm
Realm.init(context.applicationContext)
val password = "encryp".encodeToByteArray().copyInto(ByteArray(64))

View File

@@ -2,6 +2,9 @@ package com.navinfo.omqs.ui.activity.map
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.MotionEvent
import android.view.View
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
import androidx.core.view.WindowCompat
@@ -56,6 +59,26 @@ class MainActivity : BaseActivity() {
//给xml传递viewModel对象
binding.viewModel = viewModel
binding.mainActivityVoice.setOnTouchListener(object : View.OnTouchListener {
@RequiresApi(Build.VERSION_CODES.Q)
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
Log.e("qj",event?.action.toString())
when (event?.action) {
MotionEvent.ACTION_DOWN ->{
voiceOnTouchStart()//Do Something
Log.e("qj","voiceOnTouchStart")
}
MotionEvent.ACTION_UP ->{
voiceOnTouchStop()//Do Something
Log.e("qj","voiceOnTouchStop")
}
}
return v?.onTouchEvent(event) ?: true
}
})
viewModel.liveDataQsRecordIdList.observe(this) {
//处理页面跳转
viewModel.navigation(this, it)
@@ -113,8 +136,19 @@ class MainActivity : BaseActivity() {
* 点击录音按钮
*/
fun voiceOnclick() {
val naviController = findNavController(R.id.main_activity_right_fragment)
naviController.navigate(R.id.EvaluationResultFragment)
/* val naviController = findNavController(R.id.main_activity_right_fragment)
naviController.navigate(R.id.EvaluationResultFragment)*/
}
fun voiceOnTouchStart(){
viewModel!!.startSoundMetter(this,binding.mainActivityVoice)
}
@RequiresApi(Build.VERSION_CODES.Q)
fun voiceOnTouchStop(){
if(Constant.IS_VIDEO_SPEED){
viewModel!!.stopSoundMeter()
}
}
// override fun onBackPressed() {

View File

@@ -1,20 +1,29 @@
package com.navinfo.omqs.ui.activity.map
import android.app.Activity
import android.content.Context
import android.content.DialogInterface
import android.graphics.drawable.AnimationDrawable
import android.graphics.drawable.BitmapDrawable
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.PopupWindow
import androidx.annotation.RequiresApi
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.findNavController
import com.blankj.utilcode.util.ToastUtils
import com.navinfo.collect.library.data.dao.impl.TraceDataBase
import com.navinfo.collect.library.data.entity.NiLocation
import com.navinfo.collect.library.data.entity.RenderEntity
import com.navinfo.collect.library.map.NIMapController
import com.navinfo.collect.library.map.handler.NiLocationListener
import com.navinfo.collect.library.map.handler.OnQsRecordItemClickListener
import com.navinfo.collect.library.utils.GeometryTools
import com.navinfo.collect.library.utils.GeometryToolsKt
@@ -23,14 +32,18 @@ import com.navinfo.omqs.R
import com.navinfo.omqs.db.RealmOperateHelper
import com.navinfo.omqs.ui.dialog.CommonDialog
import com.navinfo.omqs.ui.manager.TakePhotoManager
import com.navinfo.omqs.util.DateTimeUtil
import com.navinfo.omqs.util.SoundMeter
import com.navinfo.omqs.util.SpeakMode
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ActivityContext
import dagger.hilt.android.qualifiers.ApplicationContext
import io.realm.RealmSet
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.oscim.core.GeoPoint
import org.videolan.libvlc.LibVlcUtil
import java.io.File
import java.util.*
import javax.inject.Inject
/**
@@ -52,9 +65,15 @@ class MainViewModel @Inject constructor(
//看板数据
val liveDataSignList = MutableLiveData<List<SignBean>>()
// private var niLocationList: MutableList<NiLocation> = ArrayList<NiLocation>()
var testPoint = GeoPoint(0, 0)
//语音窗体
private var pop: PopupWindow? = null
private var mSpeakMode: SpeakMode? = null
//录音图标
var volume: ImageView? = null
var mSoundMeter: SoundMeter? = null
init {
mapController.markerHandle.setOnQsRecordItemClickListener(object :
@@ -102,11 +121,12 @@ class MainViewModel @Inject constructor(
}
Log.e("jingo", "定位点插入 ${Thread.currentThread().name}")
traceDataBase.niLocationDao.insert(location)
mapController.mMapView.vtmMap.updateMap(true)
}
}
//用于定位点捕捉道路
viewModelScope.launch(Dispatchers.Default) {
mapController.locationLayerHandler.niLocationFlow.collect { location ->
mapController.locationLayerHandler.niLocationFlow.collectLatest { location ->
Log.e("jingo", "定位点绑定道路 ${Thread.currentThread().name}")
location.longitude = testPoint.longitude
location.latitude = testPoint.latitude
@@ -150,6 +170,7 @@ class MainViewModel @Inject constructor(
//显示轨迹图层
mapController.layerManagerHandler.showNiLocationLayer()
}
/**
@@ -161,9 +182,6 @@ class MainViewModel @Inject constructor(
override fun onCleared() {
super.onCleared()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mapController.lineHandler.removeLine()
}
}
//点击相机按钮
@@ -171,8 +189,6 @@ class MainViewModel @Inject constructor(
Log.e("qj", LibVlcUtil.hasCompatibleCPU(context).toString())
//ToastUtils.showShort("点击了相机")
if (mCameraDialog == null) {
mCameraDialog = CommonDialog(
context,
@@ -206,57 +222,82 @@ class MainViewModel @Inject constructor(
})
}
fun startSoundMetter(context: Context, v: View) {
// fun startSaveTraceThread(context: Context) {
// Thread(Runnable {
// try {
// while (true) {
//
// if (niLocationList != null && niLocationList.size > 0) {
//
// var niLocation = niLocationList[0]
// val geometry = GeometryTools.createGeometry(
// GeoPoint(
// niLocation.latitude,
// niLocation.longitude
// )
// )
// val tileX = RealmSet<Int>()
// GeometryToolsKt.getTileXByGeometry(geometry.toString(), tileX)
// val tileY = RealmSet<Int>()
// GeometryToolsKt.getTileYByGeometry(geometry.toString(), tileY)
//
// //遍历存储tile对应的x与y的值
// tileX.forEach { x ->
// tileY.forEach { y ->
// niLocation.tilex = x
// niLocation.tiley = y
// }
// }
//
// TraceDataBase.getDatabase(
// context,
// Constant.USER_DATA_PATH + "/trace.sqlite"
// ).niLocationDao.insert(niLocation)
// niLocationList.remove(niLocation)
//
// Log.e("qj", "saveTrace==${niLocationList.size}")
// }
// Thread.sleep(30)
// }
// } catch (e: InterruptedException) {
// e.printStackTrace()
// Log.e("qj", "异常==${e.message}")
// }
// }).start()
// }
if(mSpeakMode==null){
mSpeakMode = SpeakMode(context as Activity?)
}
//语音识别动画
if (pop == null) {
pop = PopupWindow()
pop!!.width = ViewGroup.LayoutParams.MATCH_PARENT
pop!!.height = ViewGroup.LayoutParams.WRAP_CONTENT
pop!!.setBackgroundDrawable(BitmapDrawable())
val view = View.inflate(context, R.layout.cv_card_voice_rcd_hint_window, null)
pop!!.contentView = view
volume = view.findViewById(R.id.volume)
}
pop!!.update()
Constant.IS_VIDEO_SPEED = true
//录音动画
//录音动画
if (pop != null) {
pop!!.showAtLocation(v, Gravity.CENTER, 0, 0)
}
volume!!.setBackgroundResource(R.drawable.pop_voice_img)
val animation = volume!!.background as AnimationDrawable
animation.start()
val name: String = DateTimeUtil.getTimeSSS().toString() + ".m4a"
if (mSoundMeter == null) {
mSoundMeter = SoundMeter()
}
mSoundMeter!!.setmListener(object : SoundMeter.OnSoundMeterListener {
@RequiresApi(Build.VERSION_CODES.Q)
override fun onSuccess(filePath: String?) {
if (!TextUtils.isEmpty(filePath) && File(filePath).exists()) {
if (File(filePath) == null || File(filePath).length() < 1600) {
ToastUtils.showLong("语音时间太短,无效!")
mSpeakMode!!.speakText("语音时间太短,无效")
stopSoundMeter()
return
}
}
mSpeakMode!!.speakText("结束录音")
//获取右侧fragment容器
val naviController = (context as Activity).findNavController(R.id.main_activity_right_fragment)
val bundle = Bundle()
bundle.putString("filePath", filePath)
naviController.navigate(R.id.EvaluationResultFragment, bundle)
}
@RequiresApi(api = Build.VERSION_CODES.Q)
override fun onfaild(message: String?) {
ToastUtils.showLong("录制失败!")
mSpeakMode!!.speakText("录制失败")
stopSoundMeter()
}
})
mSoundMeter!!.start(Constant.USER_DATA_ATTACHEMNT_PATH + name)
ToastUtils.showLong("开始录音")
mSpeakMode!!.speakText("开始录音")
}
//停止语音录制
@RequiresApi(api = Build.VERSION_CODES.Q)
fun stopSoundMeter() {
//先重置标识,防止按钮抬起时触发语音结束
Constant.IS_VIDEO_SPEED = false
if (mSoundMeter != null && mSoundMeter!!.isStartSound()) {
mSoundMeter!!.stop()
}
if (pop != null && pop!!.isShowing) pop!!.dismiss()
}
// //增加轨迹存储
// fun addSaveTrace(niLocation: NiLocation) {
// if (niLocation != null && niLocationList != null) {
// niLocationList.add(niLocation)
// }
// }
/**
* 处理页面调转
@@ -292,6 +333,5 @@ class MainViewModel @Inject constructor(
}
}
}
}
}

View File

@@ -23,6 +23,5 @@ class SignAdapter : BaseRecyclerViewAdapter<SignBean>() {
val item = data[position]
bd.signMainIcon.background = holder.viewBinding.root.context.getDrawable(item.iconId)
bd.signMainIcon.text = item.iconText
}
}

View File

@@ -1,15 +1,22 @@
package com.navinfo.omqs.ui.fragment.evaluationresult
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.databinding.DataBindingUtil
import androidx.navigation.NavOptions
import androidx.navigation.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.navinfo.omqs.Constant
import com.navinfo.omqs.R
import com.navinfo.omqs.databinding.FragmentEvaluationResultBinding
import com.navinfo.omqs.ui.fragment.BaseFragment
import com.navinfo.omqs.ui.fragment.tasklist.TaskListAdapter
import com.navinfo.omqs.ui.other.shareViewModels
import dagger.hilt.android.AndroidEntryPoint
@@ -17,15 +24,26 @@ import dagger.hilt.android.AndroidEntryPoint
class EvaluationResultFragment : BaseFragment(), View.OnClickListener {
private lateinit var binding: FragmentEvaluationResultBinding
private val viewModel by shareViewModels<EvaluationResultViewModel>("QsRecode")
private val adapter: SoundtListAdapter by lazy {
SoundtListAdapter()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
binding =
DataBindingUtil.inflate(inflater, R.layout.fragment_evaluation_result, container, false)
binding.fragment = this
val layoutManager = LinearLayoutManager(context)
binding.viewModel = viewModel
binding.lifecycleOwner = this
//// 设置 RecyclerView 的固定大小,避免在滚动时重新计算视图大小和布局,提高性能
binding.evaluationVoiceRecyclerview.setHasFixedSize(true)
binding.evaluationVoiceRecyclerview.layoutManager = layoutManager
binding.evaluationVoiceRecyclerview.adapter = adapter
viewModel.listDataChatMsgEntityList.observe(viewLifecycleOwner) {
adapter.refreshData(it)
}
viewModel.getChatMsgEntityList()
return binding.root
}
@@ -53,18 +71,40 @@ class EvaluationResultFragment : BaseFragment(), View.OnClickListener {
else -> true
}
}
binding.evaluationVoice.setOnTouchListener(object : View.OnTouchListener {
@RequiresApi(Build.VERSION_CODES.Q)
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
Log.e("qj",event?.action.toString())
when (event?.action) {
MotionEvent.ACTION_DOWN ->{
voiceOnTouchStart()//Do Something
Log.e("qj","voiceOnTouchStart")
}
MotionEvent.ACTION_UP ->{
voiceOnTouchStop()//Do Something
Log.e("qj","voiceOnTouchStop")
}
}
return v?.onTouchEvent(event) ?: true
}
})
/**
* 读取元数据
*/
if (arguments != null) {
val id = requireArguments().getString("QsId")
//语音路径
val filePath = requireArguments().getString("filePath")
if (id != null) {
viewModel.initData(id)
} else {
viewModel.initNewData()
viewModel.initNewData(filePath!!)
}
} else {
viewModel.initNewData()
viewModel.initNewData("")
}
// //监听大分类数据变化
@@ -227,4 +267,16 @@ class EvaluationResultFragment : BaseFragment(), View.OnClickListener {
}
}
}
fun voiceOnTouchStart(){
viewModel!!.startSoundMetter(requireActivity(),binding.evaluationVoice)
}
@RequiresApi(Build.VERSION_CODES.Q)
fun voiceOnTouchStop(){
if(Constant.IS_VIDEO_SPEED){
viewModel!!.stopSoundMeter()
}
}
}

View File

@@ -1,23 +1,47 @@
package com.navinfo.omqs.ui.fragment.evaluationresult
import android.app.Activity
import android.content.Context
import android.graphics.drawable.AnimationDrawable
import android.graphics.drawable.BitmapDrawable
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.PopupWindow
import androidx.annotation.RequiresApi
import androidx.core.util.rangeTo
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.findNavController
import com.blankj.utilcode.util.ToastUtils
import com.navinfo.collect.library.data.entity.AttachmentBean
import com.navinfo.collect.library.data.entity.QsRecordBean
import com.navinfo.collect.library.data.entity.RenderEntity.Companion.LinkTable
import com.navinfo.collect.library.map.NIMapController
import com.navinfo.collect.library.utils.GeometryTools
import com.navinfo.omqs.Constant
import com.navinfo.omqs.R
import com.navinfo.omqs.bean.ChatMsgEntity
import com.navinfo.omqs.db.RealmOperateHelper
import com.navinfo.omqs.db.RoomAppDatabase
import com.navinfo.omqs.util.DateTimeUtil
import com.navinfo.omqs.util.SoundMeter
import com.navinfo.omqs.util.SpeakMode
import dagger.hilt.android.lifecycle.HiltViewModel
import io.realm.Realm
import io.realm.RealmList
import io.realm.kotlin.where
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.oscim.core.GeoPoint
import java.io.File
import java.util.*
import javax.inject.Inject
@@ -50,11 +74,22 @@ class EvaluationResultViewModel @Inject constructor(
*/
val liveDataRightTypeList = MutableLiveData<List<RightBean>>()
var liveDataQsRecordBean = MutableLiveData<QsRecordBean>()
var listDataChatMsgEntityList = MutableLiveData<MutableList<ChatMsgEntity>>()
var oldBean: QsRecordBean? = null
//语音窗体
private var pop: PopupWindow? = null
private var mSpeakMode: SpeakMode? = null
//录音图标
var volume: ImageView? = null
var mSoundMeter: SoundMeter? = null
init {
liveDataQsRecordBean.value = QsRecordBean(id = UUID.randomUUID().toString())
Log.e("jingo", "EvaluationResultViewModel 创建了 ${hashCode()}")
@@ -82,7 +117,7 @@ class EvaluationResultViewModel @Inject constructor(
/**
* 查询数据库,获取问题分类
*/
fun initNewData() {
fun initNewData(filePath: String) {
viewModelScope.launch(Dispatchers.IO) {
getClassTypeList()
getProblemLinkList()
@@ -95,6 +130,8 @@ class EvaluationResultViewModel @Inject constructor(
captureLink(geoPoint.longitude, geoPoint.latitude)
}
}
addChatMsgEntity(filePath)
}
/**
@@ -304,4 +341,126 @@ class EvaluationResultViewModel @Inject constructor(
}
}
}
/**
* 查询问题类型列表
*/
@RequiresApi(Build.VERSION_CODES.N)
fun getChatMsgEntityList() {
val chatMsgEntityList: MutableList<ChatMsgEntity> = ArrayList()
liveDataQsRecordBean.value?.attachmentBeanList?.forEach {
//1 录音
if (it.type == 1) {
val chatMsgEntity = ChatMsgEntity()
chatMsgEntity.name = it.name
chatMsgEntity.voiceUri = Constant.USER_DATA_ATTACHEMNT_PATH
chatMsgEntityList.add(chatMsgEntity)
}
}
listDataChatMsgEntityList.postValue(chatMsgEntityList)
}
fun addChatMsgEntity(filePath: String) {
if(filePath!=null){
var chatMsgEntityList: MutableList<ChatMsgEntity> = ArrayList()
if(listDataChatMsgEntityList.value?.isEmpty() == false){
chatMsgEntityList = listDataChatMsgEntityList.value!!
}
val chatMsgEntity = ChatMsgEntity()
chatMsgEntity.name = filePath.replace(Constant.USER_DATA_ATTACHEMNT_PATH,"").toString()
chatMsgEntity.voiceUri = Constant.USER_DATA_ATTACHEMNT_PATH
chatMsgEntityList.add(chatMsgEntity)
var attachmentList: RealmList<AttachmentBean> = RealmList()
//赋值处理
if(liveDataQsRecordBean.value?.attachmentBeanList?.isEmpty() == false){
attachmentList = liveDataQsRecordBean.value?.attachmentBeanList!!
}
val attachmentBean = AttachmentBean()
attachmentBean.name = chatMsgEntity.name!!
attachmentBean.type = 1
attachmentList.add(attachmentBean)
liveDataQsRecordBean.value?.attachmentBeanList = attachmentList
listDataChatMsgEntityList.postValue(chatMsgEntityList)
}
}
fun startSoundMetter(activity: Activity, v: View) {
if(mSpeakMode==null){
mSpeakMode = SpeakMode(activity)
}
//语音识别动画
if (pop == null) {
pop = PopupWindow()
pop!!.width = ViewGroup.LayoutParams.MATCH_PARENT
pop!!.height = ViewGroup.LayoutParams.WRAP_CONTENT
pop!!.setBackgroundDrawable(BitmapDrawable())
val view = View.inflate(activity as Context, R.layout.cv_card_voice_rcd_hint_window, null)
pop!!.contentView = view
volume = view.findViewById(R.id.volume)
}
pop!!.update()
Constant.IS_VIDEO_SPEED = true
//录音动画
//录音动画
if (pop != null) {
pop!!.showAtLocation(v, Gravity.CENTER, 0, 0)
}
volume!!.setBackgroundResource(R.drawable.pop_voice_img)
val animation = volume!!.background as AnimationDrawable
animation.start()
val name: String = DateTimeUtil.getTimeSSS().toString() + ".m4a"
if (mSoundMeter == null) {
mSoundMeter = SoundMeter()
}
mSoundMeter!!.setmListener(object : SoundMeter.OnSoundMeterListener {
@RequiresApi(Build.VERSION_CODES.Q)
override fun onSuccess(filePath: String?) {
if (!TextUtils.isEmpty(filePath) && File(filePath).exists()) {
if (File(filePath) == null || File(filePath).length() < 1600) {
ToastUtils.showLong("语音时间太短,无效!")
mSpeakMode!!.speakText("语音时间太短,无效")
stopSoundMeter()
return
}
}
mSpeakMode!!.speakText("结束录音")
addChatMsgEntity(filePath!!)
}
@RequiresApi(api = Build.VERSION_CODES.Q)
override fun onfaild(message: String?) {
ToastUtils.showLong("录制失败!")
mSpeakMode!!.speakText("录制失败")
stopSoundMeter()
}
})
mSoundMeter!!.start(Constant.USER_DATA_ATTACHEMNT_PATH + name)
ToastUtils.showLong("开始录音")
mSpeakMode!!.speakText("开始录音")
}
//停止语音录制
@RequiresApi(api = Build.VERSION_CODES.Q)
fun stopSoundMeter() {
//先重置标识,防止按钮抬起时触发语音结束
Constant.IS_VIDEO_SPEED = false
if (mSoundMeter != null && mSoundMeter!!.isStartSound()) {
mSoundMeter!!.stop()
}
if (pop != null && pop!!.isShowing) pop!!.dismiss()
}
}

View File

@@ -0,0 +1,241 @@
package com.navinfo.omqs.ui.fragment.evaluationresult
import android.graphics.drawable.AnimationDrawable
import android.media.AudioFormat
import android.media.AudioManager
import android.media.AudioTrack
import android.media.MediaPlayer
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import com.navinfo.omqs.R
import com.navinfo.omqs.bean.ChatMsgEntity
import com.navinfo.omqs.databinding.AdapterSoundListBinding
import com.navinfo.omqs.ui.other.BaseRecyclerViewAdapter
import com.navinfo.omqs.ui.other.BaseViewHolder
import java.io.*
/**
* 语音 RecyclerView 适配器
*
* 在 RecycleView 的 ViewHolder 中监听 ViewModel 的 LiveData然后此时传递的 lifecycleOwner 是对应的 Fragment。由于 ViewHolder 的生命周期是比 Fragment 短的,所以当 ViewHolder 销毁时,由于 Fragment 的 Lifecycle 还没有结束,此时 ViewHolder 会发生内存泄露(监听的 LiveData 没有解绑)
* 这种场景下有两种解决办法:
*使用 LiveData 的 observeForever 然后在 ViewHolder 销毁前手动调用 removeObserver
*使用 LifecycleRegistry 给 ViewHolder 分发生命周期(这里使用了这个)
*/
class SoundtListAdapter(
) : BaseRecyclerViewAdapter<ChatMsgEntity>() {
//媒体播放器
private val mMediaPlayer = MediaPlayer()
//媒体播放器
private var md: MediaPlayer? = null
private var mAudioTrack: AudioTrack? = null
//录音结束后,右侧显示图片
private var animView: View? = null
//录音时动画效果
private var animaV: AnimationDrawable? = null
private var itemClick: OnItemClickListner? = null
//最大宽度
private val maxWidth = 0
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
val viewBinding =
AdapterSoundListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return BaseViewHolder(viewBinding)
}
override fun onViewRecycled(holder: BaseViewHolder) {
super.onViewRecycled(holder)
}
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
val binding: AdapterSoundListBinding =
holder.viewBinding as AdapterSoundListBinding
val entity = data[position]
//tag 方便onclick里拿到数据
holder.tag = entity.name.toString()
holder.viewBinding.tvTime.isSelected = entity.isDelete
holder.viewBinding.rlSoundContent.isSelected = entity.isDelete
holder.viewBinding.ivSoundAnim.setBackgroundResource(R.drawable.icon_sound_03)
/* if (itemClick != null) {
holder.viewBinding.rlSoundContent.setOnClickListener {
itemClick!!.onItemClick(it.findViewById<View>(R.id.rl_sound_content), position)
}
}*/
//mixWidth
if (!TextUtils.isEmpty(entity.name)) {
/* if (entity.name.indexOf(".pcm") > 0) {
val file: File = File(entity.voiceUri + entity.name)
if (file != null) {
val time = (file.length() / 16000).toInt()
val layoutParams: ViewGroup.LayoutParams =
holder.viewBinding.rlSoundContent.getLayoutParams()
layoutParams.width = 115 + time * 10
layoutParams.width =
if (layoutParams.width > layoutParams.width) maxWidth else layoutParams.width
holder.viewBinding.rlSoundContent.setLayoutParams(layoutParams)
holder.viewBinding.tvTime.text = time.toString() + "\""
}
} else {
try {
md = MediaPlayer()
md!!.reset()
md!!.setDataSource(entity.voiceUri+entity.name)
md!!.prepare()
} catch (e: Exception) {
// TODO Auto-generated catch block
e.printStackTrace()
}
var time =
if (entity.voiceTimeLong == null) md!!.duration.toString() + "" else entity.voiceTimeLong
.toString() + ""
if (!TextUtils.isEmpty(time)) {
val i = md!!.duration / 1000
time = i.toString() + "\""
val layoutParams: ViewGroup.LayoutParams =
holder.viewBinding.rlSoundContent.getLayoutParams()
layoutParams.width = 115 + i * 10
layoutParams.width =
if (layoutParams.width > layoutParams.width) maxWidth else layoutParams.width
holder.viewBinding.rlSoundContent.layoutParams = layoutParams
}
holder.viewBinding.tvTime.text = time
md!!.release()
}*/
}
}
/**
* 播放某段录音
*
* @param view 显示动画
* @param index 录音在集合中索引
*/
fun setPlayerIndex(view: View, index: Int) {
val imageV = view.findViewById<View>(R.id.iv_sound_anim) as ImageView
val width = view.width
if (animView != null) {
animaV?.stop()
animView!!.setBackgroundResource(R.drawable.icon_sound_03)
}
animView = imageV
val entity: ChatMsgEntity = data.get(index)
playMusic(entity.voiceUri + entity.name, imageV)
}
/**
* 播放录音
*
* @param name 录音名称
* @Description
*/
fun playMusic(name: String, imageV: ImageView) {
imageV.setBackgroundResource(R.drawable.sound_anim)
animaV = imageV.background as AnimationDrawable
animaV!!.start()
/* if (name.index(".pcm") > 0) {
audioTrackPlay(name, imageV)
} else {
mediaPlayer(name, imageV)
}*/
}
fun mediaPlayer(name: String, imageV: ImageView) {
try {
if (mMediaPlayer.isPlaying) {
mMediaPlayer.stop()
}
mMediaPlayer.reset()
mMediaPlayer.setDataSource(name)
mMediaPlayer.prepare()
mMediaPlayer.start()
//播放结束
mMediaPlayer.setOnCompletionListener {
animaV!!.stop()
imageV.setBackgroundResource(R.drawable.icon_sound_03)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
fun audioTrackPlay(name: String, imageV: ImageView) {
var dis: DataInputStream? = null
try {
//从音频文件中读取声音
dis = DataInputStream(BufferedInputStream(FileInputStream(name)))
} catch (e: FileNotFoundException) {
e.printStackTrace()
}
//最小缓存区
val bufferSizeInBytes = AudioTrack.getMinBufferSize(
16000,
AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT
)
//创建AudioTrack对象 依次传入 :流类型、采样率与采集的要一致、音频通道采集是IN 播放时OUT、量化位数、最小缓冲区、模式
if (mAudioTrack != null) {
mAudioTrack!!.stop()
mAudioTrack!!.release()
mAudioTrack = null
}
mAudioTrack = AudioTrack(
AudioManager.STREAM_MUSIC,
16000,
AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT,
bufferSizeInBytes,
AudioTrack.MODE_STREAM
)
val data = ByteArray(bufferSizeInBytes)
mAudioTrack!!.play() //开始播放
while (true) {
var i = 0
try {
while (dis!!.available() > 0 && i < data.size) {
data[i] = dis.readByte() //录音时write Byte 那么读取时就该为readByte要相互对应
i++
}
} catch (e: IOException) {
// TODO Auto-generated catch block
e.printStackTrace()
}
mAudioTrack!!.write(data, 0, data.size)
if (i != bufferSizeInBytes) //表示读取完了
{
break
}
}
mAudioTrack!!.stop() //停止播放
mAudioTrack!!.release() //释放资源
mAudioTrack = null
imageV.post {
animaV?.stop()
imageV.setBackgroundResource(R.drawable.icon_sound_03)
}
}
fun setOnItemClickListener(clickListner: OnItemClickListner) {
itemClick = clickListner
}
interface OnItemClickListner {
fun onItemClick(view: View?, postion: Int)
}
override fun getItemViewRes(position: Int): Int {
return R.layout.adapter_sound_list
}
}

View File

@@ -198,7 +198,7 @@ class TaskListAdapter(
}
override fun getItemViewRes(position: Int): Int {
return R.layout.adapter_offline_map_city
return R.layout.adapter_task_list
}
}