Android 仿微信聊天位置选择(基于高德地图,BottomSheetBehavior)
废话不多说,直接上图
和微信效果差别不大
编辑器不会用,
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
android:id="@+id/cl_chouti"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorWhite"
tools:background="@color/colorAppNormal">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorTransparent">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:id="@+id/bottomPlaceView"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_260" />
android:id="@+id/titleBar"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_68"
android:background="@drawable/shape_gray_gradient_top"
app:layout_constraintTop_toTopOf="parent"
app:title_statusBarLightMode="false"
app:title_titleMainText="" />
android:id="@+id/tvCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/dp_10"
android:text="取消"
android:textColor="@color/colorWhite"
android:textSize="@dimen/sp_16"
android:textStyle="bold"
android:onClick="onClick"
app:layout_constraintBottom_toBottomOf="@+id/rtvSure"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/rtvSure" />
android:id="@+id/rtvSure"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/dp_10"
android:layout_marginBottom="@dimen/dp_28"
android:paddingHorizontal="@dimen/dp_10"
android:paddingVertical="@dimen/dp_4"
android:onClick="onClick"
android:text="发送"
android:textColor="@color/colorWhite"
android:textSize="@dimen/sp_14"
app:layout_constraintBottom_toBottomOf="@+id/titleBar"
app:layout_constraintEnd_toEndOf="parent"
app:rv_backgroundColor="@color/colorAppNormal"
app:rv_backgroundPressedColor="@color/colorAppPress"
app:rv_radius="@dimen/dp_4" />
android:id="@+id/llBottomMenuPanel"
android:layout_width="match_parent"
android:layout_height="550dp"
android:layout_alignParentBottom="true"
android:background="@drawable/shape_round_top_20_white"
android:orientation="vertical"
app:behavior_hideable="false"
app:behavior_peekHeight="280dp"
app:behavior_fitToContents="true"
app:layout_behavior="@string/bottom_sheet_behavior">
android:id="@+id/viewCloseHeader"
android:layout_width="match_parent"
android:layout_height="0dp"
android:paddingHorizontal="@dimen/dp_10"
tools:layout_height="@dimen/dp_36">
android:id="@+id/ivCloseBottom"
android:layout_width="@dimen/dp_50"
android:layout_height="@dimen/dp_24"
android:layout_marginTop="@dimen/dp_10"
android:onClick="onClick"
android:background="@drawable/shape_round_10_gray2"
android:src="@mipmap/drop_down_unselected_icon"
android:tint="@color/color666666"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:id="@+id/llContentPanel"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
android:layout_width="match_parent"
android:layout_height="@dimen/dp_30"
android:layout_marginTop="@dimen/dp_14"
android:layout_marginBottom="@dimen/dp_10"
android:paddingHorizontal="@dimen/dp_10">
android:id="@+id/llSearchPlace"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/shape_round_5_gray2"
android:gravity="center"
android:orientation="horizontal"
android:visibility="visible"
android:onClick="onClick"
app:layout_constraintTop_toTopOf="parent">
android:layout_width="@dimen/dp_18"
android:layout_height="@dimen/dp_18"
android:src="@mipmap/ic_app_search"
android:tint="@color/color999999" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_6"
android:text="搜索地点"
android:textColor="@color/color999999"
android:textSize="@dimen/sp_13" />
android:id="@+id/llSearchInput"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center"
android:orientation="horizontal"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent">
android:id="@+id/etSearchContent"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/shape_round_5_gray2"
android:drawableStart="@mipmap/ic_app_search"
android:drawablePadding="@dimen/dp_6"
android:drawableTint="@color/color999999"
android:gravity="center_vertical"
android:hint="搜索地点"
android:paddingHorizontal="@dimen/dp_10"
android:singleLine="true"
android:textSize="@dimen/sp_13" />
android:id="@+id/tvSearchCancel"
android:layout_width="@dimen/dp_40"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/dp_6"
android:gravity="center_vertical|right"
android:text="取消"
android:onClick="onClick"
android:textColor="@color/color666666"
android:textSize="@dimen/sp_15" />
android:layout_width="match_parent"
android:layout_height="match_parent">
android:id="@+id/loadingLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:isFirstVisible="true">
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:srlEnableLoadMore="true"
app:srlEnableRefresh="false">
android:id="@+id/rvPoiList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorBackground"
android:overScrollMode="never"
android:nestedScrollingEnabled="true"
android:scrollbars="none"
tools:listitem="@layout/item_map_poi" />
public class ChooseMapActivityextends BaseActivityimplements View.OnClickListener {
private AMapaMap;
private BottomSheetBehaviorbehavior;
private int bottomPanelCollapsedHeight;
private int bottomPanelDifferenceHeight;
private int closeHeaderHeight;
private float lastSlide =0;
private LinearLayout.LayoutParamsbottomPlaceLayoutParams;
private LinearLayout.LayoutParamscloseHeaderLayoutParams;
private MarkerscreenMarker =null;
private MyLocationStylemyLocationStyle;
private boolean isMoveFromUserCheckItem;
private RecyclerCommonAdapterpoiAdapter;
private PoiItemV2currentPoi;
private KeyboardUtils.OnSoftInputChangedListenersoftInputChangedListener = height -> {
LogUtils.e("OnSoftInputChangedListener", height);
if (height >0) {
if (viewBinding.llSearchInput.getVisibility() == View.VISIBLE) {
if (behavior.getState() != BottomSheetBehavior.STATE_EXPANDED) {
viewBinding.etSearchContent.requestFocus();
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
}
}else {
if (viewBinding.llSearchInput.getVisibility() == View.VISIBLE) {
viewBinding.llSearchInput.requestFocus();
}
}
};
@Override
protected void initView(Bundle savedInstanceState) {
KeyboardUtils.registerSoftInputChangedListener(this,softInputChangedListener);
bottomPanelCollapsedHeight = SizeUtils.dp2px(260);
bottomPanelDifferenceHeight = SizeUtils.dp2px(270);
closeHeaderHeight = SizeUtils.dp2px(36);
AMapLocationClient.updatePrivacyShow(mContext,true,true);
AMapLocationClient.updatePrivacyAgree(mContext,true);
viewBinding.map.onCreate(savedInstanceState);// 此方法必须重写
if (aMap ==null) {
aMap =viewBinding.map.getMap();
}
setUpMap();
behavior = BottomSheetBehavior.from(viewBinding.llBottomMenuPanel);
behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
bottomPlaceLayoutParams = (LinearLayout.LayoutParams)viewBinding.bottomPlaceView.getLayoutParams();
closeHeaderLayoutParams = (LinearLayout.LayoutParams)viewBinding.viewCloseHeader.getLayoutParams();
behavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet,int newState) {
if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
LogUtils.e("BottomSheetBehavior.BottomSheetCallback:onStateChanged:STATE_COLLAPSED:: ", newState);
if (StringUtils.isTrimEmpty(viewBinding.etSearchContent.getText().toString())) {
viewBinding.llSearchPlace.setVisibility(View.VISIBLE);
viewBinding.llSearchInput.setVisibility(View.GONE);
}else {
viewBinding.llSearchInput.requestFocus();
}
addMarkerInScreenCenter();
}else {
LogUtils.e("BottomSheetBehavior.BottomSheetCallback:onStateChanged:STATE_HIDDEN:: ", newState);
addMarkerInScreenCenter();
}
}
@Override
public void onSlide(@NonNull View view,float slide) {
if (slide ==lastSlide)return;
LogUtils.e("BottomSheetBehavior.BottomSheetCallback:onSlide:: ",lastSlide, slide,lastSlide > slide ?"向下减小高度" :"向上增加高度");
bottomPlaceLayoutParams.height = (int) (bottomPanelCollapsedHeight +bottomPanelDifferenceHeight * slide);
viewBinding.bottomPlaceView.setLayoutParams(bottomPlaceLayoutParams);
closeHeaderLayoutParams.height = (int) (closeHeaderHeight * slide);
viewBinding.viewCloseHeader.setLayoutParams(closeHeaderLayoutParams);
lastSlide = slide;
}
});
viewBinding.rvPoiList.setLayoutManager(new LinearLayoutManager(mContext));
viewBinding.rvPoiList.addItemDecoration(new SpacesItemDecoration() {
@Override
public int getSpaceTop(int position,int itemCount) {
return position !=0 ? SizeUtils.dp2px(1) :0;
}
});
viewBinding.rvPoiList.setAdapter(poiAdapter =new RecyclerCommonAdapter(mContext, R.layout.item_map_poi,new ArrayList<>()) {
@Override
protected void convert(RecyclerViewHolder holder, PoiItemV2 item,int position) {
holder.setText(R.id.tvItemTitle, item.getTitle());
float distance = AMapUtils.calculateLineDistance(new LatLng(lastPoint.getLatitude(),lastPoint.getLongitude()),new LatLng(item.getLatLonPoint().getLatitude(), item.getLatLonPoint().getLongitude()));
String format = distance >1000 ?"%.1fkm | %s" :"%.1fm | %s";
holder.setText(R.id.tvItemDescription, String.format(format, distance >1000 ? distance /1000 : distance, item.getSnippet() +""));
CheckView cvItemCheck = holder.getView(R.id.cvItemCheck);
if (currentPoi !=null &¤tPoi.getPoiId().equals(item.getPoiId())) {
cvItemCheck.setVisibility(View.VISIBLE);
cvItemCheck.setColor(ContextCompat.getColor(mContext, R.color.colorAppNormal));
}else {
cvItemCheck.setVisibility(View.INVISIBLE);
}
}
});
poiAdapter.setOnItemClickListener((view, holder, position) -> {
if (NoDoubleClickUtils.isDoubleClick())return;
PoiItemV2 itemV2 =poiAdapter.getData().get(position);
if (currentPoi !=null &¤tPoi.getPoiId().equals(itemV2.getPoiId()))return;
currentPoi = itemV2;
poiAdapter.notifyDataSetChanged();
isMoveFromUserCheckItem =true;
viewBinding.map.setEnabled(false);
aMap.moveCamera(CameraUpdateFactory.newCameraPosition(new CameraPosition(new LatLng(itemV2.getLatLonPoint().getLatitude(), itemV2.getLatLonPoint().getLongitude()),18,30,30)));
});
viewBinding.refreshLayout.setEnableOverScrollDrag(false);
viewBinding.refreshLayout.setEnableOverScrollBounce(false);
viewBinding.refreshLayout.setNestedScrollingEnabled(false);
viewBinding.refreshLayout.setEnableNestedScroll(false);
//viewBinding.loadingLayout.setNestedScrollingEnabled(true);
viewBinding.loadingLayout.showState(LoadingState.class);
/* viewBinding.loadingLayout.setOnReloadListener((v, reloadCode) -> {
currentPage = 0;
doSearch();
});*/
viewBinding.refreshLayout.setOnLoadMoreListener(refreshLayout -> {
currentPage++;
doSearch();
});
/*aMap.setOnMapLoadedListener(new AMap.OnMapLoadedListener() {
@Override
public void onMapLoaded() {
addMarkerInScreenCenter();
aMap.moveCamera(CameraUpdateFactory.newCameraPosition(new CameraPosition(aMap.getCameraPosition().target, 18, 30, 30)));
currentPage = 0;
lastPoint = new LatLonPoint(aMap.getCameraPosition().target.latitude, aMap.getCameraPosition().target.longitude);
doSearch();
}
});*/
// 设置可视范围变化时的回调的接口方法
aMap.setOnCameraChangeListener(new AMap.OnCameraChangeListener() {
@Override
public void onCameraChange(CameraPosition position) {
}
@Override
public void onCameraChangeFinish(CameraPosition position) {
//屏幕中心的Marker跳动
startJumpAnimation();
if (isMoveFromUserCheckItem) {
isMoveFromUserCheckItem =false;
viewBinding.map.setEnabled(true);
}else {
lastPoint =new LatLonPoint(position.target.latitude, position.target.longitude);
currentPage =0;
doSearch();
}
}
});
startLocation();
}
//声明AMapLocationClient类对象
private AMapLocationClientmLocationClient =null;
//声明定位回调监听器
private AMapLocationListenermLocationListener =new AMapLocationListener() {
@Override
public void onLocationChanged(AMapLocation aMapLocation) {
addMarkerInScreenCenter();
aMap.moveCamera(CameraUpdateFactory.newCameraPosition(new CameraPosition(new LatLng(aMapLocation.getLatitude(), aMapLocation.getLongitude()),18,30,30)));
lastPoint =new LatLonPoint(aMapLocation.getLatitude(), aMapLocation.getLongitude());
currentPage =0;
doSearch();
}
};
private void doSearch() {
searchQuery.setCityLimit(true);
searchQuery.setPageNum(currentPage);
poiSearch.setQuery(searchQuery);
poiSearch.setBound(new PoiSearchV2.SearchBound(lastPoint,5000));
// 根据经纬度执行异步查询
poiSearch.searchPOIAsyn();
}
private PoiSearchV2poiSearch;
private PoiSearchV2.QuerysearchQuery;
int currentPage =0;
private LatLonPointlastPoint;
private PoiSearchV2.OnPoiSearchListeneronPoiSearchListener =new PoiSearchV2.OnPoiSearchListener() {
@SuppressLint("NewApi")
@Override
public void onPoiSearched(PoiResultV2 poiResultV2,int code) {
if (code ==1000 && poiResultV2.getCount() >0) {
poiResultV2.getPois().stream().distinct();
if (currentPage ==0) {
currentPoi = poiResultV2.getPois().get(0);
poiAdapter.getData().clear();
}
viewBinding.refreshLayout.finishLoadMore(true);
viewBinding.loadingLayout.showContent();
poiAdapter.getData().addAll(poiResultV2.getPois());
poiAdapter.notifyDataSetChanged();
}else {
if (currentPage ==0) {
viewBinding.loadingLayout.showState(EmptyState.class);
}
viewBinding.refreshLayout.finishLoadMore(true);
}
}
@Override
public void onPoiItemSearched(PoiItemV2 poiItemV2,int i) {
}
};
private void startLocation() {
//初始化定位
try {
mLocationClient =new AMapLocationClient(getApplicationContext());
//设置定位回调监听
AMapLocationClientOption mLocationOption =new AMapLocationClientOption();
mLocationOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy);
mLocationOption.setOnceLocation(true);
mLocationOption.setOnceLocationLatest(true);
mLocationOption.setNeedAddress(true);
mLocationClient.setLocationOption(mLocationOption);
mLocationClient.setLocationListener(mLocationListener);
searchQuery =new PoiSearchV2.Query("","","");
searchQuery.setPageSize(20);
// 创建GeocodeSearch对象
poiSearch =new PoiSearchV2(this,searchQuery);
// 设置解析监听器
poiSearch.setOnPoiSearchListener(onPoiSearchListener);
requestPermissions();
}catch (Exception e) {
e.printStackTrace();
}
}
private void requestPermissions() {
if (XXPermissions.isGranted(getApplicationContext(), Permission.Group.LOCATION)) {
mLocationClient.startLocation();
}else {
XXPermissions.with(mContext)
.permission(Permission.Group.LOCATION)
.request(new OnPermissionCallback() {
@Override
public void onGranted(List permissions,boolean all) {
mLocationClient.startLocation();
}
@Override
public void onDenied(List permissions,boolean never) {
if (never) {
new XPopup.Builder(mContext)
.isDestroyOnDismiss(true)
.asConfirm("系统提示","请授予位置权限","否","是",new OnConfirmListener() {
@Override
public void onConfirm() {
XXPermissions.startPermissionActivity(mContext, Permission.Group.LOCATION);
}
},null,false)
.show();
}else {
requestPermissions();
}
}
});
}
}
/**
* 屏幕中心marker 跳动
*/
public void startJumpAnimation() {
if (screenMarker !=null) {
//根据屏幕距离计算需要移动的目标点
final LatLng latLng =screenMarker.getPosition();
Point point =aMap.getProjection().toScreenLocation(latLng);
point.y -= SizeUtils.dp2px(80);
LatLng target =aMap.getProjection()
.fromScreenLocation(point);
//使用TranslateAnimation,填写一个需要移动的目标点
Animation animation =new TranslateAnimation(target);
animation.setInterpolator(new Interpolator() {
@Override
public float getInterpolation(float input) {
// 模拟重加速度的interpolator
if (input <=0.5) {
return (float) (0.5f -2 * (0.5 - input) * (0.5 - input));
}else {
return (float) (0.5f - Math.sqrt((input -0.5f) * (1.5f - input)));
}
}
});
//整个移动所需要的时间
animation.setDuration(600);
//设置动画
screenMarker.setAnimation(animation);
//开始动画
screenMarker.startAnimation();
}else {
Log.e("amap","screenMarker is null");
}
}
/**
* 在屏幕中心添加一个Marker
*/
private void addMarkerInScreenCenter() {
if (screenMarker !=null) {
screenMarker.destroy();
}
aMap.clear();
LatLng latLng =aMap.getCameraPosition().target;
Point screenPosition =aMap.getProjection().toScreenLocation(latLng);
screenMarker =aMap.addMarker(new MarkerOptions()
.anchor(0.5f,0.5f)
.icon(BitmapDescriptorFactory.fromResource(R.drawable.purple_pin)));
//设置Marker在屏幕上,不跟随地图移动
screenMarker.setPositionByPixels(screenPosition.x, screenPosition.y);
}
@Override
public void onClick(View v) {
if (NoDoubleClickUtils.isDoubleClick())return;
switch (v.getId()) {
case R.id.ivCloseBottom:
behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
break;
case R.id.llSearchPlace:
viewBinding.llSearchPlace.setVisibility(View.GONE);
viewBinding.llSearchInput.setVisibility(View.VISIBLE);
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
ThreadUtils.runOnUiThreadDelayed(() -> {
//打卡键盘
KeyboardUtils.showSoftInput(viewBinding.etSearchContent);
},200);
break;
case R.id.tvSearchCancel:
KeyboardUtils.hideSoftInput(getWindow());
ThreadUtils.runOnUiThreadDelayed(() -> {
viewBinding.llSearchPlace.setVisibility(View.VISIBLE);
viewBinding.llSearchInput.setVisibility(View.GONE);
behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
},200);
break;
case R.id.tvCancel:
setResult(RESULT_CANCELED);
finish();
break;
case R.id.rtvSure:
if (currentPoi ==null) {
showToast("请选择一个位置");
return;
}
setResult(RESULT_OK,new Intent().putExtra("extra",currentPoi.getLatLonPoint()));
finish();
break;
}
}
/**
* 设置一些amap的属性
*/
private void setUpMap() {
// 如果要设置定位的默认状态,可以在此处进行设置
myLocationStyle =new MyLocationStyle();
aMap.getUiSettings().setZoomControlsEnabled(false);
aMap.getUiSettings().setLogoBottomMargin(-100);
//aMap.getUiSettings().setMyLocationButtonEnabled(true);// 设置默认定位按钮是否显示
aMap.setMyLocationEnabled(true);// 设置为true表示显示定位层并可触发定位,false表示隐藏定位层并不可触发定位,默认是false
myLocationStyle.myLocationIcon(BitmapDescriptorFactory.fromResource(R.mipmap.gps_point));
myLocationStyle.anchor(0.5f,0.5f);
myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATE);
myLocationStyle.showMyLocation(true);
aMap.setMyLocationStyle(myLocationStyle);
}
@Override
protected void onActivityResult(int requestCode,int resultCode,@Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
}
@Override
protected void onResume() {
super.onResume();
viewBinding.map.onResume();
}
@Override
protected void onPause() {
super.onPause();
viewBinding.map.onPause();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
viewBinding.map.onSaveInstanceState(outState);
}
@Override
protected void onDestroy() {
KeyboardUtils.unregisterSoftInputChangedListener(getWindow());
super.onDestroy();
if (mLocationClient !=null) {
mLocationClient.stopLocation();
}
viewBinding.map.onDestroy();
}
}