package com.casic.smarttube.fragment import android.graphics.Color import android.graphics.Point import android.os.Bundle import android.os.Handler import android.os.Message import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.DividerItemDecoration import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions import com.amap.api.maps.AMapUtils import com.amap.api.maps.CameraUpdateFactory import com.amap.api.maps.CoordinateConverter import com.amap.api.maps.model.CameraPosition import com.amap.api.maps.model.LatLng import com.amap.api.maps.model.Marker import com.amap.api.maps.model.MarkerOptions import com.amap.api.services.core.LatLonPoint import com.amap.api.services.geocoder.GeocodeResult import com.amap.api.services.geocoder.GeocodeSearch import com.amap.api.services.geocoder.RegeocodeQuery import com.amap.api.services.geocoder.RegeocodeResult import com.casic.smarttube.R import com.casic.smarttube.adapter.GroupListAdapter import com.casic.smarttube.databinding.FragmentHomeBinding import com.casic.smarttube.model.MapDeviceModel import com.casic.smarttube.model.ProjectGroupModel import com.casic.smarttube.utils.LoadingDialogHub import com.casic.smarttube.utils.LocaleConstant import com.casic.smarttube.utils.RouteOnMap import com.casic.smarttube.view.AddDeviceActivity import com.casic.smarttube.view.MapDeviceBriefActivity import com.casic.smarttube.vm.DeviceViewModel import com.casic.smarttube.vm.ProjectGroupViewModel import com.casic.smarttube.widgets.GaoDeClusterMarkerView import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.LoadState import com.pengxh.kt.lite.utils.WeakReferenceHandler import com.pengxh.kt.lite.widget.EasyPopupWindow import com.pengxh.kt.lite.widget.dialog.AlertControlDialog import com.pengxh.kt.lite.widget.dialog.BottomActionSheet class HomePageFragment : KotlinBaseFragment<FragmentHomeBinding>(), Handler.Callback, AMap.OnCameraChangeListener, AMap.OnMarkerClickListener, AMap.InfoWindowAdapter, AMap.OnInfoWindowClickListener { private val kTag = "HomePageFragment" private val geocoderSearch by lazy { GeocodeSearch(requireContext()) } private val easyPopupWindow by lazy { EasyPopupWindow(requireContext()) } private lateinit var weakReferenceHandler: WeakReferenceHandler private lateinit var aMap: AMap private lateinit var deviceViewModel: DeviceViewModel private lateinit var groupViewModel: ProjectGroupViewModel private lateinit var groupListAdapter: GroupListAdapter private lateinit var multiDevice: ArrayList<String> private var dataBeans: MutableList<ProjectGroupModel.DataModel> = ArrayList() private var isRefresh = false /** * 所有的marker */ private var allMarkerOptions: MutableList<MarkerOptions> = ArrayList() /** * 视野内的marker */ private var markerOptionsInView: MutableList<MarkerOptions> = ArrayList() /** * 自定义Marker弹出框 * */ private var infoView: View? = null /** * 所有设备列表信息集合 * */ private var deviceModels: MutableList<MapDeviceModel.DataModel> = ArrayList() /** * 保存地图缩放等级 * */ private var mapZooms = ArrayList<Float>() override fun initViewBinding( inflater: LayoutInflater, container: ViewGroup? ): FragmentHomeBinding { return FragmentHomeBinding.inflate(inflater, container, false) } override fun setupTopBarLayout() { } override fun observeRequestState() { groupViewModel.loadState.observe(this) { state -> when (state) { LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "数据加载中...") else -> LoadingDialogHub.dismiss() } } } override fun initOnCreate(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) val menuItems = ArrayList<EasyPopupWindow.MenuItem>().apply { add( EasyPopupWindow.MenuItem( LocaleConstant.POPUP_IMAGES[0], LocaleConstant.POPUP_TITLES[0] ) ) add( EasyPopupWindow.MenuItem( LocaleConstant.POPUP_IMAGES[1], LocaleConstant.POPUP_TITLES[1] ) ) } easyPopupWindow.set(menuItems, object : EasyPopupWindow.OnPopupWindowClickListener { override fun onPopupItemClicked(position: Int) { when (position) { 0 -> aMap.mapType = AMap.MAP_TYPE_NORMAL 1 -> aMap.mapType = AMap.MAP_TYPE_SATELLITE } } }) //初始化vm deviceViewModel = ViewModelProvider(this)[DeviceViewModel::class.java] groupViewModel = ViewModelProvider(this)[ProjectGroupViewModel::class.java] //默认数据 groupViewModel.obtainProGroupList() //地图初始化,默认地图缩放13级 mapZooms.add(13f) initMap(savedInstanceState) //数据监听 deviceViewModel.mapDeviceModel.observe(this) { if (it.code == 200) { if (allMarkerOptions.isNotEmpty()) { allMarkerOptions.clear() } val latitudeList: MutableList<Double> = ArrayList() val longitudeList: MutableList<Double> = ArrayList() if (deviceModels.isNotEmpty()) { deviceModels.clear() } it.data.forEach { device -> val lat = device.latGaode.toString() val lng = device.lngGaode.toString() if (lat.isNotBlank() && lng.isNotBlank()) { //返回true代表当前位置在大陆、港澳地区,反之不在 val latitude = lat.toDouble() val longitude = lng.toDouble() if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { //缓存所有设备详情,点击Marker时候需要 deviceModels.add(device) //分别缓存经、纬度 latitudeList.add(latitude) longitudeList.add(longitude) //将所有设备信息转化缓存为Marker点 allMarkerOptions.add( MarkerOptions().position(LatLng(latitude, longitude)) .title(device.devcode).snippet(device.modelName) ) } } } val centerLatLng = LatLng(latitudeList[0], longitudeList[0]) moveToPosition(centerLatLng, 13f) } } groupViewModel.groupModel.observe(this) { if (it.code == 200) { val dataRows = it.data when { isRefresh -> { dataBeans.clear() dataBeans = dataRows!! isRefresh = false } else -> { dataBeans = dataRows!! } } weakReferenceHandler.sendEmptyMessage(2022090201) } } groupViewModel.groupDeviceModel.observe(this) { if (it.code == 200) { val latitudeList: MutableList<Double> = ArrayList() val longitudeList: MutableList<Double> = ArrayList() it.data.forEach { device -> val lat = device.latGaode.toString() val lng = device.lngGaode.toString() if (lat.isNotBlank() && lng.isNotBlank()) { //返回true代表当前位置在大陆、港澳地区,反之不在 val latitude = lat.toDouble() val longitude = lng.toDouble() if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { //分别缓存经、纬度 latitudeList.add(latitude) longitudeList.add(longitude) } } } //计算所有点的中心点位置 val centerLatLng = LatLng(latitudeList[0], longitudeList[0]) moveToPosition(centerLatLng, 16f) } } } override fun initEvent() { binding.rightOptionView.setOnClickListener { easyPopupWindow.setBackgroundDrawable(null) val x = binding.rightOptionView.width - easyPopupWindow.width easyPopupWindow.showAsDropDown(binding.rightOptionView, x, 0) } binding.addDeviceButton.setOnClickListener { requireContext().navigatePageTo<AddDeviceActivity>() } } override fun onResume() { super.onResume() binding.mapView.onResume() //获取所有设备数据 deviceViewModel.obtainMapDeviceList() //获取组 groupViewModel.obtainProGroupList() } override fun handleMessage(msg: Message): Boolean { if (msg.what == 2022090201) { if (isRefresh) { groupListAdapter.notifyDataSetChanged() } else { groupListAdapter = GroupListAdapter(requireContext(), dataBeans) binding.homeRecyclerView.addItemDecoration( DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL) ) binding.homeRecyclerView.adapter = groupListAdapter groupListAdapter.setOnItemClickListener(object : GroupListAdapter.OnItemClickListener { override fun onClicked(position: Int) { // 根据groupId查询组下设备 groupViewModel.obtainDeviceListByGroup(dataBeans[position].groupId) } }) } } return true } private fun initMap(savedInstanceState: Bundle?) { binding.mapView.onCreate(savedInstanceState) aMap = binding.mapView.map aMap.mapType = AMap.MAP_TYPE_NORMAL val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isTiltGesturesEnabled = false//不许地图随手势倾斜角度 uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图缩放监听 aMap.addOnCameraChangeListener(this) // marker 点击事件监听 aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup aMap.setInfoWindowAdapter(this) // 点击popup aMap.setOnInfoWindowClickListener(this) } //移动到指定经纬度 private fun moveToPosition(latLng: LatLng, scale: Float) { //移动到指定经纬度 val cameraPosition = CameraPosition(latLng, scale, 0f, 0f) val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) aMap.animateCamera(cameraUpdate, 1500, null) } override fun onCameraChange(cameraPosition: CameraPosition?) { } //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker override fun onCameraChangeFinish(cameraPosition: CameraPosition?) { if (mapZooms.last() != cameraPosition?.zoom) { aMap.clear() mapZooms.add(cameraPosition?.zoom!!) } //地图缩放之后显示聚合点数据 initClustersMarkers() } private fun initClustersMarkers() { val proj = aMap.projection val dm = resources.displayMetrics var screenLocation: Point markerOptionsInView.clear() // 获取在当前视野内的marker allMarkerOptions.forEach { screenLocation = proj.toScreenLocation(it.position) if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { //在当前可观区域内 markerOptionsInView.add(it) } } // 自定义的聚合类MarkerCluster val clustersMarkers: MutableList<GaoDeClusterMarkerView> = ArrayList() markerOptionsInView.forEach { if (clustersMarkers.size == 0) { //添加一个新的自定义marker clustersMarkers.add( GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) ) } else { var isInRange = false //Kotlin foreach不能用break for (view in clustersMarkers) { //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 if (view.bounds.contains(it.position)) { view.addMarker(it) isInRange = true break } } //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 if (!isInRange) { clustersMarkers.add( GaoDeClusterMarkerView( requireContext(), it, proj, LocaleConstant.RADIUS_SIZE ) )//相距多少才聚合 } } } // 设置聚合点的位置和icon clustersMarkers.forEach { it.setPositionAndIcon() } // 重新添加 marker clustersMarkers.forEach { aMap.addMarker(it.options) } } override fun onMarkerClick(marker: Marker?): Boolean { //显示设备基本信息 marker?.showInfoWindow() return true } override fun getInfoWindow(marker: Marker?): View? { if (infoView == null) { infoView = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) } val v = infoView!! //反射得到popup里面的控件对象 val deviceCodeView = v.findViewById<TextView>(R.id.deviceCodeView) val deviceModelView = v.findViewById<TextView>(R.id.deviceModelView) val deviceValueView = v.findViewById<TextView>(R.id.deviceValueView) val updateTimeView = v.findViewById<TextView>(R.id.updateTimeView) val locationView = v.findViewById<TextView>(R.id.locationView) multiDevice = ArrayList() //绑定数据 val clickedLatLng = marker?.position!! for (device in deviceModels) { if (clickedLatLng.latitude == device.latGaode!!.toDouble() && clickedLatLng.longitude == device.lngGaode!!.toDouble()) { deviceCodeView.text = String.format("设备编号: ${device.devcode}") deviceModelView.text = String.format("设备模型: ${device.modelName}") //获取设备最新浓度信息 deviceViewModel.obtainTubeLastData(device.groupId, device.devcode) deviceViewModel.lastDataModel.observe(requireActivity(), { if (it.code == 200) { deviceValueView.text = String.format("最新浓度: ${it.data.strength}") updateTimeView.text = String.format("更新时间: ${it.data.uptime}") } else { deviceValueView.text = String.format("最新浓度: 未知") updateTimeView.text = String.format("更新时间: 未知") } }) if (device.latGaode.isBlank() || device.lngGaode.isBlank()) { locationView.text = "经纬度异常,无法查看具体位置" } else { val queryParam = RegeocodeQuery( LatLonPoint(device.latGaode.toDouble(), device.lngGaode.toDouble()), 200f, GeocodeSearch.AMAP ) geocoderSearch.getFromLocationAsyn(queryParam) geocoderSearch.setOnGeocodeSearchListener(object : GeocodeSearch.OnGeocodeSearchListener { override fun onRegeocodeSearched(result: RegeocodeResult?, rCode: Int) { if (rCode == 1000) { val address = result?.regeocodeAddress?.formatAddress val temp = StringBuilder() if (address!!.length > 16) { temp.append(address.substring(0, 14)).append("\r\n") .append(address.substring(16)) } else { temp.append(address) } locationView.text = String.format("详细位置: $temp") } } override fun onGeocodeSearched(result: GeocodeResult?, rCode: Int) { } }) } return infoView } } deviceModels.forEach { val lat = it.latGaode.toString() val lng = it.lngGaode.toString() if (lat.isNotBlank() && lng.isNotBlank()) { //返回true代表当前位置在大陆、港澳地区,反之不在 val latitude = lat.toDouble() val longitude = lng.toDouble() val calculateLineDistance = AMapUtils.calculateLineDistance( clickedLatLng, LatLng(latitude, longitude) ) if (calculateLineDistance < LocaleConstant.RADIUS_SIZE) { multiDevice.add(it.devcode) } } } //查看聚合点,单独开页面导航过去 BottomActionSheet.Builder().setContext(requireContext()).setActionItemTitle(multiDevice) .setItemTextColor(Color.BLUE) .setOnActionSheetListener(object : BottomActionSheet.OnActionSheetListener { override fun onActionItemClick(position: Int) { deviceModels.forEach { if (it.devcode == multiDevice[position]) { requireContext().navigatePageTo<MapDeviceBriefActivity>( arrayListOf(it.groupId, it.devcode, it.modelName) ) } } } }).build().show() return null } /** * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 * */ override fun getInfoContents(p0: Marker?): View? = null override fun onInfoWindowClick(p0: Marker?) { if (p0 != null) { AlertControlDialog.Builder().setContext(requireContext()).setTitle("操作提示") .setMessage("确定要前往吗").setNegativeButton("取消").setPositiveButton("确定") .setOnDialogButtonClickListener(object : AlertControlDialog.OnDialogButtonClickListener { override fun onConfirmClick() { val latLng = p0.position val lat = latLng.latitude.toString() val lng = latLng.longitude.toString() if (lat.isBlank() || lng.isBlank()) { "窨井经纬度异常,无法开启导航".show(requireContext()) return } RouteOnMap.startNavigation( requireContext(), p0.snippet, LatLng(lat.toDouble(), lng.toDouble()) ) } override fun onCancelClick() { p0.destroy() } }).build().show() } } /***以下是地图生命周期管理************************************************************************/ override fun onPause() { super.onPause() binding.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) binding.mapView.onSaveInstanceState(outState) } override fun onDestroy() { super.onDestroy() binding.mapView.onDestroy() } }