需要全部源码请点赞关注收藏后评论区留言私信~~~
艺术家常说“距离产生美”,其实距离近才是优势,谁不希望自己的工作事少钱多离家近呢?不光是工作,像租房买房、恋爱交友,大家都希望找个近点的,比如58、赶集主打同城交易,微信、陌陌主打同城交友,所谓近水楼台先得月嘛。 正因为位置信息如此重要,所以手机早早支持定位功能,还锲而不舍推进卫星定位、基站定位、WiFi定位等手段。 通过分享自己的位置,人们可以迅速找到附近志同道合的朋友,从而在传统社交之外开辟了新领域——周边社交。
一、需求描述
附近的人除了交友聊天,还存在下列的周边互动场景:
(1)个人有闲置物品,扔了可惜,想送给有需要的乡里乡亲;
(2)家里水电坏了,想临时找个附近的水电工上门修理;
(3)孩子长大了,看看周边有没有美术老师练习绘画、音乐老师练习钢琴之类; 为此需要增加地图导航功能,不仅在地图上标出周围人群的所在地,还需提供导航服务以便用户出行。
二、功能分析
(1)详情对话框:人员详情既包括头像图片,也包括昵称、性别、爱好、地址等文字描述。 (2)地图定位:自己位置和他人位置都用到了定位功能。
(3)地图导航:从当前位置驱车前往对方所在地,需要地图服务提供导航路线以便出行。
(4)网络通信框架:上传人员信息与获取人员列表均需与后端交互。
(5)图片加载框架:利用Glide框架加载人员头像。
(6)移动数据格式JSON:通过JSON结构封装http交互数据。
下面介绍一些代码模块之间的互相关系
(1)ChooseLocationActivity.java:这是选择自身位置的地图界面。
(2)InfoEditActivity.java:这是个人信息的编辑页面。
(3)NearbyActivity.java:这是显示附近人员的地图界面。
(4)NearbyLoadTask.java:这是附近人员列表的加载任务。
(5)PersonDialog.java:这是人员详情对话框,支持打对方电话、去对方那里等功能。
三、效果展示
首先选择自己的位置
点击下一步输入一些自己的基本信息
可以在下拉框中选择查看的人们列表 此处最好用多部手机进行测试并且开启后台服务器
四、代码
部分代码如下 如需要全部源码请点赞关注收藏后评论区留言私信~~~
package com.example.location; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.TextView; import android.widget.Toast; import com.tencent.lbssearch.TencentSearch; import com.tencent.lbssearch.object.param.Geo2AddressParam; import com.tencent.lbssearch.object.result.Geo2AddressResultObject; import com.tencent.map.geolocation.TencentLocation; import com.tencent.map.geolocation.TencentLocationListener; import com.tencent.map.geolocation.TencentLocationManager; import com.tencent.map.geolocation.TencentLocationRequest; import com.tencent.map.tools.net.http.HttpResponseListener; import com.tencent.tencentmap.mapsdk.maps.CameraUpdate; import com.tencent.tencentmap.mapsdk.maps.CameraUpdateFactory; import com.tencent.tencentmap.mapsdk.maps.MapView; import com.tencent.tencentmap.mapsdk.maps.TencentMap; import com.tencent.tencentmap.mapsdk.maps.model.BitmapDescriptor; import com.tencent.tencentmap.mapsdk.maps.model.BitmapDescriptorFactory; import com.tencent.tencentmap.mapsdk.maps.model.CameraPosition; import com.tencent.tencentmap.mapsdk.maps.model.LatLng; import com.tencent.tencentmap.mapsdk.maps.model.Marker; import com.tencent.tencentmap.mapsdk.maps.model.MarkerOptions; public class ChooseLocationActivity extends AppCompatActivity implements TencentLocationListener, TencentMap.OnMapClickListener, TencentMap.OnMarkerDragListener, TencentMap.OnCameraChangeListener { private final static String TAG = "ChooseLocationActivity"; private TencentLocationManager mLocationManager; // 声明一个腾讯定位管理器对象 private MapView mMapView; // 声明一个地图视图对象 private TencentMap mTencentMap; // 声明一个腾讯地图对象 private boolean isFirstLoc = true; // 是否首次定位 private float mZoom=12; // 缩放级别 private LatLng mMyPos; // 当前的经纬度 private String mAddress; // 详细地址 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_choose_location); initLocation(); // 初始化定位服务 findViewById(R.id.btn_next).setOnClickListener(v -> gotoNext()); } // 初始化定位服务 private void initLocation() { mMapView = findViewById(R.id.mapView); mTencentMap = mMapView.getMap(); // 获取腾讯地图对象 mTencentMap.setOnMapClickListener(this); // 设置地图的点击监听器 mTencentMap.setOnMarkerDragListener(this); // 设置地图标记的拖动监听器 mTencentMap.setOnCameraChangeListener(this); // 设置相机视角的变更监听器 mLocationManager = TencentLocationManager.getInstance(this); // 创建腾讯定位请求对象 TencentLocationRequest request = TencentLocationRequest.create(); request.setInterval(30000).setAllowGPS(true); request.setRequestLevel(TencentLocationRequest.REQUEST_LEVEL_ADMIN_AREA); mLocationManager.requestLocationUpdates(request, this); // 开始定位监听 } // 跳到个人信息填写页面 private void gotoNext() { if (mMyPos == null) { Toast.makeText(this, "请先选择您的常住地点", Toast.LENGTH_SHORT).show(); return; } Intent intent = new Intent(this, InfoEditActivity.class); intent.putExtra("latitude", mMyPos.latitude); intent.putExtra("longitude", mMyPos.longitude); intent.putExtra("address", mAddress); startActivity(intent); } @Override public void onLocationChanged(TencentLocation location, int resultCode, String resultDesc) { if (resultCode == TencentLocation.ERROR_OK) { // 定位成功 if (location != null && isFirstLoc) { // 首次定位 isFirstLoc = false; // 创建一个经纬度对象 LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude()); moveLocation(latLng, location.getAddress()); // 将地图移动到当前位置 } } else { // 定位失败 Log.d(TAG, "定位失败,错误代码为"+resultCode+",错误描述为"+resultDesc); } } @Override public void onStatusUpdate(String s, int i, String s1) {} // 将地图移动到当前位置 private void moveLocation(LatLng latLng, String address) { mMyPos = latLng; mAddress = address; CameraUpdate update = CameraUpdateFactory.newLatLngZoom(latLng, mZoom); mTencentMap.moveCamera(update); // 把相机视角移动到指定地点 // 从指定视图中获取位图描述 BitmapDescriptor bitmapDesc = BitmapDescriptorFactory .fromView(getMarkerView(mAddress)); MarkerOptions marker = new MarkerOptions(latLng).draggable(true) // 可以拖动 .visible(true).icon(bitmapDesc).snippet("这是您的当前位置"); mTencentMap.addMarker(marker); // 往地图添加标记 } // 获取标记视图 private View getMarkerView(String address) { View view = getLayoutInflater().inflate(R.layout.marker_me, null); TextView tv_address = view.findViewById(R.id.tv_address); tv_address.setText(address); return view; } @Override public void onMapClick(LatLng latLng) { mTencentMap.clearAllOverlays(); // 清除所有覆盖物 mMyPos = latLng; // 创建一个腾讯搜索对象 TencentSearch tencentSearch = new TencentSearch(this); Geo2AddressParam param = new Geo2AddressParam(mMyPos); // 根据经纬度查询地图上的详细地址 tencentSearch.geo2address(param, new HttpResponseListener() { @Override public void onSuccess(int i, Object o) { Geo2AddressResultObject result = (Geo2AddressResultObject) o; String address = String.format("%s(%s)", result.result.address, result.result.formatted_addresses.recommend); moveLocation(mMyPos, address); // 将地图移动到当前位置 } @Override public void onFailure(int i, String s, Throwable throwable) { Log.d(TAG, "geo2address onFailure code="+i+", msg="+s); } }); } @Override public void onMarkerDragStart(Marker marker) {} @Override public void onMarkerDrag(Marker marker) {} @Override public void onMarkerDragEnd(Marker marker) { onMapClick(marker.getPosition()); // 触发该位置的地图点击事件 } @Override public void onCameraChange(CameraPosition cameraPosition) {} @Override public void onCameraChangeFinished(CameraPosition cameraPosition) { mZoom = cameraPosition.zoom; } @Override protected void onStart() { super.onStart(); mMapView.onStart(); } @Override protected void onStop() { super.onStop(); mMapView.onStop(); } @Override public void onPause() { super.onPause(); mMapView.onPause(); } @Override public void onResume() { super.onResume(); mMapView.onResume(); } @Override protected void onDestroy() { super.onDestroy(); mLocationManager.removeUpdates(this); // 移除定位监听 mMapView.onDestroy(); } }
编辑自身页面的代码
package com.example.location; import androidx.appcompat.app.AppCompatActivity; import android.app.ProgressDialog; import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.text.TextUtils; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.ImageView; import android.widget.RadioGroup; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import com.example.location.bean.JoinResponse; import com.example.location.constant.UrlConstant; import com.example.location.util.BitmapUtil; import com.example.location.util.DateUtil; import com.example.location.util.SharedUtil; import com.google.gson.Gson; import java.io.File; import java.io.IOException; import okhttp3.Call; import okhttp3.Callback; import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; public class InfoEditActivity extends AppCompatActivity { private final static String TAG = "InfoEditActivity"; private int CHOOSE_CODE = 3; // 只在相册挑选图片的请求码 private EditText et_name, et_phone, et_info; // 姓名、手机号、个人信息的编辑框 private TextView tv_location; // 声明一个文本视图对象 private ImageView iv_face; // 声明一个图像视图对象 public boolean isMale = true; // 是否男性 public int mLoveType=0; // 爱好类型 public double mLatitude, mLongitude; // 经纬度 public String mAddress; // 详细地址 public Bitmap mOriginFace; // 原始的头像位图 private ProgressDialog mDialog; // 声明一个对话框对象 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_info_edit); et_name = findViewById(R.id.et_name); RadioGroup rg_sex = findViewById(R.id.rg_sex); rg_sex.setOnCheckedChangeListener((group, checkedId) -> isMale = checkedId == R.id.rb_male); et_phone = findViewById(R.id.et_phone); tv_location = findViewById(R.id.tv_location); iv_face = findViewById(R.id.iv_face); et_info = findViewById(R.id.et_info); iv_face.setOnClickListener(v -> { // 创建一个内容获取动作的意图(准备跳到系统相册) Intent albumIntent = new Intent(Intent.ACTION_GET_CONTENT); albumIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); // 是否允许多选 albumIntent.setType("image/*"); // 类型为图像 startActivityForResult(albumIntent, CHOOSE_CODE); // 打开系统相册 }); findViewById(R.id.btn_confirm).setOnClickListener(v -> saveInfo()); initIntentData(); // 初始化意图数据 initLoveSpinner(); // 初始化爱好类型下拉框 } // 初始化意图数据 private void initIntentData() { Bundle bundle = getIntent().getExtras(); mLatitude = bundle.getDouble("latitude"); mLongitude = bundle.getDouble("longitude"); mAddress = bundle.getString("address"); tv_location.setText(mAddress); } // 初始化爱好类型下拉框 private void initLoveSpinner() { Spinner sp_love = findViewById(R.id.sp_love); ArrayAdapter<String> love_adapter = new ArrayAdapter<>(this, R.layout.item_select, loveArray); sp_love.setPrompt("请选择兴趣爱好"); sp_love.setAdapter(love_adapter); sp_love.setOnItemSelectedListener(new LoveSelectedListener()); sp_love.setSelection(0); } private String[] loveArray = {"唱歌", "跳舞", "绘画", "弹琴", "摄影", "出售闲置物品"}; class LoveSelectedListener implements AdapterView.OnItemSelectedListener { public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) { mLoveType = arg2; } public void onNothingSelected(AdapterView<?> arg0) {} } @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); if (resultCode == RESULT_OK && requestCode == CHOOSE_CODE) { // 从相册返回 if (intent.getData() != null) { // 从相册选择一张照片 Uri uri = intent.getData(); // 获得已选择照片的路径对象 // 根据指定图片的uri,获得自动缩小后的位图对象 mOriginFace = BitmapUtil.getAutoZoomImage(this, uri, 500); iv_face.setImageBitmap(mOriginFace); // 设置图像视图的位图对象 } } } // 保存个人信息 private void saveInfo() { String name = et_name.getText().toString(); String phone = et_phone.getText().toString(); String info = et_info.getText().toString(); if (TextUtils.isEmpty(name)) { Toast.makeText(this, "请先输入您的昵称", Toast.LENGTH_SHORT).show(); return; } if (TextUtils.isEmpty(phone)) { Toast.makeText(this, "请先输入您的手机号码", Toast.LENGTH_SHORT).show(); return; } if (TextUtils.isEmpty(info)) { Toast.makeText(this, "请先输入要发布的信息", Toast.LENGTH_SHORT).show(); return; } if (mOriginFace == null) { Toast.makeText(this, "请先选择您的头像", Toast.LENGTH_SHORT).show(); return; } int minScope = Math.min(mOriginFace.getWidth(), mOriginFace.getHeight()); // 从原始位图裁剪出一个正方形位图 Bitmap cropFace = Bitmap.createBitmap(mOriginFace, 0, 0, minScope, minScope); String path = String.format("%s/%s.jpg", getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString(), DateUtil.getNowDateTime()); BitmapUtil.saveImage(path, cropFace); // 把位图保存为图片文件 // 弹出进度对话框 mDialog = ProgressDialog.show(this, "请稍候", "正在保存位置信息......"); // 下面把用户信息(包含头像)提交给HTTP服务端 MultipartBody.Builder builder = new MultipartBody.Builder(); // 往建造器对象添加文本格式的分段数据 builder.addFormDataPart("name", name); // 昵称 builder.addFormDataPart("sex", isMale?"0":"1"); // 性别 builder.addFormDataPart("phone", phone); // 手机号 builder.addFormDataPart("love", loveArray[mLoveType]); // 爱好 builder.addFormDataPart("info", info); // 发布信息 builder.addFormDataPart("address", mAddress); // 地址 builder.addFormDataPart("latitude", mLatitude+""); // 纬度 builder.addFormDataPart("longitude", mLongitude+""); // 经度 // 往建造器对象添加图像格式的分段数据 builder.addFormDataPart("image", path.substring(path.lastIndexOf("/")), RequestBody.create(new File(path), MediaType.parse("image/*"))); RequestBody body = builder.build(); // 根据建造器生成请求结构 OkHttpClient client = new OkHttpClient(); // 创建一个okhttp客户端对象 // 创建一个POST方式的请求结构 Request request = new Request.Builder().post(body) .url(UrlConstant.HTTP_PREFIX+"joinNearby").build(); Call call = client.newCall(request); // 根据请求结构创建调用对象 // 加入HTTP请求队列。异步调用,并设置接口应答的回调方法 call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { // 请求失败 // 回到主线程操纵界面 runOnUiThread(() -> { mDialog.dismiss(); // 关闭进度对话框 Toast.makeText(InfoEditActivity.this, "保存位置信息出错:"+e.getMessage(), Toast.LENGTH_SHORT).show(); }); } @Override public void onResponse(Call call, final Response response) throws IOException { // 请求成功 String resp = response.body().string(); JoinResponse joinResponse = new Gson().fromJson(resp, JoinResponse.class); // 回到主线程操纵界面 runOnUiThread(() -> { mDialog.dismiss(); // 关闭进度对话框 if ("0".equals(joinResponse.getCode())) { finishSave(); // 结束信息保存动作 } else { Toast.makeText(InfoEditActivity.this, "保存位置信息失败:"+joinResponse.getDesc(), Toast.LENGTH_SHORT).show(); } }); } }); } // 结束信息保存动作 private void finishSave() { Toast.makeText(this, "成功保存您的位置信息", Toast.LENGTH_SHORT).show(); SharedUtil.getIntance(this).writeString("commitMyInfo", "true"); Intent intent = new Intent(this, NearbyActivity.class); // 设置启动标志:跳转到新页面时,栈中的原有实例都被清空,同时开辟新任务的活动栈 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } }
创作不易 觉得有帮助请点赞关注收藏~~~