fix: 完善消息详情界面
This commit is contained in:
commit
cf712a6b86
@ -83,14 +83,21 @@ dependencies {
|
||||
implementation 'com.google.code.gson:gson:2.10'
|
||||
implementation 'com.yanzhenjie.recyclerview:x:1.3.2'
|
||||
|
||||
// 动态权限申请 https://github.com/permissions-dispatcher/PermissionsDispatcher
|
||||
implementation "com.github.permissions-dispatcher:permissionsdispatcher:4.9.2"
|
||||
annotationProcessor "com.github.permissions-dispatcher:permissionsdispatcher-processor:4.9.2"
|
||||
// 权限请求框架:https://github.com/getActivity/XXPermissions
|
||||
implementation 'com.github.getActivity:XXPermissions:16.5'
|
||||
|
||||
// 相机库 https://natario1.github.io/CameraView/about/getting-started
|
||||
implementation("com.otaliastudios:cameraview:2.7.2")
|
||||
// 图片压缩算法 https://github.com/Curzibn/Luban
|
||||
implementation 'top.zibin:Luban:1.1.8'
|
||||
// Android工具类库 https://github.com/l123456789jy/Lazy
|
||||
implementation 'com.github.lazylibrary:lazylibrary:1.0.2'
|
||||
// Android工具类库 https://github.com/gycold/EasyAndroid
|
||||
implementation 'io.github.gycold:easyandroid:2.0.7'
|
||||
// 日志工具 https://github.com/elvishew/xLog/blob/master/README_ZH.md
|
||||
implementation 'com.elvishew:xlog:1.10.1'
|
||||
//加载图片的依赖包
|
||||
implementation ("com.github.bumptech.glide:glide:4.11.0") {
|
||||
exclude group: "com.android.support"
|
||||
}
|
||||
// 显示错误提示 https://github.com/nhaarman/supertooltips
|
||||
implementation 'com.nhaarman.supertooltips:library:3.0.0'
|
||||
}
|
@ -1,13 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--拍照-->
|
||||
<uses-permission android:name="android.permission.CAMERA" android:required="false"/>
|
||||
<!--网络请求-->
|
||||
<uses-permission android:name="android.permission.INTERNET" android:required="false"/>
|
||||
<!--录音-->
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" android:required="false"/>
|
||||
<!--读写文件-->
|
||||
<!-- 拍照 -->
|
||||
<uses-permission
|
||||
android:name="android.permission.CAMERA"
|
||||
android:required="false" /> <!-- 网络请求 -->
|
||||
<uses-permission
|
||||
android:name="android.permission.INTERNET"
|
||||
android:required="false" /> <!-- 录音 -->
|
||||
<uses-permission
|
||||
android:name="android.permission.RECORD_AUDIO"
|
||||
android:required="false" /> <!-- 读写文件 -->
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
@ -18,11 +21,21 @@
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.NavinfoVolvo"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".ui.message.MessageActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/title_activity_second"
|
||||
android:theme="@style/Theme.NavinfoVolvo.NoActionBar">
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.navinfo.volvo.MainActivity"
|
||||
android:exported="true"
|
||||
@ -32,11 +45,12 @@
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="" />
|
||||
</activity>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="" />
|
||||
<meta-data android:name="ScopedStorage" android:value="true" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -1,24 +1,42 @@
|
||||
package com.navinfo.volvo
|
||||
|
||||
import android.Manifest
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.ui.AppBarConfiguration
|
||||
import androidx.navigation.ui.setupActionBarWithNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import com.easytools.tools.FileUtils
|
||||
import com.elvishew.xlog.LogConfiguration
|
||||
import com.elvishew.xlog.LogLevel
|
||||
import com.elvishew.xlog.XLog
|
||||
import com.elvishew.xlog.interceptor.BlacklistTagsFilterInterceptor
|
||||
import com.elvishew.xlog.printer.AndroidPrinter
|
||||
import com.elvishew.xlog.printer.ConsolePrinter
|
||||
import com.elvishew.xlog.printer.Printer
|
||||
import com.elvishew.xlog.printer.file.FilePrinter
|
||||
import com.elvishew.xlog.printer.file.backup.NeverBackupStrategy
|
||||
import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.hjq.permissions.OnPermissionCallback
|
||||
import com.hjq.permissions.Permission
|
||||
import com.hjq.permissions.XXPermissions
|
||||
import com.navinfo.volvo.databinding.ActivityMainBinding
|
||||
import permissions.dispatcher.*
|
||||
import com.navinfo.volvo.ui.message.MessageActivity
|
||||
import com.navinfo.volvo.utils.SystemConstant
|
||||
|
||||
|
||||
//@RuntimePermissions
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
|
||||
@NeedsPermission(Manifest.permission.CAMERA)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
@ -34,12 +52,101 @@ class MainActivity : AppCompatActivity() {
|
||||
R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications, R.id.navigation_obtain_message
|
||||
)
|
||||
)
|
||||
// setupActionBarWithNavController(navController, appBarConfiguration)
|
||||
setupActionBarWithNavController(navController, appBarConfiguration)
|
||||
navView.setupWithNavController(navController)
|
||||
findViewById<View>(R.id.fab_new_message).apply {
|
||||
this.setOnClickListener {
|
||||
// 跳转到Message的Fragment
|
||||
val messageIntent:Intent = Intent(this@MainActivity, MessageActivity::class.java)
|
||||
startActivity(messageIntent)
|
||||
// findNavController(R.id.layer_main_child_fragment).navigate(R.id.navigation_obtain_message)
|
||||
}
|
||||
}
|
||||
|
||||
XXPermissions.with(this)
|
||||
// 申请单个权限
|
||||
.permission(Permission.WRITE_EXTERNAL_STORAGE)
|
||||
.permission(Permission.READ_EXTERNAL_STORAGE)
|
||||
// 设置权限请求拦截器(局部设置)
|
||||
//.interceptor(new PermissionInterceptor())
|
||||
// 设置不触发错误检测机制(局部设置)
|
||||
//.unchecked()
|
||||
.request(object : OnPermissionCallback {
|
||||
|
||||
override fun onGranted(permissions: MutableList<String>, all: Boolean) {
|
||||
if (!all) {
|
||||
Toast.makeText(this@MainActivity, "获取部分权限成功,但部分权限未正常授予", Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
// 在SD卡创建项目目录
|
||||
createRootFolder()
|
||||
}
|
||||
|
||||
override fun onDenied(permissions: MutableList<String>, never: Boolean) {
|
||||
if (never) {
|
||||
Toast.makeText(this@MainActivity, "永久拒绝授权,请手动授权文件读写权限", Toast.LENGTH_SHORT).show()
|
||||
// 如果是被永久拒绝就跳转到应用权限系统设置页面
|
||||
XXPermissions.startPermissionActivity(this@MainActivity, permissions)
|
||||
} else {
|
||||
onSDCardDenied()
|
||||
showRationaleForSDCard(permissions)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@OnShowRationale(Manifest.permission.CAMERA)
|
||||
fun showRationaleForCamera(request: PermissionRequest) {
|
||||
// @NeedsPermission(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
|
||||
fun createRootFolder() {
|
||||
// 在SD卡创建项目目录
|
||||
val sdCardPath = getExternalFilesDir(null)
|
||||
// SystemConstant.ROOT_PATH = "${sdCardPath}/${SystemConstant.FolderName}"
|
||||
SystemConstant.ROOT_PATH = sdCardPath!!.absolutePath
|
||||
SystemConstant.LogFolder = "${sdCardPath!!.absolutePath}/log"
|
||||
FileUtils.createOrExistsDir(SystemConstant.LogFolder)
|
||||
SystemConstant.CameraFolder = "${sdCardPath!!.absolutePath}/camera"
|
||||
FileUtils.createOrExistsDir(SystemConstant.CameraFolder)
|
||||
SystemConstant.SoundFolder = "${sdCardPath!!.absolutePath}/sound"
|
||||
FileUtils.createOrExistsDir(SystemConstant.SoundFolder)
|
||||
xLogInit(SystemConstant.LogFolder)
|
||||
}
|
||||
|
||||
fun xLogInit(logFolder: String) {
|
||||
val config = LogConfiguration.Builder()
|
||||
.logLevel(
|
||||
if (BuildConfig.DEBUG)
|
||||
LogLevel.ALL // 指定日志级别,低于该级别的日志将不会被打印,默认为 LogLevel.ALL
|
||||
else LogLevel.NONE
|
||||
)
|
||||
.tag("Volvo") // 指定 TAG,默认为 "X-LOG"
|
||||
.enableThreadInfo() // 允许打印线程信息,默认禁止
|
||||
.enableStackTrace(2) // 允许打印深度为 2 的调用栈信息,默认禁止
|
||||
.enableBorder() // 允许打印日志边框,默认禁止
|
||||
.addInterceptor(
|
||||
BlacklistTagsFilterInterceptor( // 添加黑名单 TAG 过滤器
|
||||
"blacklist1", "blacklist2", "blacklist3"
|
||||
)
|
||||
)
|
||||
.build()
|
||||
|
||||
val androidPrinter: Printer = AndroidPrinter(true) // 通过 android.util.Log 打印日志的打印器
|
||||
|
||||
val consolePrinter: Printer = ConsolePrinter() // 通过 System.out 打印日志到控制台的打印器
|
||||
|
||||
val filePrinter: Printer = FilePrinter.Builder("${SystemConstant.ROOT_PATH}/Logs") // 指定保存日志文件的路径
|
||||
.fileNameGenerator(DateFileNameGenerator()) // 指定日志文件名生成器,默认为 ChangelessFileNameGenerator("log")
|
||||
.backupStrategy(NeverBackupStrategy()) // 指定日志文件备份策略,默认为 FileSizeBackupStrategy(1024 * 1024)
|
||||
.build()
|
||||
|
||||
XLog.init( // 初始化 XLog
|
||||
config, // 指定日志配置,如果不指定,会默认使用 new LogConfiguration.Builder().build()
|
||||
androidPrinter, // 添加任意多的打印器。如果没有添加任何打印器,会默认使用 AndroidPrinter(Android)/ConsolePrinter(java)
|
||||
consolePrinter,
|
||||
filePrinter
|
||||
)
|
||||
}
|
||||
|
||||
// @OnShowRationale(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
|
||||
fun showRationaleForSDCard(permissions: MutableList<String>) {
|
||||
// showRationaleDialog(R.string.permission_camera_rationale, request)
|
||||
// Toast.makeText(context, "当前操作需要您授权相机权限!", Toast.LENGTH_SHORT).show()
|
||||
MaterialAlertDialogBuilder(this)
|
||||
@ -47,19 +154,13 @@ class MainActivity : AppCompatActivity() {
|
||||
.setMessage("当前操作需要您授权读写SD卡权限!")
|
||||
.setPositiveButton("确定", DialogInterface.OnClickListener { dialogInterface, i ->
|
||||
dialogInterface.dismiss()
|
||||
// 在SD卡创建项目目录
|
||||
|
||||
XXPermissions.startPermissionActivity(this@MainActivity, permissions)
|
||||
})
|
||||
.show()
|
||||
}
|
||||
|
||||
@OnPermissionDenied(Manifest.permission.CAMERA)
|
||||
fun onCameraDenied() {
|
||||
// @OnPermissionDenied(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
|
||||
fun onSDCardDenied() {
|
||||
Toast.makeText(this, "当前操作需要您授权读写SD卡权限!", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
@OnNeverAskAgain(Manifest.permission.CAMERA)
|
||||
fun onCameraNeverAskAgain() {
|
||||
Toast.makeText(this, "您已永久拒绝授权读写SD卡权限!", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package com.navinfo.volvo
|
||||
|
||||
import android.media.MediaRecorder
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.easytools.tools.DateUtils
|
||||
import com.elvishew.xlog.XLog
|
||||
import com.navinfo.volvo.utils.SystemConstant
|
||||
import java.util.*
|
||||
|
||||
class RecorderLifecycleObserver: DefaultLifecycleObserver {
|
||||
private var mediaRecorder: MediaRecorder? = null
|
||||
private lateinit var recorderAudioPath: String
|
||||
|
||||
fun initAndStartRecorder() {
|
||||
recorderAudioPath = "${SystemConstant.SoundFolder}/${DateUtils.date2Str(Date(), DateUtils.FORMAT_YMDHMS)}.m4a"
|
||||
mediaRecorder = MediaRecorder().apply {
|
||||
setAudioSource(MediaRecorder.AudioSource.MIC)
|
||||
setOutputFormat(MediaRecorder.OutputFormat.DEFAULT)
|
||||
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
|
||||
// 开始录音
|
||||
setOutputFile(recorderAudioPath)
|
||||
try {
|
||||
prepare()
|
||||
} catch (e: Exception) {
|
||||
XLog.e("prepare() failed")
|
||||
}
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
fun stopAndReleaseRecorder(): String {
|
||||
mediaRecorder?.stop()
|
||||
mediaRecorder?.release()
|
||||
mediaRecorder = null
|
||||
return recorderAudioPath
|
||||
}
|
||||
|
||||
// override fun onCreate(owner: LifecycleOwner) {
|
||||
// super.onCreate(owner)
|
||||
//
|
||||
// }
|
||||
//
|
||||
// override fun onStart(owner: LifecycleOwner) {
|
||||
// super.onStart(owner)
|
||||
// }
|
||||
//
|
||||
// override fun onResume(owner: LifecycleOwner) {
|
||||
// super.onResume(owner)
|
||||
// }
|
||||
|
||||
override fun onPause(owner: LifecycleOwner) {
|
||||
super.onPause(owner)
|
||||
mediaRecorder?.pause()
|
||||
}
|
||||
|
||||
override fun onStop(owner: LifecycleOwner) {
|
||||
super.onStop(owner)
|
||||
mediaRecorder?.stop()
|
||||
}
|
||||
|
||||
override fun onDestroy(owner: LifecycleOwner) {
|
||||
super.onDestroy(owner)
|
||||
mediaRecorder?.release()
|
||||
}
|
||||
}
|
@ -7,9 +7,17 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.get
|
||||
import androidx.navigation.Navigation
|
||||
import com.easytools.tools.DateUtils
|
||||
import com.easytools.tools.FileUtils
|
||||
import com.elvishew.xlog.XLog
|
||||
import com.navinfo.volvo.databinding.FragmentCameraBinding
|
||||
import com.navinfo.volvo.ui.message.ObtainMessageViewModel
|
||||
import com.navinfo.volvo.utils.SystemConstant
|
||||
import com.otaliastudios.cameraview.CameraListener
|
||||
import com.otaliastudios.cameraview.CameraView
|
||||
import com.otaliastudios.cameraview.FileCallback
|
||||
import com.otaliastudios.cameraview.PictureResult
|
||||
import top.zibin.luban.Luban
|
||||
import top.zibin.luban.OnCompressListener
|
||||
@ -24,6 +32,9 @@ class CameraFragment : Fragment() {
|
||||
// This property is only valid between onCreateView and
|
||||
// onDestroyView.
|
||||
private val binding get() = _binding!!
|
||||
// private val exportFolderPath by lazy {
|
||||
// "${SystemConstant.ROOT_PATH}/exportPic/"
|
||||
// }
|
||||
|
||||
private val cameraLifeCycleObserver: CameraLifeCycleObserver by lazy {
|
||||
CameraLifeCycleObserver()
|
||||
@ -34,7 +45,7 @@ class CameraFragment : Fragment() {
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
lifecycle.addObserver(cameraLifeCycleObserver)
|
||||
// lifecycle.addObserver(cameraLifeCycleObserver)
|
||||
|
||||
val cameraViewModel =
|
||||
ViewModelProvider(this).get(CameraViewModel::class.java)
|
||||
@ -47,41 +58,55 @@ class CameraFragment : Fragment() {
|
||||
cameraView.addCameraListener(object:CameraListener() { // 添加拍照回调
|
||||
override fun onPictureTaken(result: PictureResult) {
|
||||
super.onPictureTaken(result)
|
||||
result.toFile()
|
||||
// 压缩图片文件
|
||||
Luban.with(context)
|
||||
.load<Any>(photos)
|
||||
.ignoreBy(100)
|
||||
.setTargetDir(getPath())
|
||||
.filter { path ->
|
||||
!(TextUtils.isEmpty(path) || path.lowercase(Locale.getDefault())
|
||||
.endsWith(".gif"))
|
||||
// FileUtils.createOrExistsDir(cameraFolderPath)
|
||||
val resultFile = File("${SystemConstant.CameraFolder}/${DateUtils.date2Str(Date(), DateUtils.FORMAT_YMDHMS)}.jpg")
|
||||
result.toFile(resultFile, object: FileCallback {
|
||||
override fun onFileReady(resultFile: File?) {
|
||||
// 压缩图片文件
|
||||
Luban.with(context)
|
||||
.load<Any>(mutableListOf(resultFile) as List<Any>?)
|
||||
.ignoreBy(200)
|
||||
.setTargetDir("${SystemConstant.CameraFolder}")
|
||||
.filter { path ->
|
||||
!(TextUtils.isEmpty(path) || path.lowercase(Locale.getDefault())
|
||||
.endsWith(".gif"))
|
||||
}
|
||||
.setCompressListener(object : OnCompressListener {
|
||||
override fun onStart() {
|
||||
XLog.d("开始压缩图片")
|
||||
}
|
||||
|
||||
override fun onSuccess(file: File?) {
|
||||
XLog.d("压缩图片成功:${file?.absolutePath}")
|
||||
// 删除源文件
|
||||
if (!resultFile!!.absolutePath.equals(file!!.absolutePath)) {
|
||||
resultFile!!.delete()
|
||||
}
|
||||
// 跳转回原Fragment,展示拍摄的照片
|
||||
ViewModelProvider(requireActivity()).get(ObtainMessageViewModel::class.java).updateMessagePic(file!!.absolutePath)
|
||||
// 跳转回原界面
|
||||
Navigation.findNavController(root).popBackStack()
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
XLog.d("压缩图片失败:${e.message}")
|
||||
}
|
||||
}).launch()
|
||||
}
|
||||
.setCompressListener(object : OnCompressListener {
|
||||
override fun onStart() {
|
||||
// TODO 压缩开始前调用,可以在方法内启动 loading UI
|
||||
}
|
||||
|
||||
override fun onSuccess(file: File?) {
|
||||
// TODO 压缩成功后调用,返回压缩后的图片文件
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
// TODO 当压缩过程出现问题时调用
|
||||
}
|
||||
}).launch()
|
||||
})
|
||||
}
|
||||
})
|
||||
// 点击拍照
|
||||
binding.imgStartCamera.setOnClickListener {
|
||||
cameraView.takePicture()
|
||||
}
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
lifecycle.removeObserver(cameraLifeCycleObserver)
|
||||
// lifecycle.removeObserver(cameraLifeCycleObserver)
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.navinfo.volvo.ui.message
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.navigation.Navigation
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.ui.AppBarConfiguration
|
||||
import androidx.navigation.ui.navigateUp
|
||||
import androidx.navigation.ui.setupActionBarWithNavController
|
||||
import com.navinfo.volvo.R
|
||||
import com.navinfo.volvo.databinding.ActivityMessageBinding
|
||||
|
||||
class MessageActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var appBarConfiguration: AppBarConfiguration
|
||||
private lateinit var binding: ActivityMessageBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivityMessageBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(binding.toolbar)
|
||||
binding.toolbar.setOnClickListener {
|
||||
Navigation.findNavController(it).popBackStack()
|
||||
}
|
||||
|
||||
val navController = findNavController(R.id.nav_host_fragment_message)
|
||||
appBarConfiguration = AppBarConfiguration(navController.graph)
|
||||
setupActionBarWithNavController(navController, appBarConfiguration)
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
val navController = findNavController(R.id.nav_host_fragment_message)
|
||||
return navController.navigateUp(appBarConfiguration)
|
||||
|| super.onSupportNavigateUp()
|
||||
}
|
||||
}
|
@ -1,27 +1,56 @@
|
||||
package com.navinfo.volvo.ui.message
|
||||
|
||||
import android.Manifest
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.View.*
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView
|
||||
import android.widget.AdapterView.OnItemSelectedListener
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Toast
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.navigation.Navigation
|
||||
import com.easytools.tools.DateUtils
|
||||
import com.easytools.tools.ToastUtils
|
||||
import com.elvishew.xlog.XLog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.gredicer.datetimepicker.DateTimePickerFragment
|
||||
import com.hjq.permissions.OnPermissionCallback
|
||||
import com.hjq.permissions.Permission
|
||||
import com.hjq.permissions.XXPermissions
|
||||
import com.navinfo.volvo.R
|
||||
import com.navinfo.volvo.RecorderLifecycleObserver
|
||||
import com.navinfo.volvo.databinding.FragmentObtainMessageBinding
|
||||
import com.navinfo.volvo.db.dao.entity.AttachmentType
|
||||
import com.navinfo.volvo.db.dao.entity.Message
|
||||
import com.navinfo.volvo.ui.markRequiredInRed
|
||||
import permissions.dispatcher.*
|
||||
import com.navinfo.volvo.utils.EasyMediaFile
|
||||
import com.navinfo.volvo.utils.SystemConstant
|
||||
import com.nhaarman.supertooltips.ToolTip
|
||||
import top.zibin.luban.Luban
|
||||
import top.zibin.luban.OnCompressListener
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
|
||||
//@RuntimePermissions
|
||||
class ObtainMessageFragment: Fragment() {
|
||||
private var _binding: FragmentObtainMessageBinding? = null
|
||||
private val obtainMessageViewModel by lazy {
|
||||
ViewModelProvider(requireActivity()).get(ObtainMessageViewModel::class.java)
|
||||
}
|
||||
private val photoHelper by lazy {
|
||||
EasyMediaFile().setCrop(true)
|
||||
}
|
||||
private val recorderLifecycleObserver by lazy {
|
||||
RecorderLifecycleObserver()
|
||||
}
|
||||
|
||||
// This property is only valid between onCreateView and
|
||||
// onDestroyView.
|
||||
@ -35,16 +64,43 @@ class ObtainMessageFragment: Fragment() {
|
||||
_binding = FragmentObtainMessageBinding.inflate(inflater, container, false)
|
||||
val root: View = binding.root
|
||||
|
||||
obtainMessageViewModel.setCurrentMessage(Message())
|
||||
|
||||
obtainMessageViewModel?.getMessageLiveData()?.observe(
|
||||
viewLifecycleOwner, Observer {
|
||||
// 初始化界面显示内容
|
||||
if(it.title!=null)
|
||||
if(it.title?.isNotEmpty() == true)
|
||||
binding.tvMessageTitle?.setText(it.title)
|
||||
if (it.sendDate!=null) {
|
||||
binding.btnSendTime.setText(it.sendDate)
|
||||
if (it.sendDate?.isNotEmpty() == true) {
|
||||
binding.btnSendTime.text = it.sendDate
|
||||
}
|
||||
var hasPhoto = false
|
||||
var hasAudio = false
|
||||
if (it.attachment.isNotEmpty()) {
|
||||
// 展示照片文件或录音文件
|
||||
for (attachment in it.attachment) {
|
||||
if (attachment.attachmentType == AttachmentType.PIC) {
|
||||
// Glide.with(context!!)
|
||||
// .asBitmap().fitCenter()
|
||||
// .load(attachment.pathUrl)
|
||||
// .into(binding.imgMessageAttachment)
|
||||
// 显示名称
|
||||
binding.tvPhotoName.text = attachment.pathUrl.replace("\\", "/").substringAfterLast("/")
|
||||
hasPhoto = true
|
||||
}
|
||||
if (attachment.attachmentType == AttachmentType.AUDIO) {
|
||||
binding.tvAudioName.text = attachment.pathUrl.replace("\\", "/").substringAfterLast("/")
|
||||
hasAudio = true
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.layerPhotoResult.visibility = if (hasPhoto) VISIBLE else GONE
|
||||
binding.layerGetPhoto.visibility = if (hasPhoto) GONE else VISIBLE
|
||||
binding.layerAudioResult.visibility = if (hasAudio) VISIBLE else GONE
|
||||
binding.layerGetAudio.visibility = if (hasAudio) GONE else VISIBLE
|
||||
}
|
||||
)
|
||||
lifecycle.addObserver(recorderLifecycleObserver)
|
||||
initView()
|
||||
return root
|
||||
}
|
||||
@ -52,12 +108,42 @@ class ObtainMessageFragment: Fragment() {
|
||||
fun initView() {
|
||||
// 设置问候信息提示的红色星号
|
||||
binding.tiLayoutTitle.markRequiredInRed()
|
||||
binding.tvMessageTitle.addTextChangedListener {
|
||||
obtainMessageViewModel.updateMessageTitle(it.toString())
|
||||
}
|
||||
|
||||
binding.imgPhotoDelete.setOnClickListener {
|
||||
obtainMessageViewModel.updateMessagePic(null)
|
||||
}
|
||||
|
||||
binding.imgAudioDelete.setOnClickListener {
|
||||
obtainMessageViewModel.updateMessageAudio(null)
|
||||
}
|
||||
|
||||
val sendToArray = mutableListOf<String>("绑定车辆1(LYVXFEFEXNL754427)")
|
||||
binding.edtSendTo.adapter = ArrayAdapter<String>(context!!,
|
||||
android.R.layout.simple_dropdown_item_1line, android.R.id.text1, sendToArray)
|
||||
binding.edtSendTo.onItemSelectedListener = object: OnItemSelectedListener {
|
||||
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
|
||||
obtainMessageViewModel.getMessageLiveData().value?.toId = sendToArray[p2]
|
||||
}
|
||||
|
||||
override fun onNothingSelected(p0: AdapterView<*>?) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 设置点击按钮选择发送时间
|
||||
binding.btnSendTime.setOnClickListener {
|
||||
val dialog = DateTimePickerFragment.newInstance().mode(0)
|
||||
dialog.listener = object : DateTimePickerFragment.OnClickListener {
|
||||
override fun onClickListener(selectTime: String) {
|
||||
obtainMessageViewModel.updateMessageSendTime(selectTime)
|
||||
val sendDate = DateUtils.str2Date(selectTime, "yyyy-MM-dd HH:mm")
|
||||
if (sendDate <= Date()) {
|
||||
obtainMessageViewModel.updateMessageSendTime("现在")
|
||||
} else {
|
||||
obtainMessageViewModel.updateMessageSendTime(selectTime)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -67,7 +153,195 @@ class ObtainMessageFragment: Fragment() {
|
||||
// 点击按钮选择拍照
|
||||
binding.btnStartCamera.setOnClickListener {
|
||||
// 启动相机
|
||||
startCamera()
|
||||
XXPermissions.with(this)
|
||||
// 申请单个权限
|
||||
.permission(Permission.CAMERA)
|
||||
.request(object : OnPermissionCallback {
|
||||
|
||||
override fun onGranted(permissions: MutableList<String>, all: Boolean) {
|
||||
if (!all) {
|
||||
Toast.makeText(activity, "获取部分权限成功,但部分权限未正常授予", Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
// 开始启动拍照界面
|
||||
photoHelper.setCrop(true).takePhoto(activity!!)
|
||||
}
|
||||
|
||||
override fun onDenied(permissions: MutableList<String>, never: Boolean) {
|
||||
if (never) {
|
||||
Toast.makeText(activity, "永久拒绝授权,请手动授权拍照权限", Toast.LENGTH_SHORT).show()
|
||||
// 如果是被永久拒绝就跳转到应用权限系统设置页面
|
||||
XXPermissions.startPermissionActivity(context!!, permissions)
|
||||
} else {
|
||||
onCameraDenied()
|
||||
showRationaleForCamera(permissions)
|
||||
}
|
||||
}
|
||||
})
|
||||
// startCamera(it)
|
||||
}
|
||||
|
||||
binding.btnStartPhoto.setOnClickListener {
|
||||
photoHelper.setCrop(true).selectPhoto(activity!!)
|
||||
}
|
||||
|
||||
// 用户选择录音文件
|
||||
binding.btnSelectSound.setOnClickListener {
|
||||
photoHelper.setCrop(false).selectAudio(activity!!)
|
||||
}
|
||||
|
||||
// 开始录音
|
||||
binding.btnStartRecord.setOnClickListener {
|
||||
// 申请权限
|
||||
XXPermissions.with(this)
|
||||
// 申请单个权限
|
||||
.permission(Permission.RECORD_AUDIO)
|
||||
.request(object : OnPermissionCallback {
|
||||
override fun onGranted(permissions: MutableList<String>, all: Boolean) {
|
||||
if (!all) {
|
||||
Toast.makeText(activity, "获取部分权限成功,但部分权限未正常授予", Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
if (it.isSelected) {
|
||||
it.isSelected = false
|
||||
val recorderAudioPath = recorderLifecycleObserver.stopAndReleaseRecorder()
|
||||
if (File(recorderAudioPath).exists()) {
|
||||
obtainMessageViewModel.updateMessageAudio(recorderAudioPath)
|
||||
}
|
||||
} else{
|
||||
it.isSelected = true
|
||||
|
||||
recorderLifecycleObserver.initAndStartRecorder()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDenied(permissions: MutableList<String>, never: Boolean) {
|
||||
if (never) {
|
||||
Toast.makeText(activity, "永久拒绝授权,请手动授权拍照权限", Toast.LENGTH_SHORT).show()
|
||||
// 如果是被永久拒绝就跳转到应用权限系统设置页面
|
||||
XXPermissions.startPermissionActivity(context!!, permissions)
|
||||
} else {
|
||||
onCameraDenied()
|
||||
showRationaleForCamera(permissions)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取照片文件和音频文件
|
||||
photoHelper.setCallback {
|
||||
if (it.exists()) {
|
||||
val fileName = it.name.lowercase()
|
||||
if (fileName.endsWith(".jpg")||fileName.endsWith(".jpeg")||fileName.endsWith(".png")) {
|
||||
// 获取选中的图片,自动压缩图片质量
|
||||
// 压缩图片文件
|
||||
Luban.with(context)
|
||||
.load<Any>(mutableListOf(it) as List<Any>?)
|
||||
.ignoreBy(200)
|
||||
.setTargetDir("${SystemConstant.CameraFolder}")
|
||||
.filter { path ->
|
||||
!(TextUtils.isEmpty(path) || path.lowercase(Locale.getDefault())
|
||||
.endsWith(".gif"))
|
||||
}
|
||||
.setCompressListener(object : OnCompressListener {
|
||||
override fun onStart() {
|
||||
XLog.d("开始压缩图片/${it.absolutePath}")
|
||||
}
|
||||
|
||||
override fun onSuccess(file: File?) {
|
||||
XLog.d("压缩图片成功:${file?.absolutePath}")
|
||||
// 删除源文件
|
||||
if (!it.absolutePath.equals(file?.absolutePath)) {
|
||||
it?.delete()
|
||||
}
|
||||
// 跳转回原Fragment,展示拍摄的照片
|
||||
ViewModelProvider(requireActivity()).get(ObtainMessageViewModel::class.java).updateMessagePic(file!!.absolutePath)
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
XLog.d("压缩图片失败:${e.message}")
|
||||
}
|
||||
}).launch()
|
||||
} else if (fileName.endsWith(".mp3")||fileName.endsWith(".wav")||fileName.endsWith(".amr")||fileName.endsWith(".m4a")) {
|
||||
ToastUtils.showToast(it.absolutePath)
|
||||
obtainMessageViewModel.updateMessageAudio(it.absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
photoHelper.setError {
|
||||
ToastUtils.showToast(it.message)
|
||||
}
|
||||
|
||||
binding.btnObtainMessageBack.setOnClickListener {
|
||||
Navigation.findNavController(it).popBackStack()
|
||||
}
|
||||
|
||||
binding.btnObtainMessageConfirm.setOnClickListener {
|
||||
// 检查当前输入数据
|
||||
val messageData = obtainMessageViewModel.getMessageLiveData().value
|
||||
if (messageData?.title?.isEmpty() == true) {
|
||||
val toolTipRelativeLayout =
|
||||
binding.ttTitle
|
||||
val toolTip = ToolTip()
|
||||
.withText("请输入问候信息")
|
||||
.withColor(com.navinfo.volvo.R.color.purple_200)
|
||||
.withShadow()
|
||||
.withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
|
||||
toolTipRelativeLayout.showToolTipForView(toolTip, binding.tiLayoutTitle)
|
||||
}
|
||||
var hasPic = false
|
||||
var hasAudio = false
|
||||
for (attachment in messageData?.attachment!!) {
|
||||
if (attachment.attachmentType == AttachmentType.PIC) {
|
||||
hasPic = true
|
||||
}
|
||||
if (attachment.attachmentType == AttachmentType.AUDIO) {
|
||||
hasAudio = true
|
||||
}
|
||||
}
|
||||
if (!hasPic) {
|
||||
val toolTipRelativeLayout =
|
||||
binding.ttPic
|
||||
val toolTip = ToolTip()
|
||||
.withText("需要提供照片文件")
|
||||
.withColor(com.navinfo.volvo.R.color.purple_200)
|
||||
.withShadow()
|
||||
.withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
|
||||
toolTipRelativeLayout.showToolTipForView(toolTip, binding.tvUploadPic)
|
||||
}
|
||||
if (!hasAudio) {
|
||||
val toolTipRelativeLayout =
|
||||
binding.ttAudio
|
||||
val toolTip = ToolTip()
|
||||
.withText("需要提供音频文件")
|
||||
.withColor(com.navinfo.volvo.R.color.purple_200)
|
||||
.withShadow()
|
||||
.withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
|
||||
toolTipRelativeLayout.showToolTipForView(toolTip, binding.tvUploadPic)
|
||||
}
|
||||
|
||||
if (messageData?.fromId?.isEmpty()==true) {
|
||||
val toolTipRelativeLayout =
|
||||
binding.ttSendFrom
|
||||
val toolTip = ToolTip()
|
||||
.withText("请输入您的名称")
|
||||
.withColor(com.navinfo.volvo.R.color.purple_200)
|
||||
.withShadow()
|
||||
.withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
|
||||
toolTipRelativeLayout.showToolTipForView(toolTip, binding.edtSendFrom)
|
||||
}
|
||||
if (messageData?.toId?.isEmpty()==true) {
|
||||
val toolTipRelativeLayout =
|
||||
binding.ttSendTo
|
||||
val toolTip = ToolTip()
|
||||
.withText("请选择要发送的车辆")
|
||||
.withColor(com.navinfo.volvo.R.color.purple_200)
|
||||
.withShadow()
|
||||
.withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
|
||||
toolTipRelativeLayout.showToolTipForView(toolTip, binding.edtSendTo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,32 +350,44 @@ class ObtainMessageFragment: Fragment() {
|
||||
_binding = null
|
||||
}
|
||||
|
||||
@NeedsPermission(Manifest.permission.CAMERA)
|
||||
fun startCamera() {
|
||||
|
||||
fun startCamera(it: View) {
|
||||
Navigation.findNavController(binding.root).navigate(com.navinfo.volvo.R.id.nav_2_camera)
|
||||
}
|
||||
|
||||
@OnShowRationale(Manifest.permission.CAMERA)
|
||||
fun showRationaleForCamera(request: PermissionRequest) {
|
||||
fun showRationaleForCamera(permissions: MutableList<String>) {
|
||||
// showRationaleDialog(R.string.permission_camera_rationale, request)
|
||||
// Toast.makeText(context, "当前操作需要您授权相机权限!", Toast.LENGTH_SHORT).show()
|
||||
MaterialAlertDialogBuilder(context!!)
|
||||
.setTitle("提示")
|
||||
.setMessage("当前操作需要您授权相机权限!")
|
||||
.setMessage("当前操作需要您授权拍摄权限!")
|
||||
.setPositiveButton("确定", DialogInterface.OnClickListener { dialogInterface, i ->
|
||||
startCamera()
|
||||
dialogInterface.dismiss()
|
||||
})
|
||||
XXPermissions.startPermissionActivity(activity!!, permissions)
|
||||
})
|
||||
.show()
|
||||
}
|
||||
|
||||
@OnPermissionDenied(Manifest.permission.CAMERA)
|
||||
// @OnPermissionDenied(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
|
||||
fun onCameraDenied() {
|
||||
Toast.makeText(context, "当前操作需要您授权相机权限!", Toast.LENGTH_SHORT).show()
|
||||
ToastUtils.showToast("当前操作需要您授权拍摄权限!")
|
||||
}
|
||||
|
||||
@OnNeverAskAgain(Manifest.permission.CAMERA)
|
||||
fun onCameraNeverAskAgain() {
|
||||
Toast.makeText(context, "您已永久拒绝授权相机权限!", Toast.LENGTH_SHORT).show()
|
||||
fun showRationaleForRecorder(permissions: MutableList<String>) {
|
||||
MaterialAlertDialogBuilder(context!!)
|
||||
.setTitle("提示")
|
||||
.setMessage("当前操作需要您授权录音权限!")
|
||||
.setPositiveButton("确定", DialogInterface.OnClickListener { dialogInterface, i ->
|
||||
dialogInterface.dismiss()
|
||||
XXPermissions.startPermissionActivity(activity!!, permissions)
|
||||
})
|
||||
.show()
|
||||
}
|
||||
fun onRecorderDenied() {
|
||||
ToastUtils.showToast("当前操作需要您授权录音权限!")
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
lifecycle.removeObserver(recorderLifecycleObserver)
|
||||
}
|
||||
}
|
@ -4,8 +4,10 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.liveData
|
||||
import com.navinfo.volvo.db.dao.entity.Attachment
|
||||
import com.navinfo.volvo.db.dao.entity.Message
|
||||
import com.navinfo.volvo.db.dao.entity.AttachmentType
|
||||
import java.util.UUID
|
||||
|
||||
class ObtainMessageViewModel: ViewModel() {
|
||||
private val msgLiveData: MutableLiveData<Message> by lazy {
|
||||
@ -27,22 +29,41 @@ class ObtainMessageViewModel: ViewModel() {
|
||||
}
|
||||
|
||||
// 更新消息附件中的照片文件
|
||||
fun updateMessagePic(picUrl: String) {
|
||||
fun updateMessagePic(picUrl: String?) {
|
||||
var hasPic = false
|
||||
|
||||
for (attachment in this.msgLiveData.value!!.attachment) {
|
||||
if (attachment.attachmentType == AttachmentType.PIC) {
|
||||
attachment.pathUrl = picUrl
|
||||
if (picUrl==null||picUrl.isEmpty()) {
|
||||
this.msgLiveData.value!!.attachment.remove(attachment)
|
||||
} else {
|
||||
attachment.pathUrl = picUrl
|
||||
}
|
||||
hasPic = true
|
||||
}
|
||||
}
|
||||
if (!hasPic&&picUrl!=null) {
|
||||
this.msgLiveData.value!!.attachment.add(Attachment(UUID.randomUUID().toString(), picUrl, AttachmentType.PIC))
|
||||
}
|
||||
this.msgLiveData.postValue(this.msgLiveData.value)
|
||||
}
|
||||
|
||||
// 更新消息附件中的录音文件
|
||||
fun updateMessageAudio(audioUrl: String) {
|
||||
fun updateMessageAudio(audioUrl: String?) {
|
||||
var hasAudio = false
|
||||
for (attachment in this.msgLiveData.value!!.attachment) {
|
||||
if (attachment.attachmentType == AttachmentType.AUDIO) {
|
||||
attachment.pathUrl = audioUrl
|
||||
if (audioUrl==null||audioUrl.isEmpty()) {
|
||||
this.msgLiveData.value!!.attachment.remove(attachment)
|
||||
} else {
|
||||
attachment.pathUrl = audioUrl
|
||||
}
|
||||
hasAudio = true
|
||||
}
|
||||
}
|
||||
if (!hasAudio&&audioUrl!=null) {
|
||||
this.msgLiveData.value!!.attachment.add(Attachment(UUID.randomUUID().toString(), audioUrl, AttachmentType.AUDIO))
|
||||
}
|
||||
this.msgLiveData.postValue(this.msgLiveData.value)
|
||||
}
|
||||
|
||||
|
@ -1,187 +0,0 @@
|
||||
package com.navinfo.volvo.ui.widget
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.util.AttributeSet
|
||||
import android.view.*
|
||||
import android.widget.Scroller
|
||||
import androidx.core.view.forEach
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import java.lang.Math.abs
|
||||
|
||||
class SlideRecyclerView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : RecyclerView(context, attrs, defStyleAttr) {
|
||||
//系统最小移动距离
|
||||
private val mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
||||
|
||||
//最小有效速度
|
||||
private val mMinVelocity = 600
|
||||
|
||||
//增加手势控制,双击快速完成侧滑
|
||||
private var isDoubleClick = false
|
||||
private var mGestureDetector: GestureDetector =
|
||||
GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
|
||||
override fun onDoubleTap(e: MotionEvent?): Boolean {
|
||||
e?.let { event ->
|
||||
getSelectItem(event)
|
||||
mItem?.let {
|
||||
val deleteWith = it.getChildAt(it.childCount - 1).width
|
||||
//触发移动至完全展开deleteWidth
|
||||
if (it.scrollX == 0) {
|
||||
mScroller.startScroll(0, 0, deleteWith, 0)
|
||||
} else {
|
||||
mScroller.startScroll(it.scrollX, 0, -it.scrollX, 0)
|
||||
}
|
||||
isDoubleClick = true
|
||||
invalidate()
|
||||
return true
|
||||
}
|
||||
}
|
||||
//不进行拦截,只作为工具判断下双击
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
//使用速度控制器,增加侧滑速度判定滑动成功,
|
||||
//VelocityTracker 由native实现,需要及时释放内存
|
||||
private var mVelocityTracker: VelocityTracker? = null
|
||||
|
||||
//流畅滑动
|
||||
private var mScroller = Scroller(context)
|
||||
|
||||
//当前选中item
|
||||
private var mItem: ViewGroup? = null
|
||||
|
||||
//上次按下的横坐标
|
||||
private var mLastX = 0f
|
||||
|
||||
//当前RecyclerView被上层ViewGroup分发到事件,所有事件都会通过dispatchTouchEvent给到
|
||||
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
|
||||
mGestureDetector.onTouchEvent(ev)
|
||||
return super.dispatchTouchEvent(ev)
|
||||
}
|
||||
|
||||
//viewGroup对子控件的事件拦截,一旦拦截,后续事件序列不会再调用onInterceptTouchEvent
|
||||
override fun onInterceptTouchEvent(e: MotionEvent?): Boolean {
|
||||
e?.let {
|
||||
when (e.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
getSelectItem(e)
|
||||
mLastX = e.x
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
//移动控件
|
||||
return moveItem(e)
|
||||
}
|
||||
// MotionEvent.ACTION_UP -> {
|
||||
// stopMove(e)
|
||||
// }
|
||||
}
|
||||
}
|
||||
return super.onInterceptTouchEvent(e)
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onTouchEvent(e: MotionEvent?): Boolean {
|
||||
e?.let {
|
||||
when (e.action) {
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
moveItem(e)
|
||||
mLastX = e.x
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
stopMove()
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.onTouchEvent(e)
|
||||
}
|
||||
|
||||
//活动结束
|
||||
//判断一下结束的位置,补充或恢复位置
|
||||
private fun stopMove() {
|
||||
mItem?.let {
|
||||
//如果移动过半,判定左划成功
|
||||
val deleteWidth = it.getChildAt(it.childCount - 1).width
|
||||
//如果整个移动过程速度大于600,也判定滑动成功
|
||||
//注意如果没有拦截ACTION_MOVE,mVelocityTracker是没有初始化的
|
||||
var velocity = 0f
|
||||
mVelocityTracker?.let { tracker ->
|
||||
tracker.computeCurrentVelocity(1000)
|
||||
velocity = tracker.xVelocity
|
||||
}
|
||||
//判断结束情况,移动过半或者向左速度很快都展开
|
||||
if ((abs(it.scrollX) >= deleteWidth / 2f) || (velocity < -mMinVelocity)) {
|
||||
//触发移动至完全展开
|
||||
mScroller.startScroll(it.scrollX, 0, deleteWidth - it.scrollX, 0)
|
||||
invalidate()
|
||||
} else {
|
||||
//如果移动未过半应恢复状态
|
||||
mScroller.startScroll(it.scrollX, 0, -it.scrollX, 0)
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
//清除状态
|
||||
mLastX = 0f
|
||||
//mVeloctityTracker由native实现,需要及时释放
|
||||
mVelocityTracker?.apply {
|
||||
clear()
|
||||
recycle()
|
||||
}
|
||||
mVelocityTracker = null
|
||||
}
|
||||
|
||||
//移动Item
|
||||
//绝对值小于删除按钮长度随便移动,大于则不移动
|
||||
@SuppressLint("Recycle")
|
||||
private fun moveItem(e: MotionEvent): Boolean {
|
||||
mItem?.let {
|
||||
val dx = mLastX - e.x
|
||||
//最小的移动距离应该舍弃,onInterceptTouchEvent不拦截,onTouchEvent内才更新mLastX
|
||||
// if (abs(dx) > mTouchSlop) {
|
||||
//检查mItem移动后应该在【-deleteLength,0】内
|
||||
val deleteWith = it.getChildAt(it.childCount - 1).width
|
||||
if ((it.scrollX + dx) <= deleteWith && (it.scrollX + dx) >= 0) {
|
||||
//触发移动
|
||||
it.scrollBy(dx.toInt(), 0)
|
||||
//触发速度计算
|
||||
//这里Rectycle不存在问题,一旦返回true,就会拦截事件,就会到达ACTION_UP去回收
|
||||
mVelocityTracker = mVelocityTracker ?: VelocityTracker.obtain()
|
||||
mVelocityTracker!!.addMovement(e)
|
||||
return true
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//获取点击位置
|
||||
//通过点击的y坐标除以Item高度得出
|
||||
private fun getSelectItem(e: MotionEvent) {
|
||||
val frame = Rect()
|
||||
mItem = null
|
||||
forEach {
|
||||
if (it.visibility != GONE) {
|
||||
it.getHitRect(frame)
|
||||
if (frame.contains(e.x.toInt(), e.y.toInt())) {
|
||||
mItem = it as ViewGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//流畅地滑动
|
||||
override fun computeScroll() {
|
||||
if (mScroller.computeScrollOffset()) {
|
||||
mItem?.scrollBy(mScroller.currX, mScroller.currY)
|
||||
postInvalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
648
app/src/main/java/com/navinfo/volvo/utils/EasyMediaFile.kt
Normal file
648
app/src/main/java/com/navinfo/volvo/utils/EasyMediaFile.kt
Normal file
@ -0,0 +1,648 @@
|
||||
package com.navinfo.volvo.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Fragment
|
||||
import android.content.*
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.os.*
|
||||
import android.os.Environment.*
|
||||
import android.provider.MediaStore
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* 创建日期:2018/8/21 0021on 下午 4:40
|
||||
* 描述:多媒体选择工具类
|
||||
* @author:Vincent
|
||||
*/
|
||||
class EasyMediaFile {
|
||||
/**
|
||||
* 设置图片选择结果回调
|
||||
*/
|
||||
private var callback: ((file: File) -> Unit)? = null
|
||||
private var isCrop: Boolean = false
|
||||
private var error: ((error: Exception) -> Unit)? = null
|
||||
|
||||
/**
|
||||
* 视频录制/音频录制/拍照/剪切后图片的存放位置(参考file_provider_paths.xml中的路径)
|
||||
*/
|
||||
private var mFilePath: File? = null
|
||||
|
||||
private val mainHandler = Handler(Looper.getMainLooper())
|
||||
|
||||
fun setError(error: ((error: Exception) -> Unit)?): EasyMediaFile {
|
||||
this.error = error
|
||||
return this
|
||||
}
|
||||
|
||||
fun setCallback(callback: ((file: File) -> Unit)): EasyMediaFile {
|
||||
this.callback = callback
|
||||
return this
|
||||
}
|
||||
|
||||
fun setCrop(isCrop: Boolean): EasyMediaFile {
|
||||
this.isCrop = isCrop
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改图片的存储路径(默认的图片存储路径是SD卡上 Android/data/应用包名/时间戳.jpg)
|
||||
*
|
||||
* @param imgPath 图片的存储路径(包括文件名和后缀)
|
||||
*/
|
||||
fun setFilePath(imgPath: String?): EasyMediaFile {
|
||||
if (imgPath.isNullOrEmpty()) {
|
||||
this.mFilePath = null
|
||||
} else {
|
||||
this.mFilePath = File(imgPath)
|
||||
this.mFilePath?.parentFile?.mkdirs()
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 选择文件
|
||||
* 支持图片、音频、视频
|
||||
*/
|
||||
// fun selectFile(activity: Activity) {
|
||||
// isCrop = false
|
||||
// val intent = Intent(Intent.ACTION_PICK, null).apply {
|
||||
// setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "*/*")
|
||||
// setDataAndType(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, "*/*")
|
||||
// setDataAndType(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "*/*")
|
||||
// }
|
||||
// if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
// selectFileInternal(intent, activity, -1)
|
||||
// } else {
|
||||
// mainHandler.post { selectFileInternal(intent, activity, -1) }
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* 选择视频
|
||||
*/
|
||||
fun selectVideo(activity: Activity) {
|
||||
isCrop = false
|
||||
val intent = Intent(Intent.ACTION_PICK, null).apply {
|
||||
setDataAndType(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/*")
|
||||
}
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
selectFileInternal(intent, activity, 2)
|
||||
} else {
|
||||
mainHandler.post { selectFileInternal(intent, activity, 2) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择音频
|
||||
*/
|
||||
fun selectAudio(activity: Activity) {
|
||||
isCrop = false
|
||||
val intent = Intent(Intent.ACTION_PICK, null).apply {
|
||||
setDataAndType(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, "audio/*")
|
||||
}
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
selectFileInternal(intent, activity, 1)
|
||||
} else {
|
||||
mainHandler.post { selectFileInternal(intent, activity, 1) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择图片
|
||||
*/
|
||||
fun selectPhoto(activity: Activity) {
|
||||
val intent = Intent(Intent.ACTION_PICK, null).apply {
|
||||
setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*")
|
||||
}
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
selectFileInternal(intent, activity, 0)
|
||||
} else {
|
||||
mainHandler.post { selectFileInternal(intent, activity, 0) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 选择文件
|
||||
*/
|
||||
private fun selectFileInternal(intent: Intent, activity: Activity, type: Int) {
|
||||
val resolveInfoList = activity.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
|
||||
if (resolveInfoList.isEmpty()) {
|
||||
error?.invoke(IllegalStateException("No Activity found to handle Intent "))
|
||||
} else {
|
||||
PhotoFragment.findOrCreate(activity).start(intent, PhotoFragment.REQ_SELECT_FILE) { requestCode: Int, data: Intent? ->
|
||||
if (requestCode != PhotoFragment.REQ_SELECT_FILE) {
|
||||
return@start
|
||||
}
|
||||
data ?: return@start
|
||||
data.data ?: return@start
|
||||
try {
|
||||
val inputFile = if (type != -1) {
|
||||
uriToFile(activity, data.data!!, type)
|
||||
} else {
|
||||
if (data.data!!.path!!.contains(".")) {
|
||||
File(data.data!!.path!!)
|
||||
} else {
|
||||
when {
|
||||
data.data!!.path!!.contains("images") -> {
|
||||
uriToFile(activity, data.data!!, 0)
|
||||
}
|
||||
data.data!!.path!!.contains("video") -> {
|
||||
uriToFile(activity, data.data!!, 2)
|
||||
}
|
||||
else -> {
|
||||
uriToFile(activity, data.data!!, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isCrop) {//裁剪
|
||||
zoomPhoto(inputFile, mFilePath
|
||||
?: File(generateFilePath(activity)), activity)
|
||||
} else {//不裁剪
|
||||
callback?.invoke(inputFile)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
error?.invoke(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun uriToFile(activity: Activity, uri: Uri): File {
|
||||
|
||||
// 首先使用系统提供的CursorLoader进行file获取
|
||||
val context = activity.application
|
||||
val projection = arrayOf(MediaStore.Images.Media.DATA)
|
||||
var path: String
|
||||
try {
|
||||
CursorLoader(context, uri, projection, null, null, null)
|
||||
.loadInBackground().apply {
|
||||
getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
|
||||
val index = getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
|
||||
moveToFirst()
|
||||
path = getString(index)
|
||||
close()
|
||||
|
||||
}
|
||||
return File(path)
|
||||
} catch (e: Exception) {
|
||||
// 当没获取到。再使用别的方式进行获取
|
||||
val scheme = uri.scheme
|
||||
path = uri.path ?: throw RuntimeException("Could not find path in this uri:[$uri]")
|
||||
when (scheme) {
|
||||
"file" -> {
|
||||
val cr = context.contentResolver
|
||||
val buff = StringBuffer()
|
||||
buff.append("(").append(MediaStore.Images.ImageColumns.DATA).append("=").append("'$path'").append(")")
|
||||
cr.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, arrayOf(MediaStore.Images.ImageColumns._ID,
|
||||
MediaStore.Images.ImageColumns.DATA), buff.toString(), null, null).apply {
|
||||
this ?: throw RuntimeException("cursor is null")
|
||||
var dataIdx: Int
|
||||
while (!this.isAfterLast) {
|
||||
dataIdx = this.getColumnIndex(MediaStore.Images.ImageColumns.DATA)
|
||||
path = this.getString(dataIdx)
|
||||
this.moveToNext()
|
||||
}
|
||||
close()
|
||||
}
|
||||
|
||||
return File(path)
|
||||
}
|
||||
"content" -> {
|
||||
|
||||
context.contentResolver.query(uri, arrayOf(MediaStore.Images.Media.DATA), null, null, null).apply {
|
||||
this ?: throw RuntimeException("cursor is null")
|
||||
if (this.moveToFirst()) {
|
||||
val columnIndex = this.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
|
||||
path = this.getString(columnIndex)
|
||||
}
|
||||
close()
|
||||
}
|
||||
return File(path)
|
||||
|
||||
}
|
||||
else -> {
|
||||
throw IllegalArgumentException("Could not find file by this uri:$uri")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拍照获取
|
||||
*/
|
||||
fun takePhoto(activity: Activity) {
|
||||
val imgFile = if (isCrop) {
|
||||
File(generateFilePath(activity))
|
||||
} else {
|
||||
mFilePath ?: File(generateFilePath(activity))
|
||||
}
|
||||
|
||||
val imgUri = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
Uri.fromFile(imgFile)
|
||||
} else {
|
||||
//兼容android7.0 使用共享文件的形式
|
||||
val contentValues = ContentValues(1)
|
||||
contentValues.put(MediaStore.Images.Media.DATA, imgFile.absolutePath)
|
||||
activity.application.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
|
||||
}
|
||||
|
||||
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
|
||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUri)
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
takeFileInternal(imgFile, intent, activity)
|
||||
} else {
|
||||
mainHandler.post { takeFileInternal(imgFile, intent, activity) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 音频录制
|
||||
*/
|
||||
fun takeAudio(activity: Activity) {
|
||||
isCrop = false
|
||||
val imgFile = mFilePath ?: File(generateFilePath(activity, 1))
|
||||
val imgUri = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
Uri.fromFile(imgFile)
|
||||
} else {
|
||||
//兼容android7.0 使用共享文件的形式
|
||||
val contentValues = ContentValues(1)
|
||||
contentValues.put(MediaStore.Audio.Media.DATA, imgFile.absolutePath)
|
||||
activity.application.contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, contentValues)
|
||||
}
|
||||
|
||||
val intent = Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION)
|
||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUri)
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
takeFileInternal(imgFile, intent, activity, 1)
|
||||
} else {
|
||||
mainHandler.post { takeFileInternal(imgFile, intent, activity, 1) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频录制
|
||||
*/
|
||||
fun takeVideo(activity: Activity) {
|
||||
isCrop = false
|
||||
val imgFile = mFilePath ?: File(generateFilePath(activity, 2))
|
||||
val imgUri = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
Uri.fromFile(imgFile)
|
||||
} else {
|
||||
//兼容android7.0 使用共享文件的形式
|
||||
val contentValues = ContentValues(1)
|
||||
contentValues.put(MediaStore.Video.Media.DATA, imgFile.absolutePath)
|
||||
activity.application.contentResolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentValues)
|
||||
}
|
||||
|
||||
val intent = Intent(MediaStore.ACTION_VIDEO_CAPTURE).apply {
|
||||
putExtra(MediaStore.EXTRA_OUTPUT, imgUri)
|
||||
// 默认录制时间10秒 部分手机该设置无效
|
||||
// putExtra(MediaStore.EXTRA_DURATION_LIMIT, 10000)
|
||||
}
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
takeFileInternal(imgFile, intent, activity, 2)
|
||||
} else {
|
||||
mainHandler.post { takeFileInternal(imgFile, intent, activity, 2) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 拍照或选择
|
||||
*/
|
||||
fun getImage(activity: Activity) {
|
||||
|
||||
val imgFile = if (isCrop) {
|
||||
File(generateFilePath(activity))
|
||||
} else {
|
||||
mFilePath ?: File(generateFilePath(activity))
|
||||
}
|
||||
|
||||
val imgUri = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
Uri.fromFile(imgFile)
|
||||
} else {
|
||||
//兼容android7.0 使用共享文件的形式
|
||||
val contentValues = ContentValues(1)
|
||||
contentValues.put(MediaStore.Images.Media.DATA, imgFile.absolutePath)
|
||||
activity.application.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
|
||||
}
|
||||
|
||||
|
||||
val cameraIntents = ArrayList<Intent>()
|
||||
val captureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
|
||||
val packageManager = activity.packageManager
|
||||
val camList = packageManager.queryIntentActivities(captureIntent, 0)
|
||||
for (res in camList) {
|
||||
val packageName = res.activityInfo.packageName
|
||||
val intent = Intent(captureIntent)
|
||||
intent.component = ComponentName(res.activityInfo.packageName, res.activityInfo.name)
|
||||
intent.setPackage(packageName)
|
||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUri)
|
||||
cameraIntents.add(intent)
|
||||
}
|
||||
val intent = Intent.createChooser(createPickMore(), "请选择").also {
|
||||
it.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toTypedArray())
|
||||
|
||||
}
|
||||
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
takeFileInternal(imgFile, intent, activity)
|
||||
} else {
|
||||
mainHandler.post { takeFileInternal(imgFile, intent, activity) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 音频录制或选择
|
||||
*/
|
||||
fun getAudio(activity: Activity) {
|
||||
isCrop = false
|
||||
val imgFile = mFilePath ?: File(generateFilePath(activity))
|
||||
|
||||
val imgUri = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
Uri.fromFile(imgFile)
|
||||
} else {
|
||||
//兼容android7.0 使用共享文件的形式
|
||||
val contentValues = ContentValues(1)
|
||||
contentValues.put(MediaStore.Audio.Media.DATA, imgFile.absolutePath)
|
||||
activity.application.contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, contentValues)
|
||||
}
|
||||
val cameraIntents = ArrayList<Intent>()
|
||||
val captureIntent = Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION)
|
||||
val packageManager = activity.packageManager
|
||||
val camList = packageManager.queryIntentActivities(captureIntent, 0)
|
||||
for (res in camList) {
|
||||
val packageName = res.activityInfo.packageName
|
||||
val intent = Intent(captureIntent)
|
||||
intent.component = ComponentName(res.activityInfo.packageName, res.activityInfo.name)
|
||||
intent.setPackage(packageName)
|
||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUri)
|
||||
cameraIntents.add(intent)
|
||||
}
|
||||
val intent = Intent.createChooser(createPickMore("audio/*"), "请选择").also {
|
||||
it.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toTypedArray())
|
||||
|
||||
}
|
||||
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
takeFileInternal(imgFile, intent, activity, 1)
|
||||
} else {
|
||||
mainHandler.post { takeFileInternal(imgFile, intent, activity, 1) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频拍摄或选择
|
||||
*/
|
||||
fun getVideo(activity: Activity) {
|
||||
isCrop = false
|
||||
val imgFile = mFilePath ?: File(generateFilePath(activity, 2))
|
||||
|
||||
val imgUri = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
Uri.fromFile(imgFile)
|
||||
} else {
|
||||
//兼容android7.0 使用共享文件的形式
|
||||
val contentValues = ContentValues(1)
|
||||
contentValues.put(MediaStore.Video.Media.DATA, imgFile.absolutePath)
|
||||
activity.application.contentResolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentValues)
|
||||
}
|
||||
val cameraIntents = ArrayList<Intent>()
|
||||
val captureIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
|
||||
// 某些手机此设置是不生效的,需要自行封装解决
|
||||
// captureIntent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 10000)
|
||||
val packageManager = activity.packageManager
|
||||
val camList = packageManager.queryIntentActivities(captureIntent, 0)
|
||||
for (res in camList) {
|
||||
val packageName = res.activityInfo.packageName
|
||||
val intent = Intent(captureIntent)
|
||||
intent.component = ComponentName(res.activityInfo.packageName, res.activityInfo.name)
|
||||
intent.setPackage(packageName)
|
||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUri)
|
||||
cameraIntents.add(intent)
|
||||
}
|
||||
val intent = Intent.createChooser(createPickMore("video/*"), "请选择").also {
|
||||
it.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toTypedArray())
|
||||
|
||||
}
|
||||
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
takeFileInternal(imgFile, intent, activity, 2)
|
||||
} else {
|
||||
mainHandler.post { takeFileInternal(imgFile, intent, activity, 2) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向系统发出指令
|
||||
*/
|
||||
private fun takeFileInternal(takePhotoPath: File, intent: Intent, activity: Activity, type: Int = 0) {
|
||||
val fragment = PhotoFragment.findOrCreate(activity)
|
||||
fragment.start(intent, PhotoFragment.REQ_TAKE_FILE) { requestCode: Int, data: Intent? ->
|
||||
if (requestCode == PhotoFragment.REQ_TAKE_FILE) {
|
||||
if (data?.data != null) {
|
||||
mFilePath = when (type) {
|
||||
0 -> {
|
||||
uriToFile(activity, data.data!!)
|
||||
}
|
||||
else -> uriToFile(activity, data.data!!, type)
|
||||
}
|
||||
|
||||
if (isCrop) {
|
||||
zoomPhoto(takePhotoPath, mFilePath
|
||||
?: File(generateFilePath(activity)), activity)
|
||||
} else {
|
||||
callback?.invoke(mFilePath!!)
|
||||
mFilePath = null
|
||||
}
|
||||
return@start
|
||||
}
|
||||
if (isCrop) {
|
||||
zoomPhoto(takePhotoPath, mFilePath
|
||||
?: File(generateFilePath(activity)), activity)
|
||||
} else {
|
||||
callback?.invoke(takePhotoPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun uriToFile(activity: Activity, data: Uri, type: Int): File {
|
||||
val cursor = activity.managedQuery(data, arrayOf(if (type == 1) MediaStore.Audio.Media.DATA else MediaStore.Video.Media.DATA), null,
|
||||
null, null)
|
||||
val path = if (cursor == null) {
|
||||
data.path
|
||||
} else {
|
||||
val index = cursor.getColumnIndexOrThrow(if (type == 1) MediaStore.Audio.Media.DATA else MediaStore.Video.Media.DATA)
|
||||
cursor.moveToFirst()
|
||||
cursor.getString(index)
|
||||
}
|
||||
// 手动关掉报错如下
|
||||
// Caused by: android.database.StaleDataException: Attempted to access a cursor after it has been closed.
|
||||
// cursor.close()
|
||||
return File(path!!)
|
||||
}
|
||||
|
||||
/***
|
||||
* 图片裁剪
|
||||
*/
|
||||
private fun zoomPhoto(inputFile: File?, outputFile: File, activity: Activity) {
|
||||
try {
|
||||
val intent = Intent("com.android.camera.action.CROP")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
intent.setDataAndType(getImageContentUri(activity, inputFile), "image/*")
|
||||
} else {
|
||||
intent.setDataAndType(Uri.fromFile(inputFile), "image/*")
|
||||
}
|
||||
intent.putExtra("crop", "true")
|
||||
|
||||
// 是否返回uri
|
||||
intent.putExtra("return-data", false)
|
||||
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString())
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
val imgFile = File("${Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES)}/${outputFile.name}")
|
||||
// 通过 MediaStore API 插入file 为了拿到系统裁剪要保存到的uri(因为App没有权限不能访问公共存储空间,需要通过 MediaStore API来操作)
|
||||
val values = ContentValues()
|
||||
values.put(MediaStore.Images.Media.DATA, imgFile.getAbsolutePath());
|
||||
values.put(MediaStore.Images.Media.DISPLAY_NAME, outputFile.name);
|
||||
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
|
||||
val uri = activity.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
|
||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(outputFile))
|
||||
zoomPhotoInternal(outputFile, intent, activity)
|
||||
}else {
|
||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(outputFile))
|
||||
zoomPhotoInternal(outputFile, intent, activity)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
error?.invoke(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun zoomPhotoInternal(outputFile: File, intent: Intent, activity: Activity) {
|
||||
PhotoFragment.findOrCreate(activity).start(intent, PhotoFragment.REQ_ZOOM_PHOTO) { requestCode: Int, data: Intent? ->
|
||||
if (requestCode == PhotoFragment.REQ_ZOOM_PHOTO) {
|
||||
data ?: return@start
|
||||
callback?.invoke(outputFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**构建文件多选Intent*/
|
||||
private fun createPickMore(fileType: String = "image/*"): Intent {
|
||||
val pictureChooseIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI).apply {
|
||||
type = fileType
|
||||
}
|
||||
pictureChooseIntent.putExtra(Intent.EXTRA_LOCAL_ONLY, true)
|
||||
/**临时授权app访问URI代表的文件所有权*/
|
||||
pictureChooseIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
return pictureChooseIntent
|
||||
}
|
||||
/**
|
||||
* 产生图片的路径,带文件夹和文件名,文件名为当前毫秒数
|
||||
*/
|
||||
private fun generateFilePath(activity: Activity, fileType: Int = 0): String {
|
||||
val file = when (fileType) {
|
||||
// 音频路径
|
||||
1 -> "${SystemConstant.SoundFolder}" + File.separator + System.currentTimeMillis().toString() + ".m4a"
|
||||
// 视频路径
|
||||
2 -> "${SystemConstant.CameraFolder}" + File.separator + System.currentTimeMillis().toString() + ".mp4"
|
||||
// 图片路径
|
||||
else -> "${SystemConstant.CameraFolder}" + File.separator + System.currentTimeMillis().toString() + ".jpg"
|
||||
}
|
||||
File(file).parentFile.mkdirs()
|
||||
return file
|
||||
}
|
||||
/**
|
||||
* 获取SD下的应用目录
|
||||
*/
|
||||
private fun getExternalStoragePath(activity: Activity): String {
|
||||
val sb = "${activity.getExternalFilesDir(null)}/tmp"
|
||||
return sb
|
||||
}
|
||||
/**
|
||||
* 安卓7.0裁剪根据文件路径获取uri
|
||||
*/
|
||||
private fun getImageContentUri(context: Context, imageFile: File?): Uri? {
|
||||
val filePath = imageFile?.absolutePath
|
||||
val cursor = context.contentResolver.query(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
arrayOf(MediaStore.Images.Media._ID),
|
||||
MediaStore.Images.Media.DATA + "=? ",
|
||||
arrayOf(filePath), null)
|
||||
cursor.use { _ ->
|
||||
return if (cursor != null && cursor.moveToFirst()) {
|
||||
val id = cursor.getInt(cursor
|
||||
.getColumnIndex(MediaStore.MediaColumns._ID))
|
||||
val baseUri = Uri.parse("content://media/external/images/media")
|
||||
Uri.withAppendedPath(baseUri, "" + id)
|
||||
} else {
|
||||
imageFile?.let {
|
||||
if (it.exists()) {
|
||||
val values = ContentValues()
|
||||
values.put(MediaStore.Images.Media.DATA, filePath)
|
||||
context.contentResolver.insert(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 用于获取图片的Fragment
|
||||
*/
|
||||
class PhotoFragment : Fragment() {
|
||||
/**
|
||||
* Fragment处理照片后返回接口
|
||||
*/
|
||||
private var callback: ((requestCode: Int, intent: Intent?) -> Unit)? = null
|
||||
/**
|
||||
* 开启系统相册
|
||||
* 裁剪图片、打开相册选择单张图片、拍照
|
||||
*/
|
||||
fun start(intent: Intent, requestCode: Int, callback: ((requestCode: Int, intent: Intent?) -> Unit)) {
|
||||
this.callback = callback
|
||||
startActivityForResult(intent, requestCode)
|
||||
}
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
callback?.invoke(requestCode, data)
|
||||
}
|
||||
}
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
retainInstance = true
|
||||
}
|
||||
companion object {
|
||||
const val REQ_TAKE_FILE = 10001
|
||||
const val REQ_SELECT_FILE = 10002
|
||||
const val REQ_ZOOM_PHOTO = 10003
|
||||
private const val TAG = "EasyPhoto:PhotoFragment"
|
||||
@JvmStatic
|
||||
fun findOrCreate(activity: Activity): PhotoFragment {
|
||||
var fragment: PhotoFragment? = activity.fragmentManager.findFragmentByTag(TAG) as PhotoFragment?
|
||||
if (fragment == null) {
|
||||
fragment = PhotoFragment()
|
||||
activity.fragmentManager.beginTransaction()
|
||||
.add(fragment, TAG)
|
||||
.commitAllowingStateLoss()
|
||||
activity.fragmentManager.executePendingTransactions()
|
||||
}
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
app/src/main/java/com/navinfo/volvo/utils/SystemConstant.kt
Normal file
10
app/src/main/java/com/navinfo/volvo/utils/SystemConstant.kt
Normal file
@ -0,0 +1,10 @@
|
||||
package com.navinfo.volvo.utils
|
||||
|
||||
class SystemConstant {
|
||||
companion object {
|
||||
lateinit var ROOT_PATH: String
|
||||
lateinit var CameraFolder: String
|
||||
lateinit var SoundFolder: String
|
||||
lateinit var LogFolder: String
|
||||
}
|
||||
}
|
5
app/src/main/res/drawable/ic_add_24dp.xml
Normal file
5
app/src/main/res/drawable/ic_add_24dp.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_baseline_delete_24.xml
Normal file
5
app/src/main/res/drawable/ic_baseline_delete_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_baseline_person_24.xml
Normal file
5
app/src/main/res/drawable/ic_baseline_person_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_baseline_volume_down_24.xml
Normal file
5
app/src/main/res/drawable/ic_baseline_volume_down_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:tint="#000000" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M18.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM5,9v6h4l5,5V4L9,9H5z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_baseline_volume_mute_24.xml
Normal file
5
app/src/main/res/drawable/ic_baseline_volume_mute_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:tint="#000000" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M7,9v6h4l5,5V4l-5,5H7z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_baseline_volume_up_24.xml
Normal file
5
app/src/main/res/drawable/ic_baseline_volume_up_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:tint="#000000" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"/>
|
||||
</vector>
|
@ -12,6 +12,7 @@
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:background="?android:attr/windowBackground"
|
||||
app:labelVisibilityMode="labeled"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
@ -29,4 +30,26 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:navGraph="@navigation/mobile_navigation" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab_new_message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:src="@drawable/ic_add_24dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:elevation="8dp"
|
||||
/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/layer_main_child_fragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
app:navGraph="@navigation/nav_graph"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"></FrameLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
35
app/src/main/res/layout/activity_message.xml
Normal file
35
app/src/main/res/layout/activity_message.xml
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.message.MessageActivity">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/Theme.NavinfoVolvo.AppBarOverlay">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:navigationIcon="@drawable/ic_back_file_picker"
|
||||
app:popupTheme="@style/Theme.NavinfoVolvo.PopupOverlay" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<include layout="@layout/content_message" />
|
||||
|
||||
<!-- <com.google.android.material.floatingactionbutton.FloatingActionButton-->
|
||||
<!-- android:id="@+id/fab"-->
|
||||
<!-- android:layout_width="wrap_content"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:layout_gravity="bottom|end"-->
|
||||
<!-- android:layout_marginEnd="@dimen/fab_margin"-->
|
||||
<!-- android:layout_marginBottom="16dp"-->
|
||||
<!-- app:srcCompat="@android:drawable/ic_dialog_email" />-->
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -9,7 +9,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:src="@mipmap/ic_launcher"
|
||||
android:src="@mipmap/volvo_logo_small"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
19
app/src/main/res/layout/content_message.xml
Normal file
19
app/src/main/res/layout/content_message.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_host_fragment_message"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:defaultNavHost="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:navGraph="@navigation/nav_graph" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -6,8 +6,8 @@
|
||||
<com.otaliastudios.cameraview.CameraView
|
||||
android:id="@+id/camera"
|
||||
android:keepScreenOn="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/img_start_camera"
|
||||
|
28
app/src/main/res/layout/fragment_first.xml
Normal file
28
app/src/main/res/layout/fragment_first.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".FirstFragment">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textview_first"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/hello_first_fragment"
|
||||
app:layout_constraintBottom_toTopOf="@id/button_first"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_first"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/next"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/textview_first" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -27,7 +27,7 @@
|
||||
app:counterEnabled="true"
|
||||
app:counterMaxLength="10"
|
||||
app:errorEnabled="true"
|
||||
android:hint="问候信息"
|
||||
android:hint="请输入问候信息"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
@ -40,13 +40,19 @@
|
||||
tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.nhaarman.supertooltips.ToolTipRelativeLayout
|
||||
android:id="@+id/tt_title"
|
||||
app:layout_constraintTop_toBottomOf="@id/ti_layout_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/label_message_subtitle"
|
||||
style="@style/TextAppearance.AppCompat.Subhead"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="问候信息"
|
||||
app:layout_constraintTop_toBottomOf="@id/ti_layout_title"></com.google.android.material.textview.MaterialTextView>
|
||||
android:text="问候附件"
|
||||
app:layout_constraintTop_toBottomOf="@id/tt_title"></com.google.android.material.textview.MaterialTextView>
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:id="@+id/div_message"
|
||||
@ -77,31 +83,74 @@
|
||||
android:textColor="@color/red"></TextView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_upload_pic"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="上传图片:"></TextView>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_start_camera"
|
||||
<LinearLayout
|
||||
android:id="@+id/layer_get_photo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
app:icon="@drawable/ic_baseline_camera_24"
|
||||
android:text="点击拍照"
|
||||
android:padding="@dimen/default_widget_padding"></com.google.android.material.button.MaterialButton>
|
||||
<Space
|
||||
android:layout_width="@dimen/default_widget_padding"
|
||||
android:layout_height="wrap_content"></Space>
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_start_photo"
|
||||
android:layout_width="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_start_camera"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
app:icon="@drawable/ic_baseline_camera_24"
|
||||
android:text="点击拍照"
|
||||
android:padding="@dimen/default_widget_padding"></com.google.android.material.button.MaterialButton>
|
||||
<Space
|
||||
android:layout_width="@dimen/default_widget_padding"
|
||||
android:layout_height="wrap_content"></Space>
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_start_photo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
app:icon="@drawable/ic_baseline_image_search_24"
|
||||
android:text="相册选择"
|
||||
android:padding="@dimen/default_widget_padding"></com.google.android.material.button.MaterialButton>
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/layer_photo_result"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
app:icon="@drawable/ic_baseline_image_search_24"
|
||||
android:text="相册选择"
|
||||
android:padding="@dimen/default_widget_padding"></com.google.android.material.button.MaterialButton>
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
android:orientation="horizontal">
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/tv_photo_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""></com.google.android.material.textview.MaterialTextView>
|
||||
<Space
|
||||
android:layout_width="@dimen/default_widget_padding"
|
||||
android:layout_height="wrap_content"></Space>
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/img_photo_delete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:src="@drawable/ic_baseline_delete_24"></com.google.android.material.imageview.ShapeableImageView>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/img_message_attachment"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginLeft="@dimen/activity_horizontal_margin"
|
||||
android:scaleType="fitCenter">
|
||||
</androidx.appcompat.widget.AppCompatImageView>
|
||||
|
||||
<com.nhaarman.supertooltips.ToolTipRelativeLayout
|
||||
android:id="@+id/tt_pic"
|
||||
app:layout_constraintTop_toBottomOf="@id/ti_layout_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<LinearLayout
|
||||
style="@style/default_line"
|
||||
android:layout_width="match_parent"
|
||||
@ -119,25 +168,69 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="上传音频:"></TextView>
|
||||
<LinearLayout
|
||||
android:id="@+id/layer_get_audio"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_start_record"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
app:icon="@drawable/ic_baseline_fiber_manual_record_24"
|
||||
android:text="录制音频"
|
||||
android:padding="@dimen/default_widget_padding"></com.google.android.material.button.MaterialButton>
|
||||
<Space
|
||||
android:layout_width="@dimen/default_widget_padding"
|
||||
android:layout_height="wrap_content"></Space>
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_select_sound"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
app:icon="@drawable/ic_baseline_audio_file_24"
|
||||
android:text="音频选择"
|
||||
android:padding="@dimen/default_widget_padding"></com.google.android.material.button.MaterialButton>
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:id="@+id/layer_audio_result"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
app:icon="@drawable/ic_baseline_fiber_manual_record_24"
|
||||
android:text="长按录音"
|
||||
android:padding="@dimen/default_widget_padding"></com.google.android.material.button.MaterialButton>
|
||||
<Space
|
||||
android:layout_width="@dimen/default_widget_padding"
|
||||
android:layout_height="wrap_content"></Space>
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
app:icon="@drawable/ic_baseline_audio_file_24"
|
||||
android:text="音频选择"
|
||||
android:padding="@dimen/default_widget_padding"></com.google.android.material.button.MaterialButton>
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
android:orientation="horizontal">
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/tv_audio_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""></com.google.android.material.textview.MaterialTextView>
|
||||
<Space
|
||||
android:layout_width="@dimen/default_widget_padding"
|
||||
android:layout_height="wrap_content"></Space>
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/img_audio_delete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:src="@drawable/ic_baseline_delete_24"></com.google.android.material.imageview.ShapeableImageView>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/img_sound_play"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_baseline_volume_up_24"
|
||||
android:visibility="gone"
|
||||
android:layout_gravity="center"></com.google.android.material.imageview.ShapeableImageView>
|
||||
<com.nhaarman.supertooltips.ToolTipRelativeLayout
|
||||
android:id="@+id/tt_audio"
|
||||
app:layout_constraintTop_toBottomOf="@id/ti_layout_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
@ -188,6 +281,12 @@
|
||||
android:background="@drawable/selector_bg_4_round_corner"></androidx.appcompat.widget.AppCompatEditText>
|
||||
</LinearLayout>
|
||||
|
||||
<com.nhaarman.supertooltips.ToolTipRelativeLayout
|
||||
android:id="@+id/tt_send_from"
|
||||
app:layout_constraintTop_toBottomOf="@id/ti_layout_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<LinearLayout
|
||||
style="@style/default_line"
|
||||
android:layout_width="match_parent"
|
||||
@ -206,13 +305,18 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="发给谁:"></TextView>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
<androidx.appcompat.widget.AppCompatSpinner
|
||||
android:id="@+id/edt_send_to"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/selector_bg_4_round_corner"></androidx.appcompat.widget.AppCompatEditText>
|
||||
android:layout_height="wrap_content"></androidx.appcompat.widget.AppCompatSpinner>
|
||||
</LinearLayout>
|
||||
|
||||
<com.nhaarman.supertooltips.ToolTipRelativeLayout
|
||||
android:id="@+id/tt_send_to"
|
||||
app:layout_constraintTop_toBottomOf="@id/ti_layout_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<LinearLayout
|
||||
style="@style/default_line"
|
||||
android:layout_width="match_parent"
|
||||
@ -240,7 +344,11 @@
|
||||
android:text="现在"
|
||||
android:padding="@dimen/default_widget_padding"></com.google.android.material.button.MaterialButton>
|
||||
</LinearLayout>
|
||||
|
||||
<com.nhaarman.supertooltips.ToolTipRelativeLayout
|
||||
android:id="@+id/tt_send_time"
|
||||
app:layout_constraintTop_toBottomOf="@id/ti_layout_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@ -252,6 +360,7 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_obtain_message_back"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@ -263,6 +372,7 @@
|
||||
android:layout_height="wrap_content"></Space>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_obtain_message_confirm"
|
||||
style="@style/Widget.Material3.Button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
27
app/src/main/res/layout/fragment_second.xml
Normal file
27
app/src/main/res/layout/fragment_second.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".SecondFragment">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textview_second"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toTopOf="@id/button_second"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_second"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/previous"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/textview_second" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -5,16 +5,22 @@
|
||||
android:id="@+id/navigation_home"
|
||||
android:icon="@drawable/ic_home_black_24dp"
|
||||
android:title="@string/title_home" />
|
||||
|
||||
<item
|
||||
android:id="@+id/navigation_dashboard"
|
||||
android:icon="@drawable/ic_dashboard_black_24dp"
|
||||
android:title="@string/title_dashboard" />
|
||||
|
||||
<item
|
||||
android:id="@+id/navigation_none"
|
||||
android:icon="@color/black"
|
||||
android:title=" " />
|
||||
<item
|
||||
android:id="@+id/navigation_notifications"
|
||||
android:icon="@drawable/ic_notifications_black_24dp"
|
||||
android:title="@string/title_notifications" />
|
||||
<!-- <item-->
|
||||
<!-- android:id="@+id/navigation_my"-->
|
||||
<!-- android:icon="@drawable/ic_baseline_person_24"-->
|
||||
<!-- android:title="@string/my" />-->
|
||||
|
||||
<item
|
||||
android:id="@+id/navigation_obtain_message"
|
||||
|
BIN
app/src/main/res/mipmap-xxhdpi/volvo_logo_small.png
Normal file
BIN
app/src/main/res/mipmap-xxhdpi/volvo_logo_small.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.7 KiB |
@ -22,24 +22,16 @@
|
||||
android:label="@string/title_dashboard"
|
||||
tools:layout="@layout/fragment_dashboard" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/fab_new_message"
|
||||
android:name="com.navinfo.volvo.ui.dashboard.DashboardFragment"
|
||||
android:label="@string/title_dashboard"
|
||||
tools:layout="@layout/fragment_dashboard" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/navigation_notifications"
|
||||
android:name="com.navinfo.volvo.ui.notifications.NotificationsFragment"
|
||||
android:label="@string/title_notifications"
|
||||
tools:layout="@layout/fragment_notifications" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/navigation_obtain_message"
|
||||
android:name="com.navinfo.volvo.ui.message.ObtainMessageFragment"
|
||||
android:label="问候编辑"
|
||||
tools:layout="@layout/fragment_obtain_message" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/navigation_camera"
|
||||
android:name="com.navinfo.volvo.ui.camera.CameraFragment"
|
||||
android:label="@string/title_dashboard"
|
||||
tools:layout="@layout/fragment_camera" />
|
||||
|
||||
<action android:id="@+id/nav_2_camera"
|
||||
app:destination="@id/nav_2_camera"></action>
|
||||
</navigation>
|
23
app/src/main/res/navigation/nav_graph.xml
Normal file
23
app/src/main/res/navigation/nav_graph.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/nav_graph"
|
||||
app:startDestination="@id/navigation_obtain_message">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/navigation_obtain_message"
|
||||
android:name="com.navinfo.volvo.ui.message.ObtainMessageFragment"
|
||||
android:label="问候编辑"
|
||||
tools:layout="@layout/fragment_obtain_message" >
|
||||
<action
|
||||
android:id="@+id/nav_2_camera"
|
||||
app:destination="@id/navigation_camera"></action>
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/navigation_camera"
|
||||
android:name="com.navinfo.volvo.ui.camera.CameraFragment"
|
||||
android:label="拍照"
|
||||
tools:layout="@layout/fragment_camera" />
|
||||
</navigation>
|
3
app/src/main/res/values-land/dimens.xml
Normal file
3
app/src/main/res/values-land/dimens.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<dimen name="fab_margin">48dp</dimen>
|
||||
</resources>
|
@ -1,6 +1,6 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.NavinfoVolvo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<style name="Theme.NavinfoVolvo" parent="Theme.Material3.Dark">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_200</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
|
3
app/src/main/res/values-w1240dp/dimens.xml
Normal file
3
app/src/main/res/values-w1240dp/dimens.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<dimen name="fab_margin">200dp</dimen>
|
||||
</resources>
|
3
app/src/main/res/values-w600dp/dimens.xml
Normal file
3
app/src/main/res/values-w600dp/dimens.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<dimen name="fab_margin">48dp</dimen>
|
||||
</resources>
|
@ -5,4 +5,5 @@
|
||||
<string name="title_notifications">Notifications</string>
|
||||
<string name="delete">删除</string>
|
||||
<string name="share">分享</string>
|
||||
<string name="my">我的</string>
|
||||
</resources>
|
@ -5,4 +5,5 @@
|
||||
<dimen name="activity_default_padding">12dp</dimen>
|
||||
<dimen name="default_font_size">18sp</dimen>
|
||||
<dimen name="default_widget_padding">6dp</dimen>
|
||||
<dimen name="fab_margin">16dp</dimen>
|
||||
</resources>
|
@ -5,4 +5,14 @@
|
||||
<string name="title_notifications">Notifications</string>
|
||||
<string name="delete">Del</string>
|
||||
<string name="share">Share</string>
|
||||
<string name="my">My</string>
|
||||
<string name="title_activity_second">SecondActivity</string>
|
||||
<!-- Strings used for fragments for navigation -->
|
||||
<string name="first_fragment_label">First Fragment</string>
|
||||
<string name="second_fragment_label">Second Fragment</string>
|
||||
<string name="next">Next</string>
|
||||
<string name="previous">Previous</string>
|
||||
|
||||
<string name="hello_first_fragment">Hello first fragment</string>
|
||||
<string name="hello_second_fragment">Hello second fragment. Arg: %1$s</string>
|
||||
</resources>
|
@ -1,6 +1,6 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.NavinfoVolvo" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<style name="Theme.NavinfoVolvo" parent="Theme.Material3.Light">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_500</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
@ -13,4 +13,13 @@
|
||||
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
|
||||
<style name="Theme.NavinfoVolvo.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.NavinfoVolvo.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||
|
||||
<style name="Theme.NavinfoVolvo.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
|
||||
</resources>
|
Loading…
x
Reference in New Issue
Block a user