同步悬浮窗和Activity的状态

This commit is contained in:
litingting 2025-06-23 17:36:08 +08:00
parent d8180a1ff6
commit b98970177e
15 changed files with 224 additions and 56 deletions

View File

@ -32,15 +32,19 @@
tools:targetApi="31">
<activity
android:name=".activity.PlayActivity"
android:screenOrientation="portrait"
android:exported="false" />
<activity
android:name=".activity.ImageViewActivity"
android:screenOrientation="portrait"
android:exported="false" />
<activity
android:name=".activity.PreviewActivity"
android:screenOrientation="portrait"
android:exported="false" />
<activity
android:name=".activity.MainActivity1"
android:screenOrientation="portrait"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -63,6 +67,7 @@
android:name=".activity.ScreenPermissionActivity"
android:excludeFromRecents="true"
android:taskAffinity=""
android:screenOrientation="portrait"
android:theme="@style/Theme.Transparent"
android:launchMode="singleTop"
android:exported="true" />

View File

@ -14,4 +14,6 @@ class App : Application() {
super.onCreate()
instanceApp = this
}
}

View File

@ -215,15 +215,49 @@ class MainActivity1 : BaseActivity<ActivityMain1Binding>(), ConnectionListener,F
}
override fun onServiceConnected() {
Common.showLog(" MainActivity1 onServiceConnected registerCallback")
viewModel.updateServiceConnectStatus(true)
FloatingWindowBridge.registerCallback(this)
// updateFloatingBtnStatus()
//新开进程的时候检测当前悬浮窗状态刷新当前各ImageView状态
FloatingWindowBridge.sendCommand(FloatingWindowBridge.COMMEND_sync_view_status)
}
/**
* 新开进程的时候检测当前悬浮窗状态刷新当前各ImageView状态
*/
private fun updateFloatingBtnStatus(){
FloatingWindowBridge.sendCommand(FloatingWindowBridge.COMMEND_sync_view_status)
}
override fun onResume() {
super.onResume()
//初始进程中在桌面回到Activity的时候更新当前录制状态
FloatingWindowBridge.sendCommand(FloatingWindowBridge.COMMEND_sync_recording_status)
}
override fun onRefreshViewShow(
webCamShow: Boolean,
screenshotShow: Boolean,
ballShow: Boolean
) {
super.onRefreshViewShow(webCamShow, screenshotShow, ballShow)
Common.showLog(" 收到onRefreshViewShow webCamShow=${webCamShow} screenshotShow=${screenshotShow} ballShow=${ballShow}")
viewModel.updateWebcamStatus(webCamShow)
viewModel.updateScreenshotStatus(screenshotShow)
viewModel.updateBallStatus(ballShow)
}
override fun onRefreshRecordingStatus(status: Int) {
super.onRefreshRecordingStatus(status)
viewModel.updateRecordingStatus(status)
}
override fun onDestroy() {
super.onDestroy()
FloatingWindowBridge.unregisterCallback(this)
FloatingWindowBridge.removeListener(this)
FloatingWindowBridge.unbind(this)
}
}

View File

@ -27,7 +27,6 @@ class ImageInfoAdapter(
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val itemHolder: VHolder<AdapterImageInfoBinding> = holder as VHolder<AdapterImageInfoBinding>
Common.showLog("----$position ")
val item = data[position]
itemHolder.vb.run {

View File

@ -20,7 +20,6 @@ class VideoInfoAdapter(context: Context, private var click: (cropRatio: String)
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val itemHolder: VHolder<AdapterVideoInfoBinding> =
holder as VHolder<AdapterVideoInfoBinding>
Common.showLog("----$position ")
val item = data[position]
itemHolder.vb.run {

View File

@ -4,10 +4,16 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import com.audio.record.screen.test.R
import com.audio.record.screen.test.base.BaseFragment
import com.audio.record.screen.test.databinding.FragmentRecordBinding
import com.audio.record.screen.test.service.FloatingWindowBridge
import com.audio.record.screen.test.tool.Common
import com.audio.record.screen.test.tool.ConstValue
import com.audio.record.screen.test.viewmodel.MainViewModel
class RecordMainFragment : BaseFragment<FragmentRecordBinding>() {
companion object {
@ -18,20 +24,52 @@ class RecordMainFragment : BaseFragment<FragmentRecordBinding>() {
}
}
override fun initBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRecordBinding =
private lateinit var viewModel: MainViewModel
private lateinit var navController: NavController
override fun initBinding(
inflater: LayoutInflater,
container: ViewGroup?
): FragmentRecordBinding =
FragmentRecordBinding.inflate(inflater, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity())[MainViewModel::class.java]
initNavigation()
viewModel.recordingStatus.observe(requireActivity()) { it ->
when (it) {
ConstValue.status_recording -> {
Common.showLog(" recordingStatus status_recording")
navController.currentDestination?.let { navdestination ->
if (navdestination.id == R.id.record_normal_fragment) {
Common.showLog(" recordingStatus status_recording action_to_recording")
navController.navigate(R.id.action_to_recording)
}
}
}
ConstValue.status_complete -> {
Common.showLog(" recordingStatus status_complete")
navController.currentDestination?.let { navdestination ->
if (navdestination.id == R.id.recording_fragment) {
Common.showLog(" recordingStatus status_complete navigateUp")
navController.navigateUp()
}
}
}
}
}
}
private fun initNavigation(){
val navHostFragment = childFragmentManager
.findFragmentById(R.id.nav_host_view) as NavHostFragment
val navController = navHostFragment.navController
private fun initNavigation() {
val navHostFragment = childFragmentManager.findFragmentById(R.id.nav_host_view) as NavHostFragment
navController = navHostFragment.navController
viewModel.updateNavController(navController)
navController.addOnDestinationChangedListener { controller, destination, arguments ->
when (destination.id){
when (destination.id) {
}

View File

@ -43,7 +43,7 @@ class RecordNormalFragment : BaseFragment<FragmentRecordNormalBinding>(), Floati
private lateinit var cameraLauncher: ActivityResultLauncher<String>
//截屏
private lateinit var screenCaptureLauncher: ActivityResultLauncher<Intent>
// private lateinit var screenCaptureLauncher: ActivityResultLauncher<Intent>
//录制
private lateinit var recorderLauncher: ActivityResultLauncher<Intent>
@ -72,7 +72,7 @@ class RecordNormalFragment : BaseFragment<FragmentRecordNormalBinding>(), Floati
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
navController = findNavController()
mediaProjectionManager =
requireActivity().getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
viewModel = ViewModelProvider(requireActivity())[MainViewModel::class.java]
@ -80,29 +80,31 @@ class RecordNormalFragment : BaseFragment<FragmentRecordNormalBinding>(), Floati
initLauncher()
viewModel.serviceConnectStatus.observe(requireActivity()) {
Common.showLog(" FragmentRecordNormal registerCallback")
FloatingWindowBridge.registerCallback(this)
}
viewModel.ballStatus.observe(requireActivity()) {
Common.showLog(" FragmentRecordNormal 更新ballStatus")
binding.btnFloatingBall.isSelected = it
setBall(it)
}
viewModel.screenshotStatus.observe(requireActivity()) {
Common.showLog(" FragmentRecordNormal 更新screenshotStatus")
binding.btnScreenshot.isSelected = it
setScreenshot(it)
}
viewModel.webcamStatus.observe(requireActivity()) {
Common.showLog(" FragmentRecordNormal 更新webcamStatus")
binding.btnWebcam.isSelected = it
setWebcam(it)
}
viewModel.navController.observe(requireActivity()){
navController = it
}
}
private fun initLauncher() {
cameraLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
@ -116,20 +118,6 @@ class RecordNormalFragment : BaseFragment<FragmentRecordNormalBinding>(), Floati
}
}
screenCaptureLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data
FloatingWindowBridge.updateMediaProjection(result.resultCode, data!!)
} else {
Common.showLog("用户取消了录屏权限授权")
}
}
recorderLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK && result.data != null) {
@ -274,22 +262,20 @@ class RecordNormalFragment : BaseFragment<FragmentRecordNormalBinding>(), Floati
}
override fun onStartRecording() {
Common.showLog(" FragmentRecordNormal 回调 onStartRecording")
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
navController.navigate(R.id.action_to_recording)
Common.showLog(" FragmentRecordNormal 回调 onStartRecording 执行111111111")
} else {
Common.showLog(" FragmentRecordNormal 回调 onStartRecording 延迟执行")
// 延迟执行
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
Common.showLog(" FragmentRecordNormal 回调 onStartRecording 延迟执行 11111111")
navController.navigate(R.id.action_to_recording)
}
}
}
// else {
// // 延迟执行
// lifecycleScope.launch {
// lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
// Common.showLog(" FragmentRecordNormal 回调 onStartRecording 延迟执行 11111111")
// navController.navigate(R.id.action_to_recording)
// }
// }
// }
}

View File

@ -4,15 +4,21 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.NavController
import androidx.navigation.fragment.findNavController
import com.audio.record.screen.test.R
import com.audio.record.screen.test.base.BaseFragment
import com.audio.record.screen.test.databinding.FragmentRecordingBinding
import com.audio.record.screen.test.service.FloatingCallback
import com.audio.record.screen.test.service.FloatingWindowBridge
import com.audio.record.screen.test.tool.Common
import com.audio.record.screen.test.tool.ConstValue
import com.audio.record.screen.test.viewmodel.MainViewModel
import kotlinx.coroutines.launch
class RecordingFragment : BaseFragment<FragmentRecordingBinding>(), FloatingCallback {
@ -43,11 +49,12 @@ class RecordingFragment : BaseFragment<FragmentRecordingBinding>(), FloatingCall
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mFindNavController = findNavController()
viewModel = ViewModelProvider(requireActivity())[MainViewModel::class.java]
initClick()
viewModel.navController.observe(requireActivity()){
mFindNavController = it
}
viewModel.serviceConnectStatus.observe(requireActivity()) {
Common.showLog(" RecordingFragment registerCallback")
FloatingWindowBridge.registerCallback(this)
}
viewModel.ballStatus.observe(requireActivity()) {
@ -60,6 +67,14 @@ class RecordingFragment : BaseFragment<FragmentRecordingBinding>(), FloatingCall
viewModel.webcamStatus.observe(requireActivity()) {
binding.imWebcam.isSelected = it
}
viewModel.recordingStatus.observe(requireActivity()){
when(it){
ConstValue.status_pause->{
Common.showLog(" recordingStatus status_pause")
binding.imPauseResume.isSelected = true
}
}
}
}
private fun initClick() {
@ -87,7 +102,6 @@ class RecordingFragment : BaseFragment<FragmentRecordingBinding>(), FloatingCall
}
imStop.setOnClickListener {
FloatingWindowBridge.sendCommand(FloatingWindowBridge.COMMEND_stop_record)
mFindNavController.navigateUp()
}
}
}
@ -96,4 +110,28 @@ class RecordingFragment : BaseFragment<FragmentRecordingBinding>(), FloatingCall
super.onUpdateRecordTime(time)
binding.tvTimer.text = time
}
override fun onPauseRecord() {
super.onPauseRecord()
binding.imPauseResume.isSelected = true
}
override fun onResumeRecord() {
super.onResumeRecord()
binding.imPauseResume.isSelected = false
}
override fun onStopRecord() {
super.onStopRecord()
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
Common.showLog(" RecordingFragment 回调 onStopRecord")
mFindNavController.navigateUp()
}
}
override fun onDestroy() {
super.onDestroy()
FloatingWindowBridge.unregisterCallback(this)
}
}

View File

@ -64,7 +64,7 @@ class VideoFragment : BaseFragment<FragmentVideoBinding>() {
Common.showLog("removeRecent.isEmpty()")
return
}
Common.showLog("--------------------videoRecycler-")
val videoGroupAdapter = VideoGroupAdapter(requireContext()) {
}

View File

@ -1,5 +1,7 @@
package com.audio.record.screen.test.service
import androidx.camera.core.processing.SurfaceProcessorNode.In
interface FloatingCallback {
//开始录制
fun onStartRecording(){}
@ -8,5 +10,13 @@ interface FloatingCallback {
fun onStopRecord(){}
fun onPauseRecord(){}
fun onResumeRecord(){}
fun onRefreshViewShow(webCamShow:Boolean,screenshotShow:Boolean,ballShow:Boolean){}
fun onRefreshRecordingStatus(status:Int){}
}

View File

@ -15,21 +15,23 @@ object FloatingWindowBridge {
//activity 向service发送
val COMMEND_show_camera = "show_camera"
val COMMEND_show_camera = "show_camera" //显示前置摄像头
val COMMEND_hide_camera = "hide_camera"
val COMMEND_show_screenshot = "show_screenshot"
val COMMEND_show_screenshot = "show_screenshot" //显示截屏按钮
val COMMEND_hide_screenshot = "hide_screenshot"
val COMMEND_show_ball = "show_ball"
val COMMEND_show_ball = "show_ball" //显示录制球
val COMMEND_hide_ball = "hide_ball"
val COMMEND_show_countdown = "show_countdown"
val COMMEND_show_countdown = "show_countdown" //显示倒计时
val COMMEND_pause_record = "pause_record"
val COMMEND_resume_record = "resume_record"
val COMMEND_stop_record = "stop_record"
val COMMEND_sync_view_status = "sync_view_status" //请求同步当前三个悬浮按钮状态
val COMMEND_sync_recording_status = "sync_recording_status" //请求同步当前录制状态
//service 向activity反馈
val CALL_start_recording = "start_recording"
@ -42,14 +44,17 @@ object FloatingWindowBridge {
private var mBinder: ScreenRecordService.FloatingBinder? = null
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
Common.showLog("=======onServiceConnected")
Common.showLog("Service-=======onServiceConnected")
isBound = true
mBinder = binder as? ScreenRecordService.FloatingBinder
service = mBinder?.getService()
listeners.forEach { it.get()?.onServiceConnected() }
}
override fun onServiceDisconnected(name: ComponentName?) {
Common.showLog("=======onServiceDisconnected")
//----------------异常断连
Common.showLog("Service-=======onServiceDisconnected")
service = null
isBound = false
}
@ -70,7 +75,7 @@ object FloatingWindowBridge {
if (!isBound) {
//通信
context.bindService(intent, connection, Context.BIND_AUTO_CREATE)
isBound = true
}
}
@ -92,6 +97,13 @@ object FloatingWindowBridge {
COMMEND_pause_record -> service?.pauseRecording()
COMMEND_resume_record -> service?.resumeRecording()
COMMEND_stop_record -> service?.stopRecording()
COMMEND_sync_view_status -> {
service?.getViewStatus()
}
COMMEND_sync_recording_status->{
service?.getRecordingStatus()
}
else -> {
}
@ -114,8 +126,12 @@ object FloatingWindowBridge {
}
fun unbind(context: Context) {
Common.showLog("Service-=======unbindService")
if (isBound) {
Common.showLog("Service-=======unbindService 1111")
context.unbindService(connection)
service = null
mBinder = null
isBound = false
}
}

View File

@ -144,7 +144,7 @@ class ScreenRecordService : Service() {
private val timeUpdateRunnable = object : Runnable {
override fun run() {
Common.showLog("-------timeUpdateRunnable 更新时间.....")
val currentRecordingTimeInMillis =
System.currentTimeMillis() - recordingStartTime - totalPausedTime
val elapsed = currentRecordingTimeInMillis / 1000
@ -233,6 +233,9 @@ class ScreenRecordService : Service() {
Common.showLog("Service---onStartCommand")
lifecycleOwner?.onStart()
// 初始化 MediaProjection、MediaRecorder 逻辑等
// TODO: onStartCommand
return START_NOT_STICKY
}
@ -453,10 +456,10 @@ class ScreenRecordService : Service() {
if (aX >= screenWidth/2) {
layoutParamsBallExpand.x = aX - 10.dpToPx(this)
Common.showLog("--111111111--aX=${aX} layoutParamsBallExpand.x=${ layoutParamsBallExpand.x}")
} else {
layoutParamsBallExpand.x = aX + 10.dpToPx(this)
Common.showLog("---22222222--aX=${aX} layoutParamsBallExpand.x=${ layoutParamsBallExpand.x}")
}
layoutParamsBallExpand.y = aY
windowManager.addView(ballViewExpand, layoutParamsBallExpand)
@ -794,6 +797,9 @@ class ScreenRecordService : Service() {
mediaRecorder.pause()
mRecordingStatus = ConstValue.status_pause
updateBall()
callbacks.forEach {
it.get()?.onPauseRecord()
}
pauseStartTime = System.currentTimeMillis()
isPause = true
}
@ -808,6 +814,9 @@ class ScreenRecordService : Service() {
mediaRecorder.resume()
mRecordingStatus = ConstValue.status_recording
updateBall()
callbacks.forEach {
it.get()?.onResumeRecord()
}
totalPausedTime += System.currentTimeMillis() - pauseStartTime
isPause = false
mRecorderHandler.post(timeUpdateRunnable)
@ -888,6 +897,18 @@ class ScreenRecordService : Service() {
}
}
fun getViewStatus(){
callbacks.forEach {
it.get()?.onRefreshViewShow(isWebcamViewAdded,isScreenshotViewAdded,isBallViewAdded)
}
}
fun getRecordingStatus(){
callbacks.forEach {
it.get()?.onRefreshRecordingStatus(mRecordingStatus)
}
}
fun stopRecording() {
// TODO: 录屏完成
mRecordingStatus = ConstValue.status_complete

View File

@ -341,7 +341,7 @@ object Common {
val minutes = seconds / 60
val sec = seconds % 60
val formatted = String.format("%02d:%02d", minutes, sec)
showLog("Recorder Recording duration: $formatted")
// showLog("Recorder Recording duration: $formatted")
return formatted
// 示例:更新 UI TextView
// binding.timerText.text = formatted

View File

@ -116,7 +116,6 @@ object VideoFileHelper {
*/
fun queryVideoInfoListInFolder(context: Context, folderName: String): List<VideoInfo> {
Common.showLog("------folderName=${folderName}")
val videoInfoList = mutableListOf<VideoInfo>()
val resolver = context.contentResolver

View File

@ -5,6 +5,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.navigation.NavController
class MainViewModel : ViewModel() {
@ -28,6 +29,21 @@ class MainViewModel : ViewModel() {
val ballStatus: LiveData<Boolean> get() = _ballStatus
//录制状态
private val _recordingStatus = MutableLiveData<Int>()
val recordingStatus: LiveData<Int> get() = _recordingStatus
//录制导航器
private val _navController = MutableLiveData<NavController>()
val navController: LiveData<NavController> get() = _navController
fun updateNavController(message: NavController) {
_navController.value = message
}
fun updateServiceConnectStatus(message: Boolean) {
_serviceConnectStatus.value = message
}
@ -41,4 +57,9 @@ class MainViewModel : ViewModel() {
fun updateScreenshotStatus(message: Boolean) {
_screenshotStatus.value = message
}
fun updateRecordingStatus(message: Int) {
_recordingStatus.value = message
}
}