Kotlin高仿微信-第37篇-拍照
创始人
2024-03-13 19:38:19
0

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

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

效果图:

实现代码:




/*** Author : wangning* Email : maoning20080809@163.com* Date : 2022/5/23 22:01* Description : 拍照*/
class CameraFragment : BaseDataBindingFragment(){override fun getLayoutRes() = R.layout.wc_svideo_cameraprivate lateinit var outputDirectory: Fileprivate lateinit var videoCapture: VideoCaptureprivate var activeRecording: ActiveRecording? = nullprivate lateinit var recordingState: VideoRecordEventprivate var audioEnabled = trueprivate val mainThreadExecutor by lazy { ContextCompat.getMainExecutor(requireContext()) }private var isBack = trueprivate var imageCapture: ImageCapture? = nullprivate lateinit var cameraExecutor: ExecutorServiceprivate val REQ_CAMREA_CODE = 101val EXTENSION_WHITELIST = arrayOf("JPG")var enterType = 0enum 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, 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.rotationval cameraProvider: ProcessCameraProvider = ProcessCameraProvider.getInstance(requireContext()).await()val cameraSelector = if (isBack) CameraSelector.DEFAULT_BACK_CAMERA else CameraSelector.DEFAULT_FRONT_CAMERAval 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 { event ->if (event !is VideoRecordEvent.Status) recordingState = eventupdateUI(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+ APIif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// Display flash animation to indicate that photo was capturedcontainer.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 = !isBacklifecycleScope.launch {bindCameraUseCases()}}private fun changeFlashMode() {when (imageCapture?.flashMode) {ImageCapture.FLASH_MODE_AUTO -> {imageCapture?.flashMode = ImageCapture.FLASH_MODE_ONiv_torch.setImageResource(R.drawable.icon_flash_always_on)}ImageCapture.FLASH_MODE_ON -> {imageCapture?.flashMode = ImageCapture.FLASH_MODE_OFFiv_torch.setImageResource(R.drawable.icon_flash_always_off)}ImageCapture.FLASH_MODE_OFF -> {imageCapture?.flashMode = ImageCapture.FLASH_MODE_AUTOiv_torch.setImageResource(R.drawable.icon_flash_auto)}else -> Unit}}@SuppressLint("ClickableViewAccessibility", "MissingPermission")private fun initializeUI() {enterType = arguments?.getInt(TYPE_ENTER) as IntlifecycleScope.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 = audioEnabledaudio_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) = Unitoverride fun onRecordFinishedListener() {if (activeRecording == null || recordingState is VideoRecordEvent.Finalize) returnval recording = activeRecordingif (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.recordingStatsval size = stats.numBytesRecorded / 1000val 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 = textTagUtils.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.VISIBLEaudio_selection.visibility = View.VISIBLE}UiState.RECORDING -> {btn_switch_camera.visibility = View.INVISIBLEaudio_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 = falseaudio_selection.isChecked = audioEnabled}private fun showVideo(event: VideoRecordEvent) {TagUtils.d("0小视频路径:showVideo ")if (event !is VideoRecordEvent.Finalize) returnlifecycleScope.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.simpleNameprivate const val FILENAME = "yyyyMMddHHmmss"private const val VIDEO_EXTENSION = ".mp4"private const val PHOTO_EXTENSION = ".jpg"private const val IMMERSIVE_FLAG_TIMEOUT = 500Lconst val ANIMATION_FAST_MILLIS = 50Lconst 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.applicationContextval 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 = 1private var mBigCirclePaint: Paint? = nullprivate var mSmallCirclePaint: Paint? = nullprivate var mProgressCirclePaint: Paint? = nullprivate var mHeight //当前View的高= 0private var mWidth //当前View的宽= 0private var mInitBitRadius = 0fprivate var mInitSmallRadius = 0fprivate var mBigRadius = 0fprivate var mSmallRadius = 0fprivate var mStartTime: Long = 0private var mEndTime: Long = 0private var isRecording //录制状态= falseprivate var isMaxTime //达到最大录制时间= falseprivate var mCurrentProgress //当前进度= 0fprivate val mLongClickTime: Long = 500 //长按最短时间(毫秒),private var mTime = 15 //录制最大时间sprivate var mMinTime = 3 //录制最短时间private var mProgressColor //进度条颜色= 0private var mProgressW = 18f //圆环宽度//当前手指处于按压状态private var isPressed2 = false//圆弧进度变化private var mProgressAni : ValueAnimator? = nullprivate 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.75fmInitBitRadius = mBigRadiusmSmallRadius = mBigRadius * 0.75fmInitSmallRadius = 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 = mProgressWmProgressCirclePaint!!.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 = truemStartTime = System.currentTimeMillis()val mMessage = Message.obtain()mMessage.what = WHAT_LONG_CLICKmHandler.sendMessageDelayed(mMessage, mLongClickTime)}MotionEvent.ACTION_UP -> {isPressed2 = falseisRecording = falsemEndTime = 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 = 150bigObjAni.addUpdateListener { animation: ValueAnimator ->mBigRadius = animation.animatedValue as Floatinvalidate()}val smallObjAni = ValueAnimator.ofFloat(smallStart, smallEnd)smallObjAni.duration = 150smallObjAni.addUpdateListener { animation: ValueAnimator ->mSmallRadius = animation.animatedValue as Floatinvalidate()}bigObjAni.start()smallObjAni.start()smallObjAni.addListener(object : Animator.AnimatorListener {override fun onAnimationStart(animation: Animator) {isRecording = false}override fun onAnimationEnd(animation: Animator) {//开始绘制圆形进度if (isPressed2) {isRecording = trueisMaxTime = falsestartProgressAnimation()}}override fun onAnimationCancel(animation: Animator) {}override fun onAnimationRepeat(animation: Animator) {}})}private fun startProgressAnimation() {mProgressAni!!.start()mProgressAni!!.addUpdateListener { animation: ValueAnimator ->mCurrentProgress = animation.animatedValue as Floatinvalidate()}mProgressAni!!.addListener(object : Animator.AnimatorListener {override fun onAnimationStart(animation: Animator) {}override fun onAnimationEnd(animation: Animator) {//录制动画结束时,即为录制全部完成if (onLongClickListener2 != null && isPressed2) {isPressed2 = falseisMaxTime = trueonLongClickListener2?.onRecordFinishedListener()startAnimation(mBigRadius, mInitBitRadius, mSmallRadius, mInitSmallRadius)//影藏进度进度条mCurrentProgress = 0finvalidate()}}override fun onAnimationCancel(animation: Animator) {}override fun onAnimationRepeat(animation: Animator) {}})}interface OnLongClickListener {fun onLongClick()//未达到最小录制时间fun onNoMinRecord(currentTime: Int)//录制完成fun onRecordFinishedListener()}var onLongClickListener2: OnLongClickListener? = nullfun setOnLongClickListener(onLongClickListener: OnLongClickListener?) {this.onLongClickListener2 = onLongClickListener}interface OnClickListener {fun onClick()}var onClickListener2: OnClickListener? = nullfun setOnClickListener(onClickListener: OnClickListener) {this.onClickListener2 = onClickListener}}

相关内容

热门资讯

VITURE与XREAL专利纠... 文/VR陀螺 12月圣诞假期前夕,一则有关VITURE和XREAL的专利纠纷,将两家AR头部厂商卷入...
*ST奥维及控股子公司新增诉讼... 12月25日,*ST奥维(002231)发布公告,截至本公告披露日,公司及控股子公司在连续12个月内...
“十四五”时期临沂市深化医保制... 大众网记者王萍 临沂报道 12月25日,临沂市人民政府新闻办公室召开临沂市“高质量完成‘十四五’规...
平顶山学院:一堂聚焦矛盾调解的... 为深化法治校园建设,提升师生法治意识与基层治理认知,12月23日下午,平顶山市公安局新华分局“老贺调...
原创 6... 在充满变数的时代浪潮中,历史的阴影始终笼罩着东亚大地。日前,日本解密的6800多页外交档案犹如一把尖...
浙江出台首部海洋经济省级综合性... 记者12月25日从浙江省海洋经济发展厅获悉,近日浙江省十四届人大常委会第二十一次会议审议并通过《浙江...
比亚迪起诉自媒体“龙哥讲电车”... IT之家 12 月 25 日消息,今日比亚迪法务部官微发文称,近期,就比亚迪起诉“龙哥讲电车”、“满...
为解燃眉之急,珠海法官将三地6... “我们的账户解冻了,公司有救了!” 拿到账户解封通知的那一刻,某建筑公司负责人李某悬了几个月的心终于...
泽大律师事务所律师马恺浓:快手... 12月22日晚间,快手平台突发大规模内容安全事件,多个直播间短时间内涌入大量露骨色情内容,违规内容传...
江苏首部民营经济专项法规,明年... 新悉,《南通市人民代表大会常务委员会关于促进民营经济高质量发展的决定》将于2026年1月1日起正式施...