tip1——LinearLayout

LinearLayout布局详解
本文详细解析了LinearLayout中button等元素的layout_gravity属性在不同orientation设置下(horizontal与vertical)的有效范围,指出在horizontal方向时,只有left、right、center_horizontal等垂直方向设置生效;而在vertical方向时,则只有水平方向的设置才有效。
当LinearLayout的orientation设置为horizontal(水平)时,里面的button等的layout_gravity只有垂直方向的设置才有效,即:left,right,center_horizontal 是生效的。;反之,为vertical时,只有水平方向的设置才有效
现在的最大问题就是,点击搜索按钮后,地图没有缩回60%处,目前代码如下:package com.example.bus.ui.map; 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.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import com.amap.api.services.core.AMapException; // 已导入 ✅ 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.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.services.core.PoiItem; import com.amap.api.services.poisearch.PoiResult; import com.amap.api.services.poisearch.PoiSearch; import com.amap.api.services.help.Inputtips; import com.amap.api.services.help.Tip; import com.example.bus.R; import com.example.bus.RoutePlanActivity; import com.example.bus.ResultAdapter; import com.example.bus.databinding.FragmentMapBinding; import java.util.ArrayList; import java.util.List; public class MapFragment extends Fragment implements PoiSearch.OnPoiSearchListener { private FragmentMapBinding binding; private MapView mapView; private AMap aMap; private Inputtips inputTips; // 数据 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 static final int LOCATION_PERMISSION_REQUEST_CODE = 1001; @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() { // 初始化 RecyclerView 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); // 如果只有起点变了 → 回到选择起点 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(selectedStartPoi != null); 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, "", "全国"); 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 = aMap.addMarker(new MarkerOptions() .position(latLng) .title("起点:" + item.getTitle())); selectedStartPoi = item; aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 14f)); // 自动进入第二阶段 String keyword = binding.mapInput2.getText().toString().trim(); showEndpointSelection(keyword); } else if (selectionStage == 2) { if (endMarker != null) endMarker.remove(); endMarker = aMap.addMarker(new MarkerOptions() .position(latLng) .title("终点:" + item.getTitle())); selectedEndPoi = item; aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 14f)); binding.btnGoTo.setEnabled(true); } } @Override public void onPoiSearched(PoiResult result, int rCode) { requireActivity().runOnUiThread(() -> { if (rCode == 1000 && result != null && !result.getPois().isEmpty()) { List<PoiItem> newList = result.getPois(); poiList.clear(); poiList.addAll(newList); adapter.notifyDataSetChanged(); binding.emptyView.setVisibility(View.GONE); binding.resultList.setVisibility(View.VISIBLE); } else { handleSearchError(rCode); binding.resultList.setVisibility(View.GONE); binding.emptyView.setVisibility(View.VISIBLE); binding.emptyView.setText("⚠️ 未找到相关地点"); } }); } @Override public void onPoiItemSearched(PoiItem item, int rCode) {} private void setupMap(Bundle savedInstanceState) { mapView.onCreate(savedInstanceState); 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++ < 50) { new Handler(Looper.getMainLooper()).postDelayed(this, 200); } } }, 200); } private void initMapSettings() { UiSettings uiSettings = aMap.getUiSettings(); uiSettings.setZoomControlsEnabled(true); uiSettings.setCompassEnabled(true); uiSettings.setScrollGesturesEnabled(true); aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(39.909186, 116.397411), 10f)); } private void setupSearchSuggestion() { // 初始化 Inputtips try { inputTips = new Inputtips(requireContext(), new Inputtips.InputtipsListener() { @Override public void onGetInputtips(List<Tip> tipList, int rCode) { if (rCode == 1000 && tipList != null && !tipList.isEmpty()) { String[] arr = new String[tipList.size()]; for (int i = 0; i < tipList.size(); i++) { arr[i] = tipList.get(i).getName(); } ArrayAdapter<String> adapter = new ArrayAdapter<>( requireContext(), android.R.layout.simple_dropdown_item_1line, arr ); requireActivity().runOnUiThread(() -> { if (requireActivity().getCurrentFocus() == binding.mapInput1) { binding.mapInput1.setAdapter(adapter); } else if (requireActivity().getCurrentFocus() == binding.mapInput2) { binding.mapInput2.setAdapter(adapter); } }); } else { requireActivity().runOnUiThread(() -> { binding.mapInput1.setAdapter(null); binding.mapInput2.setAdapter(null); }); handleSearchError(rCode); } } }); } catch (Exception e) { e.printStackTrace(); Toast.makeText(requireContext(), "智能提示初始化失败", Toast.LENGTH_SHORT).show(); } Handler handler = new Handler(Looper.getMainLooper()); Runnable[] pending1 = {null}, pending2 = {null}; // 输入框1监听 —— 已添加 try-catch 处理 AMapException binding.mapInput1.addTextChangedListener(new SimpleTextWatcher(s -> { if (pending1[0] != null) handler.removeCallbacks(pending1[0]); if (s.length() == 0) { binding.mapInput1.setAdapter(null); } else { pending1[0] = () -> { try { inputTips.requestInputtips(s.toString(), "全国"); } catch (AMapException e) { e.printStackTrace(); // 可选:调试时提示 // Toast.makeText(requireContext(), "建议请求失败", Toast.LENGTH_SHORT).show(); } }; handler.postDelayed(pending1[0], 600); } })); // 输入框2监听 —— 已添加 try-catch 处理 AMapException binding.mapInput2.addTextChangedListener(new SimpleTextWatcher(s -> { if (pending2[0] != null) handler.removeCallbacks(pending2[0]); if (s.length() == 0) { binding.mapInput2.setAdapter(null); } else { pending2[0] = () -> { try { inputTips.requestInputtips(s.toString(), "全国"); } catch (AMapException e) { e.printStackTrace(); } }; handler.postDelayed(pending2[0], 600); } })); } 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(); } @Override public void onResume() { super.onResume(); mapView.onResume(); enableMyLocationLayer(); } @Override public void onPause() { super.onPause(); mapView.onPause(); } @Override public void onDestroyView() { super.onDestroyView(); mapView.onDestroy(); inputTips = null; binding = null; } @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); mapView.onSaveInstanceState(outState); } private void enableMyLocationLayer() { if (aMap == null) return; if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { aMap.setMyLocationEnabled(true); } 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) aMap.setMyLocationEnabled(true); } } } 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); } } }
11-28
你当前的问题是: > **点击搜索按钮后,地图没有缩回 60% 高度(即未还原地图可视区域)** 这个问题的本质是: 👉 **你在 `performSearch()` 中展示了结果面板(`containerResultList`),但没有控制地图的显示比例或布局行为**。 --- ## 🧩 现状分析 你的布局结构应该是这样的(从命名推断): ```xml <ConstraintLayout> <com.amap.api.maps.MapView android:id="@+id/mapView" ... /> <LinearLayout android:id="@+id/containerResultList" android:layout_height="wrap_content" android:layout_alignParentBottom="true" ... > <!-- 搜索结果、按钮组等 --> </LinearLayout> </ConstraintLayout> ``` 你现在做了: - 点击搜索 → 显示 `containerResultList` - 期望:**地图自动“上移”,留出下半部分给搜索结果(比如占 60% 屏幕)** 但目前: ✅ `containerResultList.setVisibility(View.VISIBLE);` → 成功显示 ❌ 地图没有被“挤压”或“缩放”,仍然全屏占据,导致遮挡 --- ## ✅ 根本原因 你只设置了 `containerResultList` 可见,**但没有通过 ConstraintLayout 的约束让 `mapView` 自动调整高度**! 也就是说:**布局未响应式变化!** --- ## ✅ 解决方案:使用 `ConstraintSet` 或预设两种布局状态 我们采用最简单有效的方式:在 XML 中定义好约束,在代码中切换 `mapView` 和 `containerResultList` 的相对位置。 --- ### ✅ 步骤一:修改 XML 布局文件(关键) 确保你的 `fragment_map.xml` 类似如下结构(使用 `ConstraintLayout`): ```xml <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <com.amap.api.maps.MapView android:id="@+id/mapView" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toTopOf="@+id/containerResultList" app:layout_constraintVertical_bias="0.4" /> <LinearLayout android:id="@+id/containerResultList" android:layout_width="0dp" android:layout_height="0dp" android:orientation="vertical" android:background="@color/white" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHeight_percent="0.6" android:visibility="gone"> <!-- 结果列表、按钮等 --> <androidx.recyclerview.widget.RecyclerView android:id="@+id/resultList" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <LinearLayout android:id="@+id/buttonGroup" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="8dp"> <Button android:id="@+id/btnSwitchTarget" ... /> <Button android:id="@+id/btnGoTo" ... /> </LinearLayout> </LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout> ``` ### 🔑 关键点说明: | 属性 | 作用 | |------|------| | `app:layout_constraintBottom_toTopOf="@+id/containerResultList"` | 让地图底部贴住结果面板顶部 | | `app:layout_constraintHeight_percent="0.6"` | 结果面板占 60% 高度 | | `android:visibility="gone"` | 初始隐藏 | --- ### ✅ 步骤二:在 `performSearch()` 中正确显示并触发布局重绘 你原来的代码: ```java binding.containerResultList.setVisibility(View.VISIBLE); binding.buttonGroup.setVisibility(View.VISIBLE); ``` ✅ 显示了 UI,但可能缺少强制重新测量布局。 建议改为: ```java // 在 performSearch() 最后添加: binding.containerResultList.setVisibility(View.VISIBLE); binding.containerResultList.requestLayout(); // 强制重新布局 ``` 或者更稳妥地延迟一点刷新: ```java binding.containerResultList.post(() -> { binding.containerResultList.setVisibility(View.VISIBLE); binding.mapView.requestLayout(); }); ``` --- ### ✅ 推荐增强版 `performSearch()`(仅改最后部分) ```java 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); // 强制重新布局(可选) binding.containerResultList.requestLayout(); // 如果只有起点变了 → 回到选择起点 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); } } ``` --- ### ✅ 补充:关闭面板时恢复地图全屏 你可以加个“收起”按钮或点击空白处隐藏: ```java // 示例:点击空白区域收起 binding.emptyView.setOnClickListener(v -> { binding.containerResultList.setVisibility(View.GONE); binding.mapView.requestLayout(); }); ``` --- ## ✅ 总结:你现在要做的三件事 1. ✅ **确认 `fragment_map.xml` 使用 `ConstraintLayout`** 2. ✅ **为 `mapView` 设置:`app:layout_constraintBottom_toTopOf="@+id/containerResultList"`** 3. ✅ **为 `containerResultList` 设置:`app:layout_constraintHeight_percent="0.6"` + `visibility="gone"` 初始** 4. ✅ (可选)调用 `requestLayout()` 触发更新 --- ## ✅ 效果预期 | 操作 | 效果 | |------|------| | 打开页面 | 地图全屏 | | 点击搜索 | 下方弹出占 60% 高度的结果面板,地图被“顶上去”变成占 40% | | 收起面板 | 地图恢复全屏 | 这才是你想要的交互逻辑 ✅ --- ##
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值