悬浮窗功能
This commit is contained in:
parent
481452e688
commit
d8180a1ff6
@ -59,6 +59,13 @@
|
||||
<!-- <category android:name="android.intent.category.LAUNCHER" /> -->
|
||||
<!-- </intent-filter> -->
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.ScreenPermissionActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/Theme.Transparent"
|
||||
android:launchMode="singleTop"
|
||||
android:exported="true" />
|
||||
|
||||
<service
|
||||
android:name=".service.ScreenRecordService"
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -4,14 +4,14 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
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) :
|
||||
FragmentStateAdapter(fragmentActivity) {
|
||||
private val fragments: List<Fragment> = arrayListOf(
|
||||
MainFragment.newInstance(),
|
||||
RecorderFragment.newInstance(),
|
||||
RecordMainFragment.newInstance(),
|
||||
MainFragment.newInstance()
|
||||
)
|
||||
override fun getItemCount(): Int = fragments.size
|
||||
|
||||
@ -4,19 +4,16 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavDestination
|
||||
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.tool.Common
|
||||
|
||||
class RecorderFragment : BaseFragment<FragmentRecordBinding>() {
|
||||
class RecordMainFragment : BaseFragment<FragmentRecordBinding>() {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun newInstance() =
|
||||
RecorderFragment().apply {
|
||||
RecordMainFragment().apply {
|
||||
|
||||
}
|
||||
}
|
||||
@ -10,7 +10,10 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
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
|
||||
@ -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.FloatingWindowBridge
|
||||
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.ScreenCaptureHelper
|
||||
import com.audio.record.screen.test.viewmodel.MainViewModel
|
||||
import com.audio.record.screen.test.viewmodel.PreviewViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
class RecordNormalFragment : BaseFragment<FragmentRecordNormalBinding>(), FloatingCallback {
|
||||
|
||||
private lateinit var navController: NavController
|
||||
private val REQUEST_SCREENSHOT = 125
|
||||
|
||||
private lateinit var viewModel: MainViewModel
|
||||
private lateinit var mediaProjectionManager: MediaProjectionManager
|
||||
|
||||
@ -51,6 +56,9 @@ class RecordNormalFragment : BaseFragment<FragmentRecordNormalBinding>(), Floati
|
||||
//是否带音频录制
|
||||
private var isWithAudio = false
|
||||
|
||||
|
||||
private var pendingType: Int = -1
|
||||
private val key_type = "check_type"
|
||||
override fun initBinding(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?
|
||||
@ -115,7 +123,7 @@ class RecordNormalFragment : BaseFragment<FragmentRecordNormalBinding>(), Floati
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
val data: Intent? = result.data
|
||||
FloatingWindowBridge.updateMediaProjection(result.resultCode, data!!)
|
||||
FloatingWindowBridge.sendCommand(FloatingWindowBridge.COMMEND_show_screenshot)
|
||||
|
||||
} else {
|
||||
Common.showLog("用户取消了录屏权限授权")
|
||||
}
|
||||
@ -125,10 +133,16 @@ class RecordNormalFragment : BaseFragment<FragmentRecordNormalBinding>(), Floati
|
||||
recorderLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK && result.data != null) {
|
||||
val data = result.data
|
||||
val intent = result.data
|
||||
val resultCode = result.resultCode
|
||||
FloatingWindowBridge.updateMediaProjection(result.resultCode, data!!)
|
||||
startCountDown()
|
||||
FloatingWindowBridge.updateMediaProjection(result.resultCode, intent!!)
|
||||
when(pendingType){
|
||||
ConstValue.type_record->{ startCountDown()}
|
||||
ConstValue.type_show_screenshot->{
|
||||
FloatingWindowBridge.sendCommand(FloatingWindowBridge.COMMEND_show_screenshot)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
Common.showLog("录屏授权失败 ")
|
||||
}
|
||||
@ -196,10 +210,7 @@ class RecordNormalFragment : BaseFragment<FragmentRecordNormalBinding>(), Floati
|
||||
|
||||
private fun setScreenshot(boolean: Boolean) {
|
||||
if (boolean) {
|
||||
if (FloatingWindowBridge.getMediaProjection() == null) {
|
||||
val captureIntent = mediaProjectionManager.createScreenCaptureIntent()
|
||||
screenCaptureLauncher.launch(captureIntent)
|
||||
} else {
|
||||
requestRecordPermission(ConstValue.type_show_screenshot){
|
||||
FloatingWindowBridge.sendCommand(FloatingWindowBridge.COMMEND_show_screenshot)
|
||||
}
|
||||
|
||||
@ -217,14 +228,28 @@ class RecordNormalFragment : BaseFragment<FragmentRecordNormalBinding>(), Floati
|
||||
}
|
||||
|
||||
private fun startRecorder() {
|
||||
if (FloatingWindowBridge.getMediaProjection() == null) {
|
||||
val captureIntent = mediaProjectionManager.createScreenCaptureIntent()
|
||||
recorderLauncher.launch(captureIntent)
|
||||
} else {
|
||||
requestRecordPermission( ConstValue.type_record){
|
||||
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() {
|
||||
mAudioDialog = mAudioDialog ?: DialogAudio {
|
||||
when (it) {
|
||||
@ -249,7 +274,22 @@ class RecordNormalFragment : BaseFragment<FragmentRecordNormalBinding>(), Floati
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -7,4 +7,6 @@ interface FloatingCallback {
|
||||
fun onUpdateRecordTime(time:String){}
|
||||
|
||||
fun onStopRecord(){}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -80,6 +80,7 @@ object FloatingWindowBridge {
|
||||
}
|
||||
|
||||
fun sendCommand(command: String, countdownValues: Array<String>? = null) {
|
||||
Common.showLog("-------------command=${command}")
|
||||
when (command) {
|
||||
COMMEND_hide_camera -> service?.hideFrontCamera()
|
||||
COMMEND_show_camera -> service?.showCameraView()
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
package com.audio.record.screen.test.service
|
||||
|
||||
|
||||
enum class RecordState {
|
||||
DEFAULT,
|
||||
RECORDING,
|
||||
PAUSE }
|
||||
@ -5,7 +5,6 @@ import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.content.ContentUris
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.PixelFormat
|
||||
@ -17,12 +16,11 @@ import android.media.projection.MediaProjectionManager
|
||||
import android.net.Uri
|
||||
import android.os.Binder
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.os.CountDownTimer
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import android.os.Looper
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.provider.MediaStore
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@ -30,6 +28,7 @@ import android.view.WindowManager
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.RemoteViews
|
||||
import android.widget.TextView
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
@ -39,21 +38,26 @@ import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
import androidx.camera.view.PreviewView
|
||||
import androidx.core.app.NotificationCompat
|
||||
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.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.ConstValue
|
||||
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.VideoFileHelper
|
||||
import com.audio.record.screen.test.view.CountDownFloatingManager
|
||||
import java.io.File
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class ScreenRecordService : Service() {
|
||||
|
||||
private var lifecycleOwner: CustomLifecycleOwner? = null
|
||||
|
||||
private var isBallViewAdded = false
|
||||
private var isBallExpandViewAdded = false
|
||||
private var isScreenshotViewAdded = false
|
||||
private var isWebcamViewAdded = false
|
||||
private var isRecordViewAdded = false
|
||||
@ -64,6 +68,17 @@ class ScreenRecordService : Service() {
|
||||
|
||||
//悬浮球View
|
||||
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
|
||||
private var screenshotView: View? = null
|
||||
@ -74,9 +89,10 @@ class ScreenRecordService : Service() {
|
||||
private var index = 0
|
||||
|
||||
//录屏成功View
|
||||
private var recordView: View? = null
|
||||
private var recordCompleteView: View? = null
|
||||
|
||||
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 layoutParamsScreenshot: WindowManager.LayoutParams
|
||||
private lateinit var layoutParamsCameraView: WindowManager.LayoutParams
|
||||
@ -93,17 +109,22 @@ class ScreenRecordService : Service() {
|
||||
private var callbackRef: FloatingCallback? = null
|
||||
|
||||
//截屏
|
||||
|
||||
var mIntent: Intent? = null
|
||||
private var mCode: Int = 0
|
||||
|
||||
private lateinit var mediaRecorder: MediaRecorder
|
||||
private lateinit var tmpVideoUri: Uri
|
||||
private lateinit var videoName: String
|
||||
private var tmpVideoPfd: ParcelFileDescriptor? = null
|
||||
|
||||
//当前录制是否带音频
|
||||
private var isWithAudio = false
|
||||
|
||||
private val callbacks = mutableListOf<WeakReference<FloatingCallback>>()
|
||||
private var countDownTimer: CountDownTimer? = null
|
||||
|
||||
//录制状态标志 true 录制中
|
||||
private var mRecordingStatus = -1
|
||||
|
||||
//录制时间监听相关
|
||||
private var recordingStartTime: Long = 0L // 本次录制开始时间
|
||||
@ -116,7 +137,10 @@ class ScreenRecordService : Service() {
|
||||
private var isPause = false
|
||||
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 {
|
||||
override fun run() {
|
||||
@ -125,6 +149,8 @@ class ScreenRecordService : Service() {
|
||||
System.currentTimeMillis() - recordingStartTime - totalPausedTime
|
||||
val elapsed = currentRecordingTimeInMillis / 1000
|
||||
val onRecordingTimeChanged = Common.onRecordingTimeChanged(elapsed)
|
||||
tvRecordTime?.text = onRecordingTimeChanged
|
||||
smallTvTime?.text = onRecordingTimeChanged
|
||||
callbacks.forEach {
|
||||
it.get()?.onUpdateRecordTime(onRecordingTimeChanged)
|
||||
}
|
||||
@ -180,9 +206,26 @@ class ScreenRecordService : Service() {
|
||||
mIntent = m
|
||||
mCode = code
|
||||
// mediaProjection = mediaProjectionManager.getMediaProjection(code, m)
|
||||
}
|
||||
fun resetIntent(){
|
||||
mIntent = null
|
||||
ballType?.let {
|
||||
when (it) {
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 开始截屏
|
||||
*/
|
||||
private fun startScreenshot(){
|
||||
// TODO: 开始截屏
|
||||
mIntent?.let { intent->
|
||||
mediaProjectionManager.getMediaProjection(mCode, intent)?.let { mediaProjection->
|
||||
hideScreenshot()
|
||||
ScreenCaptureHelper.startScreenCapture(this, mediaProjection) {
|
||||
//截屏完成
|
||||
showScreenshot()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
fun showCameraView() {
|
||||
if (frontCameraView == null) {
|
||||
@ -255,6 +314,10 @@ class ScreenRecordService : Service() {
|
||||
hideFrontCamera() // 自动清除
|
||||
hideBall()
|
||||
hideScreenshot()
|
||||
hideBallExpand()
|
||||
hideRecordCompleteView()
|
||||
ballViewExpand = null
|
||||
recordCompleteView = null
|
||||
frontCameraView = null
|
||||
ballView = null
|
||||
screenshotView = null
|
||||
@ -298,9 +361,15 @@ class ScreenRecordService : Service() {
|
||||
layoutParamsBall.gravity = Gravity.TOP or Gravity.START
|
||||
layoutParamsBall.x = screenWH.first - size - 20
|
||||
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}")
|
||||
}
|
||||
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() {
|
||||
if (screenshotView == null) {
|
||||
@ -327,17 +506,7 @@ class ScreenRecordService : Service() {
|
||||
windowManager,
|
||||
true
|
||||
) {
|
||||
|
||||
Common.showLog("--------------callbackRef=${callbackRef}")
|
||||
// callbackRef?.onFloatingButtonClicked(FloatingWindowBridge.CLICK_screenshot)
|
||||
mIntent?.let {
|
||||
mediaProjectionManager.getMediaProjection(mCode, it)?.let {
|
||||
hideScreenshot()
|
||||
ScreenCaptureHelper.startScreenCapture(this, it) {
|
||||
// showScreenshot()
|
||||
}
|
||||
}
|
||||
}
|
||||
startScreenshot()
|
||||
}
|
||||
}
|
||||
if (!isScreenshotViewAdded) {
|
||||
@ -349,16 +518,21 @@ class ScreenRecordService : Service() {
|
||||
|
||||
//显示截屏
|
||||
fun showRecordView() {
|
||||
if (recordView == null) {
|
||||
recordView = LayoutInflater.from(this).inflate(R.layout.floating_record_complete, null)
|
||||
if (recordCompleteView == null) {
|
||||
recordCompleteView =
|
||||
LayoutInflater.from(this).inflate(R.layout.floating_record_complete, null)
|
||||
layoutParamsRecordView = getLayoutParams()
|
||||
layoutParamsRecordView.gravity = Gravity.CENTER
|
||||
val imClose = recordView!!.findViewById<ImageView>(R.id.close).setOnClickListener {
|
||||
hideRecordView()
|
||||
}
|
||||
recordView!!.findViewById<FrameLayout>(R.id.layout_video).setOnClickListener {
|
||||
val imClose =
|
||||
recordCompleteView!!.findViewById<ImageView>(R.id.close).setOnClickListener {
|
||||
hideRecordCompleteView()
|
||||
}
|
||||
recordCompleteView!!.findViewById<FrameLayout>(R.id.layout_video).setOnClickListener {
|
||||
// TODO: 跳到播放页面
|
||||
hideRecordCompleteView()
|
||||
val intent = Intent(this, PlayActivity::class.java).apply {
|
||||
putExtra(PlayActivity.KEY_URI, tmpVideoUri)
|
||||
putExtra(PlayActivity.KEY_name, videoName)
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
this.startActivity(intent)
|
||||
@ -366,15 +540,15 @@ class ScreenRecordService : Service() {
|
||||
}
|
||||
}
|
||||
if (!isRecordViewAdded) {
|
||||
|
||||
val videoThumbnail = Common.getVideoThumbnail(tmpVideoPfd!!)
|
||||
Common.showLog("--------------videoThumbnail=${videoThumbnail}")
|
||||
val thumb = recordView!!.findViewById<ImageView>(R.id.image)
|
||||
|
||||
val thumb = recordCompleteView!!.findViewById<ImageView>(R.id.image)
|
||||
thumb.setImageBitmap(videoThumbnail)
|
||||
|
||||
windowManager.addView(recordView, layoutParamsRecordView)
|
||||
windowManager.addView(recordCompleteView, layoutParamsRecordView)
|
||||
isRecordViewAdded = true
|
||||
delayRemove {
|
||||
hideRecordCompleteView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -392,9 +566,23 @@ class ScreenRecordService : Service() {
|
||||
// frontCameraView = null
|
||||
}
|
||||
|
||||
fun hideBallExpand() {
|
||||
ballViewExpand?.let {
|
||||
try {
|
||||
if (isBallExpandViewAdded) {
|
||||
windowManager.removeView(it)
|
||||
isBallExpandViewAdded = false
|
||||
}
|
||||
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
// frontCameraView = null
|
||||
}
|
||||
|
||||
//隐藏录制预览View
|
||||
fun hideRecordView() {
|
||||
recordView?.let {
|
||||
fun hideRecordCompleteView() {
|
||||
recordCompleteView?.let {
|
||||
try {
|
||||
if (isRecordViewAdded) {
|
||||
windowManager.removeView(it)
|
||||
@ -451,7 +639,7 @@ class ScreenRecordService : Service() {
|
||||
hideCountDown()
|
||||
mIntent?.let { intent ->
|
||||
mediaProjectionManager.getMediaProjection(mCode, intent)
|
||||
?.let { media->
|
||||
?.let { media ->
|
||||
mediaProjection = media
|
||||
startRecording()
|
||||
callbacks.forEach {
|
||||
@ -516,11 +704,14 @@ class ScreenRecordService : Service() {
|
||||
*/
|
||||
fun startRecording() {
|
||||
Common.showLog("-------录屏中.....")
|
||||
// TODO: 录屏中.
|
||||
val fullScreenSize = Common.getFullScreenSize(this)
|
||||
val width = VideoFileHelper.alignTo16(fullScreenSize.first)
|
||||
val height = VideoFileHelper.alignTo16(fullScreenSize.second)
|
||||
initRecorder(width, height)
|
||||
mediaRecorder.start()
|
||||
mRecordingStatus = ConstValue.status_recording
|
||||
updateBall()
|
||||
recordingStartTime = System.currentTimeMillis()
|
||||
totalPausedTime = 0L
|
||||
mRecorderHandler.post(timeUpdateRunnable)
|
||||
@ -556,8 +747,6 @@ class ScreenRecordService : Service() {
|
||||
}, null
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -600,8 +789,11 @@ class ScreenRecordService : Service() {
|
||||
* 暂停录制视频
|
||||
*/
|
||||
fun pauseRecording() {
|
||||
// TODO: 暂停录制视频
|
||||
Common.showLog("-------暂停.....")
|
||||
mediaRecorder.pause()
|
||||
mRecordingStatus = ConstValue.status_pause
|
||||
updateBall()
|
||||
pauseStartTime = System.currentTimeMillis()
|
||||
isPause = true
|
||||
}
|
||||
@ -610,15 +802,67 @@ class ScreenRecordService : Service() {
|
||||
* 继续录制视频
|
||||
*/
|
||||
fun resumeRecording() {
|
||||
// TODO: 继续录制视频
|
||||
Common.showLog("------继续.....")
|
||||
|
||||
mediaRecorder.resume()
|
||||
mRecordingStatus = ConstValue.status_recording
|
||||
updateBall()
|
||||
totalPausedTime += System.currentTimeMillis() - pauseStartTime
|
||||
isPause = false
|
||||
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) {
|
||||
val (Uri, pfd) = VideoFileHelper.createVideoFile(this, Common.folderName)
|
||||
videoName = System.currentTimeMillis().toString()
|
||||
val (Uri, pfd) = VideoFileHelper.createVideoFile(this, Common.folderName, videoName)
|
||||
tmpVideoUri = Uri
|
||||
tmpVideoPfd = pfd
|
||||
mediaRecorder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
@ -626,14 +870,18 @@ class ScreenRecordService : Service() {
|
||||
} else {
|
||||
MediaRecorder()
|
||||
}.apply {
|
||||
if (isWithAudio)
|
||||
if (isWithAudio) {
|
||||
setAudioSource(MediaRecorder.AudioSource.MIC)
|
||||
}
|
||||
setVideoSource(MediaRecorder.VideoSource.SURFACE)
|
||||
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
|
||||
setOutputFile(pfd?.fileDescriptor)
|
||||
setVideoSize(width, height)
|
||||
if (isWithAudio) {
|
||||
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
|
||||
}
|
||||
setVideoEncoder(MediaRecorder.VideoEncoder.H264)
|
||||
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
|
||||
|
||||
setVideoEncodingBitRate(8 * 1000 * 1000)
|
||||
setVideoFrameRate(30)
|
||||
prepare()
|
||||
@ -641,6 +889,9 @@ class ScreenRecordService : Service() {
|
||||
}
|
||||
|
||||
fun stopRecording() {
|
||||
// TODO: 录屏完成
|
||||
mRecordingStatus = ConstValue.status_complete
|
||||
updateBall()
|
||||
Common.showLog("-------录屏完成.....")
|
||||
mRecorderHandler.removeCallbacks(timeUpdateRunnable)
|
||||
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)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -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 //录制完成
|
||||
}
|
||||
@ -6,6 +6,9 @@ import android.media.Image
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.ContextCompat
|
||||
|
||||
object Extend {
|
||||
|
||||
@ -44,11 +47,18 @@ object Extend {
|
||||
layoutParams = params
|
||||
}
|
||||
|
||||
fun ImageView.setDrawableCompat(@DrawableRes resId: Int) {
|
||||
val drawable = ContextCompat.getDrawable(context, resId)
|
||||
this.setImageDrawable(drawable)
|
||||
}
|
||||
|
||||
fun Int.dpToPx(context: Context): Int =
|
||||
TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, this.toFloat(),
|
||||
context.resources.displayMetrics
|
||||
).toInt()
|
||||
fun Context.pxToDp(px: Int): Float {
|
||||
return px / resources.displayMetrics.density
|
||||
}
|
||||
|
||||
}
|
||||
@ -68,8 +68,8 @@ object ScreenCaptureHelper {
|
||||
if (image != null) {
|
||||
val bitmap = imageToBitmap(image)
|
||||
saveBitmap(context, bitmap, folderName)
|
||||
isOK.invoke()
|
||||
showScreenshotPreviewAnimation(context, bitmap,width,height)
|
||||
isOK.invoke()
|
||||
image.close()
|
||||
}
|
||||
imageReader.close()
|
||||
|
||||
12
app/src/main/res/drawable/ball_audio_record.xml
Normal file
12
app/src/main/res/drawable/ball_audio_record.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ball_continue_recording.xml
Normal file
9
app/src/main/res/drawable/ball_continue_recording.xml
Normal 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>
|
||||
17
app/src/main/res/drawable/ball_home.xml
Normal file
17
app/src/main/res/drawable/ball_home.xml
Normal 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>
|
||||
12
app/src/main/res/drawable/ball_pause.xml
Normal file
12
app/src/main/res/drawable/ball_pause.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ball_recording.xml
Normal file
9
app/src/main/res/drawable/ball_recording.xml
Normal 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>
|
||||
15
app/src/main/res/drawable/ball_screenshot.xml
Normal file
15
app/src/main/res/drawable/ball_screenshot.xml
Normal 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>
|
||||
23
app/src/main/res/drawable/ball_without_audio_record.xml
Normal file
23
app/src/main/res/drawable/ball_without_audio_record.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/bg_ball.xml
Normal file
9
app/src/main/res/drawable/bg_ball.xml
Normal 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>
|
||||
7
app/src/main/res/drawable/recording_bg_oval.xml
Normal file
7
app/src/main/res/drawable/recording_bg_oval.xml
Normal 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>
|
||||
7
app/src/main/res/drawable/recording_oval_red.xml
Normal file
7
app/src/main/res/drawable/recording_oval_red.xml
Normal 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>
|
||||
5
app/src/main/res/drawable/status_ball_record.xml
Normal file
5
app/src/main/res/drawable/status_ball_record.xml
Normal 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>
|
||||
14
app/src/main/res/drawable/status_ball_record_three.xml
Normal file
14
app/src/main/res/drawable/status_ball_record_three.xml
Normal 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>
|
||||
@ -9,4 +9,29 @@
|
||||
android:layout_height="36dp"
|
||||
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>
|
||||
60
app/src/main/res/layout/floating_ball_expand.xml
Normal file
60
app/src/main/res/layout/floating_ball_expand.xml
Normal 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>
|
||||
@ -6,4 +6,13 @@
|
||||
</style>
|
||||
|
||||
<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>
|
||||
Loading…
Reference in New Issue
Block a user