悬浮窗功能

This commit is contained in:
litingting 2025-06-19 18:20:08 +08:00
parent 481452e688
commit d8180a1ff6
27 changed files with 719 additions and 64 deletions

View File

@ -59,6 +59,13 @@
<!-- <category android:name="android.intent.category.LAUNCHER" /> --> <!-- <category android:name="android.intent.category.LAUNCHER" /> -->
<!-- </intent-filter> --> <!-- </intent-filter> -->
</activity> </activity>
<activity
android:name=".activity.ScreenPermissionActivity"
android:excludeFromRecents="true"
android:taskAffinity=""
android:theme="@style/Theme.Transparent"
android:launchMode="singleTop"
android:exported="true" />
<service <service
android:name=".service.ScreenRecordService" android:name=".service.ScreenRecordService"

View File

@ -0,0 +1,73 @@
package com.audio.record.screen.test.activity
import android.content.Context
import android.content.Intent
import android.media.projection.MediaProjectionManager
import android.os.Bundle
import android.os.PersistableBundle
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import com.audio.record.screen.test.base.BaseActivity
import com.audio.record.screen.test.databinding.ActivityPlayBinding
import com.audio.record.screen.test.service.FloatingWindowBridge
import com.audio.record.screen.test.tool.Common
/**
* Service悬浮窗中请求权限的透明Activity
*/
class ScreenPermissionActivity : AppCompatActivity() {
private lateinit var screenCaptureLauncher: ActivityResultLauncher<Intent>
//录音权限
private lateinit var micLauncher: ActivityResultLauncher<String>
companion object{
const val key_with_audio="key_with_audio"
}
private var withAudio = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Common.setStatusBarTextColor(this, true)
withAudio = intent.getBooleanExtra(key_with_audio, false)
screenCaptureLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK && result.data != null) {
val data: Intent? = result.data
FloatingWindowBridge.updateMediaProjection(result.resultCode, data!!)
}
finish()
}
micLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
Common.showLog("mic 权限授予")
requestScreenPermission()
} else {
Common.showLog("mic 权限拒绝")
}
}
if(withAudio){
micLauncher.launch(android.Manifest.permission.RECORD_AUDIO)
}else{
requestScreenPermission()
}
}
private fun requestScreenPermission() {
val mediaProjectionManager = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
val intent = mediaProjectionManager.createScreenCaptureIntent()
screenCaptureLauncher.launch(intent)
}
}

View File

@ -4,14 +4,14 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import com.audio.record.screen.test.fragment.MainFragment import com.audio.record.screen.test.fragment.MainFragment
import com.audio.record.screen.test.fragment.RecorderFragment import com.audio.record.screen.test.fragment.RecordMainFragment
class ViewPager2Adapter(fragmentActivity: FragmentActivity) : class ViewPager2Adapter(fragmentActivity: FragmentActivity) :
FragmentStateAdapter(fragmentActivity) { FragmentStateAdapter(fragmentActivity) {
private val fragments: List<Fragment> = arrayListOf( private val fragments: List<Fragment> = arrayListOf(
MainFragment.newInstance(), MainFragment.newInstance(),
RecorderFragment.newInstance(), RecordMainFragment.newInstance(),
MainFragment.newInstance() MainFragment.newInstance()
) )
override fun getItemCount(): Int = fragments.size override fun getItemCount(): Int = fragments.size

View File

@ -4,19 +4,16 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import com.audio.record.screen.test.R import com.audio.record.screen.test.R
import com.audio.record.screen.test.base.BaseFragment import com.audio.record.screen.test.base.BaseFragment
import com.audio.record.screen.test.databinding.FragmentRecordBinding import com.audio.record.screen.test.databinding.FragmentRecordBinding
import com.audio.record.screen.test.tool.Common
class RecorderFragment : BaseFragment<FragmentRecordBinding>() { class RecordMainFragment : BaseFragment<FragmentRecordBinding>() {
companion object { companion object {
@JvmStatic @JvmStatic
fun newInstance() = fun newInstance() =
RecorderFragment().apply { RecordMainFragment().apply {
} }
} }

View File

@ -10,7 +10,10 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.audio.record.screen.test.R import com.audio.record.screen.test.R
@ -21,16 +24,18 @@ import com.audio.record.screen.test.dialog.DialogPermission
import com.audio.record.screen.test.service.FloatingCallback import com.audio.record.screen.test.service.FloatingCallback
import com.audio.record.screen.test.service.FloatingWindowBridge import com.audio.record.screen.test.service.FloatingWindowBridge
import com.audio.record.screen.test.tool.Common import com.audio.record.screen.test.tool.Common
import com.audio.record.screen.test.tool.ConstValue
import com.audio.record.screen.test.tool.Permission import com.audio.record.screen.test.tool.Permission
import com.audio.record.screen.test.tool.ScreenCaptureHelper import com.audio.record.screen.test.tool.ScreenCaptureHelper
import com.audio.record.screen.test.viewmodel.MainViewModel import com.audio.record.screen.test.viewmodel.MainViewModel
import com.audio.record.screen.test.viewmodel.PreviewViewModel import com.audio.record.screen.test.viewmodel.PreviewViewModel
import kotlinx.coroutines.launch
class RecordNormalFragment : BaseFragment<FragmentRecordNormalBinding>(), FloatingCallback { class RecordNormalFragment : BaseFragment<FragmentRecordNormalBinding>(), FloatingCallback {
private lateinit var navController: NavController private lateinit var navController: NavController
private val REQUEST_SCREENSHOT = 125
private lateinit var viewModel: MainViewModel private lateinit var viewModel: MainViewModel
private lateinit var mediaProjectionManager: MediaProjectionManager private lateinit var mediaProjectionManager: MediaProjectionManager
@ -51,6 +56,9 @@ class RecordNormalFragment : BaseFragment<FragmentRecordNormalBinding>(), Floati
//是否带音频录制 //是否带音频录制
private var isWithAudio = false private var isWithAudio = false
private var pendingType: Int = -1
private val key_type = "check_type"
override fun initBinding( override fun initBinding(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup? container: ViewGroup?
@ -115,7 +123,7 @@ class RecordNormalFragment : BaseFragment<FragmentRecordNormalBinding>(), Floati
if (result.resultCode == Activity.RESULT_OK) { if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data val data: Intent? = result.data
FloatingWindowBridge.updateMediaProjection(result.resultCode, data!!) FloatingWindowBridge.updateMediaProjection(result.resultCode, data!!)
FloatingWindowBridge.sendCommand(FloatingWindowBridge.COMMEND_show_screenshot)
} else { } else {
Common.showLog("用户取消了录屏权限授权") Common.showLog("用户取消了录屏权限授权")
} }
@ -125,10 +133,16 @@ class RecordNormalFragment : BaseFragment<FragmentRecordNormalBinding>(), Floati
recorderLauncher = recorderLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK && result.data != null) { if (result.resultCode == Activity.RESULT_OK && result.data != null) {
val data = result.data val intent = result.data
val resultCode = result.resultCode val resultCode = result.resultCode
FloatingWindowBridge.updateMediaProjection(result.resultCode, data!!) FloatingWindowBridge.updateMediaProjection(result.resultCode, intent!!)
startCountDown() when(pendingType){
ConstValue.type_record->{ startCountDown()}
ConstValue.type_show_screenshot->{
FloatingWindowBridge.sendCommand(FloatingWindowBridge.COMMEND_show_screenshot)
}
}
} else { } else {
Common.showLog("录屏授权失败 ") Common.showLog("录屏授权失败 ")
} }
@ -196,10 +210,7 @@ class RecordNormalFragment : BaseFragment<FragmentRecordNormalBinding>(), Floati
private fun setScreenshot(boolean: Boolean) { private fun setScreenshot(boolean: Boolean) {
if (boolean) { if (boolean) {
if (FloatingWindowBridge.getMediaProjection() == null) { requestRecordPermission(ConstValue.type_show_screenshot){
val captureIntent = mediaProjectionManager.createScreenCaptureIntent()
screenCaptureLauncher.launch(captureIntent)
} else {
FloatingWindowBridge.sendCommand(FloatingWindowBridge.COMMEND_show_screenshot) FloatingWindowBridge.sendCommand(FloatingWindowBridge.COMMEND_show_screenshot)
} }
@ -217,14 +228,28 @@ class RecordNormalFragment : BaseFragment<FragmentRecordNormalBinding>(), Floati
} }
private fun startRecorder() { private fun startRecorder() {
if (FloatingWindowBridge.getMediaProjection() == null) { requestRecordPermission( ConstValue.type_record){
val captureIntent = mediaProjectionManager.createScreenCaptureIntent()
recorderLauncher.launch(captureIntent)
} else {
startCountDown() startCountDown()
} }
} }
/**
* 请求录屏权限
*/
private fun requestRecordPermission(type:Int,check:(()->Unit)? = null){
// TODO: requestRecordPermission
if (FloatingWindowBridge.getMediaProjection() == null) {
val captureIntent = mediaProjectionManager.createScreenCaptureIntent()
pendingType = type
recorderLauncher.launch(captureIntent)
}else{
check?.invoke()
}
}
private fun showAudioDialog() { private fun showAudioDialog() {
mAudioDialog = mAudioDialog ?: DialogAudio { mAudioDialog = mAudioDialog ?: DialogAudio {
when (it) { when (it) {
@ -249,7 +274,22 @@ class RecordNormalFragment : BaseFragment<FragmentRecordNormalBinding>(), Floati
} }
override fun onStartRecording() { override fun onStartRecording() {
navController.navigate(R.id.action_to_recording) 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)
}
}
}
} }

View File

@ -7,4 +7,6 @@ interface FloatingCallback {
fun onUpdateRecordTime(time:String){} fun onUpdateRecordTime(time:String){}
fun onStopRecord(){} fun onStopRecord(){}
} }

View File

@ -80,6 +80,7 @@ object FloatingWindowBridge {
} }
fun sendCommand(command: String, countdownValues: Array<String>? = null) { fun sendCommand(command: String, countdownValues: Array<String>? = null) {
Common.showLog("-------------command=${command}")
when (command) { when (command) {
COMMEND_hide_camera -> service?.hideFrontCamera() COMMEND_hide_camera -> service?.hideFrontCamera()
COMMEND_show_camera -> service?.showCameraView() COMMEND_show_camera -> service?.showCameraView()

View File

@ -0,0 +1,7 @@
package com.audio.record.screen.test.service
enum class RecordState {
DEFAULT,
RECORDING,
PAUSE }

View File

@ -5,7 +5,6 @@ import android.app.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.Service import android.app.Service
import android.content.ContentUris
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.PixelFormat import android.graphics.PixelFormat
@ -17,12 +16,11 @@ import android.media.projection.MediaProjectionManager
import android.net.Uri import android.net.Uri
import android.os.Binder import android.os.Binder
import android.os.Build import android.os.Build
import android.os.Environment import android.os.CountDownTimer
import android.os.Handler import android.os.Handler
import android.os.IBinder import android.os.IBinder
import android.os.Looper import android.os.Looper
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import android.provider.MediaStore
import android.view.Gravity import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -30,6 +28,7 @@ import android.view.WindowManager
import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.RemoteViews import android.widget.RemoteViews
import android.widget.TextView import android.widget.TextView
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
@ -39,21 +38,26 @@ import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView import androidx.camera.view.PreviewView
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider import androidx.core.view.isVisible
import com.audio.record.screen.test.R import com.audio.record.screen.test.R
import com.audio.record.screen.test.activity.PlayActivity import com.audio.record.screen.test.activity.PlayActivity
import com.audio.record.screen.test.activity.ScreenPermissionActivity
import com.audio.record.screen.test.tool.Common import com.audio.record.screen.test.tool.Common
import com.audio.record.screen.test.tool.ConstValue
import com.audio.record.screen.test.tool.DraggableViewHelper import com.audio.record.screen.test.tool.DraggableViewHelper
import com.audio.record.screen.test.tool.Extend.dpToPx
import com.audio.record.screen.test.tool.Extend.pxToDp
import com.audio.record.screen.test.tool.ScreenCaptureHelper import com.audio.record.screen.test.tool.ScreenCaptureHelper
import com.audio.record.screen.test.tool.VideoFileHelper import com.audio.record.screen.test.tool.VideoFileHelper
import com.audio.record.screen.test.view.CountDownFloatingManager import com.audio.record.screen.test.view.CountDownFloatingManager
import java.io.File
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
class ScreenRecordService : Service() { class ScreenRecordService : Service() {
private var lifecycleOwner: CustomLifecycleOwner? = null private var lifecycleOwner: CustomLifecycleOwner? = null
private var isBallViewAdded = false private var isBallViewAdded = false
private var isBallExpandViewAdded = false
private var isScreenshotViewAdded = false private var isScreenshotViewAdded = false
private var isWebcamViewAdded = false private var isWebcamViewAdded = false
private var isRecordViewAdded = false private var isRecordViewAdded = false
@ -64,6 +68,17 @@ class ScreenRecordService : Service() {
//悬浮球View //悬浮球View
private var ballView: View? = null private var ballView: View? = null
private var imIconVideo: ImageView? = null
private var smallLayoutRecording: LinearLayout? = null
private var smallTvTime: TextView? = null
//悬浮球展开View
private var ballViewExpand: View? = null
private var imRecord: ImageView? = null
private var tvRecordTime: TextView? = null
private var imNoAudioRecord: ImageView? = null
private var imScreenshot:ImageView? = null
//截屏View //截屏View
private var screenshotView: View? = null private var screenshotView: View? = null
@ -74,9 +89,10 @@ class ScreenRecordService : Service() {
private var index = 0 private var index = 0
//录屏成功View //录屏成功View
private var recordView: View? = null private var recordCompleteView: View? = null
private val windowManager by lazy { getSystemService(WINDOW_SERVICE) as WindowManager } private val windowManager by lazy { getSystemService(WINDOW_SERVICE) as WindowManager }
private lateinit var layoutParamsBallExpand: WindowManager.LayoutParams
private lateinit var layoutParamsBall: WindowManager.LayoutParams private lateinit var layoutParamsBall: WindowManager.LayoutParams
private lateinit var layoutParamsScreenshot: WindowManager.LayoutParams private lateinit var layoutParamsScreenshot: WindowManager.LayoutParams
private lateinit var layoutParamsCameraView: WindowManager.LayoutParams private lateinit var layoutParamsCameraView: WindowManager.LayoutParams
@ -93,17 +109,22 @@ class ScreenRecordService : Service() {
private var callbackRef: FloatingCallback? = null private var callbackRef: FloatingCallback? = null
//截屏 //截屏
var mIntent: Intent? = null var mIntent: Intent? = null
private var mCode: Int = 0 private var mCode: Int = 0
private lateinit var mediaRecorder: MediaRecorder private lateinit var mediaRecorder: MediaRecorder
private lateinit var tmpVideoUri: Uri private lateinit var tmpVideoUri: Uri
private lateinit var videoName: String
private var tmpVideoPfd: ParcelFileDescriptor? = null private var tmpVideoPfd: ParcelFileDescriptor? = null
//当前录制是否带音频
private var isWithAudio = false private var isWithAudio = false
private val callbacks = mutableListOf<WeakReference<FloatingCallback>>() private val callbacks = mutableListOf<WeakReference<FloatingCallback>>()
private var countDownTimer: CountDownTimer? = null
//录制状态标志 true 录制中
private var mRecordingStatus = -1
//录制时间监听相关 //录制时间监听相关
private var recordingStartTime: Long = 0L // 本次录制开始时间 private var recordingStartTime: Long = 0L // 本次录制开始时间
@ -116,7 +137,10 @@ class ScreenRecordService : Service() {
private var isPause = false private var isPause = false
private lateinit var virtualDisplay: VirtualDisplay private lateinit var virtualDisplay: VirtualDisplay
private var mediaProjection:MediaProjection? = null //悬浮球点击进行的动作 type_record_audio/type_record_without_audio/type_screenshot
private var ballType: Int? = null
private var mediaProjection: MediaProjection? = null
private val timeUpdateRunnable = object : Runnable { private val timeUpdateRunnable = object : Runnable {
override fun run() { override fun run() {
@ -125,6 +149,8 @@ class ScreenRecordService : Service() {
System.currentTimeMillis() - recordingStartTime - totalPausedTime System.currentTimeMillis() - recordingStartTime - totalPausedTime
val elapsed = currentRecordingTimeInMillis / 1000 val elapsed = currentRecordingTimeInMillis / 1000
val onRecordingTimeChanged = Common.onRecordingTimeChanged(elapsed) val onRecordingTimeChanged = Common.onRecordingTimeChanged(elapsed)
tvRecordTime?.text = onRecordingTimeChanged
smallTvTime?.text = onRecordingTimeChanged
callbacks.forEach { callbacks.forEach {
it.get()?.onUpdateRecordTime(onRecordingTimeChanged) it.get()?.onUpdateRecordTime(onRecordingTimeChanged)
} }
@ -180,9 +206,26 @@ class ScreenRecordService : Service() {
mIntent = m mIntent = m
mCode = code mCode = code
// mediaProjection = mediaProjectionManager.getMediaProjection(code, m) // mediaProjection = mediaProjectionManager.getMediaProjection(code, m)
} ballType?.let {
fun resetIntent(){ when (it) {
mIntent = null ConstValue.type_record_without_audio -> {
isWithAudio = false
val arrayOf = arrayOf("3", "2", "1")
showCountDownView(arrayOf)
}
ConstValue.type_record_audio -> {
isWithAudio = true
val arrayOf = arrayOf("3", "2", "1")
showCountDownView(arrayOf)
}
ConstValue.type_screenshot -> {
startScreenshot()
}
}
ballType = null
}
} }
@ -224,6 +267,22 @@ class ScreenRecordService : Service() {
.build() .build()
} }
/**
* 开始截屏
*/
private fun startScreenshot(){
// TODO: 开始截屏
mIntent?.let { intent->
mediaProjectionManager.getMediaProjection(mCode, intent)?.let { mediaProjection->
hideScreenshot()
ScreenCaptureHelper.startScreenCapture(this, mediaProjection) {
//截屏完成
showScreenshot()
}
}
}
}
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
fun showCameraView() { fun showCameraView() {
if (frontCameraView == null) { if (frontCameraView == null) {
@ -255,6 +314,10 @@ class ScreenRecordService : Service() {
hideFrontCamera() // 自动清除 hideFrontCamera() // 自动清除
hideBall() hideBall()
hideScreenshot() hideScreenshot()
hideBallExpand()
hideRecordCompleteView()
ballViewExpand = null
recordCompleteView = null
frontCameraView = null frontCameraView = null
ballView = null ballView = null
screenshotView = null screenshotView = null
@ -298,9 +361,15 @@ class ScreenRecordService : Service() {
layoutParamsBall.gravity = Gravity.TOP or Gravity.START layoutParamsBall.gravity = Gravity.TOP or Gravity.START
layoutParamsBall.x = screenWH.first - size - 20 layoutParamsBall.x = screenWH.first - size - 20
layoutParamsBall.y = screenWH.second / 2 - size.div(2) // 居中 layoutParamsBall.y = screenWH.second / 2 - size.div(2) // 居中
smallLayoutRecording = ballView!!.findViewById<LinearLayout>(R.id.layout_recording)
imIconVideo = ballView!!.findViewById<ImageView>(R.id.image)
smallTvTime = ballView!!.findViewById<TextView>(R.id.tv_small_time)
DraggableViewHelper.attachToWindow(ballView!!, layoutParamsBall, windowManager, true) DraggableViewHelper.attachToWindow(ballView!!, layoutParamsBall, windowManager, true) {
hideBall()
showBallExpand()
}
Common.showLog("-------layoutParamsBall.x=${layoutParamsBall.x} layoutParamsBall.y=${layoutParamsBall.y}") Common.showLog("-------layoutParamsBall.x=${layoutParamsBall.x} layoutParamsBall.y=${layoutParamsBall.y}")
} }
if (!isBallViewAdded) { if (!isBallViewAdded) {
@ -310,6 +379,116 @@ class ScreenRecordService : Service() {
} }
fun showBallExpand() {
if (ballViewExpand == null) {
ballViewExpand = LayoutInflater.from(this).inflate(R.layout.floating_ball_expand, null)
layoutParamsBallExpand = getLayoutParams()
layoutParamsBallExpand.gravity = Gravity.TOP or Gravity.START
imRecord = ballViewExpand!!.findViewById<ImageView>(R.id.ic_record)
tvRecordTime = ballViewExpand!!.findViewById<TextView>(R.id.tv_record_time)
imNoAudioRecord = ballViewExpand!!.findViewById<ImageView>(R.id.ic_without_audio)
imScreenshot = ballViewExpand!!.findViewById<ImageView>(R.id.ic_screenshot)
imRecord?.setOnClickListener {
it.isSelected.let { select ->
if (!select) {
//录制带音频的视频
ballType = ConstValue.type_record_audio
intentPermission(true)
} else {
//录制完成
stopRecording()
}
}
}
imNoAudioRecord?.setOnClickListener {
// TODO:
when (mRecordingStatus) {
ConstValue.status_recording -> {
//暂停录制
pauseRecording()
}
ConstValue.status_pause -> {
//继续录制
resumeRecording()
}
else -> {
// 开始录制没有声音的视频
ballType = ConstValue.type_record_without_audio
intentPermission(false)
}
}
}
imScreenshot?.setOnClickListener {
ballType = ConstValue.type_screenshot
intentPermission(false)
}
}
if (!isBallExpandViewAdded) {
val size = Common.dpToPx(36, this)
val aWidthPx = Common.dpToPx(55, this)
val aHeightPx = Common.dpToPx(250, this)
// 转换 B 的宽高为像素
val bWidthPx = size
val bHeightPx = size
// 计算 B 的中心点坐标
val bCenterX = layoutParamsBall.x + bWidthPx / 2
val bCenterY = layoutParamsBall.y + bHeightPx / 2
// 让 A 的左上角从 B 的中心点出发(居中显示)
var aX = bCenterX - aWidthPx / 2
var aY = bCenterY - aHeightPx / 2
val (screenWidth, screenHeight) = Common.getScreenWH(this)
// 修正边界:防止 A 悬浮窗超出屏幕边界
aX = aX.coerceIn(0, screenWidth - aWidthPx)
aY = aY.coerceIn(0, screenHeight - aHeightPx)
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)
isBallExpandViewAdded = true
delayRemove {
Common.showLog("-------切换ball显示")
hideBallExpand()
showBall()
}
}
}
/**
* 3秒后自动隐藏
*/
private fun delayRemove(onFinish: (() -> Unit)?) {
countDownTimer?.cancel()
countDownTimer = object : CountDownTimer(3000L, 1000L) {
override fun onTick(millisUntilFinished: Long) {
}
override fun onFinish() {
onFinish?.invoke()
countDownTimer = null
}
}
countDownTimer?.start()
}
//显示截屏 //显示截屏
fun showScreenshot() { fun showScreenshot() {
if (screenshotView == null) { if (screenshotView == null) {
@ -327,17 +506,7 @@ class ScreenRecordService : Service() {
windowManager, windowManager,
true true
) { ) {
startScreenshot()
Common.showLog("--------------callbackRef=${callbackRef}")
// callbackRef?.onFloatingButtonClicked(FloatingWindowBridge.CLICK_screenshot)
mIntent?.let {
mediaProjectionManager.getMediaProjection(mCode, it)?.let {
hideScreenshot()
ScreenCaptureHelper.startScreenCapture(this, it) {
// showScreenshot()
}
}
}
} }
} }
if (!isScreenshotViewAdded) { if (!isScreenshotViewAdded) {
@ -349,16 +518,21 @@ class ScreenRecordService : Service() {
//显示截屏 //显示截屏
fun showRecordView() { fun showRecordView() {
if (recordView == null) { if (recordCompleteView == null) {
recordView = LayoutInflater.from(this).inflate(R.layout.floating_record_complete, null) recordCompleteView =
LayoutInflater.from(this).inflate(R.layout.floating_record_complete, null)
layoutParamsRecordView = getLayoutParams() layoutParamsRecordView = getLayoutParams()
layoutParamsRecordView.gravity = Gravity.CENTER layoutParamsRecordView.gravity = Gravity.CENTER
val imClose = recordView!!.findViewById<ImageView>(R.id.close).setOnClickListener { val imClose =
hideRecordView() recordCompleteView!!.findViewById<ImageView>(R.id.close).setOnClickListener {
} hideRecordCompleteView()
recordView!!.findViewById<FrameLayout>(R.id.layout_video).setOnClickListener { }
recordCompleteView!!.findViewById<FrameLayout>(R.id.layout_video).setOnClickListener {
// TODO: 跳到播放页面 // TODO: 跳到播放页面
hideRecordCompleteView()
val intent = Intent(this, PlayActivity::class.java).apply { val intent = Intent(this, PlayActivity::class.java).apply {
putExtra(PlayActivity.KEY_URI, tmpVideoUri)
putExtra(PlayActivity.KEY_name, videoName)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
} }
this.startActivity(intent) this.startActivity(intent)
@ -366,15 +540,15 @@ class ScreenRecordService : Service() {
} }
} }
if (!isRecordViewAdded) { if (!isRecordViewAdded) {
val videoThumbnail = Common.getVideoThumbnail(tmpVideoPfd!!) val videoThumbnail = Common.getVideoThumbnail(tmpVideoPfd!!)
Common.showLog("--------------videoThumbnail=${videoThumbnail}") Common.showLog("--------------videoThumbnail=${videoThumbnail}")
val thumb = recordView!!.findViewById<ImageView>(R.id.image) val thumb = recordCompleteView!!.findViewById<ImageView>(R.id.image)
thumb.setImageBitmap(videoThumbnail) thumb.setImageBitmap(videoThumbnail)
windowManager.addView(recordCompleteView, layoutParamsRecordView)
windowManager.addView(recordView, layoutParamsRecordView)
isRecordViewAdded = true isRecordViewAdded = true
delayRemove {
hideRecordCompleteView()
}
} }
} }
@ -392,9 +566,23 @@ class ScreenRecordService : Service() {
// frontCameraView = null // frontCameraView = null
} }
fun hideBallExpand() {
ballViewExpand?.let {
try {
if (isBallExpandViewAdded) {
windowManager.removeView(it)
isBallExpandViewAdded = false
}
} catch (_: Exception) {
}
}
// frontCameraView = null
}
//隐藏录制预览View //隐藏录制预览View
fun hideRecordView() { fun hideRecordCompleteView() {
recordView?.let { recordCompleteView?.let {
try { try {
if (isRecordViewAdded) { if (isRecordViewAdded) {
windowManager.removeView(it) windowManager.removeView(it)
@ -451,7 +639,7 @@ class ScreenRecordService : Service() {
hideCountDown() hideCountDown()
mIntent?.let { intent -> mIntent?.let { intent ->
mediaProjectionManager.getMediaProjection(mCode, intent) mediaProjectionManager.getMediaProjection(mCode, intent)
?.let { media-> ?.let { media ->
mediaProjection = media mediaProjection = media
startRecording() startRecording()
callbacks.forEach { callbacks.forEach {
@ -516,11 +704,14 @@ class ScreenRecordService : Service() {
*/ */
fun startRecording() { fun startRecording() {
Common.showLog("-------录屏中.....") Common.showLog("-------录屏中.....")
// TODO: 录屏中.
val fullScreenSize = Common.getFullScreenSize(this) val fullScreenSize = Common.getFullScreenSize(this)
val width = VideoFileHelper.alignTo16(fullScreenSize.first) val width = VideoFileHelper.alignTo16(fullScreenSize.first)
val height = VideoFileHelper.alignTo16(fullScreenSize.second) val height = VideoFileHelper.alignTo16(fullScreenSize.second)
initRecorder(width, height) initRecorder(width, height)
mediaRecorder.start() mediaRecorder.start()
mRecordingStatus = ConstValue.status_recording
updateBall()
recordingStartTime = System.currentTimeMillis() recordingStartTime = System.currentTimeMillis()
totalPausedTime = 0L totalPausedTime = 0L
mRecorderHandler.post(timeUpdateRunnable) mRecorderHandler.post(timeUpdateRunnable)
@ -556,8 +747,6 @@ class ScreenRecordService : Service() {
}, null }, null
) )
} }
} }
@ -600,8 +789,11 @@ class ScreenRecordService : Service() {
* 暂停录制视频 * 暂停录制视频
*/ */
fun pauseRecording() { fun pauseRecording() {
// TODO: 暂停录制视频
Common.showLog("-------暂停.....") Common.showLog("-------暂停.....")
mediaRecorder.pause() mediaRecorder.pause()
mRecordingStatus = ConstValue.status_pause
updateBall()
pauseStartTime = System.currentTimeMillis() pauseStartTime = System.currentTimeMillis()
isPause = true isPause = true
} }
@ -610,15 +802,67 @@ class ScreenRecordService : Service() {
* 继续录制视频 * 继续录制视频
*/ */
fun resumeRecording() { fun resumeRecording() {
// TODO: 继续录制视频
Common.showLog("------继续.....") Common.showLog("------继续.....")
mediaRecorder.resume() mediaRecorder.resume()
mRecordingStatus = ConstValue.status_recording
updateBall()
totalPausedTime += System.currentTimeMillis() - pauseStartTime totalPausedTime += System.currentTimeMillis() - pauseStartTime
isPause = false isPause = false
mRecorderHandler.post(timeUpdateRunnable) mRecorderHandler.post(timeUpdateRunnable)
} }
/**
* 录制状态更新
*/
private fun updateBall() {
// TODO: 录制状态更新
when (mRecordingStatus) {
ConstValue.status_recording -> {
imNoAudioRecord?.run {
isSelected = true
isActivated = false
}
imRecord?.isSelected = true
tvRecordTime?.isVisible = true
imIconVideo?.isVisible = false
smallLayoutRecording?.isVisible = true
}
ConstValue.status_pause -> {
imNoAudioRecord?.run {
isSelected = false
isActivated = true
}
imIconVideo?.isVisible = false
smallLayoutRecording?.isVisible = true
}
ConstValue.status_complete -> {
imNoAudioRecord?.run {
isSelected = false
isActivated = false
}
imRecord?.isSelected = false
tvRecordTime?.isVisible = false
imIconVideo?.isVisible = true
smallLayoutRecording?.isVisible = false
hideBallExpand()
showBall()
}
}
}
private fun initRecorder(width: Int, height: Int) { private fun initRecorder(width: Int, height: Int) {
val (Uri, pfd) = VideoFileHelper.createVideoFile(this, Common.folderName) videoName = System.currentTimeMillis().toString()
val (Uri, pfd) = VideoFileHelper.createVideoFile(this, Common.folderName, videoName)
tmpVideoUri = Uri tmpVideoUri = Uri
tmpVideoPfd = pfd tmpVideoPfd = pfd
mediaRecorder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { mediaRecorder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@ -626,14 +870,18 @@ class ScreenRecordService : Service() {
} else { } else {
MediaRecorder() MediaRecorder()
}.apply { }.apply {
if (isWithAudio) if (isWithAudio) {
setAudioSource(MediaRecorder.AudioSource.MIC) setAudioSource(MediaRecorder.AudioSource.MIC)
}
setVideoSource(MediaRecorder.VideoSource.SURFACE) setVideoSource(MediaRecorder.VideoSource.SURFACE)
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
setOutputFile(pfd?.fileDescriptor) setOutputFile(pfd?.fileDescriptor)
setVideoSize(width, height) setVideoSize(width, height)
if (isWithAudio) {
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
}
setVideoEncoder(MediaRecorder.VideoEncoder.H264) setVideoEncoder(MediaRecorder.VideoEncoder.H264)
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
setVideoEncodingBitRate(8 * 1000 * 1000) setVideoEncodingBitRate(8 * 1000 * 1000)
setVideoFrameRate(30) setVideoFrameRate(30)
prepare() prepare()
@ -641,6 +889,9 @@ class ScreenRecordService : Service() {
} }
fun stopRecording() { fun stopRecording() {
// TODO: 录屏完成
mRecordingStatus = ConstValue.status_complete
updateBall()
Common.showLog("-------录屏完成.....") Common.showLog("-------录屏完成.....")
mRecorderHandler.removeCallbacks(timeUpdateRunnable) mRecorderHandler.removeCallbacks(timeUpdateRunnable)
releaseAll() releaseAll()
@ -669,4 +920,12 @@ class ScreenRecordService : Service() {
} }
private fun intentPermission(requestAudio: Boolean) {
val intent = Intent(this, ScreenPermissionActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.putExtra(ScreenPermissionActivity.key_with_audio, requestAudio)
this.startActivity(intent)
}
} }

View File

@ -0,0 +1,26 @@
package com.audio.record.screen.test.tool
object ConstValue {
//--------------RecordNormalFragment
//开始录制
const val type_record = 0
//显示截屏悬浮球
const val type_show_screenshot = 1
//--------------ScreenRecordService
//一次性使用
const val type_record_audio = 3
const val type_record_without_audio = 4
const val type_screenshot = 5
//当前状态
const val status_recording = 6 //录制当中
const val status_pause = 7 //录制暂停
const val status_complete = 8 //录制完成
}

View File

@ -6,6 +6,9 @@ import android.media.Image
import android.util.TypedValue import android.util.TypedValue
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
object Extend { object Extend {
@ -44,11 +47,18 @@ object Extend {
layoutParams = params layoutParams = params
} }
fun ImageView.setDrawableCompat(@DrawableRes resId: Int) {
val drawable = ContextCompat.getDrawable(context, resId)
this.setImageDrawable(drawable)
}
fun Int.dpToPx(context: Context): Int = fun Int.dpToPx(context: Context): Int =
TypedValue.applyDimension( TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), TypedValue.COMPLEX_UNIT_DIP, this.toFloat(),
context.resources.displayMetrics context.resources.displayMetrics
).toInt() ).toInt()
fun Context.pxToDp(px: Int): Float {
return px / resources.displayMetrics.density
}
} }

View File

@ -68,8 +68,8 @@ object ScreenCaptureHelper {
if (image != null) { if (image != null) {
val bitmap = imageToBitmap(image) val bitmap = imageToBitmap(image)
saveBitmap(context, bitmap, folderName) saveBitmap(context, bitmap, folderName)
isOK.invoke()
showScreenshotPreviewAnimation(context, bitmap,width,height) showScreenshotPreviewAnimation(context, bitmap,width,height)
isOK.invoke()
image.close() image.close()
} }
imageReader.close() imageReader.close()

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="26dp"
android:height="26dp"
android:viewportWidth="26"
android:viewportHeight="26">
<path
android:pathData="M1.733,13C1.733,6.778 6.778,1.733 13,1.733C19.222,1.733 24.267,6.778 24.267,13C24.267,19.222 19.222,24.267 13,24.267C6.778,24.267 1.733,19.222 1.733,13ZM0,13C0,20.18 5.82,26 13,26C20.18,26 26,20.18 26,13C26,5.82 20.18,0 13,0C5.82,0 0,5.82 0,13Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M7,13C7,9.686 9.686,7 13,7C16.314,7 19,9.686 19,13C19,16.314 16.314,19 13,19C9.686,19 7,16.314 7,13Z"
android:fillColor="#DD2432"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="15dp"
android:height="19dp"
android:viewportWidth="28"
android:viewportHeight="32">
<path
android:pathData="M26.901,14.257C28.261,15.021 28.261,16.979 26.901,17.743L3.731,30.778C2.397,31.528 0.75,30.564 0.75,29.035L0.75,2.965C0.75,1.436 2.397,0.472 3.731,1.222L26.901,14.257Z"
android:fillColor="#ffffff"/>
</vector>

View File

@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="26dp"
android:height="26dp"
android:viewportWidth="26"
android:viewportHeight="26">
<group>
<clip-path
android:pathData="M0,0h26v26h-26z"/>
<group>
<clip-path
android:pathData="M26,0H0.005V26H26V0Z"/>
<path
android:pathData="M20.195,25.962H15.027C14.514,25.962 14.101,25.548 14.101,25.034V16.99C14.101,16.986 14.093,16.975 14.085,16.975H11.657C11.653,16.975 11.642,16.983 11.642,16.99V25.034C11.642,25.548 11.229,25.962 10.715,25.962H5.551C4.003,25.962 2.744,24.701 2.744,23.15V14.976H2.181C1.308,14.976 0.525,14.457 0.177,13.653C-0.166,12.853 -0.004,11.925 0.598,11.291L10.043,1.298C10.808,0.49 11.881,0.026 12.993,0.026C14.104,0.026 15.177,0.49 15.942,1.298L25.383,11.29C25.981,11.924 26.147,12.852 25.804,13.653C25.46,14.453 24.673,14.975 23.8,14.975H23.001V23.15C23.001,24.7 21.743,25.961 20.195,25.961V25.962ZM15.953,24.105H20.195C20.72,24.105 21.148,23.676 21.148,23.15V14.047C21.148,13.533 21.561,13.119 22.075,13.119H23.8C23.9,13.119 24.039,13.069 24.101,12.922C24.163,12.775 24.105,12.639 24.039,12.566L14.59,2.574C14.17,2.13 13.602,1.882 12.989,1.882C12.375,1.882 11.807,2.126 11.387,2.574L1.942,12.567C1.872,12.64 1.815,12.775 1.88,12.922C1.942,13.069 2.081,13.12 2.181,13.12H3.671C4.184,13.12 4.597,13.533 4.597,14.047V23.151C4.597,23.676 5.026,24.106 5.551,24.106H9.793V16.99C9.793,15.958 10.63,15.119 11.661,15.119H14.089C15.119,15.119 15.957,15.958 15.957,16.99V24.106H15.953L15.953,24.105Z"
android:fillColor="#ffffff"/>
</group>
</group>
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="15dp"
android:height="14dp"
android:viewportWidth="29"
android:viewportHeight="38">
<path
android:pathData="M1,0L10,0A1,1 0,0 1,11 1L11,37A1,1 0,0 1,10 38L1,38A1,1 0,0 1,0 37L0,1A1,1 0,0 1,1 0z"
android:fillColor="#ffffff"/>
<path
android:pathData="M19,0L28,0A1,1 0,0 1,29 1L29,37A1,1 0,0 1,28 38L19,38A1,1 0,0 1,18 37L18,1A1,1 0,0 1,19 0z"
android:fillColor="#ffffff"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="15dp"
android:height="15dp"
android:viewportWidth="15"
android:viewportHeight="15">
<path
android:pathData="M3,0L12,0A3,3 0,0 1,15 3L15,12A3,3 0,0 1,12 15L3,15A3,3 0,0 1,0 12L0,3A3,3 0,0 1,3 0z"
android:fillColor="#DD2432"/>
</vector>

View File

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="26dp"
android:height="26dp"
android:viewportWidth="26"
android:viewportHeight="26">
<path
android:pathData="M4.987,0H6.883V19.948H26V21.844H4.987V0Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M21.844,25.169H19.948V6.052H0V4.156H21.844V25.169Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M24.73,2.573L9.298,18.911L7.919,17.61L23.352,1.27L24.73,2.573Z"
android:fillColor="#ffffff"/>
</vector>

View File

@ -0,0 +1,23 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="26dp"
android:height="25dp"
android:viewportWidth="26"
android:viewportHeight="25">
<path
android:pathData="M5.871,10.903C5.871,8.124 8.124,5.871 10.903,5.871C13.682,5.871 15.936,8.124 15.936,10.903C15.936,13.682 13.682,15.936 10.903,15.936C8.124,15.936 5.871,13.682 5.871,10.903Z"
android:fillColor="#ffffff"/>
<group>
<clip-path
android:pathData="M15.097,13.419h10.903v10.903h-10.903z"/>
<group>
<clip-path
android:pathData="M26,13.419H15.099V24.323H26V13.419Z"/>
<path
android:pathData="M21.531,13.787C21.74,14.011 21.857,14.316 21.857,14.634V23.125C21.857,23.787 21.358,24.323 20.743,24.323C20.447,24.323 20.164,24.196 19.955,23.972L17.929,21.792C17.815,21.67 17.66,21.601 17.499,21.601H16.717C15.823,21.601 15.097,20.821 15.097,19.859V17.9C15.097,16.938 15.823,16.159 16.717,16.159H17.499C17.66,16.159 17.815,16.09 17.929,15.967L19.955,13.788C20.39,13.32 21.095,13.319 21.531,13.787ZM25.869,17.079C26.027,17.249 26.027,17.525 25.869,17.696L24.751,18.897L25.869,20.098C26.027,20.268 26.027,20.545 25.869,20.715C25.71,20.885 25.454,20.885 25.295,20.715L24.177,19.514L23.06,20.715C22.901,20.885 22.644,20.885 22.486,20.715C22.327,20.545 22.327,20.268 22.486,20.098L23.604,18.897L22.486,17.696C22.327,17.525 22.327,17.249 22.486,17.079C22.644,16.909 22.901,16.909 23.06,17.079L24.177,18.28L25.295,17.079C25.453,16.909 25.71,16.909 25.869,17.079L25.869,17.079Z"
android:fillColor="#ffffff"/>
</group>
</group>
<path
android:pathData="M10.903,0C16.925,0 21.807,4.882 21.807,10.903C21.807,11.186 21.792,11.465 21.771,11.742H20.313C20.338,11.466 20.353,11.186 20.353,10.903C20.353,5.685 16.122,1.454 10.903,1.454C5.685,1.454 1.454,5.685 1.454,10.903C1.454,16.122 5.685,20.353 10.903,20.353C11.775,20.353 12.618,20.232 13.42,20.011V20.163C13.42,20.177 13.422,20.191 13.422,20.205V21.51C12.613,21.701 11.771,21.807 10.903,21.807C4.882,21.807 0,16.925 0,10.903C0,4.882 4.882,0 10.903,0Z"
android:fillColor="#ffffff"/>
</vector>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="25dp"/>
<solid android:color="@color/color_CC000000"/>
</shape>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/color_CC000000"/>
</shape>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/test_red"/>
</shape>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ball_recording" android:state_selected="true" />
<item android:drawable="@drawable/ball_audio_record" android:state_selected="false" />
</selector>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 暂停状态(优先级高) -->
<item android:drawable="@drawable/ball_continue_recording"
android:state_activated="true" />
<!-- 录制状态 -->
<item android:drawable="@drawable/ball_pause"
android:state_selected="true" />
<!-- 默认状态 -->
<item android:drawable="@drawable/ball_without_audio_record" />
</selector>

View File

@ -9,4 +9,29 @@
android:layout_height="36dp" android:layout_height="36dp"
android:src="@drawable/global_ball" /> android:src="@drawable/global_ball" />
<LinearLayout
android:id="@+id/layout_recording"
android:layout_width="36dp"
android:layout_height="36dp"
android:visibility="gone"
android:background="@drawable/recording_bg_oval"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="7dp"
android:layout_height="7dp"
android:src="@drawable/recording_oval_red" />
<TextView
android:id="@+id/tv_small_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="00:00:00"
android:textColor="@color/white"
android:textSize="6sp" />
</LinearLayout>
</FrameLayout> </FrameLayout>

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="55dp"
android:layout_height="wrap_content"
android:background="@drawable/bg_ball"
android:gravity="center"
android:orientation="vertical"
android:paddingTop="25dp"
android:paddingBottom="25dp">
<ImageView
android:id="@+id/ic_record"
android:layout_width="match_parent"
android:layout_height="29dp"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:src="@drawable/status_ball_record" />
<TextView
android:id="@+id/tv_record_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00:00:00"
android:textColor="@color/white"
android:textSize="12sp"
android:visibility="gone" />
<ImageView
android:id="@+id/ic_without_audio"
android:layout_width="match_parent"
android:layout_height="29dp"
android:layout_marginTop="12dp"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:src="@drawable/status_ball_record_three" />
<ImageView
android:id="@+id/ic_screenshot"
android:layout_width="match_parent"
android:layout_height="29dp"
android:layout_marginTop="12dp"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:src="@drawable/ball_screenshot" />
<ImageView
android:id="@+id/ic_home"
android:layout_width="match_parent"
android:layout_height="29dp"
android:layout_marginTop="12dp"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:src="@drawable/ball_home" />
</LinearLayout>
</FrameLayout>

View File

@ -6,4 +6,13 @@
</style> </style>
<style name="Theme.RecordScreen" parent="Base.Theme.RecordScreen" /> <style name="Theme.RecordScreen" parent="Base.Theme.RecordScreen" />
<style name="Theme.Transparent" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@color/color_transparent</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
<item name="android:backgroundDimEnabled">false</item>
</style>
</resources> </resources>