一. 选择本地图片进行二维码扫描
不得不说BGAQRCode真的是功能强大,直接有一个方法就是扫描本地图片或者bitmap,
也就是说只要传入本地图片的地址,或者生成的bitmap对象,就可以进行解析,回调
onScanQRCodeSuccess
方法。所以需要做的就是选择本地图片得到图片地址或者生成bitmap
第一步:
selectLocalPic()
函数 调用系统相册选择图片,明确需要选择的文件是image类型,然后使用startActivityForResult
函数传入intent和requsetCode启动选择图片的activity(不同系统界面会有区别)
//选择本地图片
private fun selectLocalPic() {
val intent = Intent("android.intent.action.GET_CONTENT")
intent.type = "image/*"
startActivityForResult(intent, SELECT_LOCAL_IMAGE)
}
第二步:onActivityResult()中获取选择图片的uri(注意 这个uri并非图片地址)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
SELECT_LOCAL_IMAGE -> {
if (resultCode == Activity.RESULT_OK) {
val selectedImage = data?.data
selectedImage?.let {
scanLocalPic(it)
}
}
}
}
}
第三步:根据图片uri解析图片地址,并进行扫描:
(难点)
//扫描本地图片
private fun scanLocalPic(uri: Uri) {
val path = GetPhotoFromAlbum.getRealPathFromUri(this, uri)
Log.d("图片path", "$path")
scanView.decodeQRCode(path)
}
这里是一个难点,首先java和kotlin的处理方式不同,使用java的解析uri方式,在kotlin中为null,并且对于不同api设备,处理方式也不同。所以我在网上找到了一个工具类GetPhotoFromAlbum
原地址在这里:
https://blog.csdn.net/qq_37216116/article/details/88045161
用于kotlin开发中将图片uri解析为绝对路径:
import android.annotation.SuppressLint
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.provider.DocumentsContract
import android.provider.MediaStore
object GetPhotoFromAlbum {
/**
* 根据Uri获取图片的绝对路径
* @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
*/
fun getRealPathFromUri(context: Context, uri: Uri): String? {
val sdkVersion = Build.VERSION.SDK_INT
return if (sdkVersion >= 19) { // api >= 19
getRealPathFromUriAboveApi19(context, uri)
} else { // api < 19
getRealPathFromUriBelowAPI19(context, uri)
}
}
/**
* 适配api19以下(不包括api19),根据uri获取图片的绝对路径
*
* @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
*/
private fun getRealPathFromUriBelowAPI19(context: Context, uri: Uri): String? {
return getDataColumn(context, uri, null, null)
}
/**
* 适配api19及以上,根据uri获取图片的绝对路径
*
* @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
*/
@SuppressLint("NewApi")
private fun getRealPathFromUriAboveApi19(context: Context, uri: Uri): String? {
var filePath: String? = null
if (DocumentsContract.isDocumentUri(context, uri)) {
// 如果是document类型的 uri, 则通过document id来进行处理
val documentId = DocumentsContract.getDocumentId(uri)
if (isMediaDocument(uri)) { // MediaProvider
// 使用':'分割
val id = documentId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[1]
val selection = MediaStore.Images.Media._ID + "=?"
val selectionArgs = arrayOf(id)
filePath =
getDataColumn(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection, selectionArgs)
} else if (isDownloadsDocument(uri)) { // DownloadsProvider
val contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"),
java.lang.Long.valueOf(documentId)
)
filePath = getDataColumn(context, contentUri, null, null)
}
} else if ("content".equals(uri.scheme!!, ignoreCase = true)) {
// 如果是 content 类型的 Uri
filePath = getDataColumn(context, uri, null, null)
} else if ("file" == uri.scheme) {
// 如果是 file 类型的 Uri,直接获取图片对应的路径
filePath = uri.path
}
return filePath
}
/**
* 获取数据库表中的 _data 列,即返回Uri对应的文件路径
*/
private fun getDataColumn(context: Context, uri: Uri, selection: String?, selectionArgs: Array<String>?): String? {
var path: String? = null
val projection = arrayOf(MediaStore.Images.Media.DATA)
var cursor: Cursor? = null
try {
cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
if (cursor != null && cursor.moveToFirst()) {
val columnIndex = cursor.getColumnIndexOrThrow(projection[0])
path = cursor.getString(columnIndex)
}
} catch (e: Exception) {
cursor?.close()
}
finally {
cursor?.close()
}
return path
}
/**
* @param uri the Uri to check
*
* @return Whether the Uri authority is MediaProvider
*/
private fun isMediaDocument(uri: Uri): Boolean {
return "com.android.providers.media.documents" == uri.authority
}
/**
* @param uri the Uri to check
*
* @return Whether the Uri authority is DownloadsProvider
*/
private fun isDownloadsDocument(uri: Uri): Boolean {
return "com.android.providers.downloads.documents" == uri.authority
}
}
第四步:在onScanQRCodeSuccess
中做好判断,因为选择图片扫码的话,结果可能是空的,提示“未识别到二维码”
二. 选择本地图片进行扫码所需要的权限请求
在1.0的开发中也提到了权限请求,当时只有一个相机权限,但是现在增加了文件读写权限,这里也作一下修改,使用官方权限请求框架EasyPermissions
参考文章:https://www.jianshu.com/p/886a1ffb02b9
第一步:在Activity中实现EasyPermissions.PermissionCallbacks
接口,并且在onRequestPermissionsResult中添加一句话EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
:
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// Forward results to EasyPermissions
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
//成功
@Override
public void onPermissionsGranted(int requestCode, List<String> list) {
// Some permissions have been granted
// ...
}
//失败
@Override
public void onPermissionsDenied(int requestCode, List<String> list) {
// Some permissions have been denied
// ...
}
第二步:准备好需要请求的权限列表:
private val perms = arrayOf(
//外部文件读写权限和相机权限
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
)
第三步:检查并请求权限(onCreate中调用checkAndRequestPermission):
private fun checkAndRequestPermission() {
//检查是否有权限
if (EasyPermissions.hasPermissions(this, *perms)) {
isPermissionAuth = true
} else {
//请求权限 四个参数:回调对像实现接口的activity 权限请求对话框标题提示 权限请求码 需要请求的权限
EasyPermissions.requestPermissions(this, "", PERMS_REQUEST_CODE, *perms)
}
}
第四步在onPermissionsGranted
(申请通过)/onPermissionsDenied
(权限拒绝)回调中完成相应操作
第五步 以上过程都完成了 读写权限请求成功 但是选择图片时还是报错说没有权限?
原因
:AndroidQ以下,还是使用老的文件存储方式。Android Q仍然使用READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE作为存储相关运行时权限,但现在即使获取了这些权限,访问外部存储也受到了限制。
解决办法
:在manifest的applicaiton标签添加 android:requestLegacyExternalStorage="true"
参考:https://blog.csdn.net/MarketAndTechnology/article/details/105955441
三. 使用Android本地数据库保存扫码记录
这个需求涉及到数据库的使用了,原本打算用SQLite的,但是这玩意封装起来很烦,突然发现公司的框架中用到的是Anko
文档地址:https://github.com/Kotlin/anko/wiki/Anko-SQLite
我总结的文章:
https://www.jianshu.com/p/f4f42950ec69
四. 项目多语言
项目默认语言为英语,现在要添加日语,操作步骤如下:
1.右键res->New->Android Resouce File
image.png
5.点击OK后就可以在res目录中看到对应的文件了
image.png
需要注意的是,程序中用到的字段都需要在Strings中声明,并且特定地区的Strings也需要有同样的字段不同语言的翻译。如果某个字段在默认的String中有,而特点地区中没有,IDE会给出警告并使用默认内容。相反,如果特定地区中有的字段,而默认String中没有 则会报错。
补充点:如果想根据国家和地区显示不同图片怎么操作?
比入我这里有两张gif,分别是英文和日文,这个gif需要在启动页一开始进行播放
2
同理:英文gif作为默认选择,放在res下的
drawable
中命名为start_page_gif.gif
。那么此时需要新建日本地区的drawable文件夹,按以下步骤:res右键--->new--->Android Resource Directory
image.png
确定资源文件夹类型
image.png
选择好国家或地区后文件夹会自动命名
image.png
点击ok,将同名的日文gif资源文件放在这个文件夹下,系统会自动根据当前语言地区选择对应资源文件:
image.png
当然也要注意默认的资源必须存在
五. 状态栏全黑问题
为了隐藏掉每个界面自带的appbar,使用了以下主题:
<style name="AppTheme.FullScreen" parent="Theme.AppCompat.Light.NoActionBar">
<item name="windowNoTitle">true</item>
<item name="android:windowFullscreen">true</item>
</style>
<item name="windowNoTitle">true</item>
去掉标题栏
<item name="android:windowFullscreen">true</item>
隐藏状态栏
当添加了隐藏状态栏的代码后,状态栏全黑,无内容。所以把这句话删了,但是此时出现一个问题:状态栏内容正常显示,但是却挡住了界面内容
其实只需要再在界面根布局中加一句话就行了:
android:fitsSystemWindows="true"
<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"
android:animateLayoutChanges="true"
android:fitsSystemWindows="true">
六. 其他细节点
6.1 约束布局形成链之后,链头约束的定位改变时,各个控件也会改变,会有一个很舒服的过渡动画,自己看图,再吹一波约束布局
约束布局这里主界面根布局中加了
android:animateLayoutChanges="true"
这句话,所以控件的消失会渐入渐出
6.2 xml完成卡片背景
https://blog.csdn.net/qq1171574843/article/details/89386733
关于xml生成背景比如圆角 阴影这些 待学习(和动画比起来应该不难吧,花1小时掌握 2020-7-9)
6.3 修改项目包名
不要傻乎乎的去修改项目的文件夹路径名称,吃力不讨好,包名就像是项目的身份证号码,在module的build.gradle
文件的defaultConfig
下修改applicationId
6.4 关于报错
Error: Cannot fit requested classes in a single dex file (# methods: 66190 > 65536)
原因是引入SDK后 方法太多大于限制65536,解决方法:
https://blog.csdn.net/hanshiying007/article/details/105219378/
gradle文件的defaultConfig默认配置里面增加:
multiDexEnabled true
在dependencies中添加:
implementation 'com.android.support:multidex:1.0.3'
或者(开启androidx的话)
implementation 'androidx.multidex:multidex:2.0.1'
自定义的Application的onCreate()中添加:
MultiDex.install(this);
网友评论