Kotlin高仿微信-第37篇-拍照

  Kotlin高仿微信-项目实践58篇详细讲解了各个功能点,包括:注册、登录、主页、单聊(文本、表情、语音、图片、小视频、视频通话、语音通话、红包、转账)、群聊、个人信息、朋友圈、支付服务、扫一扫、搜索好友、添加好友、开通VIP等众多功能。

Kotlin高仿微信-项目实践58篇,点击查看详情

效果图:

实现代码:

<?xml version="1.0" encoding="utf-8"?>
<layout>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.camera.view.PreviewView
        android:id="@+id/previewView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <CheckBox
        android:id="@+id/audio_selection"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end"
        android:layout_marginTop="20dp"
        android:layout_marginEnd="100dp"
        android:visibility="gone"
        android:buttonTint="@android:color/white"
        android:text="Audio"
        android:textColor="@android:color/white" />

    <ImageButton
        android:id="@+id/iv_torch"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="end"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="@dimen/margin_small"
        android:background="@android:color/transparent"
        android:src="@drawable/icon_flash_auto"
        android:visibility="gone"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageButton
        android:id="@+id/btn_switch_camera"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_gravity="top|right"
        android:layout_marginEnd="@dimen/margin_small"
        android:layout_marginTop="@dimen/margin_small"
        android:background="@android:color/transparent"
        android:contentDescription="@string/switch_camera_button_alt"
        android:padding="@dimen/spacing_small"
        android:scaleType="fitCenter"
        app:srcCompat="@drawable/wc_svideo_switch" />

    <ImageButton
        android:id="@+id/btn_back"
        android:layout_width="@dimen/dp_40"
        android:layout_height="@dimen/dp_40"
        android:layout_gravity="bottom"
        android:layout_marginStart="@dimen/margin_small"
        android:layout_marginBottom="60dp"
        android:background="@android:color/transparent"
        android:contentDescription="@string/switch_camera_button_alt"
        android:padding="@dimen/spacing_small"
        android:scaleType="fitCenter"
        app:srcCompat="@drawable/wc_svideo_camera_back" />

    <com.wn.wechatclientdemo.svideo.CircleProgressButtonView
        android:id="@+id/btn_record"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_gravity="bottom|center"
        android:layout_marginBottom="40dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:maxTime="15"
        app:progressWidth="8dp" />

    <ImageButton
        android:id="@+id/btn_photo_view"
        android:layout_width="@dimen/round_button_medium"
        android:layout_height="@dimen/round_button_medium"
        android:layout_gravity="end|bottom"
        android:layout_marginEnd="@dimen/margin_small"
        android:layout_marginBottom="@dimen/margin_xlarge"
        android:background="@drawable/wc_svideo_outer_circle"
        android:contentDescription="@string/gallery_button_alt"
        android:visibility="gone"
        android:padding="@dimen/spacing_large"
        android:scaleType="fitCenter"
        app:srcCompat="@drawable/wc_svideo_photo" />

    <TextView
        android:id="@+id/capture_status"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|center"
        android:layout_marginBottom="20dp"
        android:lines="2"
        android:maxLines="2"
        android:visibility="gone"
        android:text="@string/Idle"
        android:textColor="#ff0" />

</FrameLayout>
</layout>

/**
 * Author : wangning
 * Email : maoning20080809@163.com
 * Date : 2022/5/23 22:01
 * Description : 拍照
 */
class CameraFragment : BaseDataBindingFragment<WcSvideoCameraBinding>(){

    override fun getLayoutRes() = R.layout.wc_svideo_camera

    private lateinit var outputDirectory: File
    private lateinit var videoCapture: VideoCapture<Recorder>
    private var activeRecording: ActiveRecording? = null
    private lateinit var recordingState: VideoRecordEvent
    private var audioEnabled = true
    private val mainThreadExecutor by lazy { ContextCompat.getMainExecutor(requireContext()) }
    private var isBack = true
    private var imageCapture: ImageCapture? = null
    private lateinit var cameraExecutor: ExecutorService
    private val REQ_CAMREA_CODE = 101
    val EXTENSION_WHITELIST = arrayOf("JPG")
    var enterType = 0

    enum class UiState {
        IDLE,       // Not recording, all UI controls are active.
        RECORDING,  // Camera is recording, only display Pause/Resume & Stop button.
        FINALIZED,  // Recording just completes, disable all RECORDING UI controls.
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        TagUtils.d("拍小视频开始。。")
        //initCameraFragment()
        handlePermission()
    }

    private fun handlePermission(){
        if(ContextCompat.checkSelfPermission(requireActivity(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED){
            requestPermissions(arrayOf(Manifest.permission.CAMERA), REQ_CAMREA_CODE)
        } else {
            initCameraFragment()
        }
    }

    override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>, grantResults: IntArray ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if(requestCode == REQ_CAMREA_CODE && grantResults != null && grantResults.size > 0){
            if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
                initCameraFragment()
            }
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        cameraExecutor.shutdown()
    }

    private fun setGalleryThumbnail(uri: Uri) {
        /*fragmentCameraBinding.btnPhotoView.let { photoViewButton ->
            photoViewButton.post {
                photoViewButton.setPadding(resources.getDimension(R.dimen.stroke_small).toInt())
                Glide.with(photoViewButton)
                    .load(uri)
                    .apply(RequestOptions.circleCropTransform())
                    .into(photoViewButton)
            }
        }*/
    }

    private suspend fun bindCameraUseCases() {
        //var degree = previewView.display.rotation

        val cameraProvider: ProcessCameraProvider = ProcessCameraProvider.getInstance(requireContext()).await()
        val cameraSelector = if (isBack) CameraSelector.DEFAULT_BACK_CAMERA else CameraSelector.DEFAULT_FRONT_CAMERA

        val preview = Preview.Builder()
            .setTargetAspectRatio(DEFAULT_ASPECT_RATIO)
            .build()
            .apply { setSurfaceProvider(previewView.surfaceProvider) }

        val recorder = Recorder.Builder()
            //.setQualitySelector(QualitySelector.of(QualitySelector.QUALITY_SD))
            .setQualitySelector(QualitySelector.of(QualitySelector.QUALITY_FHD))
            .build()

        videoCapture = VideoCapture.withOutput(recorder)

        imageCapture = ImageCapture.Builder()
            .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
            //.setTargetRotation(ROTATION_90) // 设置旋转角度
            .setFlashMode(ImageCapture.FLASH_MODE_AUTO)
            .setTargetAspectRatio(DEFAULT_ASPECT_RATIO)
            .build()

        try {
            cameraProvider.unbindAll()
            cameraProvider.bindToLifecycle(
                viewLifecycleOwner,
                cameraSelector,
                videoCapture,
                imageCapture,
                preview
            )
        } catch (e: Exception) {
            TagUtils.e("Use case binding failed ${e}")
            e.printStackTrace()
            resetUIandState("bindToLifecycle failed: $e")
        }
    }
    var outFile : File? = null
    @SuppressLint("MissingPermission")
    private fun startRecording() {
        outFile = createFile(outputDirectory, FILENAME, VIDEO_EXTENSION)
        TagUtils.i("outFile: $outFile")
        val outputOptions: FileOutputOptions = FileOutputOptions.Builder(outFile!!).build()
        activeRecording = videoCapture.output.prepareRecording(requireActivity(), outputOptions)
            .withEventListener(mainThreadExecutor, captureListener)
            .apply { if (audioEnabled) withAudioEnabled() }
            .start()

        TagUtils.i("Recording started")

    }

    private val captureListener = Consumer<VideoRecordEvent> { event ->
        if (event !is VideoRecordEvent.Status) recordingState = event

        updateUI(event)

        if (event is VideoRecordEvent.Finalize) showVideo(event)
    }

    private fun takePicture() {
        imageCapture?.let { imageCapture ->
            val photoFile = createFile(outputDirectory, FILENAME, PHOTO_EXTENSION)
            val metadata = ImageCapture.Metadata().apply {
                //isReversedHorizontal = isBack
            }
            val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile)
                .setMetadata(metadata)
                .build()

            imageCapture.takePicture(
                outputOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback {
                    override fun onError(exc: ImageCaptureException) {
                        TagUtils.e("Photo capture failed: ${exc.message}")
                    }

                    override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                        //val savedUri: Uri = output.savedUri ?: Uri.fromFile(photoFile)

                        TagUtils.d( "Photo capture succeeded: $outFile")
                        TagUtils.d( "Photo capture 成功: $photoFile")

                        lifecycleScope.launch {
                            findNavController()?.popBackStack()
                            var bundle = bundleOf(CommonUtils.Moments.TYPE_IMAGE_PATH to photoFile,
                                CommonUtils.Moments.TYPE_NAME to CommonUtils.Moments.TYPE_PICTURE,
                                TYPE_ENTER to enterType)
                            findNavController().navigate( R.id.action_svideo_play, bundle)
                            TagUtils.d("拍照成功 ${photoFile}")
                        }
                    }
                })

            // We can only change the foreground Drawable using API level 23+ API
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                // Display flash animation to indicate that photo was captured
                container.postDelayed({
                    container.foreground = ColorDrawable(Color.WHITE)
                    container.postDelayed(
                        { container.foreground = null }, ANIMATION_FAST_MILLIS
                    )
                }, ANIMATION_SLOW_MILLIS)
            }
        }
    }

    private fun initCameraFragment() {
        outputDirectory = getOutputDirectory(requireContext())
        cameraExecutor = Executors.newSingleThreadExecutor()
        initializeUI()
        viewLifecycleOwner.lifecycleScope.launch {
            bindCameraUseCases()
        }
    }

    private fun switchCamera() {
        isBack = !isBack
        lifecycleScope.launch {
            bindCameraUseCases()
        }
    }

    private fun changeFlashMode() {
        when (imageCapture?.flashMode) {
            ImageCapture.FLASH_MODE_AUTO -> {
                imageCapture?.flashMode = ImageCapture.FLASH_MODE_ON
                iv_torch.setImageResource(R.drawable.icon_flash_always_on)
            }
            ImageCapture.FLASH_MODE_ON -> {
                imageCapture?.flashMode = ImageCapture.FLASH_MODE_OFF
                iv_torch.setImageResource(R.drawable.icon_flash_always_off)
            }
            ImageCapture.FLASH_MODE_OFF -> {
                imageCapture?.flashMode = ImageCapture.FLASH_MODE_AUTO
                iv_torch.setImageResource(R.drawable.icon_flash_auto)
            }
            else -> Unit
        }
    }

    @SuppressLint("ClickableViewAccessibility", "MissingPermission")
    private fun initializeUI() {

        enterType = arguments?.getInt(TYPE_ENTER) as Int

        lifecycleScope.launch(Dispatchers.IO) {
            outputDirectory.listFiles { file ->
                EXTENSION_WHITELIST.contains(file.extension.uppercase(Locale.ROOT))
            }?.maxOrNull()?.let {
                setGalleryThumbnail(Uri.fromFile(it))
            }
        }

        btn_switch_camera.setOnClickListener {
            switchCamera()
        }

        btn_photo_view.setOnClickListener {
            TagUtils.d("点击相册。。。")
            /*findNavController().navigate(
                CameraFragmentDirections.actionCameraToGallery(
                    outputDirectory.absolutePath
                )
            )*/
        }

        audio_selection.isChecked = audioEnabled
        audio_selection.setOnClickListener {
            audioEnabled = audio_selection.isChecked
        }

        btn_record.setOnLongClickListener(object :
            CircleProgressButtonView.OnLongClickListener {
            override fun onLongClick() {
                if (!this@CameraFragment::recordingState.isInitialized || recordingState is VideoRecordEvent.Finalize) {
                    startRecording()
                }
            }

            override fun onNoMinRecord(currentTime: Int) = Unit

            override fun onRecordFinishedListener() {
                if (activeRecording == null || recordingState is VideoRecordEvent.Finalize) return
                val recording = activeRecording
                if (recording != null) {
                    recording.stop()
                    activeRecording = null
                }
            }

        })

        /*btn_record.setOnClickListener(CircleProgressButtonView.OnClickListener {
            takePicture()
        })*/
        btn_record.setOnClickListener(object : CircleProgressButtonView.OnClickListener{
            override fun onClick() {
                takePicture()
            }
        })

        iv_torch.setOnClickListener {
            changeFlashMode()
        }
    }

    private fun updateUI(event: VideoRecordEvent) {
        val state = if (event is VideoRecordEvent.Status) recordingState.getName()
        else event.getName()
        TagUtils.i("event.getName(): ${event.getName()}")
        when (event) {
            is VideoRecordEvent.Status -> {
                // placeholder: we update the UI with new status after this when() block,
                // nothing needs to do here.
            }
            is VideoRecordEvent.Start -> {
                showUI(UiState.RECORDING, event.getName())
            }
            is VideoRecordEvent.Finalize -> {
                showUI(UiState.FINALIZED, event.getName())
            }
            is VideoRecordEvent.Pause -> {
            }
            is VideoRecordEvent.Resume -> {
            }
            else -> {
                TagUtils.e("Error(Unknown Event) from Recorder")
                return
            }
        }

        val stats = event.recordingStats
        val size = stats.numBytesRecorded / 1000
        val time = java.util.concurrent.TimeUnit.NANOSECONDS.toSeconds(stats.recordedDurationNanos)
        var text = "${state}: recorded ${size}KB, in ${time}second"
        if (event is VideoRecordEvent.Finalize)
            text = "${text}\nFile saved to: ${event.outputResults.outputUri}"

        capture_status.text = text
        TagUtils.i("recording event: $text")
    }

    private fun showUI(state: UiState, status: String = "idle") {
        TagUtils.i("showUI: UiState: $status")
        when (state) {
            UiState.IDLE -> {
                btn_switch_camera.visibility = View.VISIBLE
                audio_selection.visibility = View.VISIBLE
            }
            UiState.RECORDING -> {
                btn_switch_camera.visibility = View.INVISIBLE
                audio_selection.visibility = View.INVISIBLE
            }
            UiState.FINALIZED -> {
            }
            else -> {
                val errorMsg = "Error: showUI($state) is not supported"
                TagUtils.e(errorMsg)
                return
            }
        }
        capture_status.text = status
    }

    private fun resetUIandState(reason: String) {
        showUI(UiState.IDLE, reason)
        audioEnabled = false
        audio_selection.isChecked = audioEnabled
    }

    private fun showVideo(event: VideoRecordEvent) {
        TagUtils.d("0小视频路径:showVideo ")
        if (event !is VideoRecordEvent.Finalize) return

        lifecycleScope.launch {
            findNavController()?.popBackStack()
            var bundle = bundleOf(CommonUtils.Moments.TYPE_VIDEO_PATH to outFile,
                CommonUtils.Moments.TYPE_NAME to CommonUtils.Moments.TYPE_VIDEO,
                TYPE_ENTER to enterType)
            findNavController().navigate( R.id.action_svideo_play, bundle)
        }
    }

    companion object {
        const val DEFAULT_ASPECT_RATIO = AspectRatio.RATIO_16_9
        //val TAG: String = CameraFragment::class.java.simpleName
        private const val FILENAME = "yyyyMMddHHmmss"
        private const val VIDEO_EXTENSION = ".mp4"
        private const val PHOTO_EXTENSION = ".jpg"

        private const val IMMERSIVE_FLAG_TIMEOUT = 500L

        const val ANIMATION_FAST_MILLIS = 50L
        const val ANIMATION_SLOW_MILLIS = 100L

        //聊天页面小视频
        const val TYPE_CHAT = 1
        //朋友圈小视频
        const val TYPE_MOMENT = 2
        //进入类型
        const val TYPE_ENTER = "type_enter"
        //返回类型
        const val TYPE_BACK = "type_back"

        fun getOutputDirectory(context: Context): File {
            /*val appContext = context.applicationContext
            val mediaDir = context.externalMediaDirs.firstOrNull()?.let {
                File(it, "SVideo").apply { mkdirs() }
            }
            return if (mediaDir != null && mediaDir.exists())
                mediaDir else appContext.filesDir*/
            return File(FileUtils.getFilePath())
        }

        fun createFile(baseFolder: File, format: String, extension: String) =
            File(baseFolder, SimpleDateFormat(format, Locale.US).format(System.currentTimeMillis()) + extension)
    }
}

fun VideoRecordEvent.getName(): String {
    return when (this) {
        is VideoRecordEvent.Status -> "Status"
        is VideoRecordEvent.Start -> "Started"
        is VideoRecordEvent.Finalize -> "Finalized"
        is VideoRecordEvent.Pause -> "Paused"
        is VideoRecordEvent.Resume -> "Resumed"
        else -> "Error(Unknown)"
    }
}

/**
 * Author : wangning
 * Email : maoning20080809@163.com
 * Date : 2022/5/23 22:05
 * Description : 录制视频
 */
class CircleProgressButtonView : View {

    constructor(context: Context) : this(context, null)
    constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0)
    constructor(context: Context, attributeSet: AttributeSet?, defStyleAttr: Int) : super(context, attributeSet, defStyleAttr) {
        init(context, attributeSet)
    }


    private val WHAT_LONG_CLICK = 1
    private var mBigCirclePaint: Paint? = null
    private var mSmallCirclePaint: Paint? = null
    private var mProgressCirclePaint: Paint? = null
    private var mHeight //当前View的高
            = 0
    private var mWidth //当前View的宽
            = 0
    private var mInitBitRadius = 0f
    private var mInitSmallRadius = 0f
    private var mBigRadius = 0f
    private var mSmallRadius = 0f
    private var mStartTime: Long = 0
    private var mEndTime: Long = 0
    private var isRecording //录制状态
            = false
    private var isMaxTime //达到最大录制时间
            = false
    private var mCurrentProgress //当前进度
            = 0f

    private val mLongClickTime: Long = 500 //长按最短时间(毫秒),

    private var mTime = 15 //录制最大时间s

    private var mMinTime = 3 //录制最短时间

    private var mProgressColor //进度条颜色
            = 0
    private var mProgressW = 18f //圆环宽度

    //当前手指处于按压状态
    private var isPressed2 = false
    //圆弧进度变化
    private var mProgressAni : ValueAnimator? = null


    private fun init(context: Context, attrs: AttributeSet?) {
        val a = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressButtonView)
        mMinTime = a.getInt(R.styleable.CircleProgressButtonView_minTime, 0)
        mTime = a.getInt(R.styleable.CircleProgressButtonView_maxTime, 10)
        mProgressW = a.getDimension(R.styleable.CircleProgressButtonView_progressWidth, 12f)
        mProgressColor = a.getColor(
            R.styleable.CircleProgressButtonView_progressColor,
            Color.parseColor("#6ABF66")
        )
        a.recycle()
        initPaint()
        mProgressAni = ValueAnimator.ofFloat(0f, 360f)
        mProgressAni?.setDuration((mTime * 1000).toLong())
    }

    private fun initPaint() {
        //初始画笔抗锯齿、颜色
        mBigCirclePaint = Paint(Paint.ANTI_ALIAS_FLAG)
        mBigCirclePaint!!.color = Color.parseColor("#DDDDDD")
        mSmallCirclePaint = Paint(Paint.ANTI_ALIAS_FLAG)
        mSmallCirclePaint!!.color = Color.parseColor("#FFFFFF")
        mProgressCirclePaint = Paint(Paint.ANTI_ALIAS_FLAG)
        mProgressCirclePaint!!.color = mProgressColor
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        mWidth = MeasureSpec.getSize(widthMeasureSpec)
        mHeight = MeasureSpec.getSize(heightMeasureSpec)
        mBigRadius = mWidth / 2f * 0.75f
        mInitBitRadius = mBigRadius
        mSmallRadius = mBigRadius * 0.75f
        mInitSmallRadius = mSmallRadius
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        //绘制外圆
        canvas.drawCircle(mWidth / 2f, mHeight / 2f, mBigRadius, mBigCirclePaint!!)
        //绘制内圆
        canvas.drawCircle(mWidth / 2f, mHeight / 2f, mSmallRadius, mSmallCirclePaint!!)
        //录制的过程中绘制进度条
        if (isRecording) drawProgress(canvas)
    }

    private fun drawProgress(canvas: Canvas) {
        mProgressCirclePaint!!.strokeWidth = mProgressW
        mProgressCirclePaint!!.style = Paint.Style.STROKE
        //用于定义的圆弧的形状和大小的界限
        val oval = RectF(
            mWidth / 2f - (mBigRadius - mProgressW / 2),
            mHeight / 2f - (mBigRadius - mProgressW / 2),
            mWidth / 2f + (mBigRadius - mProgressW / 2),
            mHeight / 2f + (mBigRadius - mProgressW / 2)
        )
        //根据进度画圆弧
        canvas.drawArc(oval, -90f, mCurrentProgress, false, mProgressCirclePaint!!)
    }

    private val mHandler: Handler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            when (msg.what) {
                WHAT_LONG_CLICK -> {
                    //长按事件触发
                    onLongClickListener2?.onLongClick()
                    //内外圆动画,内圆缩小,外圆放大
                    startAnimation(
                        mBigRadius,
                        mBigRadius * 1.33f,
                        mSmallRadius,
                        mSmallRadius * 0.7f
                    )
                }
            }
        }
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                isPressed2 = true
                mStartTime = System.currentTimeMillis()
                val mMessage = Message.obtain()
                mMessage.what = WHAT_LONG_CLICK
                mHandler.sendMessageDelayed(mMessage, mLongClickTime)
            }
            MotionEvent.ACTION_UP -> {
                isPressed2 = false
                isRecording = false
                mEndTime = System.currentTimeMillis()
                if (mEndTime - mStartTime < mLongClickTime) {
                    mHandler.removeMessages(WHAT_LONG_CLICK)
                    onClickListener2?.onClick()
                } else {
                    startAnimation(
                        mBigRadius,
                        mInitBitRadius,
                        mSmallRadius,
                        mInitSmallRadius
                    ) //手指离开时动画复原
                    if (mProgressAni != null && mProgressAni!!.currentPlayTime / 1000 < mMinTime && !isMaxTime) {
                        onLongClickListener2?.onNoMinRecord(mMinTime)
                        mProgressAni!!.cancel()
                    } else {
                        //录制完成
                        if (onLongClickListener2 != null && !isMaxTime) {
                            onLongClickListener2?.onRecordFinishedListener()
                        }
                    }
                }
            }
        }
        return true
    }

    private fun startAnimation(bigStart: Float, bigEnd: Float, smallStart: Float, smallEnd: Float) {
        val bigObjAni = ValueAnimator.ofFloat(bigStart, bigEnd)
        bigObjAni.duration = 150
        bigObjAni.addUpdateListener { animation: ValueAnimator ->
            mBigRadius = animation.animatedValue as Float
            invalidate()
        }
        val smallObjAni = ValueAnimator.ofFloat(smallStart, smallEnd)
        smallObjAni.duration = 150
        smallObjAni.addUpdateListener { animation: ValueAnimator ->
            mSmallRadius = animation.animatedValue as Float
            invalidate()
        }
        bigObjAni.start()
        smallObjAni.start()
        smallObjAni.addListener(object : Animator.AnimatorListener {
            override fun onAnimationStart(animation: Animator) {
                isRecording = false
            }

            override fun onAnimationEnd(animation: Animator) {
                //开始绘制圆形进度
                if (isPressed2) {
                    isRecording = true
                    isMaxTime = false
                    startProgressAnimation()
                }
            }

            override fun onAnimationCancel(animation: Animator) {}
            override fun onAnimationRepeat(animation: Animator) {}
        })
    }

    private fun startProgressAnimation() {
        mProgressAni!!.start()
        mProgressAni!!.addUpdateListener { animation: ValueAnimator ->
            mCurrentProgress = animation.animatedValue as Float
            invalidate()
        }
        mProgressAni!!.addListener(object : Animator.AnimatorListener {
            override fun onAnimationStart(animation: Animator) {}
            override fun onAnimationEnd(animation: Animator) {
                //录制动画结束时,即为录制全部完成
                if (onLongClickListener2 != null && isPressed2) {
                    isPressed2 = false
                    isMaxTime = true
                    onLongClickListener2?.onRecordFinishedListener()
                    startAnimation(mBigRadius, mInitBitRadius, mSmallRadius, mInitSmallRadius)
                    //影藏进度进度条
                    mCurrentProgress = 0f
                    invalidate()
                }
            }

            override fun onAnimationCancel(animation: Animator) {}
            override fun onAnimationRepeat(animation: Animator) {}
        })
    }

    interface OnLongClickListener {
        fun onLongClick()

        //未达到最小录制时间
        fun onNoMinRecord(currentTime: Int)

        //录制完成
        fun onRecordFinishedListener()
    }

    var onLongClickListener2: OnLongClickListener? = null

    fun setOnLongClickListener(onLongClickListener: OnLongClickListener?) {
        this.onLongClickListener2 = onLongClickListener
    }

    interface OnClickListener {
        fun onClick()
    }

    var onClickListener2: OnClickListener? = null

    fun setOnClickListener(onClickListener: OnClickListener) {
        this.onClickListener2 = onClickListener
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

六毛六66

你的鼓励是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值