if(list !=null && list.size>0)的双重判断及先后顺序

本文详细解析了Java中List对象的判空操作与双重检查的原理,阐述了list!=null与list.size()>0的区别,并通过形象的例子帮助理解。同时,强调了正确顺序的重要性以避免空指针异常。

一、list!=null和list.size()>0的区别

1.list==null,意味着list压根没有地址,在堆内就不存在。
2.list.size()=0 意思堆内有list但是还没来得及放元素,其长度随着元素数量变化而变化,暂时为零。
3.list如果为null的话,说明没有进行初始化。这是list调用任何的方法都会抛出空异常。list.size()==0说明list已经被new过,但是里面没有值。
4.区别:
①有没有瓶子 list != null
②瓶子里有没有水 list.isEmpty()
判断的时候一定要注意先后顺序
如果没有瓶子都没有,直接判断有没有水,是会报nullException的
③另外:
list.add(null)
会造成list.isEmpty() 为 false, list.size() 为1
所以代码里要避免list.add(null)的陷阱
④比较形象一个比喻:
举个形象的例子,我有一个空着的水杯(list),而你没有,那你是null,我的size为0。你想装水需要去买个水杯(new ArrayList();),我就可以直接装水(list.add(水))。你要是没有杯子直接倒水,水就流出去啦(空指针异常)。所以用做判断的时候经常连用 list!=null && list.size()!=0 。

二、双重判断原理

List list=new ArrayList();
1.如果是以上这样的话,lis不为null 但是size=0,这样集合对于之后的判断是没用的, 甚至是报异常,如list.get(0.......n)这样取值判断,就会报异常。
2.如果先判断size 再判断null 如:
if(list.size>0&&list !=null){};
这种情况如果list 等于null 时那么list.size会报空指针异常
所以要双重判断这样写最好if(list !=null && list.size>0){};

private async Task GetChildrenRecursive(Itemv currentNode, ConcurrentBag<Itemv> result, HttpClient client, string apiUrl,List<Sys_Department> dep) { if (currentNode != null && dep.FindAll(x => x.DepartmentCode == currentNode.OrgStruc.OrgCode).Count <= 0&& !currentNode.OrgStruc.Parents.Contains("80007051")) { var savedep = new Sys_Department { DepartmentId = Guid.NewGuid(), DepartmentCode = currentNode.OrgStruc.OrgCode, DepartmentName = currentNode.OrgStruc.OrgName, ParentId = dep.Find(x => x.DepartmentCode == currentNode.OrgStruc.SupOrgCode)?.DepartmentId, CreateDate = DateTime.Now, Creator = "超级管理员", }; dep.Add(savedep); _sys_DepartmentRepository.Add(savedep); } // 添加当前节点 result.Add(currentNode); if (string.IsNullOrEmpty(currentNode.OrgStruc.OrgCode)) { return; } // 构建子节点请求 var childRequest = new RequestModel { Items = new ItemsData { OrgStruc = [ new() { Name = "SUP_ORG_CODE", Value = currentNode.OrgStruc.OrgCode, } , new() { Name = "STATUS", Value ="有效", } ] }, SplitPage = new SplitPageData { PageNumber = 1, PageSize = 200 } }; // 循环获取所有分页的子节点 bool hasMore; do { var childResponse = await GetResponseORGAsync(client, apiUrl, childRequest); hasMore = false; if (childResponse.Code && childResponse.Items.Count > 0) { foreach (var child in childResponse.Items) { // 递归处理每个子节点 await GetChildrenRecursive(child, result, client, apiUrl,dep); } // 检查是否还有下一页 hasMore = childResponse.Items.Count >= childRequest.SplitPage.PageSize; childRequest.SplitPage.PageNumber++; } } while (hasMore); } 太慢了,保证数据安全的情况下,优化速度
09-18
using UnityEngine.UI; using System.Collections; using DG.Tweening; using System.Collections.Generic; using UnityEngine.EventSystems; using UnityEngine; public class 聊天系统 : MonoBehaviour { // UI组件引用 public GameObject 聊天窗口; public InputField 消息输入框; public Button 发送按钮; public Button 关闭按钮; public Transform 消息容器; // 消息项的父物体(必须是ScrollView的Content) public GameObject 玩家消息预制体; // 玩家消息预制体(靠右) public GameObject 朋友消息预制体; // 朋友消息预制体(靠左) public ScrollRect 聊天滚动视图; // 手动指定滚动视图 public RectTransform 标题栏; // 用于拖拽的标题栏 // 地块信息显示 public GameObject 地块信息预制体; // 用于显示地块信息的预制体 // 聊天设置 public string 玩家名称 = "我"; public string 朋友名称 = "AI助手"; public Color 玩家消息颜色 = Color.blue; public Color 朋友消息颜色 = Color.green; public int 消息分割长度 = 20; // 消息分割长度 // 显示相关设置 public RectTransform 聊天内容区域; public bool 显示关键错误 = true; // 只保留关键错误显示控制 public Color 调试文本颜色 = Color.red; // 聊天状态 private bool 聊天窗口是否打开 = false; public 聊天邀请管理器 邀请管理器; // 字体相关 public Font 聊天字体; public int 字体大小 = 16; // UI层级相关 public Canvas 聊天画布; public int 消息文本层级 = 5; // 动画设置 public float 动画持续时间 = 0.3f; public float 缩放比例 = 0.8f; public float 弹性系数 = 1.1f; // 消息历史记录 private List<消息数据> 消息历史 = new List<消息数据>(); public int 最大消息数量 = 50; // 限制最大消息数量 // 拖拽相关变量 private bool 正在拖拽 = false; private Vector2 拖拽偏移量; private RectTransform 聊天窗口Rect; // 引用AI管理器 public AIManager ai管理器; // 当前显示的地块信息 private GameObject 当前地块信息显示 = null; public void 收到AI消息(string 内容) { // 使用朋友的名称和颜色显示AI消息 收到消息(朋友名称, 内容); } void Start() { 聊天窗口.SetActive(false); 聊天窗口是否打开 = false; 邀请管理器 = FindObjectOfType<聊天邀请管理器>(); // 获取聊天窗口的RectTransform用于拖拽 聊天窗口Rect = 聊天窗口.GetComponent<RectTransform>(); // 自动查找AI管理器 if (ai管理器 == null) { ai管理器 = FindObjectOfType<AIManager>(); if (ai管理器 != null) { ai管理器.聊天系统 = this; // 双向关联 } else if (显示关键错误) { Debug.LogWarning("场景中未找到AIManager组件,AI功能将不可用"); } } // 确保标题栏存在,若未指定则尝试查找 if (标题栏 == null) { Transform 标题栏Transform = 聊天窗口.transform.Find("标题栏"); if (标题栏Transform != null) { 标题栏 = 标题栏Transform.GetComponent<RectTransform>(); } else { 标题栏 = 聊天窗口Rect; } } // 自动查找滚动视图 if (聊天滚动视图 == null) 聊天滚动视图 = 消息容器.GetComponentInParent<ScrollRect>(); // 验证并修复显示问题 验证消息显示相关组件(); 修复输入框(); 修复消息容器布局(); 修复UI层级问题(); // 绑定事件 发送按钮.onClick.RemoveAllListeners(); 发送按钮.onClick.AddListener(发送消息); 关闭按钮.onClick.RemoveAllListeners(); 关闭按钮.onClick.AddListener(关闭聊天窗口); 消息输入框.onEndEdit.RemoveAllListeners(); 消息输入框.onEndEdit.AddListener(输入框回车发送); // 添加输入框按键监听,支持Ctrl+Enter换行 消息输入框.onValueChanged.AddListener(OnInputValueChanged); // 添加拖拽事件监听 添加拖拽事件监听(); StartCoroutine(监控输入框状态()); } // 添加拖拽事件监听 private void 添加拖拽事件监听() { if (标题栏 == null) return; // 添加事件触发器用于拖拽 EventTrigger trigger = 标题栏.GetComponent<EventTrigger>(); if (trigger == null) { trigger = 标题栏.gameObject.AddComponent<EventTrigger>(); } // 清除现有事件,避免重复添加 trigger.triggers.Clear(); // 鼠标按下事件 EventTrigger.Entry 按下事件 = new EventTrigger.Entry(); 按下事件.eventID = EventTriggerType.PointerDown; 按下事件.callback.AddListener((data) => 开始拖拽((PointerEventData)data)); trigger.triggers.Add(按下事件); // 鼠标拖动事件 EventTrigger.Entry 拖动事件 = new EventTrigger.Entry(); 拖动事件.eventID = EventTriggerType.Drag; 拖动事件.callback.AddListener((data) => 执行拖拽((PointerEventData)data)); trigger.triggers.Add(拖动事件); // 鼠标释放事件 EventTrigger.Entry 释放事件 = new EventTrigger.Entry(); 释放事件.eventID = EventTriggerType.PointerUp; 释放事件.callback.AddListener((data) => 结束拖拽()); trigger.triggers.Add(释放事件); // 鼠标离开事件 EventTrigger.Entry 离开事件 = new EventTrigger.Entry(); 离开事件.eventID = EventTriggerType.PointerExit; 离开事件.callback.AddListener((data) => 结束拖拽()); trigger.triggers.Add(离开事件); } // 开始拖拽 private void 开始拖拽(PointerEventData 事件数据) { 正在拖拽 = true; // 计算鼠标位置与窗口位置的偏移量 RectTransformUtility.ScreenPointToLocalPointInRectangle( 聊天窗口Rect, 事件数据.position, 事件数据.pressEventCamera, out 拖拽偏移量); } // 执行拖拽 private void 执行拖拽(PointerEventData 事件数据) { if (!正在拖拽 || 聊天窗口Rect == null) return; Vector2 本地鼠标位置; if (RectTransformUtility.ScreenPointToLocalPointInRectangle( 聊天窗口Rect.parent.GetComponent<RectTransform>(), 事件数据.position, 事件数据.pressEventCamera, out 本地鼠标位置)) { // 根据偏移量调整窗口位置 聊天窗口Rect.localPosition = 本地鼠标位置 - 拖拽偏移量; } } // 结束拖拽 private void 结束拖拽() { 正在拖拽 = false; } // 处理输入框内容变化,支持Ctrl+Enter换行 private void OnInputValueChanged(string input) { if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) { if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter)) { 消息输入框.text += "\n"; 消息输入框.caretPosition = 消息输入框.text.Length; } } } // 修复UI层级问题 private void 修复UI层级问题() { // 确保有Canvas组件 if (聊天画布 == null) { 聊天画布 = GetComponentInParent<Canvas>(); if (聊天画布 == null) { 聊天画布 = 聊天窗口.AddComponent<Canvas>(); 聊天窗口.AddComponent<CanvasScaler>(); 聊天窗口.AddComponent<GraphicRaycaster>(); } } // 确保消息容器在正确层级 if (消息容器 != null) { CanvasRenderer cr = 消息容器.GetComponent<CanvasRenderer>(); if (cr == null) { cr = 消息容器.gameObject.AddComponent<CanvasRenderer>(); } // 设置消息容器的层级 RectTransform rt = 消息容器.GetComponent<RectTransform>(); if (rt != null) { rt.SetAsLastSibling(); // 确保在最上层 } } } // 验证消息显示相关组件 private void 验证消息显示相关组件() { // 验证消息容器 if (消息容器 == null) { if (显示关键错误) Debug.LogError("错误:消息容器未赋值!"); return; } // 验证滚动视图 if (聊天滚动视图 == null) { // 移除布局提示 } else { // 确保消息容器是ScrollView的Content if (聊天滚动视图.content != 消息容器.GetComponent<RectTransform>()) { 聊天滚动视图.content = 消息容器.GetComponent<RectTransform>(); } // 修复滚动视图掩码问题 Mask mask = 聊天滚动视图.GetComponent<Mask>(); if (mask != null && !mask.showMaskGraphic) { mask.showMaskGraphic = true; } // 修复滚动条设置 if (聊天滚动视图.verticalScrollbar != null) { 聊天滚动视图.verticalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHideAndExpandViewport; 聊天滚动视图.verticalScrollbarSpacing = 2; } } // 验证预制体 if (玩家消息预制体 == null) { if (显示关键错误) Debug.LogError("错误:玩家消息预制体未赋值!"); } if (朋友消息预制体 == null) { if (显示关键错误) Debug.LogError("错误:朋友消息预制体未赋值!"); } // 检查预制体是否激活 if (玩家消息预制体 != null && !玩家消息预制体.activeSelf) { 玩家消息预制体.SetActive(true); } if (朋友消息预制体 != null && !朋友消息预制体.activeSelf) { 朋友消息预制体.SetActive(true); } // 验证文本组件 if (玩家消息预制体 != null) { Text 发送者文本 = 玩家消息预制体.transform.Find("发送者文本")?.GetComponent<Text>(); Text 内容文本 = 玩家消息预制体.transform.Find("内容文本")?.GetComponent<Text>(); if (发送者文本 == null || 内容文本 == null) { 修复消息项预制体(玩家消息预制体); } } if (朋友消息预制体 != null) { Text 发送者文本 = 朋友消息预制体.transform.Find("发送者文本")?.GetComponent<Text>(); Text 内容文本 = 朋友消息预制体.transform.Find("内容文本")?.GetComponent<Text>(); if (发送者文本 == null || 内容文本 == null) { 修复消息项预制体(朋友消息预制体); } } } // 修复消息项预制体 private void 修复消息项预制体(GameObject 预制体) { if (预制体 == null) return; // 添加发送者文本 if (预制体.transform.Find("发送者文本") == null) { GameObject 发送者文本对象 = new GameObject("发送者文本"); 发送者文本对象.transform.SetParent(预制体.transform); Text 发送者文本 = 发送者文本对象.AddComponent<Text>(); 发送者文本.font = 聊天字体 ?? Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf"); 发送者文本.fontSize = 字体大小; 发送者文本.color = Color.black; 发送者文本.enabled = true; 发送者文本.raycastTarget = false; // 设置位置 RectTransform rt = 发送者文本对象.GetComponent<RectTransform>(); rt.anchorMin = new Vector2(0, 1); rt.anchorMax = new Vector2(0, 1); rt.pivot = new Vector2(0, 1); rt.offsetMin = new Vector2(5, -25); rt.offsetMax = new Vector2(100, 0); } // 添加内容文本 if (预制体.transform.Find("内容文本") == null) { GameObject 内容文本对象 = new GameObject("内容文本"); 内容文本对象.transform.SetParent(预制体.transform); Text 内容文本 = 内容文本对象.AddComponent<Text>(); 内容文本.font = 聊天字体 ?? Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf"); 内容文本.fontSize = 字体大小; 内容文本.color = Color.black; 内容文本.enabled = true; 内容文本.raycastTarget = false; // 设置位置 RectTransform rt = 内容文本对象.GetComponent<RectTransform>(); rt.anchorMin = new Vector2(0, 1); rt.anchorMax = new Vector2(1, 1); rt.pivot = new Vector2(0, 1); rt.offsetMin = new Vector2(110, -25); rt.offsetMax = new Vector2(-5, 0); } // 移除任何可能存在的时间戳组件 Transform 时间戳Transform = 预制体.transform.Find("时间戳"); if (时间戳Transform != null) { DestroyImmediate(时间戳Transform.gameObject); } } // 修复消息容器布局 private void 修复消息容器布局() { if (消息容器 == null) return; // 确保容器激活 消息容器.gameObject.SetActive(true); // 添加垂直布局组 VerticalLayoutGroup layout = 消息容器.GetComponent<VerticalLayoutGroup>(); if (layout == null) { layout = 消息容器.gameObject.AddComponent<VerticalLayoutGroup>(); } // 修复间距和布局冲突问题 layout.spacing = 20; layout.childAlignment = TextAnchor.UpperLeft; layout.childControlWidth = true; layout.childControlHeight = true; layout.childForceExpandWidth = false; layout.childForceExpandHeight = false; layout.padding = new RectOffset(10, 10, 10, 10); // 添加内容大小适配 ContentSizeFitter fitter = 消息容器.GetComponent<ContentSizeFitter>(); if (fitter == null) { fitter = 消息容器.gameObject.AddComponent<ContentSizeFitter>(); } fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; fitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained; // 滚动视图设置优化 if (聊天滚动视图 != null) { 聊天滚动视图.vertical = true; 聊天滚动视图.horizontal = false; 聊天滚动视图.movementType = ScrollRect.MovementType.Clamped; 聊天滚动视图.content.sizeDelta = new Vector2(0, 0); 聊天滚动视图.scrollSensitivity = 40f; // 确保滚动条正确关联 if (聊天滚动视图.verticalScrollbar != null) { 聊天滚动视图.verticalScrollbar.value = 0; // 初始位置在底部 } } // 修复两个预制体的RectTransform 修复预制体RectTransform(玩家消息预制体, true); 修复预制体RectTransform(朋友消息预制体, false); } // 修复预制体的RectTransform private void 修复预制体RectTransform(GameObject 预制体, bool 是玩家消息) { if (预制体 == null) return; RectTransform rt = 预制体.GetComponent<RectTransform>(); if (rt != null) { rt.anchorMin = new Vector2(0, 1); rt.anchorMax = new Vector2(1, 1); rt.pivot = new Vector2(0.5f, 1); rt.offsetMin = new Vector2(0, 0); rt.offsetMax = new Vector2(0, 0); rt.localScale = Vector3.one; } // 确保文本可见 Text[] texts = 预制体.GetComponentsInChildren<Text>(); foreach (var text in texts) { text.enabled = true; text.font = 聊天字体 ?? Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf"); text.fontSize = 字体大小; text.horizontalOverflow = HorizontalWrapMode.Wrap; text.verticalOverflow = VerticalWrapMode.Overflow; } // 移除预制体中可能存在的时间戳 Transform 时间戳Transform = 预制体.transform.Find("时间戳"); if (时间戳Transform != null) { DestroyImmediate(时间戳Transform.gameObject); } } // 分割消息的方法 private List<string> 分割消息(string 内容) { List<string> 分割后的消息 = new List<string>(); // 如果消息长度小于等于分割长度,直接返回原消息 if (string.IsNullOrEmpty(内容) || 内容.Length <= 消息分割长度) { 分割后的消息.Add(内容); return 分割后的消息; } // 否则按指定长度分割消息 for (int i = 0; i < 内容.Length; i += 消息分割长度) { int 截取长度 = Mathf.Min(消息分割长度, 内容.Length - i); string 子消息 = 内容.Substring(i, 截取长度); 分割后的消息.Add(子消息); } return 分割后的消息; } // 显示消息项 private void 显示消息项(string 发送者, string 内容, Color 颜色, bool 是玩家消息, bool 显示发送者 = true) { if (消息容器 == null) { if (显示关键错误) Debug.LogError("消息容器未赋值!"); return; } // 根据是否是玩家消息选择不同的预制体 GameObject 消息预制体 = 是玩家消息 ? 玩家消息预制体 : 朋友消息预制体; if (消息预制体 == null) { if (显示关键错误) Debug.LogError("消息项预制体未赋值!"); return; } // 实例化消息项 GameObject 消息项 = Instantiate(消息预制体, 消息容器); 消息项.name = $"{(是玩家消息 ? "玩家" : "朋友")}_消息_{System.DateTime.Now.Ticks}"; 消息项.SetActive(true); 消息项.transform.SetAsLastSibling(); // 初始状态设置为缩放为0,为动画做准备 消息项.transform.localScale = Vector3.zero; // 设置发送者文本 Text 发送者文本 = 消息项.transform.Find("发送者文本")?.GetComponent<Text>(); if (发送者文本 != null) { 发送者文本.text = 发送者 + ":"; 发送者文本.color = 颜色; 发送者文本.font = 聊天字体 ?? Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf"); 发送者文本.fontSize = 字体大小; 发送者文本.enabled = 显示发送者; 发送者文本.rectTransform.sizeDelta = new Vector2(100, 发送者文本.preferredHeight); } else if (显示关键错误) { Debug.LogError("未找到'发送者文本'组件!"); } // 设置内容文本 Text 内容文本 = 消息项.transform.Find("内容文本")?.GetComponent<Text>(); if (内容文本 != null) { 内容文本.text = 内容; 内容文本.font = 聊天字体 ?? Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf"); 内容文本.fontSize = 字体大小; 内容文本.enabled = true; // 计算最大宽度 float 最大宽度 = 聊天滚动视图 != null ? 聊天滚动视图.GetComponent<RectTransform>().rect.width - 120 : 300; 内容文本.rectTransform.sizeDelta = new Vector2(最大宽度, 内容文本.preferredHeight); // 强制更新文本尺寸 Canvas.ForceUpdateCanvases(); LayoutRebuilder.ForceRebuildLayoutImmediate(消息项.GetComponent<RectTransform>()); } else if (显示关键错误) { Debug.LogError("未找到'内容文本'组件!"); } // 强制刷新布局 LayoutRebuilder.ForceRebuildLayoutImmediate(消息容器.GetComponent<RectTransform>()); if (聊天内容区域 != null) LayoutRebuilder.ForceRebuildLayoutImmediate(聊天内容区域); // 应用Q弹动画效果 应用消息动画(消息项.transform); // 保存消息到历史记录 消息历史.Add(new 消息数据(发送者, 内容, System.DateTime.Now, 是玩家消息)); // 检查并移除超出最大数量的消息 清理旧消息(); // 滚动到最新消息 StartCoroutine(滚动到最新消息()); } // 应用消息弹出的Q弹动画 private void 应用消息动画(Transform 消息变换) { // 确保DOTween可用 if (消息变换 == null) return; // 重置缩放 消息变换.localScale = Vector3.one * 缩放比例; // 使用DOTween创建Q弹效果 消息变换.DOScale(Vector3.one * 弹性系数, 动画持续时间 / 2) .SetEase(Ease.OutQuad) .OnComplete(() => { // 回弹效果 消息变换.DOScale(Vector3.one, 动画持续时间 / 2) .SetEase(Ease.OutElastic); }); } // 滚动到最新消息 - 优化滚动效果 private IEnumerator 滚动到最新消息() { // 等待布局刷新 yield return new WaitForEndOfFrame(); if (聊天滚动视图 != null) { // 强制更新内容大小 ContentSizeFitter fitter = 消息容器.GetComponent<ContentSizeFitter>(); if (fitter != null) { fitter.SetLayoutVertical(); } // 使用动画平滑滚动到底部 float targetPos = 0; // 0表示滚动到底部 float startPos = 聊天滚动视图.verticalNormalizedPosition; float elapsedTime = 0; float duration = 0.2f; while (elapsedTime < duration) { elapsedTime += Time.deltaTime; 聊天滚动视图.verticalNormalizedPosition = Mathf.Lerp(startPos, targetPos, elapsedTime / duration); yield return null; } // 确保最终位置正确 聊天滚动视图.verticalNormalizedPosition = targetPos; } } // 清理超出最大数量的旧消息 private void 清理旧消息() { if (消息历史.Count <= 最大消息数量) return; int 要删除的数量 = 消息历史.Count - 最大消息数量; // 从容器中移除最早的消息对象 for (int i = 0; i < 要删除的数量 && 消息容器.childCount > 0; i++) { Destroy(消息容器.GetChild(0).gameObject); } // 从历史记录中移除 消息历史.RemoveRange(0, 要删除的数量); } // 验证必要组件 private void 验证必要组件() { if (消息输入框 == null && 显示关键错误) Debug.LogError("错误:消息输入框未赋值!"); if (发送按钮 == null && 显示关键错误) Debug.LogError("错误:发送按钮未赋值!"); if (关闭按钮 == null && 显示关键错误) Debug.LogError("错误:关闭按钮未赋值!"); if (聊天字体 == null) { 聊天字体 = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf"); if (聊天字体 == null && 消息输入框 != null && 消息输入框.textComponent != null) 聊天字体 = 消息输入框.textComponent.font; } } private void 修复输入框() { if (消息输入框 == null) return; 消息输入框.gameObject.SetActive(true); 消息输入框.enabled = true; 消息输入框.interactable = true; if (消息输入框.textComponent == null) { Text textComp = 消息输入框.GetComponentInChildren<Text>(); if (textComp == null) textComp = 消息输入框.gameObject.AddComponent<Text>(); 消息输入框.textComponent = textComp; } if (消息输入框.textComponent != null) { if (聊天字体 != null) 消息输入框.textComponent.font = 聊天字体; 消息输入框.textComponent.fontSize = 字体大小; 消息输入框.textComponent.color = Color.black; } if (消息输入框.placeholder != null) { Text placeholder = 消息输入框.placeholder.GetComponent<Text>(); if (placeholder != null) { if (聊天字体 != null) placeholder.font = 聊天字体; placeholder.text = "输入消息...(Ctrl+Enter换行)"; } } } private IEnumerator 监控输入框状态() { while (true) { if (聊天窗口是否打开 && 消息输入框 != null && !消息输入框.interactable) 消息输入框.interactable = true; yield return new WaitForSeconds(0.5f); } } public void 打开聊天窗口() { if (聊天窗口是否打开) return; 聊天窗口.SetActive(true); 聊天窗口是否打开 = true; if (消息输入框 != null) 消息输入框.text = ""; StartCoroutine(延迟聚焦输入框(0.1f)); if (邀请管理器 != null) 邀请管理器.设置正在聊天(true); } // 新增:带地块信息的打开聊天窗口方法 public void 打开聊天窗口(地块Data 地块信息) { 打开聊天窗口(); // 先调用无参数版本打开窗口 // 清除旧的地块信息 if (当前地块信息显示 != null) { Destroy(当前地块信息显示); } if (地块信息预制体 != null && 地块信息 != null) { 当前地块信息显示 = Instantiate(地块信息预制体, 消息容器); 当前地块信息显示.name = $"地块信息_{地块信息.序号}"; Text 地块描述文本 = 当前地块信息显示.transform.Find("内容文本")?.GetComponent<Text>(); if (地块描述文本 != null) { 地块描述文本.text = $@"地块信息: - 序号:{地块信息.序号} - 坐标:({地块信息.X}, {地块信息.Y}) - 生态类型:{地块信息.生态类型} - 描述:{地块信息.描述} - 生物列表:{string.Join(", ", 地块信息.生物列表)}"; } // 强制布局刷新 LayoutRebuilder.ForceRebuildLayoutImmediate(消息容器.GetComponent<RectTransform>()); StartCoroutine(滚动到最新消息()); } // 发送AI请求 if (ai管理器 != null && 地块信息 != null) { string 提示词 = $@"你是一个生态科考AI助手,现在要生成一个新的地块信息。 该地块序号为 {地块信息.序号},坐标为 ({地块信息.X}, {地块信息.Y}),生态类型为:{地块信息.生态类型}。 请根据以下要求生成内容: - 描述:简洁的环境描述(1~2句话) - 生物列表:至少3种生物(可以是动物、植物、菌类)"; ai管理器.发送消息给AI(提示词); } } private IEnumerator 延迟聚焦输入框(float 延迟时间) { yield return new WaitForSeconds(延迟时间); if (消息输入框 != null) { 消息输入框.Select(); 消息输入框.ActivateInputField(); } } public void 关闭聊天窗口() { if (!聊天窗口是否打开) return; 聊天窗口.SetActive(false); 聊天窗口是否打开 = false; if (邀请管理器 != null) 邀请管理器.设置正在聊天(false); } public void 发送消息() { if (!聊天窗口是否打开 || 消息输入框 == null) return; string 消息内容 = 消息输入框.text.Trim(); if (string.IsNullOrEmpty(消息内容)) return; // 分割消息 List<string> 分割后的消息 = 分割消息(消息内容); // 发送分割后的消息 for (int i = 0; i < 分割后的消息.Count; i++) { // 只有第一条消息显示发送者,后续分割的消息隐藏发送者 bool 显示发送者 = (i == 0); 显示消息项(玩家名称, 分割后的消息[i], 玩家消息颜色, true, 显示发送者); } // 发送消息给AI if (ai管理器 != null) { ai管理器.发送消息给AI(消息内容); } else if (显示关键错误) { Debug.LogWarning("AI管理器未找到,无法发送消息给AI"); } 消息输入框.text = ""; 消息输入框.ActivateInputField(); } private void 输入框回车发送(string 内容) { if (消息输入框 == null) return; // 只有在没有按下Ctrl键时,回车才发送消息 if ((Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter)) && !(Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))) { if (聊天窗口是否打开) 发送消息(); 消息输入框.DeactivateInputField(); 消息输入框.ActivateInputField(); } } // 收到消息方法 public void 收到消息(string 发送者, string 内容) { // 分割收到的消息 List<string> 分割后的消息 = 分割消息(内容); // 显示分割后的消息 for (int i = 0; i < 分割后的消息.Count; i++) { // 只有第一条消息显示发送者,后续分割的消息隐藏发送者 bool 显示发送者 = (i == 0); 显示消息项(发送者, 分割后的消息[i], 朋友消息颜色, false, 显示发送者); } } // 在场景视图中可视化消息容器区域 void OnDrawGizmosSelected() { // 移除场景视图中的调试可视化 } // 消息数据类,用于存储消息历史 private class 消息数据 { public string 发送者; public string 内容; public System.DateTime 时间; public bool 是玩家消息; public 消息数据(string 发送者, string 内容, System.DateTime 时间, bool 是玩家消息) { this.发送者 = 发送者; this.内容 = 内容; this.时间 = 时间; this.是玩家消息 = 是玩家消息; } } private static 聊天系统 _instance; public static 聊天系统 Instance { get { if (_instance == null) { _instance = FindObjectOfType<聊天系统>(); if (_instance == null) { GameObject go = new GameObject("聊天系统"); _instance = go.AddComponent<聊天系统>(); DontDestroyOnLoad(go); } } return _instance; } } }
08-31
不不不,我在上文中有跟你说过,泛词请不要自己举例,直接用高德地图返回的数据,而且你在上文中说我的代码目前已经能够实现这个功能了。现在我需要请你再确认一遍,我的代码是不是真的有高德返回泛词的功能?目前MapFragment代码如下:package com.example.bus.ui.map; import android.Manifest; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.text.Editable; import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.ArrayAdapter; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.constraintlayout.widget.ConstraintSet; import androidx.constraintlayout.widget.ConstraintLayout; import com.amap.api.maps.AMap; import com.amap.api.maps.AMapUtils; import com.amap.api.maps.CameraUpdateFactory; import com.amap.api.maps.MapView; import com.amap.api.maps.UiSettings; 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.maps.model.MyLocationStyle; import com.amap.api.services.core.LatLonPoint; import com.amap.api.services.core.PoiItem; import com.amap.api.services.poisearch.PoiResult; import com.amap.api.services.poisearch.PoiSearch; import com.amap.api.services.geocoder.GeocodeSearch; import com.amap.api.services.geocoder.RegeocodeQuery; import com.amap.api.services.geocoder.RegeocodeResult; import com.example.bus.R; import com.example.bus.RealTimePoiSuggestHelper; import com.example.bus.RoutePlanActivity; import com.example.bus.ResultAdapter; import com.example.bus.databinding.FragmentMapBinding; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import com.amap.api.maps.model.BitmapDescriptorFactory; public class MapFragment extends Fragment implements PoiSearch.OnPoiSearchListener, GeocodeSearch.OnGeocodeSearchListener { private FragmentMapBinding binding; private MapView mapView; private AMap aMap; // 数据 private List<PoiItem> poiList = new ArrayList<>(); private ResultAdapter adapter; private PoiSearch poiSearch; // 当前阶段:1=选择起点, 2=选择终点 private int selectionStage = 0; // 缓存已选 POI private PoiItem selectedStartPoi = null; private PoiItem selectedEndPoi = null; private Marker startMarker = null; private Marker endMarker = null; // 缓存关键词 private String lastStartKeyword = ""; private String lastEndKeyword = ""; // ✅ 当前城市 private String currentCity = ""; private static final int LOCATION_PERMISSION_REQUEST_CODE = 1001; // ✅ 标记是否已居中我的位置 private boolean userHasInteracted = false; // ✅ 反地理编码 private GeocodeSearch geocodeSearch; // 【关键新增】保存定位得到的“我的位置” private double myCurrentLat = 0; private double myCurrentLng = 0; private boolean isLocationReady = false; // 定位是否完成 // 存储常见品牌的提取关键字(用于判断是否为连锁) private static final String[] CHAIN_KEYWORDS = { "麦当劳", "肯德基", "星巴克", "瑞幸", "库迪", "喜茶", "奈雪", "汉堡王", "必胜客", "7-11", "全家", "罗森", "美宜佳", "永辉", "盒马", "沃尔玛", "家乐福", "大参林", "同仁堂" }; @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = FragmentMapBinding.inflate(inflater, container, false); View root = binding.getRoot(); mapView = binding.mapView; mapView.onCreate(savedInstanceState); initViews(); setupMap(savedInstanceState); setupSearchSuggestion(); // 包含输入建议、防抖、回车事件 return root; } private void initViews() { adapter = new ResultAdapter(poiList, this::onPoiItemSelected); binding.resultList.setLayoutManager(new LinearLayoutManager(requireContext())); binding.resultList.setAdapter(adapter); binding.mapSearch.setOnClickListener(v -> performSearch()); binding.btnSwitchTarget.setOnClickListener(v -> { if (selectionStage == 1) { showEndpointSelection(binding.mapInput2.getText().toString().trim()); } else if (selectionStage == 2) { showStartpointSelection(binding.mapInput1.getText().toString().trim()); } }); binding.btnGoTo.setOnClickListener(v -> { if (selectedStartPoi != null && selectedEndPoi != null) { Intent intent = new Intent(requireContext(), RoutePlanActivity.class); intent.putExtra(RoutePlanActivity.EXTRA_SOURCE, RoutePlanActivity.SOURCE_FROM_MAP_DIRECT); intent.putExtra("start_lat", selectedStartPoi.getLatLonPoint().getLatitude()); intent.putExtra("start_lng", selectedStartPoi.getLatLonPoint().getLongitude()); intent.putExtra("target_lat", selectedEndPoi.getLatLonPoint().getLatitude()); intent.putExtra("target_lng", selectedEndPoi.getLatLonPoint().getLongitude()); intent.putExtra("target_title", selectedEndPoi.getTitle()); startActivity(intent); } else { Toast.makeText(requireContext(), "请完成起点和终点的选择", Toast.LENGTH_SHORT).show(); } }); } private void performSearch() { String startKeyword = binding.mapInput1.getText().toString().trim(); String endKeyword = binding.mapInput2.getText().toString().trim(); if (startKeyword.isEmpty()) { Toast.makeText(requireContext(), "请输入起点", Toast.LENGTH_SHORT).show(); return; } if (endKeyword.isEmpty()) { Toast.makeText(requireContext(), "请输入终点", Toast.LENGTH_SHORT).show(); return; } // 智能判断跳过搜索 if (startKeyword.equals(lastStartKeyword) && endKeyword.equals(lastEndKeyword) && selectedStartPoi != null && selectedEndPoi != null) { binding.btnGoTo.performClick(); return; } // 展示 UI binding.containerResultList.setVisibility(View.VISIBLE); binding.buttonGroup.setVisibility(View.VISIBLE); ConstraintSet constraintSet = new ConstraintSet(); constraintSet.clone((ConstraintLayout) binding.getRoot()); constraintSet.connect( R.id.map_view, ConstraintSet.BOTTOM, R.id.container_result_list, ConstraintSet.TOP, 0 ); constraintSet.applyTo((ConstraintLayout) binding.getRoot()); userHasInteracted = true; // 隐藏软键盘 View currentFocus = requireActivity().getCurrentFocus(); if (currentFocus != null) { currentFocus.clearFocus(); InputMethodManager imm = (InputMethodManager) requireContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(currentFocus.getWindowToken(), 0); } // 执行搜索 if (!startKeyword.equals(lastStartKeyword)) { lastStartKeyword = startKeyword; lastEndKeyword = endKeyword; showStartpointSelection(startKeyword); } else if (!endKeyword.equals(lastEndKeyword)) { lastEndKeyword = endKeyword; showEndpointSelection(endKeyword); } else if (selectedStartPoi == null) { showStartpointSelection(startKeyword); } else { showEndpointSelection(endKeyword); } } private void showStartpointSelection(String keyword) { selectionStage = 1; binding.btnSwitchTarget.setText("前往选择终点"); binding.btnGoTo.setEnabled(false); binding.emptyView.setText("🔍 搜索起点中..."); binding.emptyView.setVisibility(View.VISIBLE); binding.resultList.setVisibility(View.GONE); doSearch(keyword); } private void showEndpointSelection(String keyword) { selectionStage = 2; binding.btnSwitchTarget.setText("回到选择起点"); binding.btnGoTo.setEnabled(false); binding.emptyView.setText("🔍 搜索终点中..."); binding.emptyView.setVisibility(View.VISIBLE); binding.resultList.setVisibility(View.GONE); doSearch(keyword); } private void doSearch(String keyword) { if (keyword.isEmpty()) return; PoiSearch.Query query = new PoiSearch.Query(keyword, "", currentCity); query.setPageSize(20); query.setPageNum(0); try { poiSearch = new PoiSearch(requireContext(), query); poiSearch.setOnPoiSearchListener(this); poiSearch.searchPOIAsyn(); } catch (Exception e) { e.printStackTrace(); Toast.makeText(requireContext(), "搜索失败", Toast.LENGTH_SHORT).show(); } } private void onPoiItemSelected(PoiItem item) { LatLng latLng = new LatLng(item.getLatLonPoint().getLatitude(), item.getLatLonPoint().getLongitude()); if (selectionStage == 1) { if (startMarker != null) { startMarker.remove(); startMarker = null; } startMarker = aMap.addMarker(new MarkerOptions() .position(latLng) .title("起点:" + item.getTitle()) .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN))); selectedStartPoi = item; aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 14f)); updateGoToButtonState(); } else if (selectionStage == 2) { if (endMarker != null) { endMarker.remove(); endMarker = null; } endMarker = aMap.addMarker(new MarkerOptions() .position(latLng) .title("终点:" + item.getTitle()) .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED))); selectedEndPoi = item; aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 14f)); updateGoToButtonState(); } userHasInteracted = true; } private void updateGoToButtonState() { binding.btnGoTo.setEnabled(selectedStartPoi != null && selectedEndPoi != null); } @Override public void onPoiSearched(PoiResult result, int rCode) { if (rCode == 1000 && result != null && result.getPois() != null && !result.getPois().isEmpty()) { List<PoiItem> rawList = result.getPois(); // ✅ 判断是否需要按距离排序 boolean sortByDistance = shouldSortByDistance(rawList); List<PoiItem> sortedList; if (sortByDistance && isLocationReady) { sortedList = sortByDistance(rawList, myCurrentLat, myCurrentLng); } else { sortedList = rawList; // 保留原始顺序 } poiList.clear(); poiList.addAll(sortedList); if (adapter == null) { adapter = new ResultAdapter(poiList, this::onPoiItemSelected); binding.resultList.setLayoutManager(new LinearLayoutManager(requireContext())); binding.resultList.setAdapter(adapter); } else { adapter.notifyDataSetChanged(); binding.resultList.scrollToPosition(0); } binding.emptyView.setVisibility(View.GONE); binding.resultList.setVisibility(View.VISIBLE); adapter.setSelected(0); onPoiItemSelected(poiList.get(0)); } else { if (selectionStage == 1) { binding.emptyView.setText("⚠️ 未找到相关起点"); } else if (selectionStage == 2) { binding.emptyView.setText("⚠️ 未找到相关终点"); } binding.emptyView.setVisibility(View.VISIBLE); binding.resultList.setVisibility(View.GONE); } } @Override public void onPoiItemSearched(PoiItem item, int rCode) { // 必须实现 } private LatLng toLatLng(LatLonPoint point) { if (point == null) return null; return new LatLng(point.getLatitude(), point.getLongitude()); } // ✅ 判断是否应按距离排序:看是否为连锁品牌或多门店服务 private boolean shouldSortByDistance(List<PoiItem> list) { if (list.size() < 2) return false; // 统计名称中包含连锁关键词的数量 Map<String, Integer> brandCount = new HashMap<>(); for (String kw : CHAIN_KEYWORDS) { long count = list.stream().filter(item -> item.getTitle().contains(kw)).count(); if (count >= 2) { // 至少两个门店属于该品牌 return true; } } // 或者:类型高度一致(如都是“餐饮服务|快餐服务”) Map<String, Integer> mainTypeCount = new HashMap<>(); for (PoiItem item : list) { String type = item.getTypeCode(); if (type != null && type.contains("|")) { String mainType = type.split("\\|")[0]; mainTypeCount.merge(mainType, 1, Integer::sum); } } return mainTypeCount.values().stream().anyMatch(cnt -> cnt >= list.size() * 0.6); } // ✅ 按距离排序 private List<PoiItem> sortByDistance(List<PoiItem> list, double lat, double lng) { LatLng me = new LatLng(lat, lng); return list.stream() .sorted((a, b) -> { double da = AMapUtils.calculateLineDistance(toLatLng(a.getLatLonPoint()), me); double db = AMapUtils.calculateLineDistance(toLatLng(b.getLatLonPoint()), me); return Double.compare(da, db); }) .collect(Collectors.toList()); } private void setupMap(Bundle savedInstanceState) { mapView.onCreate(savedInstanceState); aMap = mapView.getMap(); if (aMap != null) { initMapSettings(); } else { new Handler(Looper.getMainLooper()).post(() -> { aMap = mapView.getMap(); if (aMap != null) { initMapSettings(); } else { waitAMapReady(); } }); } try { geocodeSearch = new GeocodeSearch(requireContext()); geocodeSearch.setOnGeocodeSearchListener(this); } catch (Exception e) { e.printStackTrace(); } } private void waitAMapReady() { new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { int retry = 0; @Override public void run() { if (mapView == null) return; aMap = mapView.getMap(); if (aMap != null) { initMapSettings(); } else if (retry++ < 30) { new Handler(Looper.getMainLooper()).postDelayed(this, 100); } } }, 100); } private void initMapSettings() { UiSettings uiSettings = aMap.getUiSettings(); uiSettings.setZoomControlsEnabled(true); uiSettings.setCompassEnabled(true); uiSettings.setScrollGesturesEnabled(true); uiSettings.setMyLocationButtonEnabled(true); new Handler(Looper.getMainLooper()).post(() -> aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(39.909186, 116.397411), 10f)) ); enableMyLocationLayer(); } private void enableMyLocationLayer() { if (aMap == null) return; if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { MyLocationStyle myLocationStyle = new MyLocationStyle(); myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE_NO_CENTER); aMap.setMyLocationStyle(myLocationStyle); aMap.setMyLocationEnabled(true); AMap.OnMyLocationChangeListener listener = location -> { if (location != null && !userHasInteracted) { LatLng curLatlng = new LatLng(location.getLatitude(), location.getLongitude()); myCurrentLat = location.getLatitude(); myCurrentLng = location.getLongitude(); isLocationReady = true; aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(curLatlng, 16f)); userHasInteracted = true; LatLonPoint point = new LatLonPoint(myCurrentLat, myCurrentLng); RegeocodeQuery query = new RegeocodeQuery(point, 200, GeocodeSearch.AMAP); try { geocodeSearch.getFromLocationAsyn(query); } catch (Exception e) { e.printStackTrace(); } aMap.setOnMyLocationChangeListener(null); } }; aMap.setOnMyLocationChangeListener(listener); } else { ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (aMap != null) { MyLocationStyle myLocationStyle = new MyLocationStyle(); myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE_NO_CENTER); aMap.setMyLocationStyle(myLocationStyle); aMap.setMyLocationEnabled(true); AMap.OnMyLocationChangeListener listener = location -> { if (location != null && !userHasInteracted) { LatLng curLatlng = new LatLng(location.getLatitude(), location.getLongitude()); myCurrentLat = location.getLatitude(); myCurrentLng = location.getLongitude(); isLocationReady = true; aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(curLatlng, 16f)); userHasInteracted = true; LatLonPoint point = new LatLonPoint(myCurrentLat, myCurrentLng); RegeocodeQuery query = new RegeocodeQuery(point, 200, GeocodeSearch.AMAP); try { geocodeSearch.getFromLocationAsyn(query); } catch (Exception e) { e.printStackTrace(); } aMap.setOnMyLocationChangeListener(null); } }; aMap.setOnMyLocationChangeListener(listener); } } } } @Override public void onRegeocodeSearched(RegeocodeResult result, int rCode) { if (rCode == 1000 && result != null && result.getRegeocodeAddress() != null) { String city = result.getRegeocodeAddress().getCity(); currentCity = (city != null && !city.isEmpty()) ? city : result.getRegeocodeAddress().getProvince(); } } @Override public void onGeocodeSearched(com.amap.api.services.geocoder.GeocodeResult geocodeResult, int i) { // 忽略 } @Override public void onResume() { super.onResume(); mapView.onResume(); if (!userHasInteracted) { enableMyLocationLayer(); } } @Override public void onPause() { super.onPause(); mapView.onPause(); } @Override public void onDestroyView() { super.onDestroyView(); mapView.onDestroy(); geocodeSearch = null; binding = null; } @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); mapView.onSaveInstanceState(outState); } // ✅ 修改 RealTimePoiSuggestHelper 来支持距离排序建议 private void setupSearchSuggestion() { RealTimePoiSuggestHelper suggestHelper = new RealTimePoiSuggestHelper(requireContext()); suggestHelper.setCurrentCity(currentCity); suggestHelper.setLocationBias(myCurrentLat, myCurrentLng); // 偏置 suggestHelper.setUseDistanceSort(true); // ✅ 启用距离排序 suggestHelper.setCallback(suggestions -> { if (suggestions.length > 0) { ArrayAdapter<String> adapter = new ArrayAdapter<>( requireContext(), android.R.layout.simple_dropdown_item_1line, suggestions ); new Handler(Looper.getMainLooper()).post(() -> { binding.mapInput1.setAdapter(adapter); binding.mapInput2.setAdapter(adapter); if (requireActivity().getCurrentFocus() == binding.mapInput1) { binding.mapInput1.showDropDown(); } else if (requireActivity().getCurrentFocus() == binding.mapInput2) { binding.mapInput2.showDropDown(); } }); } }); Handler handler = new Handler(Looper.getMainLooper()); Runnable[] pending1 = {null}, pending2 = {null}; binding.mapInput1.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (pending1[0] != null) handler.removeCallbacks(pending1[0]); if (s.length() == 0) { binding.mapInput1.setAdapter(null); return; } pending1[0] = () -> suggestHelper.requestSuggestions(s.toString()); handler.postDelayed(pending1[0], 300); } @Override public void afterTextChanged(Editable s) {} }); binding.mapInput2.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (pending2[0] != null) handler.removeCallbacks(pending2[0]); if (s.length() == 0) { binding.mapInput2.setAdapter(null); return; } pending2[0] = () -> suggestHelper.requestSuggestions(s.toString()); handler.postDelayed(pending2[0], 300); } @Override public void afterTextChanged(Editable s) {} }); binding.mapInput1.setOnEditorActionListener((v, actionId, event) -> { if ((actionId & EditorInfo.IME_MASK_ACTION) == EditorInfo.IME_ACTION_SEARCH) { performSearch(); return true; } return false; }); binding.mapInput2.setOnEditorActionListener((v, actionId, event) -> { if ((actionId & EditorInfo.IME_MASK_ACTION) == EditorInfo.IME_ACTION_SEARCH) { performSearch(); return true; } return false; }); } private void handleSearchError(int rCode) { String msg; switch (rCode) { case 12: msg = "API Key 错误"; break; case 27: msg = "网络连接失败"; break; case 30: msg = "SHA1 或包名错误"; break; case 33: msg = "请求频繁"; break; default: msg = "搜索失败: " + rCode; break; } Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show(); } private static class SimpleTextWatcher implements android.text.TextWatcher { private final java.util.function.Consumer<CharSequence> onTextChanged; public SimpleTextWatcher(java.util.function.Consumer<CharSequence> onTextChanged) { this.onTextChanged = onTextChanged; } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void afterTextChanged(Editable s) {} @Override public void onTextChanged(CharSequence s, int start, int before, int count) { onTextChanged.accept(s); } } } SearchResultActivity代码如下:package com.example.bus; import android.Manifest; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.view.inputmethod.EditorInfo; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.amap.api.maps.AMap; import com.amap.api.maps.CameraUpdateFactory; import com.amap.api.maps.MapView; import com.amap.api.maps.UiSettings; 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.maps.model.MyLocationStyle; import com.amap.api.services.core.LatLonPoint; import com.amap.api.services.core.PoiItem; import com.amap.api.services.poisearch.PoiResult; import com.amap.api.services.poisearch.PoiSearch; import com.amap.api.services.geocoder.GeocodeSearch; import com.amap.api.services.geocoder.RegeocodeQuery; import com.amap.api.services.geocoder.RegeocodeResult; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class SearchResultActivity extends AppCompatActivity implements PoiSearch.OnPoiSearchListener, GeocodeSearch.OnGeocodeSearchListener { private Button searchBtn, goToBtn; private RecyclerView resultListView; private List<PoiItem> poiList = new ArrayList<>(); private ResultAdapter adapter; private PoiSearch poiSearch; // 地图相关 private MapView mapView; private AMap aMap; private Marker selectedMarker; // 终点标记 // 输入提示 private GeocodeSearch geocodeSearch; // 当前城市 private String currentCity = ""; // 是否已与地图交互(防止重复居中) private boolean userHasInteracted = false; private static final int LOCATION_PERMISSION_REQUEST_CODE = 1001; // 空状态提示视图 private TextView emptyView; // 【关键新增】保存定位得到的“我的位置” private double myCurrentLat = 0; private double myCurrentLng = 0; private boolean isLocationReady = false; // 定位是否完成 // 缓存从 HomeFragment 传来的关键词,等待定位完成后使用 private String pendingKeyword = null; // ✅ 新增:将搜索输入框提升为成员变量 private android.widget.AutoCompleteTextView searchInput; // ✅ 实时建议助手(延迟初始化) private RealTimePoiSuggestHelper suggestHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_search_result); if (getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setTitle("搜索地点"); } initViews(); setupMap(savedInstanceState); try { geocodeSearch = new GeocodeSearch(this); geocodeSearch.setOnGeocodeSearchListener(this); } catch (Exception e) { e.printStackTrace(); } // ✅ 只缓存 keyword,不再尝试提前搜索 pendingKeyword = getIntent().getStringExtra("keyword"); // ✅ 初始化 suggestHelper(但暂不设 location bias) suggestHelper = new RealTimePoiSuggestHelper(this); suggestHelper.setCurrentCity(currentCity); suggestHelper.setUseDistanceSort(true); // 启用距离排序逻辑 suggestHelper.setCallback(suggestions -> { if (suggestions.length > 0) { ArrayAdapter<String> adapter = new ArrayAdapter<>( this, android.R.layout.simple_dropdown_item_1line, suggestions ); new Handler(Looper.getMainLooper()).post(() -> { searchInput.setAdapter(adapter); if (getCurrentFocus() == searchInput) { searchInput.showDropDown(); } }); } }); } private void initViews() { searchBtn = findViewById(R.id.search_btn); resultListView = findViewById(R.id.result_list); goToBtn = findViewById(R.id.btn_go_to); emptyView = findViewById(R.id.empty_view); searchInput = findViewById(R.id.search_input); // ✅ 初始化成员变量 goToBtn.setEnabled(false); adapter = new ResultAdapter(poiList, this::onPoiItemSelected); resultListView.setLayoutManager(new LinearLayoutManager(this)); resultListView.setAdapter(adapter); resultListView.setVisibility(View.GONE); emptyView.setVisibility(View.GONE); // 修改:传递真实定位点 goToBtn.setOnClickListener(v -> { if (selectedMarker == null) { Toast.makeText(this, "请先选择一个位置", Toast.LENGTH_SHORT).show(); return; } LatLng targetPos = selectedMarker.getPosition(); // 确保定位已完成 if (!isLocationReady) { Toast.makeText(this, "正在获取您的位置,请稍后再试", Toast.LENGTH_SHORT).show(); return; } Intent intent = new Intent(SearchResultActivity.this, RoutePlanActivity.class); intent.putExtra("start_lat", myCurrentLat); intent.putExtra("start_lng", myCurrentLng); intent.putExtra("target_lat", targetPos.latitude); intent.putExtra("target_lng", targetPos.longitude); intent.putExtra(RoutePlanActivity.EXTRA_SOURCE, RoutePlanActivity.SOURCE_FROM_SEARCH_RESULT); startActivity(intent); finish(); }); } private void setupMap(Bundle savedInstanceState) { mapView = findViewById(R.id.map_view); mapView.onCreate(savedInstanceState); aMap = mapView.getMap(); if (aMap != null) { initMapSettings(); } else { new Handler(Looper.getMainLooper()).post(() -> { aMap = mapView.getMap(); if (aMap != null) { initMapSettings(); } else { waitAMapReady(); } }); } } private void waitAMapReady() { new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { int retry = 0; @Override public void run() { if (mapView == null) return; aMap = mapView.getMap(); if (aMap != null) { initMapSettings(); } else if (retry++ < 30) { new Handler(Looper.getMainLooper()).postDelayed(this, 100); } } }, 100); } private void initMapSettings() { UiSettings uiSettings = aMap.getUiSettings(); uiSettings.setZoomControlsEnabled(true); uiSettings.setCompassEnabled(true); uiSettings.setScrollGesturesEnabled(true); uiSettings.setMyLocationButtonEnabled(true); aMap.setOnMapClickListener(latLng -> { onCustomLocationSelected(latLng); }); // 初始视野:中国中心 new Handler(Looper.getMainLooper()).post(() -> aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(35.8617, 104.1954), 4f)) ); enableMyLocationLayer(); } private void enableMyLocationLayer() { if (aMap == null) return; if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { MyLocationStyle myLocationStyle = new MyLocationStyle(); myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE_NO_CENTER); aMap.setMyLocationStyle(myLocationStyle); aMap.setMyLocationEnabled(true); AMap.OnMyLocationChangeListener listener = location -> { if (location != null && !userHasInteracted) { LatLng curLatlng = new LatLng(location.getLatitude(), location.getLongitude()); // 记录真实“我的位置” myCurrentLat = location.getLatitude(); myCurrentLng = location.getLongitude(); isLocationReady = true; // ✅ 更新 suggestHelper 的 location bias suggestHelper.setLocationBias(myCurrentLat, myCurrentLng); // ✅ 先居中地图 aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(curLatlng, 16f), 500, null); userHasInteracted = true; // 获取当前城市 LatLonPoint point = new LatLonPoint(myCurrentLat, myCurrentLng); RegeocodeQuery query = new RegeocodeQuery(point, 200, GeocodeSearch.AMAP); try { geocodeSearch.getFromLocationAsyn(query); } catch (Exception e) { e.printStackTrace(); } // ✅ 关键修改:延迟 800ms 再触发搜索,给地图留出渲染时间 new Handler(Looper.getMainLooper()).postDelayed(() -> { if (pendingKeyword != null && !pendingKeyword.isEmpty()) { performSearchWithKeyword(pendingKeyword); } }, 800); // 防止重复触发 aMap.setOnMyLocationChangeListener(null); } }; aMap.setOnMyLocationChangeListener(listener); } else { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { enableMyLocationLayer(); } } } private void setupSearchSuggestion() { Handler handler = new Handler(Looper.getMainLooper()); Runnable[] pendingRunnable = {null}; searchInput.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (pendingRunnable[0] != null) { handler.removeCallbacks(pendingRunnable[0]); } if (s.length() == 0) { searchInput.setAdapter(null); return; } pendingRunnable[0] = () -> suggestHelper.requestSuggestions(s.toString()); handler.postDelayed(pendingRunnable[0], 300); } @Override public void afterTextChanged(Editable s) {} }); searchInput.setOnEditorActionListener((v, actionId, event) -> { if ((actionId & EditorInfo.IME_MASK_ACTION) == EditorInfo.IME_ACTION_SEARCH) { searchBtn.performClick(); return true; } return false; }); searchBtn.setOnClickListener(v -> { String keyword = searchInput.getText().toString().trim(); if (!keyword.isEmpty()) { performSearch(keyword); } else { Toast.makeText(this, "请输入关键词", Toast.LENGTH_SHORT).show(); } }); } // ✅ 修改:先设置文本,再模拟点击按钮(等价于用户操作) private void performSearchWithKeyword(String keyword) { searchInput.setText(keyword); searchInput.clearFocus(); searchBtn.performClick(); // ✅ 触发完整 UI 搜索流程 } private void performSearch(String keyword) { emptyView.setText("🔍 搜索中..."); emptyView.setVisibility(View.VISIBLE); resultListView.setVisibility(View.GONE); PoiSearch.Query query = new PoiSearch.Query(keyword, "", currentCity); query.setPageSize(20); query.setPageNum(0); try { poiSearch = new PoiSearch(this, query); poiSearch.setOnPoiSearchListener(this); poiSearch.searchPOIAsyn(); } catch (Exception e) { Toast.makeText(this, "搜索失败", Toast.LENGTH_SHORT).show(); emptyView.setText("⚠️ 未找到相关地点"); } } private void onPoiItemSelected(PoiItem item) { LatLng latLng = new LatLng(item.getLatLonPoint().getLatitude(), item.getLatLonPoint().getLongitude()); if (selectedMarker != null) { selectedMarker.remove(); selectedMarker = null; } selectedMarker = aMap.addMarker(new MarkerOptions() .position(latLng) .title("终点:" + item.getTitle()) .icon(com.amap.api.maps.model.BitmapDescriptorFactory.defaultMarker( com.amap.api.maps.model.BitmapDescriptorFactory.HUE_RED))); aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 14f)); goToBtn.setEnabled(true); } private void onCustomLocationSelected(LatLng latLng) { if (selectedMarker != null) { selectedMarker.remove(); } selectedMarker = aMap.addMarker(new MarkerOptions() .position(latLng) .title("终点:自定义位置") .icon(com.amap.api.maps.model.BitmapDescriptorFactory.defaultMarker( com.amap.api.maps.model.BitmapDescriptorFactory.HUE_RED))); goToBtn.setEnabled(true); } // ✅ 工具方法:转换 LatLonPoint → LatLng private LatLng toLatLng(LatLonPoint point) { if (point == null) return null; return new LatLng(point.getLatitude(), point.getLongitude()); } // ✅ 判断是否为“泛类词”:看是否有 ≥2 个 POI 主类型相同(取 typeCode 前4位) private boolean isGenericCategory(List<PoiItem> list) { if (list.size() < 2) return false; Map<String, Integer> prefixCount = new HashMap<>(); for (PoiItem item : list) { String type = item.getTypeCode(); if (type != null && type.length() >= 4) { String prefix = type.substring(0, 4); // 主类别码 prefixCount.merge(prefix, 1, Integer::sum); } } return prefixCount.values().stream().anyMatch(cnt -> cnt >= 2); } // ✅ 按距离排序 private List<PoiItem> sortByDistance(List<PoiItem> list, double lat, double lng) { LatLng me = new LatLng(lat, lng); return list.stream() .sorted(Comparator.comparingDouble(poi -> com.amap.api.maps.AMapUtils.calculateLineDistance(toLatLng(poi.getLatLonPoint()), me))) .collect(Collectors.toList()); } @Override public void onPoiSearched(PoiResult result, int rCode) { if (rCode == 1000 && result != null && result.getPois() != null && !result.getPois().isEmpty()) { List<PoiItem> rawList = result.getPois(); boolean shouldSortByDistance = isGenericCategory(rawList); List<PoiItem> sortedList; if (shouldSortByDistance && isLocationReady) { sortedList = sortByDistance(rawList, myCurrentLat, myCurrentLng); } else { sortedList = rawList; } poiList.clear(); poiList.addAll(sortedList); adapter.notifyDataSetChanged(); resultListView.scrollToPosition(0); resultListView.setVisibility(View.VISIBLE); emptyView.setVisibility(View.GONE); adapter.setSelected(0); onPoiItemSelected(poiList.get(0)); } else { emptyView.setText("⚠️ 未找到相关地点"); emptyView.setVisibility(View.VISIBLE); resultListView.setVisibility(View.GONE); } } @Override public void onPoiItemSearched(PoiItem item, int rCode) { // 必须实现 } @Override public void onRegeocodeSearched(RegeocodeResult result, int rCode) { if (rCode == 1000 && result != null && result.getRegeocodeAddress() != null) { String city = result.getRegeocodeAddress().getCity(); currentCity = (city != null && !city.isEmpty()) ? city : result.getRegeocodeAddress().getProvince(); // ✅ 同步给 suggestHelper suggestHelper.setCurrentCity(currentCity); } } @Override public void onGeocodeSearched(com.amap.api.services.geocoder.GeocodeResult geocodeResult, int i) { // 忽略 } @Override protected void onResume() { super.onResume(); mapView.onResume(); setupSearchSuggestion(); // 重新绑定监听器 } @Override protected void onPause() { super.onPause(); mapView.onPause(); } @Override protected void onDestroy() { super.onDestroy(); mapView.onDestroy(); geocodeSearch = null; } @Override protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); mapView.onSaveInstanceState(outState); } @Override public boolean onSupportNavigateUp() { onBackPressed(); return true; } } RealTimePoiSuggestHelper代码如下:package com.example.bus; import android.content.Context; import android.os.Handler; import android.os.Looper; import androidx.annotation.NonNull; import com.amap.api.maps.AMapUtils; import com.amap.api.maps.model.LatLng; import com.amap.api.services.core.LatLonPoint; import com.amap.api.services.core.PoiItem; import com.amap.api.services.poisearch.PoiResult; import com.amap.api.services.poisearch.PoiSearch; import com.amap.api.services.poisearch.PoiSearch.OnPoiSearchListener; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; public class RealTimePoiSuggestHelper implements OnPoiSearchListener { private final Context context; private final Handler mainHandler = new Handler(Looper.getMainLooper()); private PoiSearch poiSearch; private SuggestionCallback callback; private String currentCity = ""; private double lat = 0, lng = 0; private boolean useLocationBias = false; private boolean useDistanceSort = false; // 是否按距离排序建议 public interface SuggestionCallback { void onSuggestionsReady(String[] suggestions); } public RealTimePoiSuggestHelper(Context context) { this.context = context; } public void setCurrentCity(String city) { this.currentCity = city; } public void setLocationBias(double lat, double lng) { this.lat = lat; this.lng = lng; this.useLocationBias = true; } public void setUseDistanceSort(boolean use) { this.useDistanceSort = use; } public void setCallback(SuggestionCallback callback) { this.callback = callback; } public void requestSuggestions(String keyword) { if (keyword.isEmpty() || callback == null) return; PoiSearch.Query query = new PoiSearch.Query(keyword, "", currentCity); query.setPageSize(20); query.requireSubPois(false); if (useLocationBias) { LatLonPoint lp = new LatLonPoint(lat, lng); query.setLocation(lp); } try { poiSearch = new PoiSearch(context, query); poiSearch.setOnPoiSearchListener(this); poiSearch.searchPOIAsyn(); } catch (Exception e) { e.printStackTrace(); notifyEmpty(); } } @Override public void onPoiSearched(PoiResult result, int rCode) { if (rCode == 1000 && result != null && result.getPois() != null) { List<PoiItem> pois = result.getPois(); List<String> names; if (useDistanceSort && useLocationBias) { // ✅ 正确做法:将 LatLonPoint 转为 LatLng 再计算距离 LatLng me = new LatLng(lat, lng); names = pois.stream() .sorted((a, b) -> { LatLng pointA = toLatLng(a.getLatLonPoint()); LatLng pointB = toLatLng(b.getLatLonPoint()); double distA = AMapUtils.calculateLineDistance(pointA, me); double distB = AMapUtils.calculateLineDistance(pointB, me); return Double.compare(distA, distB); }) .map(PoiItem::getTitle) .collect(Collectors.toList()); } else { names = new ArrayList<>(); for (PoiItem item : pois) { names.add(item.getTitle()); } } String[] arr = names.toArray(new String[0]); notifySuccess(arr); } else { notifyEmpty(); } } @Override public void onPoiItemSearched(com.amap.api.services.core.PoiItem item, int rCode) { // 忽略 } // ✅ 新增工具方法:将 LatLonPoint 转换为 LatLng private LatLng toLatLng(LatLonPoint point) { if (point == null) return null; return new LatLng(point.getLatitude(), point.getLongitude()); } private void notifySuccess(@NonNull String[] suggestions) { mainHandler.post(() -> callback.onSuggestionsReady(suggestions)); } private void notifyEmpty() { mainHandler.post(() -> callback.onSuggestionsReady(new String[0])); } }
最新发布
12-04
package com.example.labmemotest; import androidx.appcompat.app.AppCompatActivity; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.core.content.FileProvider; import android.Manifest; import android.content.ActivityNotFoundException; import android.content.ContentValues; import android.content.Intent; import android.content.pm.PackageManager; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.graphics.Color; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.provider.MediaStore; import android.util.Log; import android.view.View; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import com.bumptech.glide.Glide; import com.example.labmemotest.bean.CustomOption; import com.example.labmemotest.db.labDbHelper; import java.io.File; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Random; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import androidx.appcompat.app.AlertDialog; public class AddInfoActivity extends AppCompatActivity { private EditText edit_title, edit_content, edit_location; private Button btn_camera, btn_photo, btn_save; private LinearLayout previewContainer; private String tmp_path; private labDbHelper mhelper; private SQLiteDatabase db; private List<String> imagePaths = new ArrayList<>(); private Handler handler = new Handler(Looper.getMainLooper()); private ActivityResultLauncher<Intent> cameraLauncher; private ActivityResultLauncher<Intent> galleryLauncher; private static final int CAMERA_PERMISSION_CODE = 100; private static final int GALLERY_PERMISSION_CODE = 200; private LinearLayout containerPrefixGroups; private List<Spinner> spinners = new ArrayList<>(); private String currentUserId = "USER001"; // 应从登录系统获取 private List<Spinner> optionSpinners = new ArrayList<>(); // 新增,修改 private Executor executor = Executors.newSingleThreadExecutor(); // 添加线程池 private boolean dbInitialized = false; // 跟踪数据库初始化状态 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_add_info); // 修复初始化顺序 initView(); // 先初始化视图 initDb(() -> { // 数据库初始化完成后回调 initLaunchers(); // 初始化结果启动器 btnOnClick(); // 设置按钮监听 checkCustomConfig(); // 检查用户是否配置过编号 }); } private void checkCustomConfig() { if (!dbInitialized || db == null || !db.isOpen()) { Toast.makeText(this, "数据库未准备好", Toast.LENGTH_SHORT).show(); return; } executor.execute(() -> { Cursor cursor = null; try { cursor = db.rawQuery( "SELECT has_custom_config FROM tb_user_config WHERE user_id=?", new String[]{currentUserId} ); boolean hasConfig = false; if (cursor != null && cursor.moveToFirst()) { hasConfig = cursor.getInt(0) == 1; } if (!hasConfig) { runOnUiThread(() -> { new AlertDialog.Builder(AddInfoActivity.this) .setTitle("提示") .setMessage("您还未设置自定义编号,是否现在设置?") .setPositiveButton("去设置", (dialog, which) -> { startActivity(new Intent(AddInfoActivity.this, SetupCustomIdActivity.class)); finish(); }) .setNegativeButton("取消", (dialog, which) -> finish()) .show(); }); } else { loadCustomIdSelecters(); // 加载用户配置的编号选择器 } } catch (Exception e) { Log.e("CheckConfig", "配置检查失败", e); runOnUiThread(() -> Toast.makeText(this, "配置检查失败: " + e.getMessage(), Toast.LENGTH_SHORT).show()); } finally { if (cursor != null && !cursor.isClosed()) { cursor.close(); } } }); } private void loadCustomIdSelecters() { runOnUiThread(() -> { if (containerPrefixGroups == null) return; containerPrefixGroups.removeAllViews(); optionSpinners.clear(); executor.execute(() -> { Map<Integer, List<CustomOption>> groupsMap = new LinkedHashMap<>(); Cursor cursor = null; try { cursor = db.rawQuery( "SELECT group_id, option_index, display_text, code_char " + "FROM tb_custom_config WHERE user_id=? ORDER BY group_id, option_index", new String[]{currentUserId} ); while (cursor != null && cursor.moveToNext()) { int groupId = cursor.getInt(0); groupsMap.computeIfAbsent(groupId, k -> new ArrayList<>()) .add(new CustomOption( currentUserId, groupId, cursor.getInt(1), cursor.getString(2), cursor.getString(3) )); } runOnUiThread(() -> { for (int groupId = 1; groupId <= 6; groupId++) { if (groupsMap.containsKey(groupId)) { addSpinnerForGroup(groupsMap.get(groupId)); } } }); } catch (Exception e) { Log.e("LoadSelectors", "加载选择器失败", e); runOnUiThread(() -> Toast.makeText(this, "加载选择器失败", Toast.LENGTH_SHORT).show()); } finally { if (cursor != null && !cursor.isClosed()) { cursor.close(); } } }); }); } private void addSpinnerForGroup(List<CustomOption> options) { if (options == null || options.isEmpty()) return; LinearLayout row = new LinearLayout(this); row.setLayoutParams(new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT )); row.setOrientation(LinearLayout.HORIZONTAL); row.setPadding(0, 0, 0, 16); TextView label = new TextView(this); label.setText("组" + options.get(0).groupId + ":"); label.setTextSize(16); label.setTextColor(Color.BLACK); label.setLayoutParams(new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT )); row.addView(label); Spinner spinner = new Spinner(this); spinner.setLayoutParams(new LinearLayout.LayoutParams( 0, LinearLayout.LayoutParams.WRAP_CONTENT, 1 )); ArrayAdapter<CustomOption> adapter = new ArrayAdapter<>( this, android.R.layout.simple_spinner_item, options ); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinner.setAdapter(adapter); optionSpinners.add(spinner); row.addView(spinner); containerPrefixGroups.addView(row); } private String generateMemoIdFromSelection() { if (optionSpinners == null || optionSpinners.isEmpty()) { return "MEM" + new Random().nextInt(100000); } StringBuilder prefix = new StringBuilder(); for (Spinner spinner : optionSpinners) { if (spinner != null && spinner.getSelectedItem() != null) { CustomOption selected = (CustomOption) spinner.getSelectedItem(); prefix.append(selected.codeChar); } } Random random = new Random(); String suffix = String.format(Locale.US, "%05d", random.nextInt(100000)); return prefix.toString() + suffix; } private void initLaunchers() { cameraLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == RESULT_OK) { File imgFile = new File(tmp_path); if (imgFile.exists()) { try { Uri imageUri = FileProvider.getUriForFile( this, getPackageName() + ".fileprovider", imgFile ); // 添加持久化权限 grantPersistablePermission(imageUri); imagePaths.add(imageUri.toString()); updateImagePreviews(); } catch (Exception e) { Toast.makeText(this, "URI转换失败: " + e.getMessage(), Toast.LENGTH_LONG).show(); } } else { Toast.makeText(this, "拍摄的图片未保存", Toast.LENGTH_LONG).show(); } } else if (result.getResultCode() == RESULT_CANCELED) { // 删除临时文件 new File(tmp_path).delete(); } } ); galleryLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == RESULT_OK && result.getData() != null) { Intent data = result.getData(); try { if (data.getClipData() != null) { int count = data.getClipData().getItemCount(); for (int i = 0; i < count; i++) { Uri uri = data.getClipData().getItemAt(i).getUri(); // 添加持久化权限 grantPersistablePermission(uri); imagePaths.add(uri.toString()); } } else if (data.getData() != null) { Uri uri = data.getData(); grantPersistablePermission(uri); imagePaths.add(uri.toString()); } updateImagePreviews(); } catch (Exception e) { Log.e("GalleryError", "相册选择失败", e); Toast.makeText(this, "图片选择失败: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } } } ); } // 统一处理持久化权限授予 private void grantPersistablePermission(Uri uri) { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // 获取持久化URI权限 getContentResolver().takePersistableUriPermission( uri, Intent.FLAG_GRANT_READ_URI_PERMISSION ); } else { // 旧版本使用临时权限 grantUriPermission( getPackageName(), uri, Intent.FLAG_GRANT_READ_URI_PERMISSION ); } // 显式添加URI权限(双重保障) grantUriPermission( getPackageName(), uri, Intent.FLAG_GRANT_READ_URI_PERMISSION ); } catch (SecurityException e) { Log.e("PermissionError", "权限授予失败: " + uri, e); Toast.makeText(this, "权限获取失败: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } } private void initView() { edit_title = findViewById(R.id.editText_title); edit_content = findViewById(R.id.editText_content); edit_location = findViewById(R.id.editText_location); btn_camera = findViewById(R.id.button_camera); btn_photo = findViewById(R.id.button_photo); btn_save = findViewById(R.id.button_save); previewContainer = findViewById(R.id.image_preview_container); containerPrefixGroups = findViewById(R.id.container_prefix_groups); } // 修复数据库初始化,添加完成回调 private void initDb(Runnable onComplete) { executor.execute(() -> { try { mhelper = new labDbHelper(AddInfoActivity.this); db = mhelper.getWritableDatabase(); dbInitialized = true; Log.d("DbInit", "数据库初始化成功"); } catch (Exception e) { Log.e("DbInit", "数据库初始化失败", e); dbInitialized = false; runOnUiThread(() -> Toast.makeText(this, "数据库初始化失败", Toast.LENGTH_SHORT).show()); } finally { runOnUiThread(() -> { if (onComplete != null) { onComplete.run(); } }); } }); } private void btnOnClick() { btn_camera.setOnClickListener(v -> checkCameraPermission()); btn_photo.setOnClickListener(v -> checkGalleryPermission()); btn_save.setOnClickListener(v -> btnSave()); } // 检查相机权限兼容Android各个版本 private void checkCameraPermission() { List<String> permissionsNeeded = new ArrayList<>(); // 1. 相机权限是必须的 if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { permissionsNeeded.add(Manifest.permission.CAMERA); } // 2. 根据Android版本处理存储权限 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // Android 13+ 需要READ_MEDIA_IMAGES权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) { permissionsNeeded.add(Manifest.permission.READ_MEDIA_IMAGES); } } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Android 10-12 不需要额外权限(使用分区存储) } else { // Android 9及以下需要WRITE_EXTERNAL_STORAGE权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { permissionsNeeded.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); } } if (permissionsNeeded.isEmpty()) { openCamera(); } else { ActivityCompat.requestPermissions( this, permissionsNeeded.toArray(new String[0]), CAMERA_PERMISSION_CODE ); } } // 检查相册权限兼容Android各个版本 private void checkGalleryPermission() { List<String> permissionsNeeded = new ArrayList<>(); // 根据Android版本处理读取权限 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // Android 13+ 需要READ_MEDIA_IMAGES权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) { permissionsNeeded.add(Manifest.permission.READ_MEDIA_IMAGES); } } else { // Android 12及以下需要READ_EXTERNAL_STORAGE权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { permissionsNeeded.add(Manifest.permission.READ_EXTERNAL_STORAGE); } } if (permissionsNeeded.isEmpty()) { openGallery(); } else { ActivityCompat.requestPermissions( this, permissionsNeeded.toArray(new String[0]), GALLERY_PERMISSION_CODE ); } } // 打开相机并创建临时文件 private void openCamera() { try { // 确保图片目录存在 File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); if (storageDir == null) { Toast.makeText(this, "无法访问存储空间", Toast.LENGTH_SHORT).show(); return; } if (!storageDir.exists() && !storageDir.mkdirs()) { Toast.makeText(this, "无法创建图片目录", Toast.LENGTH_SHORT).show(); return; } // 创建带时间戳的唯一文件名 String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); String imageFileName = "JPEG_" + timeStamp + ".jpg"; File imageFile = new File(storageDir, imageFileName); tmp_path = imageFile.getAbsolutePath(); // 创建FileProvider URI Uri photoURI = FileProvider.getUriForFile( this, getPackageName() + ".fileprovider", imageFile ); // 启动相机Intent Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); takePictureIntent.addFlags( Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION ); // 确保有相机应用可以处理该Intent if (takePictureIntent.resolveActivity(getPackageManager()) != null) { cameraLauncher.launch(takePictureIntent); } else { Toast.makeText(this, "未找到相机应用", Toast.LENGTH_SHORT).show(); } } catch (ActivityNotFoundException e) { Toast.makeText(this, "未找到相机应用", Toast.LENGTH_SHORT).show(); } catch (Exception e) { Log.e("CameraError", "相机启动失败", e); Toast.makeText(this, "相机启动失败: " + e.getMessage(), Toast.LENGTH_LONG).show(); } } // 打开相册选择图片 private void openGallery() { try { Intent galleryIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); galleryIntent.addCategory(Intent.CATEGORY_OPENABLE); galleryIntent.setType("image/*"); galleryIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); // 添加权限Flag确保可以获取持久化权限 galleryIntent.addFlags( Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION ); galleryLauncher.launch(galleryIntent); } catch (ActivityNotFoundException e) { Toast.makeText(this, "未找到图库应用", Toast.LENGTH_SHORT).show(); } catch (Exception e) { Log.e("GalleryError", "图库打开失败", e); Toast.makeText(this, "图库打开失败: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } } // 更新图片预览区域 private void updateImagePreviews() { previewContainer.removeAllViews(); for (String uriString : imagePaths) { try { Uri uri = Uri.parse(uriString); // 创建图片预览视图 ImageView imageView = new ImageView(this); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( 300, 300 ); params.setMargins(8, 8, 8, 8); imageView.setLayoutParams(params); imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); imageView.setBackgroundColor(Color.parseColor("#F5F5F5")); // 使用Glide加载图片 Glide.with(this) .load(uri) .placeholder(R.drawable.ic_default_image) .error(R.drawable.ic_error_image) .into(imageView); previewContainer.addView(imageView); } catch (Exception e) { Log.e("PreviewError", "图片预览失败: " + uriString, e); Toast.makeText(this, "图片预览失败: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } } } // 生成唯一备忘录ID // private String generateUniqueMemoId() { // if (db == null || !db.isOpen()) { // initDb(); // } // // if (db == null) { // Toast.makeText(this, "数据库不可用", Toast.LENGTH_SHORT).show(); // return ""; // } // // Random random = new Random(); // String memoId; // int attempts = 0; // boolean idExists; // // do { // // 生成9位随机数 // memoId = String.valueOf(100000000 + random.nextInt(900000000)); // idExists = false; // // // // // try { // // 检查ID是否已存在 // Cursor cursor = db.query( // "tb_memory", // new String[]{"memo_id"}, // "memo_id = ?", // new String[]{memoId}, // null, null, null // ); // idExists = cursor.getCount() > 0; // cursor.close(); // } catch (Exception e) { // Log.e("GenerateIdError", "ID检查失败", e); // Toast.makeText(this, "ID检查失败", Toast.LENGTH_SHORT).show(); // return ""; // } // // attempts++; // // 防止无限循环 // if (attempts > 100) { // Toast.makeText(this, "无法生成唯一ID", Toast.LENGTH_SHORT).show(); // return ""; // } // } while (idExists); // // return memoId; // } // 替换原有的 generateUniqueMemoId() // String uniqueMemoId = generateMemoIdFromSelection(); // 修复4:增强数据库操作安全性 private void initDb() { executor.execute(() -> { try { mhelper = new labDbHelper(AddInfoActivity.this); db = mhelper.getWritableDatabase(); } catch (Exception e) { Log.e("DbInit", "数据库初始化失败", e); runOnUiThread(() -> Toast.makeText(this, "数据库初始化失败", Toast.LENGTH_SHORT).show()); } }); } // 保存按钮点击事件 private void btnSave() { String title = edit_title.getText().toString().trim(); String content = edit_content.getText().toString().trim(); String location = edit_location.getText().toString().trim(); String uniqueMemoId = generateMemoIdFromSelection(); // ✅ 正确位置 if (uniqueMemoId.isEmpty()) { Toast.makeText(this, "无法生成有效ID", Toast.LENGTH_SHORT).show(); return; } // 验证输入 if (title.isEmpty()) { Toast.makeText(this, "请输入标题", Toast.LENGTH_SHORT).show(); return; } // 确保数据库可用 if (db == null || !db.isOpen()) { initDb(); if (db == null || !db.isOpen()) { Toast.makeText(this, "数据库未准备好", Toast.LENGTH_SHORT).show(); return; } } try { // 格式化当前时间 Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm", Locale.getDefault()); String mtime = sdf.format(date); // 插入主备忘录数据 ContentValues memoryValues = new ContentValues(); memoryValues.put("memo_id", uniqueMemoId); memoryValues.put("title", title); memoryValues.put("content", content); memoryValues.put("location", location); memoryValues.put("mtime", mtime); long insertResult = db.insert("tb_memory", null, memoryValues); if (insertResult != -1) { // 插入关联图片 for (String imagePath : imagePaths) { ContentValues imageValues = new ContentValues(); imageValues.put("memo_id", uniqueMemoId); imageValues.put("imgpath", imagePath); db.insert("tb_image", null, imageValues); } Toast.makeText(this, "保存成功,包含 " + imagePaths.size() + " 张图片", Toast.LENGTH_SHORT).show(); // 延迟1秒后返回主页面 handler.postDelayed(() -> { startActivity(new Intent(AddInfoActivity.this, MainActivity.class)); finish(); }, 1000); } else { Toast.makeText(this, "保存失败,请重试", Toast.LENGTH_SHORT).show(); } } catch (Exception e) { Log.e("SaveError", "保存失败", e); Toast.makeText(this, "保存失败: " + e.getMessage(), Toast.LENGTH_LONG).show(); } finally { // 确保关闭数据库 if (db != null && db.isOpen()) { try { db.close(); } catch (Exception e) { Log.e("DbClose", "数据库关闭失败", e); } } } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == CAMERA_PERMISSION_CODE) { boolean allGranted = true; for (int result : grantResults) { if (result != PackageManager.PERMISSION_GRANTED) { allGranted = false; break; } } if (allGranted) { openCamera(); } else { Toast.makeText(this, "需要所有权限才能使用相机", Toast.LENGTH_SHORT).show(); } } else if (requestCode == GALLERY_PERMISSION_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { openGallery(); } else { Toast.makeText(this, "需要权限才能访问相册", Toast.LENGTH_SHORT).show(); } } } @Override public void onBackPressed() { super.onBackPressed(); goBackToMain(); } @Override protected void onDestroy() { super.onDestroy(); // 清理资源 if (db != null && db.isOpen()) { try { db.close(); } catch (Exception e) { Log.e("DestroyError", "数据库关闭失败", e); } } } private void goBackToMain() { startActivity(new Intent(AddInfoActivity.this, MainActivity.class)); finish(); } } 在点击创建日志按钮进入日志创建页面时程序还是闪退,具体怎么解决;在不改变原有代码的基础上;修改完善后输出完整代码。
09-29
@Override public void createArticleOutputFromTemplateHttp(HttpServletRequest request, HttpServletResponse response, ArticleData articleData) { if (articleData==null){ log.warn("勾选模板内容生成word文档的参数为空,不作处理,返回。"); return; } // 获取勾选的内容 Long[] ids = articleData.getIds(); if (ids==null || ids.length<=0){ log.warn("没有选择需要生成word文档的数据,不作处理,返回。"); return; } // 是否导出备注详情 int notesExportFlag = articleData.getNotesExportFlag(); // 导出数据准备---------------------------------- List<ArticleData> articleDataList = articleDataMapper.selectArticleDataByIds(ids); if (articleDataList==null || articleDataList.size()<=0){ log.warn("获取的word文档数据为空,不作处理,返回。"); return; } // 批量获取模块数据 ,然后进行排序======================================= // Long[] menu_ids = new Long[articleDataList.size()]; // for(int i=0;i<articleDataList.size();i++){ // menu_ids[i] = articleDataList.get(i).getMenuId(); // } // List<ArticleMenu> menu_list = articleMenuMapper.selectArticleMenuByMenuIds(menu_ids); // // 排序 // Collections.sort(menu_list, new Comparator<ArticleMenu>() { // @Override // public int compare(ArticleMenu o1, ArticleMenu o2) { // // TODO Auto-generated method stub // int flag = (int) (o1.getParentId()-o2.getParentId()); // if(flag == 0){ // flag = (int) (o1.getOrderNum()-o2.getOrderNum()); // } // return flag; // } // }); // ================================================================== // 获取数据 有序数据 // articleData.setMenuIds(menu_ids); // articleDataList = articleDataMapper.selectArticleDataByMenuIdsOrder(articleData); //各个 Long 含义: 层级、menuId(原先为父类模块ID)、排序order_num(注?:确保排序唯一),最底层map:数据、父类ID、排序 Map<Integer,Map<Long, Map<Long, List<Map<String, Object>>>>> myDatasMap = new HashMap<Integer,Map<Long, Map<Long, List<Map<String, Object>>>>>(); //菜单最深层数 int maxLevel = 1; // 菜单纵信息 List<Long[]> list_coloum = new ArrayList<Long[]>(); // 菜单纵信息及后面按显示顺序排序 List<String[]> list_coloum_str = new ArrayList<String[]>(); // 获取模块目录信息 for(ArticleData param : articleDataList){ //这里导出的时候加上备注的内容 if(notesExportFlag>0 && StringUtils.isNotBlank(param.getNotes())){ param.setContent(param.getContent() +"<p><br/></p>" +"<p>***备注***:</p>" +"<p>"+param.getNotes()+"</p>"); } ArticleMenu menu = articleMenuMapper.selectArticleMenuByMenuId(param.getMenuId()); String[] menuIdArr = menu.getAncestors().split(","); Long[] menuIdsArr = new Long[menuIdArr.length+1]; // 用于排序(menu_id 和 序号 综合排) String[] menuIdsSort = new String[menuIdArr.length+1]; if(menuIdArr!=null && menuIdArr.length>0){ for(int i=0;i<menuIdArr.length;i++){ menuIdsArr[i] = Long.parseLong(menuIdArr[i].trim()); if(i>0){ ArticleMenu menu_temp = articleMenuMapper.selectArticleMenuByMenuId(Long.parseLong(menuIdArr[i].trim())); menuIdsSort[i] = menuIdArr[i].trim()+"&&"+menu_temp.getOrderNum(); }else{ menuIdsSort[i] = menuIdArr[i].trim(); } } } menuIdsArr[menuIdArr.length] = menu.getMenuId(); list_coloum.add(menuIdsArr); menuIdsSort[menuIdArr.length] = menu.getMenuId()+"&&"+menu.getOrderNum(); list_coloum_str.add(menuIdsSort); if(StringUtils.isNotBlank(param.getArticleVersion())){ param.setTitle(param.getTitle()+"(v"+param.getArticleVersion()+"版本)"); } // 解析获取模块信息-横信息 getArticleMenus(myDatasMap,maxLevel,menuIdsArr,param); } // 数据处理 if(myDatasMap != null && myDatasMap.size()>0){ String resourcePath = System.getProperty("user.dir") + "/src/main/resources/"; log.info("项目resource路径 resourcePath:"+resourcePath); List<Map<String, Object>> dataList = new ArrayList<Map<String, Object>>(); StringBuffer titleSb = new StringBuffer(); // 存放已经遍历到并处理的模块ID List<Long> menus_handled = new ArrayList<Long>(); // 记录多于1个模块的最深层数 int max_level = 0; int max_level_content = 0; // 菜单序号 int menu_num = 0; int ch_menu_num = 0; int ch2_menu_num = 0; int ch3_menu_num = 0; //按menu_id升序排序,之前使用的--20241016 // Collections.sort(list_coloum, new Comparator<Long[]>() { // @Override // public int compare(Long[] o1, Long[] o2) { // // TODO Auto-generated method stub // int flag = 0; // int len = o1.length>o2.length?o2.length:o1.length; // for(int i=0;i<len;i++){ // int diff = (int) (o1[i]-o2[i]); // flag = flag+diff; // } // return flag; // } // }); //按显示顺序升序排序list_coloum_str Collections.sort(list_coloum_str, new Comparator<String[]>() { @Override public int compare(String[] o1, String[] o2) { // TODO Auto-generated method stub int flag = 0; int len = o1.length>o2.length?o2.length:o1.length; for(int i=1;i<len;i++){ int num1 = Integer.parseInt(o1[i].split("&&")[1].trim()); int num2 = Integer.parseInt(o2[i].split("&&")[1].trim()); int diff = num1 - num2; flag = flag+diff; if(flag!=0){ break; } } return flag; } }); // for(Long[] arr_coloum : list_coloum){ // mu:for(int i=0;i<arr_coloum.length;i++){ // // 当前menuID // Long current_menu_id = arr_coloum[i]; for(String[] arr_coloum : list_coloum_str){ mu:for(int i=0;i<arr_coloum.length;i++){ // 当前menuID Long current_menu_id = Long.parseLong(arr_coloum[i].split("&&")[0].trim()); if(current_menu_id==null || current_menu_id==0 || current_menu_id.equals(0)){ continue; } if(menus_handled!=null&&menus_handled.size()>0){ for(Long menuid_handled : menus_handled){ if(menuid_handled.equals(current_menu_id)){ // 已处理的,就跳过 continue mu; } } } //层级、menuId(现为父类模块ID)、排序order_num(注?:确保排序唯一) Map<Long, Map<Long, List<Map<String, Object>>>> current_menu = myDatasMap.get(i); if(current_menu!=null && current_menu.size()>0){ List<Long> contentMenuIdsExist = new ArrayList<Long>(); for(Long current_parent_id : current_menu.keySet()){ Map<Long, List<Map<String, Object>>> current_parent_map = current_menu.get(current_parent_id); if(current_parent_map!=null && current_parent_map.size()>0){ for(Long current_order_num : current_parent_map.keySet()){ List<Map<String, Object>> current_order_list = current_parent_map.get(current_order_num); if(current_order_list!=null && current_order_list.size()>0){ for(Map<String, Object> current_order_map : current_order_list){ Long menuid = (Long) current_order_map.get("menuId"); if(current_menu_id.equals(menuid)){ log.info("项目resource路径 resourcePath:"+current_menu_id); // 层级、menuId(现为父类模块ID)、排序order_num(注?:确保排序唯一) int current_level_order = (int) current_order_map.get("menuLevel"); if(current_menu.size()==1 && current_parent_map.size()==1 && current_order_list.size() == 1 && (max_level_content==0 || (max_level_content>0 &&current_level_order<max_level_content ))){ //说明该模块只有一个,这里作为标题 if(max_level_content>0){ if(current_level_order<max_level_content){ titleSb.append(current_order_map.get("menuName")+"_"); max_level = current_level_order; } }else{ titleSb.append(current_order_map.get("menuName")+"_"); max_level = current_level_order; } }else{ boolean isExist = false; if(contentMenuIdsExist!=null&&contentMenuIdsExist.size()>0){ for(Long menuKey : contentMenuIdsExist){ if(menuKey.equals(menuid)){ isExist = true; break; } } } if(!isExist){ int current_level = (int) current_order_map.get("menuLevel"); int level = current_level-max_level<1?1:current_level-max_level; if(current_level>max_level_content){ max_level_content = current_level; } // 给目录加上序号 String title_num = ""; if(level==1){ menu_num++; ch_menu_num = 0; //层级有变化,低一级目录序号清零 ch2_menu_num=0; ch3_menu_num=0; if(menu_num==1){ title_num = "一."; }else if(menu_num==2){ title_num = "二."; }else if(menu_num==3){ title_num = "三."; }else if(menu_num==4){ title_num = "四."; }else if(menu_num==5){ title_num = "五."; }else if(menu_num==6){ title_num = "六."; }else if(menu_num==7){ title_num = "七."; }else if(menu_num==8){ title_num = "八."; }else if(menu_num==9){ title_num = "九."; }else if(menu_num==10){ title_num = "十."; }else if(menu_num==11){ title_num = "十一."; }else if(menu_num==12){ title_num = "十二."; }else if(menu_num==13){ title_num = "十三."; }else if(menu_num==14){ title_num = "十四."; }else if(menu_num==15){ title_num = "十五."; }else if(menu_num==16){ title_num = "十六."; }else if(menu_num==17){ title_num = "十七."; }else if(menu_num==18){ title_num = "十八."; }else if(menu_num==19){ title_num = "十九."; }else if(menu_num==20){ title_num = "二十."; }else if(menu_num==21){ title_num = "二十一."; }else if(menu_num==22){ title_num = "二十二."; }else if(menu_num==23){ title_num = "二十三."; }else if(menu_num==24){ title_num = "二十四."; }else if(menu_num==25){ title_num = "二十五."; }else if(menu_num==26){ title_num = "二十六."; }else if(menu_num==27){ title_num = "二十七."; }else if(menu_num==28){ title_num = "二十八."; }else if(menu_num==29){ title_num = "二十九."; }else if(menu_num==30){ title_num = "三十."; } } if(level==2){ ch_menu_num++; ch2_menu_num=0; ch3_menu_num=0; title_num = menu_num+"."+ch_menu_num; } if(level==3){ ch2_menu_num++; ch3_menu_num=0; title_num = menu_num+"."+ch_menu_num+"."+ch2_menu_num; } if(level==4){ ch3_menu_num++; title_num = menu_num+"."+ch_menu_num+"."+ch2_menu_num+"."+ch3_menu_num; } Map<String, Object> wordData = new HashMap<>(); wordData.put("directory", title_num+" "+current_order_map.get("menuName")); Map<String, Object> wordDataMap = new HashMap<>(); wordDataMap.put("templateParth", resourcePath+"templates/bms_menu"+level+".docx"); wordDataMap.put("wordData", wordData); dataList.add(wordDataMap); } } if(current_order_map.get("content")!=null){ //加载内容 ArticleData current_article_data = (ArticleData) current_order_map.get("content"); if(current_article_data!=null){ int current_level = (int) current_order_map.get("menuLevel"); int level = current_level-max_level<1?1:current_level-max_level+1; if(current_level>max_level_content){ max_level_content = current_level; } // 给目录加上序号 String title_num = ""; if(level==1){ menu_num++; ch_menu_num = 0; //层级有变化,低一级目录序号清零 ch2_menu_num=0; ch3_menu_num=0; if(menu_num==1){ title_num = "一."; }else if(menu_num==2){ title_num = "二."; }else if(menu_num==3){ title_num = "三."; }else if(menu_num==4){ title_num = "四."; }else if(menu_num==5){ title_num = "五."; }else if(menu_num==6){ title_num = "六."; }else if(menu_num==7){ title_num = "七."; }else if(menu_num==8){ title_num = "八."; }else if(menu_num==9){ title_num = "九."; }else if(menu_num==10){ title_num = "十."; }else if(menu_num==11){ title_num = "十一."; }else if(menu_num==12){ title_num = "十二."; }else if(menu_num==13){ title_num = "十三."; }else if(menu_num==14){ title_num = "十四."; }else if(menu_num==15){ title_num = "十五."; }else if(menu_num==16){ title_num = "十六."; }else if(menu_num==17){ title_num = "十七."; }else if(menu_num==18){ title_num = "十八."; }else if(menu_num==19){ title_num = "十九."; }else if(menu_num==20){ title_num = "二十."; }else if(menu_num==21){ title_num = "二十一."; }else if(menu_num==22){ title_num = "二十二."; }else if(menu_num==23){ title_num = "二十三."; }else if(menu_num==24){ title_num = "二十四."; }else if(menu_num==25){ title_num = "二十五."; }else if(menu_num==26){ title_num = "二十六."; }else if(menu_num==27){ title_num = "二十七."; }else if(menu_num==28){ title_num = "二十八."; }else if(menu_num==29){ title_num = "二十九."; }else if(menu_num==30){ title_num = "三十."; } } if(level==2){ ch_menu_num++; ch2_menu_num=0; ch3_menu_num=0; title_num = menu_num+"."+ch_menu_num; } if(level==3){ ch2_menu_num++; ch3_menu_num=0; title_num = menu_num+"."+ch_menu_num+"."+ch2_menu_num; } if(level==4){ ch3_menu_num++; title_num = menu_num+"."+ch_menu_num+"."+ch2_menu_num+"."+ch3_menu_num; } //目录 Map<String, Object> wordData = new HashMap<>(); wordData.put("directory", " "+title_num+" "+current_article_data.getTitle()); Map<String, Object> wordDataMap = new HashMap<>(); //int current_level = (int) current_order_map.get("menuLevel"); //int level = current_level-max_level<1?1:current_level-max_level; wordDataMap.put("templateParth", resourcePath+"templates/bms_menu"+level+".docx"); wordDataMap.put("wordData", wordData); dataList.add(wordDataMap); // 内容 Map<String, Object> theData = new HashMap<>(); //=================================================================== // 测试内容 <p><img src="/dev-api/profile/upload/2024/07/30/bef0dd24-730b-41e7-946d-b1f86b09149f-(12) 亿纬_EVE-ICR18650_26V _ 知识管理 - PingCode.png" alt="" width="872" height="436" /></p> // File inputFile = new File("D:/Users/2104020051/Desktop/一些文件/富文本导入导出/亿纬_EVE-ICR18650_26V _ 知识管理 - PingCode.png"); // String base64 = ImageConvertBase64.toBase64(inputFile, true); // log.info("base64="+base64.substring(0, 200)+"......"); // String temp_img = "<p><img src=\""+base64+"\" alt=\"\" width=\"872\" height=\"436\" /></p>"; //==================================================================== ////theData.put("menu", current_article_data.getTitle()); //theData.put("content", current_article_data.getContent()!=null? // current_article_data.getContent().replaceAll("/dev-api/profile", RuoYiConfig.getProfile())+temp_img // :"<span style=\"color: red;\">~标题为:"+current_article_data.getTitle()+"(id="+current_article_data.getId()+")的内容为空</span>"); //针对内容的处理,主要是导出的时候图片的问题 String content = ""; if(current_article_data.getContent()!=null){ //文本编辑器类型(1:Quill富文本编辑器 2:CherryMarkdown 3:Tinymce富文本编辑器) int content_type = Integer.parseInt(current_article_data.getContentType().trim()); if(content_type==1){ content = current_article_data.getContent(); }else if(content_type==2 || content_type==3){ //2:CherryMarkdown 3:Tinymce富文本编辑器 List<String> imgValues = PoiWordUtil.getTagAttributeValues(current_article_data.getContent(), "img", "src"); if(imgValues!=null && imgValues.size()>0){ // 有图片时 content = current_article_data.getContent(); for (String value : imgValues) { String[] image_url_arr = value.split(Constants.RESOURCE_PREFIX); if(image_url_arr!=null && image_url_arr.length>=2){ //String image_url_local = value.replaceAll("/dev-api/profile", RuoYiConfig.getProfile()); //图片全路径 String image_url_local = RuoYiConfig.getProfile()+image_url_arr[1]; //图片全路径 log.info("图片路径="+image_url_local); //image_url_local = "D:/Users/2104020051/Desktop/一些文件/富文本导入导出/亿纬_EVE-ICR18650_26V _ 知识管理 - PingCode.png"; //"E:/ruoyi/uploadPath/upload/2024/07/30/bef0dd24-730b-41e7-946d-b1f86b09149f-(12) 亿纬_EVE-ICR18650_26V _ 知识管理 - PingCode.png"; File inputFile = new File(image_url_local); String base64 = ImageConvertBase64.toBase64(inputFile, true); log.info("base64="+base64.substring(0, 200)+"......"); log.info("value="+value); content = content.replace(value, base64); } } }else{ content = current_article_data.getContent(); } } }else{ content = "<span style=\"color: red;\">~标题为:"+current_article_data.getTitle()+"(id="+current_article_data.getId()+")的内容为空</span>"; } theData.put("content", content); Map<String, Object> wordDataMap2 = new HashMap<>(); wordDataMap2.put("templateParth", resourcePath+"templates/bms_content.docx"); wordDataMap2.put("wordData", theData); dataList.add(wordDataMap2); contentMenuIdsExist.add(menuid); } } menus_handled.add((Long) current_order_map.get("menuId")); // 加入已处理集合 } } } } } } } } } Map<String, Object> wordHead_test = new HashMap<>(); wordHead_test.put("title", titleSb.substring(0, titleSb.length()-1)); wordHead_test.put("currentTime", DateUtils2.getCurrentDateTime()); //首页数据 Map<String, Object> wordHeadMap_test = new HashMap<>(); wordHeadMap_test.put("templateParth", resourcePath+"templates/bms_title.docx"); wordHeadMap_test.put("wordData", wordHead_test); // 上传文件路径 //String filePath = RuoYiConfig.getPathWithTemplePack(); // 新文件名称 //String current = DateUtils2.format(new Date(), "yyyyMMddHHmmssSSS"); //IdUtils.fastUUID() //String fileName = DateUtils.datePath() + "/"+ "Demo报告-"+ current +".docx"; //String documentName = titleSb+ current +".docx"; //String fileName = DateUtils.datePath() + File.separator + documentName; try { //File desc = FileUploadUtils.getAbsoluteFile(filePath, fileName); //String descLocalUrl = desc.getAbsolutePath(); //log.info("导出路径descLocalUrl:"+descLocalUrl);; // 生成并导出word文档 //PoiWordUtil.createDocxManyForRTFHtml(wordHeadMap_test, dataList, descLocalUrl); PoiWordUtil.createDocxManyForRTFHtmlWithHttp(wordHeadMap_test,dataList,request, response); //WordUtil.createDocxMany(wordHeadMap_test, dataList, descLocalUrl); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } 。/** * 多XWPFTemplate合并成一个word导出,http导出 * @param headMap * @param dataList * @param destParth */ public static void createDocxManyForRTFHtmlWithHttp(Map<String, Object> headMap,List<Map<String, Object>> dataList,HttpServletRequest request, HttpServletResponse response) { LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy(); Configure config = Configure.builder().useSpringEL() .bind("list", policy).build(); // 首页标题 String templateParth_head = headMap.get("templateParth")!=null?String.valueOf(headMap.get("templateParth")).trim():""; Map<String, Object> wordData_head = headMap.get("wordData")!=null?(Map<String, Object>) headMap.get("wordData"):null; if(StringUtils.isBlank(templateParth_head) || wordData_head==null){ log.warn("首页标题参数异常,不作解析导出"); return; } XWPFTemplate template_head = XWPFTemplate.compile(templateParth_head,config).render(wordData_head);// 首页数据调用模板,填充数据 if(dataList==null || dataList.size()<=0){ log.warn("模块数据为空,不作解析导出"); return; } try { //多文档集合 List<NiceXWPFDocument> list = new ArrayList<NiceXWPFDocument>(); NiceXWPFDocument niceXWPFDocument = template_head.getXWPFDocument(); // 生成目录 -------------------------------------------------------------- // 创建标题 XWPFParagraph title = niceXWPFDocument.createParagraph(); XWPFRun titleRun = title.createRun(); //空一行 titleRun.setText(""); // 设置为空字符串来创建一个新的段落 titleRun.addCarriageReturn(); // 确保这个运行不会与前一个运行合并 //空一行 titleRun = title.createRun(); titleRun.setText(""); // 设置为空字符串来创建一个新的段落 titleRun.addCarriageReturn(); // 确保这个运行不会与前一个运行合并 titleRun = title.createRun(); titleRun.setText("目录"); titleRun.setFontSize(16); titleRun.setBold(true); //空一行 titleRun = title.createRun(); titleRun.setText(""); // 设置为空字符串来创建一个新的段落 titleRun.addCarriageReturn(); // 确保这个运行不会与前一个运行合并 niceXWPFDocument.enforceUpdateFields(); CTAbstractNum cTAbstractNum = CTAbstractNum.Factory.newInstance(); XWPFAbstractNum abstractNum = new XWPFAbstractNum(cTAbstractNum); XWPFNumbering numbering = niceXWPFDocument.createNumbering(); BigInteger numID = numbering.addAbstractNum(abstractNum); BigInteger numberingID = numbering.addNum(numID); // 插入目录 XWPFParagraph toc = niceXWPFDocument.createParagraph(); toc.setPageBreak(true); toc.setWordWrap(true); toc.setAlignment(ParagraphAlignment.CENTER); CTP ctP = toc.getCTP(); CTSimpleField tocField = ctP.addNewFldSimple(); tocField.setInstr("TOC \\o \"1-3\" \\h \\z \\u"); toc.getCTP().addNewR().addNewFldChar().setFldCharType(STFldCharType.END); // 生成目录end------------------------------------------------------------- XWPFRun runs = niceXWPFDocument.createParagraph().createRun(); //int num = 0; for(Map<String, Object> dataMap : dataList){ String templateParth_data = dataMap.get("templateParth")!=null?String.valueOf(dataMap.get("templateParth")).trim():""; Map<String, Object> wordData_data = dataMap.get("wordData")!=null?(Map<String, Object>) dataMap.get("wordData"):null; if(StringUtils.isBlank(templateParth_data) || wordData_data==null){ log.warn("模版数据参数异常,该数据不作解析导出,下一条.."); continue; } XWPFTemplate template_data = null;//XWPFTemplate.compile(templateParth_data,config).render(wordData_data);// wordData数据调用模板,填充数据 // 富文本的数据 if(wordData_data.get("content")!=null){ String content = (String) wordData_data.get("content"); String tmpContent = content.replace("<", "<").replace(">", ">").replace(""", "\"").replace("&", "&"); // 拼接html格式内容 StringBuffer sbf = new StringBuffer(); // 这里拼接一下html标签,便于word文档能够识别 sbf.append("<html " + "xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\" xmlns:w=\"urn:schemas-microsoft-com:office:word\" xmlns:m=\"http://schemas.microsoft.com/office/2004/12/omml\" xmlns=\"http://www.w3.org/TR/REC-html40\"" + //将版式从web版式改成页面试图 ">"); sbf.append("<head>" + "<!--[if gte mso 9]><xml><w:WordDocument><w:View>Print</w:View><w:TrackMoves>false</w:TrackMoves><w:TrackFormatting/><w:ValidateAgainstSchemas/><w:SaveIfXMLInvalid>false</w:SaveIfXMLInvalid><w:IgnoreMixedContent>false</w:IgnoreMixedContent><w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText><w:DoNotPromoteQF/><w:LidThemeOther>EN-US</w:LidThemeOther><w:LidThemeAsian>ZH-CN</w:LidThemeAsian><w:LidThemeComplexScript>X-NONE</w:LidThemeComplexScript><w:Compatibility><w:BreakWrappedTables/><w:SnapToGridInCell/><w:WrapTextWithPunct/><w:UseAsianBreakRules/><w:DontGrowAutofit/><w:SplitPgBreakAndParaMark/><w:DontVertAlignCellWithSp/><w:DontBreakConstrainedForcedTables/><w:DontVertAlignInTxbx/><w:Word11KerningPairs/><w:CachedColBalance/><w:UseFELayout/></w:Compatibility><w:BrowserLevel>MicrosoftInternetExplorer4</w:BrowserLevel><m:mathPr><m:mathFont m:val=\"Cambria Math\"/><m:brkBin m:val=\"before\"/><m:brkBinSub m:val=\"--\"/><m:smallFrac m:val=\"off\"/><m:dispDef/><m:lMargin m:val=\"0\"/> <m:rMargin m:val=\"0\"/><m:defJc m:val=\"centerGroup\"/><m:wrapIndent m:val=\"1440\"/><m:intLim m:val=\"subSup\"/><m:naryLim m:val=\"undOvr\"/></m:mathPr></w:WordDocument></xml><![endif]-->" + "</head>"); sbf.append("<body>"); // 富文本内容 sbf.append(tmpContent); sbf.append("</body></html>"); HtmlRenderPolicy htmlRenderPolicy1 = new HtmlRenderPolicy(); Configure configure1 = Configure.builder() .bind("content", htmlRenderPolicy1) .build(); Map<String, Object> data1 = new HashMap<>(); data1.put("content", sbf.toString()); template_data = XWPFTemplate.compile(templateParth_data, configure1).render(data1); }else{ template_data = XWPFTemplate.compile(templateParth_data,config).render(wordData_data); } list.add(template_data.getXWPFDocument()); template_data.close(); } NiceXWPFDocument newDoc = niceXWPFDocument.merge(list,runs); //合并多个 //FileOutputStream out = new FileOutputStream(destParth);//要导出的文件名 //输出文件 request.setCharacterEncoding("utf-8"); //request.setAttribute(paramString, paramObject);.setAsyncTimeout(10000); //导出word格式"application/msword" -doc "application/vnd.openxmlformats-officedocument.wordprocessingml.document" -docx response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); //response.setHeader("Access-Control-Expose-Headers","Content-disposition"); response.addHeader("Content-Disposition", "attachment;filename=" + wordData_head.get("title") + ".docx"); //过期时间设置 //response.setDateHeader("expires",System.currentTimeMillis() + 60 * 1000); ServletOutputStream out = response.getOutputStream(); newDoc.write(out); out.flush(); out.close(); newDoc.close(); niceXWPFDocument.close(); template_head.close(); log.info("导出生成完成..."); } catch (Exception e) { e.printStackTrace(); } } 这是导出word文档的两个方法,现在文档正文比标题字体大,如何将文档正文规范化展示
07-30
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值