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

news2024/9/28 11:20:20

  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
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/55852.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ZPL II 语言编程基础

ZPL II 语言概述 ZPL语言是一种script语言&#xff0c;分为ZPL语言和ZPL II 语言Zebra打印机支持最广泛的一种语言 ZPL II语言支持复杂标签格式&#xff0c;如文字&#xff0c;图片&#xff0c;条形码&#xff0c;序列号打印等等 ZPL II文件可以通过以下两种方式实现 纯文本编…

java字符编码总结

一、字符集(Charcater Set)与字符编码(Encoding) 字符集(Charcater Set 或 Charset)&#xff1a;是一个系统支持的所有抽象字符的集合&#xff0c;也就是一系列字符的集合。字符是各种文字和符号的总称&#xff0c;包括各国家文字、标点符号、图形符号、数字等。常见的字符集有…

记录--从AI到美颜全流程讲解

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 美颜和短视频 美颜相关APP可以说是现在手机上的必备的软件&#xff0c;例如抖音&#xff0c;快手&#xff0c;拍出的“照骗”和视频不加美颜效果&#xff0c;估计没有人敢传到网上。很多人一直好奇美颜…

力扣hot100——第3天:11盛最多水的容器、15三数之和、17电话号码的字母组合

文章目录1.11盛最多水的容器1.1.题目1.2.解答1.2.1.题解1.2.2.自己对参考题解的进一步解释2.15三数之和【代码随想录已刷】3.17电话号码的字母组合【代码随想录已刷】1.11盛最多水的容器 参考&#xff1a;力扣题目链接&#xff1b;题解 1.1.题目 1.2.解答 1.2.1.题解 这道题…

Mybatis-多表联查

多表联查一、步骤一:创建pojo实体类二、步骤二&#xff1a;明确两个实体类之间的关系三、步骤三:修改pojo实体类四、步骤四&#xff1a;编写Mapper接口五、步骤五&#xff1a;编写Mapper映射文件题目1&#xff1a;通过订单id查询订单详情以及所属用户题目2&#xff1a;通过用户…

OpenCV入门(C++/Python)- 使用OpenCV读取、显示和写入图像(一)

使用OpenCV读取、显示和写入图像1.imread()读取图像imread()函数2.imshow()在窗口中显示图像waitKey()destoryAllWindows()3.imwrite()将图像写入文件目录读取、显示和写入图像是图像处理和计算机视觉的基础。即使裁剪、调整大小、旋转或应用不同的过滤器来处理图像&#xff0c…

C. Carrying Conundrum(思维 + 奇偶数位)

Problem - 1567C - Codeforces 爱丽丝刚刚学会了加法。但是&#xff0c;她还没有完全学会 "携带 "的概念--她不是携带到下一列&#xff0c;而是携带到左边两列的列。 例如&#xff0c;评估20392976这个和的常规方法是如图所示。 然而&#xff0c;爱丽丝是按照图中的…

【在SpringBoot项目中使用Validation框架检查数据格式-常用的检查注解】

常用的检查注解 使用Validation框架检查数据格式时&#xff0c;常用的检查注解有&#xff1a; NotNull&#xff1a;不允许为null值 可用于任何类型的参数NotEmpty&#xff1a;不允许为空字符串&#xff0c;即长度为0的字符串 仅用于检查字符串类型的参数NotBlank&#xff1a;不…

【D3.js】1.17-给 D3 元素添加标签

title: 【D3.js】1.17-给 D3 元素添加标签 date: 2022-12-02 14:35 tags: [JavaScript,CSS,HTML,D3.js,SVG] 为了让图更易懂&#xff0c;我们给每一个rect添加上标签。 一、学习目标 如何添加text元素&#xff1f; .append(“text”) 如何设置text元素的值&#xff1f; .attr(…

[附源码]计算机毕业设计在线图书销售系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

IPWorks macOS Edition通信组件

IPWorks macOS Edition通信组件 用于Internet通信的一整套组件。 IPWorks是一个用于Internet开发的综合框架&#xff0c;它消除了Internet开发的复杂性&#xff0c;提供了可编程的、支持SSL的组件&#xff0c;以便于执行诸如确保安全、发送电子邮件、传输文件、管理网络、浏览W…

物联网 MQTT 协议

MQTT官网&#xff1a;MQTT - The Standard for IoT Messaging MQTT中文网&#xff08;全是广告&#xff09;&#xff1a;首页 | MQTT中文网 物联网百科 物联网&#xff08;Internet of Things&#xff0c;简称IoT&#xff09;是指通过各种信息传感器、射频识别技术、全球定位…

在线编程教学技术解决方案,覆盖所有授课场景需求

在线编程教学是一种应用较为广泛的远程教学形式&#xff0c;例如&#xff1a;互动体验&#xff0c;音视频技术的普及&#xff0c;对线上教学的质量与学习效率带来了很大的提升。在线编程教学可以让教师对学生进行在线编程教学&#xff0c;以一对多小班教学为主。那么在线编程教…

线上项目源码安全性处理方案

场景&#xff1a; 最近项目提出要对线上代码进行安全性处理&#xff0c;防止客户直接通过反编译工具将代码反编译出来 方案&#xff1a; 第一种方案使用的是代码混淆 第二种方案使用的是代码加密 方案比较 方案一&#xff1a;采用的proguard-maven-plugin插件 方案二&#xf…

要花多少亿美元,HPE才能买下超融合鼻祖Nu­t­a­n­ix?

【全球存储观察 | 热点关注】据报道&#xff0c;慧与科技HPE在近几个月与超融合提供商Nutanix就收购进行了谈判。 在这之前的2017年2月&#xff0c;HPE以6.5亿美元收购了超融合全球老二SimpliVity&#xff0c;后来整合成了HPE重要的超融合产品线&#xff0c;并进一步丰富了整体…

Seal库官方示例(五):ckks_basics.cpp解析

这个代码计算的是πx30.4x1\pi \times x^30.4 \times x 1πx30.4x1。 代码解析 方案选择 首先照例是方案选择 EncryptionParameters parms(scheme_type::ckks);参数设置 CKKS方案中使用rescale方法来控制膨胀的密文规模和噪声&#xff0c;这个和modulus switching有点类似…

[激光原理与应用-28]:《激光原理与技术》-14- 激光产生技术 - 激光的主要参数与指标

目录 1、 激光器的门限电流与功率输出 2、激光器的调制增益 3、功率/能量密度 6、额定功耗 7、转换效率 8、光斑大小 9、线宽 10、激光器的谱线宽度。 11、激光器的相对强度噪声RIN。 12、激光器的线性范围。 13、带内平坦度 14、激光器的温度特性 15、激光器的交…

基于PHP+MySQL信息技术学习网站设计与实现

智多在线网络学习平台为学习各种技术查看资料的用户提供一个准确、最新的技术与相关文档&#xff0c;浏览目前流行教学的新闻&#xff0c;提出技术上遇到的难点及问题&#xff0c;帮助其他用户回答所提出的问题&#xff0c;上传想要分享的资源&#xff0c;下载要获取的相关技术…

Spirng MVC——获取参数详解

文章目录1. 什么是 Spirng MVC1.1 MVC 定义1.2 MVC 和 Spring MVC 的关系2. 创建Spring MVC 项目3. Spring MVC 学习目标3.1 实现用户和程序的映射方法1&#xff1a;RequestMapping("/xxx")方法2&#xff1a;使用 POSTMapping("/xxx")方法3&#xff1a;使用…

Kali生成windows木马程序

目录 一、生成windows执行木马程序 二、进入msfconsole进行监听目标上线 三、目标运行木马和后渗透 四、问题 Meterpreter session 2 closed. Reason: Died 一、生成windows执行木马程序 -p windows/x64/meterpreter/reverse_tcp //载入64位payload攻击载荷&#xff0c…