需要全部源码请点赞关注收藏后评论区留言私信~~~
艺术家常说“距离产生美”,其实距离近才是优势,谁不希望自己的工作事少钱多离家近呢?不光是工作,像租房买房、恋爱交友,大家都希望找个近点的,比如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);
}
}
创作不易 觉得有帮助请点赞关注收藏~~~