增加离线地图下载流程

This commit is contained in:
squallzhjch
2023-04-03 10:40:53 +08:00
parent ca1be58db2
commit dcc5f581fb
23 changed files with 705 additions and 178 deletions

View File

@@ -26,6 +26,7 @@
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!-- 读取缓存数据 --> <!-- 读取缓存数据 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!--android:largeHeap="true" 大内存 128M -->
<application <application
android:name=".OMQSApplication" android:name=".OMQSApplication"
android:allowBackup="true" android:allowBackup="true"
@@ -35,6 +36,7 @@
android:label="@string/app_name" android:label="@string/app_name"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
android:supportsRtl="true" android:supportsRtl="true"
android:largeHeap="true"
android:theme="@style/Theme.OMQualityInspection" android:theme="@style/Theme.OMQualityInspection"
tools:targetApi="31"> tools:targetApi="31">
<activity <activity

View File

@@ -7,7 +7,7 @@ class Constant {
*/ */
lateinit var ROOT_PATH: String lateinit var ROOT_PATH: String
lateinit var MAP_PATH: String lateinit var MAP_PATH: String
lateinit var OFFLINE_MAP_PATH: String
/** /**
* 服务器地址 * 服务器地址
*/ */

View File

@@ -2,11 +2,17 @@ package com.navinfo.omqs.bean
data class OfflineMapCityBean( data class OfflineMapCityBean(
val id: String, val id: String,
/**
* 文件名称
*/
val fileName: String, val fileName: String,
/**
* 城市名称
*/
val name: String, val name: String,
val url: String, val url: String,
val version: Long, val version: Long,
val fileSize: Long, var fileSize: Long,
var currentSize:Long = 0, var currentSize:Long = 0,
var status:Int = NONE var status:Int = NONE
) { ) {

View File

@@ -54,7 +54,8 @@ class GlobalModule {
} }
}.apply { }.apply {
level = if (Constant.DEBUG) { level = if (Constant.DEBUG) {
HttpLoggingInterceptor.Level.BODY //坑 下载文件时打印log 内存不足
HttpLoggingInterceptor.Level.HEADERS
} else { } else {
HttpLoggingInterceptor.Level.NONE HttpLoggingInterceptor.Level.NONE
} }

View File

@@ -2,6 +2,7 @@ package com.navinfo.omqs.hilt
import android.content.Context import android.content.Context
import com.navinfo.collect.library.map.NIMapController import com.navinfo.collect.library.map.NIMapController
import com.navinfo.omqs.http.RetrofitNetworkServiceAPI
import com.navinfo.omqs.http.offlinemapdownload.OfflineMapDownloadManager import com.navinfo.omqs.http.offlinemapdownload.OfflineMapDownloadManager
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@@ -26,8 +27,10 @@ class MainActivityModule {
*/ */
@ActivityRetainedScoped @ActivityRetainedScoped
@Provides @Provides
fun providesOfflineMapDownloadManager(@ActivityContext context: Context): OfflineMapDownloadManager = fun providesOfflineMapDownloadManager(
OfflineMapDownloadManager(context) networkServiceAPI: RetrofitNetworkServiceAPI
): OfflineMapDownloadManager =
OfflineMapDownloadManager( networkServiceAPI)
/** /**
* 实验失败这样创建viewmodel不会在activity销毁的时候同时销毁 * 实验失败这样创建viewmodel不会在activity销毁的时候同时销毁
@@ -35,7 +38,6 @@ class MainActivityModule {
// @ActivityRetainedScoped // @ActivityRetainedScoped
// @Provides // @Provides
// fun providesMainViewModel(mapController: NIMapController): MainViewModel { // fun providesMainViewModel(mapController: NIMapController): MainViewModel {
// Log.e("jingo", "MainViewModel 被创建")
// return MainViewModel(mapController) // return MainViewModel(mapController)
// } // }

View File

@@ -5,6 +5,7 @@ import com.navinfo.omqs.bean.OfflineMapCityBean
import okhttp3.ResponseBody import okhttp3.ResponseBody
import retrofit2.Response import retrofit2.Response
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Streaming import retrofit2.http.Streaming
import retrofit2.http.Url import retrofit2.http.Url
import java.util.concurrent.Flow import java.util.concurrent.Flow
@@ -47,7 +48,7 @@ interface RetrofitNetworkServiceAPI {
*/ */
@Streaming @Streaming
@GET @GET
suspend fun retrofitDownLoadFile(@Url url: String):Response<ResponseBody> suspend fun retrofitDownLoadFile(@Header("RANGE") start: String? = "0", @Url url: String):Response<ResponseBody>
/** /**

View File

@@ -1,48 +1,126 @@
package com.navinfo.omqs.http.offlinemapdownload package com.navinfo.omqs.http.offlinemapdownload
import android.content.Context import android.content.Context
import android.os.Environment
import android.text.TextUtils import android.text.TextUtils
import android.util.Log
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import com.navinfo.omqs.Constant import com.navinfo.omqs.Constant
import com.navinfo.omqs.bean.OfflineMapCityBean import com.navinfo.omqs.bean.OfflineMapCityBean
import com.navinfo.omqs.http.RetrofitNetworkServiceAPI
import dagger.hilt.android.qualifiers.ActivityContext import dagger.hilt.android.qualifiers.ActivityContext
import java.io.Serializable import kotlinx.coroutines.cancel
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject import javax.inject.Inject
/** /**
* 管理离线地图下载 * 管理离线地图下载
*/ */
class OfflineMapDownloadManager @Inject constructor(@ActivityContext context: Context) { class OfflineMapDownloadManager @Inject constructor(
private val netApi: RetrofitNetworkServiceAPI
) {
/** /**
* 最多同时下载数量 * 最多同时下载数量
*/ */
private val MAX_SCOPE = 5 private val MAX_SCOPE = 3
/** /**
* 存储有哪些城市需要下载 * 存储有哪些城市需要下载的队列
*/ */
private val scopeMap: ConcurrentHashMap<String, OfflineMapDownloadScope> by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { private val scopeMap: ConcurrentHashMap<String, OfflineMapDownloadScope> by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
ConcurrentHashMap<String, OfflineMapDownloadScope>() ConcurrentHashMap<String, OfflineMapDownloadScope>()
} }
val downloadFolder: String? by lazy { /**
Constant.MAP_PATH + "/offline/" * 存储正在下载的城市队列
*/
private val taskScopeMap: ConcurrentHashMap<String, OfflineMapDownloadScope> by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
ConcurrentHashMap<String, OfflineMapDownloadScope>()
} }
/** /**
* 启动下载任务
* 请不要直接使用此方法启动下载任务,它是交由[OfflineMapDownloadScope]进行调用
*/
fun launchScope(scope: OfflineMapDownloadScope) {
if (taskScopeMap.size >= MAX_SCOPE) {
return
}
if (taskScopeMap.contains(scope.cityBean.id)) {
return
}
taskScopeMap[scope.cityBean.id] = scope
scope.launch()
}
/**
* 启动下一个任务,如果有正在等待中的任务的话
* 请不要直接使用此方法启动下载任务,它是交由[OfflineMapDownloadScope]进行调用
* @param previousUrl 上一个下载任务的下载连接
*/
fun launchNext(previousUrl: String) {
taskScopeMap.remove(previousUrl)
for (entrySet in scopeMap) {
val downloadScope = entrySet.value
if (downloadScope.isWaiting()) {
launchScope(downloadScope)
break
}
}
}
/**
* 暂停任务
* 只有等待中的任务和正在下载中的任务才可以进行暂停操作
*/
fun pause(id: String) {
if (taskScopeMap.containsKey(id)) {
val downloadScope = taskScopeMap[id]
downloadScope?.let {
downloadScope.pause()
}
launchNext(id)
}
}
/**
* 将下载任务加入到协程作用域的下载队列里
* 请求一个下载任务[OfflineMapDownloadScope] * 请求一个下载任务[OfflineMapDownloadScope]
* 这是创建[OfflineMapDownloadScope]的唯一途径,请不要通过其他方式创建[OfflineMapDownloadScope] * 这是创建[OfflineMapDownloadScope]的唯一途径,请不要通过其他方式创建[OfflineMapDownloadScope]
*/ */
fun request(cityBean: OfflineMapCityBean): OfflineMapDownloadScope? { fun start(id: String) {
//没有下载连接的不能下载 scopeMap[id]?.start()
if (TextUtils.isEmpty(cityBean.url)) return null
// if(scopeMap.containsKey())
var downloadScope = scopeMap[cityBean.id]
if (downloadScope == null) {
scopeMap[cityBean.id] = OfflineMapDownloadScope(cityBean)
}
return downloadScope
} }
fun cancel(id: String) {
taskScopeMap.remove(id)
scopeMap[id]?.cancelTask()
}
fun addTask(cityBean: OfflineMapCityBean) {
if (scopeMap.containsKey(cityBean.id)) {
return
} else {
scopeMap[cityBean.id] = OfflineMapDownloadScope(this, netApi, cityBean)
}
}
fun observer(
id: String,
lifecycleOwner: LifecycleOwner,
observer: Observer<OfflineMapCityBean>
) {
if (scopeMap.containsKey(id)) {
val downloadScope = scopeMap[id]
downloadScope?.let {
downloadScope.observer(lifecycleOwner, observer)
}
}
}
} }

View File

@@ -1,26 +1,255 @@
package com.navinfo.omqs.http.offlinemapdownload package com.navinfo.omqs.http.offlinemapdownload
import android.util.Log
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import com.navinfo.omqs.Constant
import com.navinfo.omqs.bean.OfflineMapCityBean import com.navinfo.omqs.bean.OfflineMapCityBean
import kotlinx.coroutines.CoroutineScope import com.navinfo.omqs.http.RetrofitNetworkServiceAPI
import kotlinx.coroutines.Job import kotlinx.coroutines.*
import java.io.File
import java.io.IOException
import java.io.RandomAccessFile
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
/** /**
* 代表一个下载任务 * 代表一个下载任务
* [OfflineMapCityBean.id]将做为下载任务的唯一标识 * [OfflineMapCityBean.id]将做为下载任务的唯一标识
* 不要直接在外部直接创建此对象,那样就可能无法统一管理下载任务,请通过[OfflineMapDownloadManager.download]获取此对象 * 不要直接在外部直接创建此对象,那样就可能无法统一管理下载任务,请通过[OfflineMapDownloadManager.request]获取此对象
* 这是一个协程作用域, * 这是一个协程作用域,
* EmptyCoroutineContext 表示一个不包含任何元素的协程上下文,它通常用于创建新的协程上下文,或者作为协程上下文的基础。 * EmptyCoroutineContext 表示一个不包含任何元素的协程上下文,它通常用于创建新的协程上下文,或者作为协程上下文的基础。
*/ */
class OfflineMapDownloadScope(cityBean: OfflineMapCityBean) : CoroutineScope by CoroutineScope(EmptyCoroutineContext) { class OfflineMapDownloadScope(
private val downloadManager: OfflineMapDownloadManager,
private val netApi: RetrofitNetworkServiceAPI,
val cityBean: OfflineMapCityBean
) :
CoroutineScope by CoroutineScope(EmptyCoroutineContext) {
/** /**
* *下载任务,用来取消的
*/ */
private var downloadJob: Job? = null private var downloadJob: Job? = null
/**
* 管理观察者,同时只有一个就行了
*/
private var observer: Observer<OfflineMapCityBean>? = null
/** /**
* *
*/ */
private val downloadData = MutableLiveData<OfflineMapCityBean>() private val downloadData = MutableLiveData<OfflineMapCityBean>()
init {
downloadData.value = cityBean
}
/**
* 开始任务的下载
* [OfflineMapCityBean]是在协程中进行创建的,它的创建会优先从数据库和本地文件获取,但这种操作是异步的,详情请看init代码块
* 我们需要通过观察者观察[OfflineMapCityBean]来得知它是否已经创建完成,只有当他创建完成且不为空(如果创建完成,它一定不为空)
* 才可以交由[OfflineMapDownloadManager]进行下载任务的启动
* 任务的开始可能并不是立即的,任务会受到[OfflineMapDownloadManager]的管理
*
* 这段原来代码没看懂:要触发 Observer 得观察的对象[OfflineMapCityBean]发生变化才行原demo里没找到livedata的变化也触发了onChange这里根本触发不了
*
* 找到原因了:是[cityBean]根本没有设置到liveData中但是还是不用这样了因为cityBean是一定创建好了的
*/
//原代码
// fun start() {
// var observer: Observer<OfflineMapCityBean>? = null
// observer = Observer { cityBean ->
// Log.e("jingo","Observer 创建了bean 为null吗$cityBean")
// cityBean?.let {
// observer?.let {
// Log.e("jingo","Observer 这里为什么要解除观察?")
// downloadData.removeObserver(it)
// }
// Log.e("jingo","Observer 状态 ${cityBean.status} ")
// when (cityBean.status) {
//
// OfflineMapCityBean.PAUSE, OfflineMapCityBean.ERROR, OfflineMapCityBean.NONE -> {
// change(OfflineMapCityBean.WAITING)
// downloadManager.launchScope(this@OfflineMapDownloadScope)
// }
// }
// }
// }
// downloadData.observeForever(observer)
// }
//改进的代码
fun start() {
change(OfflineMapCityBean.WAITING)
downloadManager.launchScope(this@OfflineMapDownloadScope)
}
/**
* 暂停任务
* 其实就是取消任务,移除监听
*/
fun pause() {
downloadJob?.cancel("pause")
}
/**
* 启动协程进行下载
* 请不要尝试在外部调用此方法,那样会脱离[OfflineMapDownloadManager]的管理
*/
fun launch() {
downloadJob = launch {
try {
download()
change(OfflineMapCityBean.DONE)
} catch (e: Throwable) {
Log.e("jingo DownloadScope", "error:${e.message}")
if (e.message == "pause") {
change(OfflineMapCityBean.PAUSE)
} else {
change(OfflineMapCityBean.ERROR)
}
} finally {
downloadManager.launchNext(cityBean.id)
}
}
}
/**
* 是否是等待任务
*/
fun isWaiting(): Boolean {
val downloadInfo = downloadData.value
downloadInfo ?: return false
return downloadInfo.status == OfflineMapCityBean.WAITING
}
/**
* 更新任务
* @param status [OfflineMapCityBean.Status]
*/
private fun change(status: Int) {
downloadData.value?.let {
it.status = status
downloadData.postValue(it)
}
}
/**
* 添加下载任务观察者
*/
fun observer(lifecycleOwner: LifecycleOwner, ob: Observer<OfflineMapCityBean>) {
if (observer != null) {
downloadData.removeObserver(observer!!)
}
this.observer = ob
downloadData.observe(lifecycleOwner, observer!!)
}
/**
* 下载文件
*/
private suspend fun download() = withContext(context = Dispatchers.IO, block = {
val downloadInfo = downloadData.value ?: throw IOException("jingo Download info is null")
//创建离线地图 下载文件夹,.map文件夹的下一级
val fileDir = File("${Constant.OFFLINE_MAP_PATH}download")
if (!fileDir.exists()) {
fileDir.mkdirs()
}
//遍历文件夹,找到对应的省市.map文件
val files = fileDir.listFiles()
for (item in files) {
//用id找到对应的文件
if (item.isFile && item.name.startsWith(downloadInfo.id)) {
//判断文件的版本号是否一致
if (item.name.contains("_${downloadInfo.version}.map")) {
//都一致,说明文件已经下载完成,不用再次下载
change(OfflineMapCityBean.DONE)
return@withContext
}else{
}
break
}
}
//查看下.map文件夹在不在
val fileMap = File("${Constant.OFFLINE_MAP_PATH}${downloadInfo.fileName}")
val fileTemp =
File("${Constant.OFFLINE_MAP_PATH}download/${downloadInfo.id}_${downloadInfo.version}")
if (fileTemp.exists()) {
}
if (!fileMap.exists()) {
}
change(OfflineMapCityBean.LOADING)
val startPosition = downloadInfo.currentSize
//验证断点有效性
if (startPosition < 0) throw IOException("jingo Start position less than zero")
//下载的文件是否已经被删除
// if (startPosition > 0 && !TextUtils.isEmpty(downloadInfo.path))
// if (!File(downloadInfo.path).exists()) throw IOException("File does not exist")
val response = netApi.retrofitDownLoadFile(
start = "bytes=$startPosition-",
url = downloadInfo.url
)
val responseBody = response.body()
responseBody ?: throw IOException("jingo ResponseBody is null")
//文件长度
downloadInfo.fileSize = responseBody.contentLength()
//保存的文件名称
// if (TextUtils.isEmpty(downloadInfo.fileName))
// downloadInfo.fileName = UrlUtils.getUrlFileName(downloadInfo.url)
// //验证下载完成的任务与实际文件的匹配度
// if (startPosition == downloadInfo.fileSize && startPosition > 0) {
// if (file.exists() && startPosition == file.length()) {
// change(OfflineMapCityBean.DONE)
// return@withContext
// } else throw IOException("jingo The content length is not the same as the file length")
// }
//写入文件
val randomAccessFile = RandomAccessFile(fileTemp, "rwd")
randomAccessFile.seek(startPosition)
// if (downloadInfo.currentSize == 0L) {
// randomAccessFile.setLength(downloadInfo.fileSize)
// }
downloadInfo.currentSize = startPosition
val inputStream = responseBody.byteStream()
val bufferSize = 1024 * 2
val buffer = ByteArray(bufferSize)
try {
var readLength = 0
while (isActive) {
readLength = inputStream.read(buffer)
if (readLength != -1) {
randomAccessFile.write(buffer, 0, readLength)
downloadInfo.currentSize += readLength
change(OfflineMapCityBean.LOADING)
} else {
break
}
}
} finally {
inputStream.close()
randomAccessFile.close()
}
})
/**
*
*/
private fun checkFile(){
}
} }

View File

@@ -7,6 +7,7 @@ import android.widget.Toast
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.navinfo.omqs.R import com.navinfo.omqs.R
import com.navinfo.omqs.databinding.ActivityLoginBinding import com.navinfo.omqs.databinding.ActivityLoginBinding
@@ -30,41 +31,49 @@ class LoginActivity : PermissionsActivity() {
initView() initView()
} }
private fun initView() { /**
//登录校验,初始化成功 * 观察登录状态把Observer提出来是为了防止每次数据变化都会有新的observer创建
viewModel.loginStatus.observe(this) { * 还有为了方便释放(需不需要手动释放?不清楚)不需要释放,当viewmodel观察到activity/fragment 的生命周期时会自动释放
when (it) { * PS:不要在 observer 中修改 LiveData 的值的数据,会影响其他 observer
LoginStatus.LOGIN_STATUS_NET_LOADING -> { */
loginDialog("验证用户信息...") private val loginObserve = Observer<LoginStatus> {
} when (it) {
LoginStatus.LOGIN_STATUS_FOLDER_INIT -> { LoginStatus.LOGIN_STATUS_NET_LOADING -> {
loginDialog("检查本地数据...") loginDialog("验证用户信息...")
} }
LoginStatus.LOGIN_STATUS_FOLDER_FAILURE -> { LoginStatus.LOGIN_STATUS_FOLDER_INIT -> {
Toast.makeText(this, "文件夹初始化失败", Toast.LENGTH_SHORT).show() loginDialog("检查本地数据...")
loginDialog?.dismiss() }
loginDialog = null LoginStatus.LOGIN_STATUS_FOLDER_FAILURE -> {
} Toast.makeText(this, "文件夹初始化失败", Toast.LENGTH_SHORT).show()
LoginStatus.LOGIN_STATUS_NET_FAILURE -> { loginDialog?.dismiss()
Toast.makeText(this, "网络访问失败", Toast.LENGTH_SHORT).show() loginDialog = null
loginDialog?.dismiss() }
loginDialog = null LoginStatus.LOGIN_STATUS_NET_FAILURE -> {
} Toast.makeText(this, "网络访问失败", Toast.LENGTH_SHORT).show()
LoginStatus.LOGIN_STATUS_SUCCESS -> { loginDialog?.dismiss()
val intent = Intent(this@LoginActivity, MainActivity::class.java) loginDialog = null
startActivity(intent) }
LoginStatus.LOGIN_STATUS_SUCCESS -> {
val intent = Intent(this@LoginActivity, MainActivity::class.java)
startActivity(intent)
// finish() // finish()
loginDialog?.dismiss() loginDialog?.dismiss()
loginDialog = null loginDialog = null
} }
LoginStatus.LOGIN_STATUS_CANCEL -> { LoginStatus.LOGIN_STATUS_CANCEL -> {
loginDialog?.dismiss() loginDialog?.dismiss()
loginDialog = null loginDialog = null
}
} }
} }
} }
private fun initView() {
//登录校验,初始化成功
viewModel.loginStatus.observe(this, loginObserve)
}
/** /**
* 登录dialog * 登录dialog
*/ */
@@ -73,7 +82,7 @@ class LoginActivity : PermissionsActivity() {
loginDialog = MaterialAlertDialogBuilder( loginDialog = MaterialAlertDialogBuilder(
this, com.google.android.material.R.style.MaterialAlertDialog_Material3 this, com.google.android.material.R.style.MaterialAlertDialog_Material3
).setTitle("登录").setMessage(message).show() ).setTitle("登录").setMessage(message).show()
loginDialog!!.setCanceledOnTouchOutside(true) loginDialog!!.setCanceledOnTouchOutside(false)
loginDialog!!.setOnCancelListener { loginDialog!!.setOnCancelListener {
viewModel.cancelLogin() viewModel.cancelLogin()
} }
@@ -84,13 +93,17 @@ class LoginActivity : PermissionsActivity() {
//进应用根本不调用,待查 //进应用根本不调用,待查
override fun onPermissionsGranted() { override fun onPermissionsGranted() {
Log.e("jingo","调用了吗") Log.e("jingo", "调用了吗")
} }
override fun onPermissionsDenied() { override fun onPermissionsDenied() {
} }
override fun onDestroy() {
super.onDestroy()
}
/** /**
* 处理登录按钮 * 处理登录按钮
*/ */

View File

@@ -5,6 +5,7 @@ import android.util.Log
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.navinfo.omqs.Constant import com.navinfo.omqs.Constant
@@ -65,7 +66,7 @@ class LoginViewModel(
*/ */
fun onClick(view: View) { fun onClick(view: View) {
loginUser.value!!.username = "admin2" loginUser.value!!.username = "admin2"
loginUser.postValue(loginUser.value) loginUser.value = loginUser.value
} }
/** /**
@@ -81,7 +82,6 @@ class LoginViewModel(
//不指定IO会在主线程里运行 //不指定IO会在主线程里运行
jobLogin = viewModelScope.launch(Dispatchers.IO) { jobLogin = viewModelScope.launch(Dispatchers.IO) {
loginCheck(context, userName, password) loginCheck(context, userName, password)
Log.e("jingo", "运行完了1${Thread.currentThread().name}")
} }
} }
@@ -90,14 +90,12 @@ class LoginViewModel(
*/ */
private suspend fun loginCheck(context: Context, userName: String, password: String) { private suspend fun loginCheck(context: Context, userName: String, password: String) {
Log.e("jingo", "我在哪个线程里?${Thread.currentThread().name}")
//上面调用了线程切换,这里不用调用,即使调用了还是在同一个线程中,除非自定义协程域?(待验证) //上面调用了线程切换,这里不用调用,即使调用了还是在同一个线程中,除非自定义协程域?(待验证)
// withContext(Dispatchers.IO) { // withContext(Dispatchers.IO) {
Log.e("jingo", "delay之前${Thread.currentThread().name}")
//网络访问 //网络访问
loginStatus.postValue(LoginStatus.LOGIN_STATUS_NET_LOADING) loginStatus.postValue(LoginStatus.LOGIN_STATUS_NET_LOADING)
//假装网络访问,等待3 //假装网络访问,等待2
delay(3000) delay(1000)
//文件夹初始化 //文件夹初始化
try { try {
loginStatus.postValue(LoginStatus.LOGIN_STATUS_FOLDER_INIT) loginStatus.postValue(LoginStatus.LOGIN_STATUS_FOLDER_INIT)
@@ -108,7 +106,6 @@ class LoginViewModel(
//假装解压文件等 //假装解压文件等
delay(1000) delay(1000)
loginStatus.postValue(LoginStatus.LOGIN_STATUS_SUCCESS) loginStatus.postValue(LoginStatus.LOGIN_STATUS_SUCCESS)
Log.e("jingo", "delay之后${Thread.currentThread().name}")
// } // }
} }
@@ -121,6 +118,7 @@ class LoginViewModel(
sdCardPath?.let { sdCardPath?.let {
Constant.ROOT_PATH = sdCardPath.absolutePath Constant.ROOT_PATH = sdCardPath.absolutePath
Constant.MAP_PATH = Constant.ROOT_PATH + "/map/" Constant.MAP_PATH = Constant.ROOT_PATH + "/map/"
Constant.OFFLINE_MAP_PATH = Constant.MAP_PATH + "offline/"
val file = File(Constant.MAP_PATH) val file = File(Constant.MAP_PATH)
if (!file.exists()) { if (!file.exists()) {
file.mkdirs() file.mkdirs()
@@ -135,7 +133,7 @@ class LoginViewModel(
Log.e("jingo", "取消了?${Thread.currentThread().name}") Log.e("jingo", "取消了?${Thread.currentThread().name}")
jobLogin?.let { jobLogin?.let {
it.cancel() it.cancel()
loginStatus.postValue(LoginStatus.LOGIN_STATUS_CANCEL) loginStatus.value = LoginStatus.LOGIN_STATUS_CANCEL
} }
} }

View File

@@ -11,6 +11,7 @@ import com.navinfo.collect.library.map.NIMapController
import com.navinfo.omqs.Constant import com.navinfo.omqs.Constant
import com.navinfo.omqs.R import com.navinfo.omqs.R
import com.navinfo.omqs.databinding.ActivityMainBinding import com.navinfo.omqs.databinding.ActivityMainBinding
import com.navinfo.omqs.http.offlinemapdownload.OfflineMapDownloadManager
import com.navinfo.omqs.ui.activity.BaseActivity import com.navinfo.omqs.ui.activity.BaseActivity
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
@@ -27,6 +28,8 @@ class MainActivity : BaseActivity() {
//注入地图控制器 //注入地图控制器
@Inject @Inject
lateinit var mapController: NIMapController lateinit var mapController: NIMapController
@Inject
lateinit var offlineMapDownloadManager: OfflineMapDownloadManager
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)

View File

@@ -3,6 +3,7 @@ package com.navinfo.omqs.ui.fragment.offlinemap
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import dagger.hilt.EntryPoint
/** /**
* 离线地图主页面viewpage适配器 * 离线地图主页面viewpage适配器

View File

@@ -1,33 +1,118 @@
package com.navinfo.omqs.ui.fragment.offlinemap package com.navinfo.omqs.ui.fragment.offlinemap
import androidx.databinding.ViewDataBinding import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Observer
import com.navinfo.omqs.R import com.navinfo.omqs.R
import com.navinfo.omqs.BR
import com.navinfo.omqs.bean.OfflineMapCityBean import com.navinfo.omqs.bean.OfflineMapCityBean
import com.navinfo.omqs.databinding.AdapterOfflineMapCityBinding import com.navinfo.omqs.databinding.AdapterOfflineMapCityBinding
import com.navinfo.omqs.http.offlinemapdownload.OfflineMapDownloadManager
import com.navinfo.omqs.ui.other.BaseRecyclerViewAdapter import com.navinfo.omqs.ui.other.BaseRecyclerViewAdapter
import com.navinfo.omqs.ui.other.BaseViewHolder import com.navinfo.omqs.ui.other.BaseViewHolder
import javax.inject.Inject import javax.inject.Inject
/** /**
* 离线地图城市列表 RecyclerView 适配器 * 离线地图城市列表 RecyclerView 适配器
*
* 在 RecycleView 的 ViewHolder 中监听 ViewModel 的 LiveData然后此时传递的 lifecycleOwner 是对应的 Fragment。由于 ViewHolder 的生命周期是比 Fragment 短的,所以当 ViewHolder 销毁时,由于 Fragment 的 Lifecycle 还没有结束,此时 ViewHolder 会发生内存泄露(监听的 LiveData 没有解绑)
* 这种场景下有两种解决办法:
*使用 LiveData 的 observeForever 然后在 ViewHolder 销毁前手动调用 removeObserver
*使用 LifecycleRegistry 给 ViewHolder 分发生命周期(这里使用了这个)
*/ */
class OfflineMapCityListAdapter @Inject constructor(
private val downloadManager: OfflineMapDownloadManager, private val context: Context
) : BaseRecyclerViewAdapter<OfflineMapCityBean>() {
class OfflineMapCityListAdapter @Inject constructor() :
BaseRecyclerViewAdapter<OfflineMapCityBean>() {
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
var binding: ViewDataBinding = holder.dataBinding
//立刻刷新UI解决闪烁
// binding.executePendingBindings()
binding.setVariable(BR.cityBean, data[position])
(binding as AdapterOfflineMapCityBinding).offlineMapDownloadBtn.setOnClickListener {
private val downloadBtnClick = View.OnClickListener() {
if (it.tag != null) {
val cityBean = data[it.tag as Int]
when (cityBean.status) {
OfflineMapCityBean.NONE, OfflineMapCityBean.UPDATE, OfflineMapCityBean.PAUSE, OfflineMapCityBean.ERROR -> {
downloadManager.start(cityBean.id)
}
OfflineMapCityBean.LOADING, OfflineMapCityBean.WAITING -> {
downloadManager.pause(cityBean.id)
}
// OfflineMapCityBean.WAITING->{
// downloadManager.cancel(cityBean.id)
// }
}
} }
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
val viewBinding =
AdapterOfflineMapCityBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return BaseViewHolder(viewBinding)
}
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
val binding: AdapterOfflineMapCityBinding =
holder.viewBinding as AdapterOfflineMapCityBinding
//牺牲性能立刻刷新UI解决闪烁 这里不用
// binding.executePendingBindings()
val cityBean = data[position]
binding.offlineMapDownloadBtn.tag = position
binding.offlineMapDownloadBtn.setOnClickListener(downloadBtnClick)
binding.offlineMapCityName.text = cityBean.name
binding.offlineMapCitySize.text = cityBean.getFileSizeText()
downloadManager.addTask(cityBean)
changeViews(binding, cityBean)
downloadManager.observer(cityBean.id, holder) {
if (cityBean.id == it.id)
changeViews(binding, it)
}
}
private fun changeViews(binding: AdapterOfflineMapCityBinding, cityBean: OfflineMapCityBean) {
binding.offlineMapProgress.progress =
(cityBean.currentSize * 100 / cityBean.fileSize).toInt()
when (cityBean.status) {
OfflineMapCityBean.NONE -> {
if (binding.offlineMapProgress.visibility == View.VISIBLE) binding.offlineMapProgress.visibility =
View.INVISIBLE
binding.offlineMapDownloadBtn.text = "下载"
}
OfflineMapCityBean.WAITING -> {
if (binding.offlineMapProgress.visibility != View.VISIBLE) binding.offlineMapProgress.visibility =
View.VISIBLE
binding.offlineMapDownloadBtn.text = "等待中"
}
OfflineMapCityBean.LOADING -> {
if (binding.offlineMapProgress.visibility != View.VISIBLE) binding.offlineMapProgress.visibility =
View.VISIBLE
binding.offlineMapDownloadBtn.text = "暂停"
}
OfflineMapCityBean.PAUSE -> {
if (binding.offlineMapProgress.visibility != View.VISIBLE) binding.offlineMapProgress.visibility =
View.VISIBLE
binding.offlineMapDownloadBtn.text = "继续"
}
OfflineMapCityBean.ERROR -> {
if (binding.offlineMapProgress.visibility != View.VISIBLE) binding.offlineMapProgress.visibility =
View.VISIBLE
binding.offlineMapDownloadBtn.text = "重试"
}
OfflineMapCityBean.DONE -> {
if (binding.offlineMapProgress.visibility == View.VISIBLE) binding.offlineMapProgress.visibility =
View.INVISIBLE
binding.offlineMapDownloadBtn.text = "已完成"
}
OfflineMapCityBean.UPDATE -> {
if (binding.offlineMapProgress.visibility == View.VISIBLE) binding.offlineMapProgress.visibility =
View.INVISIBLE
binding.offlineMapDownloadBtn.text = "更新"
}
}
} }
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
return R.layout.adapter_offline_map_city return R.layout.adapter_offline_map_city
} }
}
}

View File

@@ -10,17 +10,28 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.navinfo.omqs.databinding.FragmentOfflineMapCityListBinding import com.navinfo.omqs.databinding.FragmentOfflineMapCityListBinding
import com.navinfo.omqs.http.RetrofitNetworkServiceAPI
import com.navinfo.omqs.http.offlinemapdownload.OfflineMapDownloadManager
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
/** /**
* 离线地图城市列表 * 离线地图城市列表
*/ */
@AndroidEntryPoint @AndroidEntryPoint
class OfflineMapCityListFragment : Fragment() { class OfflineMapCityListFragment : Fragment() {
@Inject
lateinit var downloadManager: OfflineMapDownloadManager
private var _binding: FragmentOfflineMapCityListBinding? = null private var _binding: FragmentOfflineMapCityListBinding? = null
private val viewModel by viewModels<OfflineMapCityListViewModel>() private val viewModel by viewModels<OfflineMapCityListViewModel>()
private val binding get() = _binding!! private val binding get() = _binding!!
private val adapter: OfflineMapCityListAdapter by lazy { OfflineMapCityListAdapter() } private val adapter: OfflineMapCityListAdapter by lazy {
OfflineMapCityListAdapter(
downloadManager,
requireContext()
)
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
@@ -33,8 +44,10 @@ class OfflineMapCityListFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val layoutManager = LinearLayoutManager(context) val layoutManager = LinearLayoutManager(context)
_binding!!.offlineMapCityListRecyclerview.layoutManager = layoutManager //// 设置 RecyclerView 的固定大小,避免在滚动时重新计算视图大小和布局,提高性能
_binding!!.offlineMapCityListRecyclerview.adapter = adapter binding.offlineMapCityListRecyclerview.setHasFixedSize(true)
binding.offlineMapCityListRecyclerview.layoutManager = layoutManager
binding.offlineMapCityListRecyclerview.adapter = adapter
viewModel.cityListLiveData.observe(viewLifecycleOwner) { viewModel.cityListLiveData.observe(viewLifecycleOwner) {
adapter.refreshData(it) adapter.refreshData(it)
} }
@@ -44,6 +57,6 @@ class OfflineMapCityListFragment : Fragment() {
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
_binding = null _binding = null
Log.e("jingo","OfflineMapCityListFragment onDestroyView") Log.e("jingo", "OfflineMapCityListFragment onDestroyView")
} }
} }

View File

@@ -32,7 +32,7 @@ class OfflineMapCityListViewModel @Inject constructor(
viewModelScope.launch { viewModelScope.launch {
when (val result = networkService.getOfflineMapCityList()) { when (val result = networkService.getOfflineMapCityList()) {
is NetResult.Success -> { is NetResult.Success -> {
cityListLiveData.postValue(result.data!!) cityListLiveData.postValue(result.data?.sortedBy { bean -> bean.id })
} }
is NetResult.Error -> { is NetResult.Error -> {
Toast.makeText(context, "${result.exception.message}", Toast.LENGTH_SHORT) Toast.makeText(context, "${result.exception.message}", Toast.LENGTH_SHORT)

View File

@@ -0,0 +1,10 @@
package com.navinfo.omqs.ui.fragment.offlinemap
import com.navinfo.omqs.databinding.AdapterOfflineMapCityBinding
import com.navinfo.omqs.ui.other.BaseViewHolder
class OfflineMapViewHolder(dataBinding: AdapterOfflineMapCityBinding) : BaseViewHolder(dataBinding) {
init{
dataBinding.offlineMapDownloadBtn
}
}

View File

@@ -1,6 +1,7 @@
package com.navinfo.omqs.ui.other package com.navinfo.omqs.ui.other
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View.OnClickListener
import android.view.ViewGroup import android.view.ViewGroup
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@@ -10,23 +11,51 @@ import androidx.recyclerview.widget.RecyclerView
*/ */
abstract class BaseRecyclerViewAdapter<T>(var data: List<T> = listOf()) : abstract class BaseRecyclerViewAdapter<T>(var data: List<T> = listOf()) :
RecyclerView.Adapter<BaseViewHolder>() { RecyclerView.Adapter<BaseViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { // private var recyclerView: RecyclerView? = null
return BaseViewHolder( // override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
DataBindingUtil.inflate( //
LayoutInflater.from(parent.context), //
viewType, //
parent, // return BaseViewHolder(
false // DataBindingUtil.inflate(
) // LayoutInflater.from(parent.context),
) // viewType,
} // parent,
// false
// )
// )
// }
override fun getItemCount(): Int { override fun getItemCount(): Int {
return data.size return data.size
} }
fun refreshData(newData:List<T>){ fun refreshData(newData: List<T>) {
this.data = newData this.data = newData
this.notifyDataSetChanged() this.notifyDataSetChanged()
} }
override fun onViewAttachedToWindow(holder: BaseViewHolder) {
super.onViewAttachedToWindow(holder)
holder.onStart()
}
override fun onViewDetachedFromWindow(holder: BaseViewHolder) {
super.onViewDetachedFromWindow(holder)
holder.apply {
onStop()
}
}
//
// override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
// super.onAttachedToRecyclerView(recyclerView)
// this.recyclerView = recyclerView
// }
//
// override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
// super.onDetachedFromRecyclerView(recyclerView)
// this.recyclerView = null
// }
} }

View File

@@ -1,11 +1,54 @@
package com.navinfo.omqs.ui.other package com.navinfo.omqs.ui.other
import androidx.databinding.ViewDataBinding import android.view.View
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
/** /**
* dataBinding viewHolder 基类 * dataBinding viewHolder 基类
* LifecycleRegistry 这是一个生命周期注册器,继承自 LifecycleLifecycleOwner 通过这个类来分发生命周期事件,并在 getLifecycle() 中返回
*/ */
open class BaseViewHolder(var dataBinding: ViewDataBinding) : open class BaseViewHolder(val viewBinding: ViewBinding) :
RecyclerView.ViewHolder(dataBinding.root) { RecyclerView.ViewHolder(viewBinding.root), LifecycleOwner {
private val lifecycleRegistry = LifecycleRegistry(this)
init {
// dataBinding.lifecycleOwner = this
lifecycleRegistry.currentState = Lifecycle.State.INITIALIZED
lifecycleRegistry.currentState = Lifecycle.State.CREATED
itemView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
// View onDetached 的时候回调 onDestroy()
override fun onViewDetachedFromWindow(v: View) {
itemView.removeOnAttachStateChangeListener(this)
onDestroy()
}
// View onAttached 的时候回调 onCreate()
override fun onViewAttachedToWindow(v: View) {
onStart()
}
})
}
fun onStart() {
lifecycleRegistry.currentState = Lifecycle.State.STARTED //
lifecycleRegistry.currentState = Lifecycle.State.RESUMED // ON_RESUME EVENT
}
fun onStop() {
lifecycleRegistry.currentState = Lifecycle.State.STARTED //
lifecycleRegistry.currentState = Lifecycle.State.CREATED // ON_STOP EVENT
}
fun onDestroy() {
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED /// ON_DESTROY EVENT
}
override fun getLifecycle(): Lifecycle {
return lifecycleRegistry
}
} }

View File

@@ -5,10 +5,13 @@ import android.graphics.Canvas
import android.graphics.Color import android.graphics.Color
import android.graphics.Paint import android.graphics.Paint
import android.graphics.Rect import android.graphics.Rect
import android.opengl.ETC1.getHeight
import android.opengl.ETC1.getWidth
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import android.widget.ProgressBar import android.widget.ProgressBar
import com.navinfo.omqs.R
/** /**
@@ -18,8 +21,13 @@ class MyProgressBar : ProgressBar {
private lateinit var mPaint: Paint private lateinit var mPaint: Paint
private var text: String = "" private var text: String = ""
private var rate = 0f private var rate = 0f
private lateinit var bar: ProgressBar
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
// LayoutInflater.from(context).inflate(
// R.layout.my_projressbar, this,
// true
// );
initView() initView()
} }
@@ -33,6 +41,7 @@ class MyProgressBar : ProgressBar {
mPaint.color = Color.BLUE mPaint.color = Color.BLUE
} }
@Synchronized @Synchronized
override fun setProgress(progress: Int) { override fun setProgress(progress: Int) {
setText(progress) setText(progress)
@@ -40,7 +49,7 @@ class MyProgressBar : ProgressBar {
} }
private fun setText(progress: Int) { private fun setText(progress: Int) {
rate = progress * 1.0f / this.getMax() rate = progress * 1.0f / this.max
val i = (rate * 100).toInt() val i = (rate * 100).toInt()
text = "$i%" text = "$i%"
} }
@@ -57,8 +66,9 @@ class MyProgressBar : ProgressBar {
// 如果为百分之百则在左边绘制。 // 如果为百分之百则在左边绘制。
x = width - rect.right x = width - rect.right
} }
val y: Int = 0 - rect.top mPaint.textSize = 24f
mPaint.textSize = 22f val y: Int = 10 - rect.top
canvas.drawText(text, x.toFloat(), y.toFloat(), mPaint) canvas.drawText(text, x.toFloat(), y.toFloat(), mPaint)
} }
} }

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!--设置ProgressBar背景色-->
<item android:id="@android:id/background">
<shape>
<!--设置ProgressBar进度条圆角半径-->
<corners android:radius="4dp" />
<solid android:color="@color/cv_gray_153" />
</shape>
</item>
<!--设置ProgressBar进度条颜色-->
<item android:id="@android:id/progress">
<clip android:clipOrientation="horizontal">
<shape>
<corners android:radius="4dp" />
<solid android:color="@color/default_blue" />
</shape>
</clip>
</item>
</layer-list>

View File

@@ -17,8 +17,7 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
>
<ImageView <ImageView
android:id="@+id/login_fragment_logo" android:id="@+id/login_fragment_logo"

View File

@@ -1,82 +1,67 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/cv_bg_color"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="5dp"
tools:context="com.navinfo.omqs.ui.fragment.offlinemap.OfflineMapCityListAdapter"> tools:context="com.navinfo.omqs.ui.fragment.offlinemap.OfflineMapCityListAdapter">
<data> <TextView
android:id="@+id/offline_map_city_name"
<import type="com.navinfo.omqs.R" /> android:layout_width="wrap_content"
<variable
name="cityBean"
type="com.navinfo.omqs.bean.OfflineMapCityBean" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="10dp" android:text="省市名称"
android:background="@color/cv_bg_color"> android:textColor="@color/white"
android:textSize="@dimen/default_font_size" />
<TextView <TextView
android:id="@+id/offline_map_city_name" android:id="@+id/offline_map_city_size"
android:layout_width="wrap_content" style="@style/map_size_font_style"
android:layout_height="wrap_content" android:layout_width="wrap_content"
android:text="@{cityBean.name}" android:layout_height="wrap_content"
android:textColor="@color/white" android:layout_below="@id/offline_map_city_name"
android:textSize="@dimen/default_font_size" android:drawableLeft="@mipmap/point_blue"
app:layout_constraintLeft_toLeftOf="@id/offline_map_city_size" android:layout_marginTop="5dp"
app:layout_constraintRight_toRightOf="@id/offline_map_city_size" android:text="文件大小"
app:layout_constraintTop_toTopOf="parent" /> android:textSize="@dimen/card_title_font_3size" />
<TextView
android:id="@+id/offline_map_city_size"
style="@style/map_size_font_style"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@mipmap/point_blue"
android:textSize="@dimen/card_title_font_3size"
android:text="@{cityBean.getFileSizeText()}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/offline_map_city_name" />
<TextView <TextView
android:id="@+id/tv_city_list_status" android:id="@+id/offline_map_download_btn"
android:layout_width="wrap_content" style="@style/map_download_style_btn"
android:layout_height="wrap_content" android:layout_width="60dp"
android:layout_centerVertical="true" android:layout_alignTop="@id/offline_map_city_name"
android:clickable="true" android:layout_alignBottom="@id/offline_map_city_size"
android:focusable="false" android:layout_alignParentRight="true"
android:shadowColor="@android:color/transparent" android:shadowColor="@android:color/transparent"
android:textColor="@color/white" android:text="下载"
android:textSize="@dimen/card_title_font_2size" android:textColor="@color/btn_blue_solid"
app:layout_constraintBottom_toBottomOf="@id/offline_map_download_btn" android:textSize="@dimen/card_title_font_2size" />
app:layout_constraintRight_toLeftOf="@id/offline_map_download_btn"
app:layout_constraintTop_toTopOf="@id/offline_map_download_btn" />
<TextView <TextView
android:id="@+id/offline_map_download_btn" android:id="@+id/tv_city_list_status"
style="@style/map_download_style_btn" android:layout_width="wrap_content"
android:layout_alignParentRight="true" android:layout_height="wrap_content"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:shadowColor="@android:color/transparent" android:layout_marginBottom="10dp"
android:text="下载" android:layout_toLeftOf="@id/offline_map_download_btn"
android:textColor="@color/btn_blue_solid" android:clickable="true"
android:textSize="@dimen/card_title_font_2size" android:focusable="false"
app:layout_constraintRight_toRightOf="parent" android:shadowColor="@android:color/transparent"
app:layout_constraintBottom_toTopOf="@id/offline_map_progress" android:textColor="@color/white"
app:layout_constraintTop_toTopOf="parent" /> android:textSize="@dimen/card_title_font_2size" />
<com.navinfo.omqs.ui.widget.MyProgressBar <com.navinfo.omqs.ui.widget.MyProgressBar
android:layout_marginTop="5dp" android:id="@+id/offline_map_progress"
android:visibility="gone" style="?android:attr/progressBarStyleHorizontal"
android:id="@+id/offline_map_progress" android:layout_width="match_parent"
style="?android:attr/progressBarStyleHorizontal" android:layout_height="16dp"
android:layout_width="match_parent" android:layout_below="@id/offline_map_download_btn"
android:layout_height="wrap_content" android:progressDrawable="@drawable/progress_bg"
android:max="100" android:paddingTop="10dp"
app:layout_constraintBottom_toBottomOf="parent" android:visibility="invisible" />
app:layout_constraintLeft_toLeftOf="parent" /> </RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -429,7 +429,6 @@ public class GeometryTools {
dList.add(lt + dis); dList.add(lt + dis);
total += dis; total += dis;
} }
Log.e("jingo", "line lengh =" + total);
total = total / 2; total = total / 2;
for (int i = 0; i < dList.size(); i++) { for (int i = 0; i < dList.size(); i++) {
double a = dList.get(i); double a = dList.get(i);
@@ -495,7 +494,6 @@ public class GeometryTools {
dList.add(lt + dis); dList.add(lt + dis);
total += dis; total += dis;
} }
Log.e("jingo", "line lengh =" + total);
total = total / 2; total = total / 2;
for (int i = 0; i < dList.size(); i++) { for (int i = 0; i < dList.size(); i++) {
double a = dList.get(i); double a = dList.get(i);