【Android App】实战项目之仿微信的附近的人(附源码和演示 超详细)

简介: 【Android App】实战项目之仿微信的附近的人(附源码和演示 超详细)

需要全部源码请点赞关注收藏后评论区留言私信~~~

艺术家常说“距离产生美”,其实距离近才是优势,谁不希望自己的工作事少钱多离家近呢?不光是工作,像租房买房、恋爱交友,大家都希望找个近点的,比如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);
    }
}

创作不易 觉得有帮助请点赞关注收藏~~~

相关文章
|
9小时前
|
Android开发
Android面试题经典之如何全局替换App的字体
在Android应用中替换字体有全局和局部方法。全局替换涉及在`Application`的`onCreate`中设置自定义字体,并创建新主题。局部替换则可在布局中通过`ResourcesCompat.getFont()`加载字体文件并应用于`TextView`。
9 2
|
1天前
|
机器学习/深度学习 人工智能 文字识别
文本,文字扫描01,OCR文本识别技术展示,一个安卓App,一个简单的设计,文字识别可以应用于人工智能,机器学习,车牌识别,身份证识别,银行卡识别,PaddleOCR+SpringBoot+Andr
文本,文字扫描01,OCR文本识别技术展示,一个安卓App,一个简单的设计,文字识别可以应用于人工智能,机器学习,车牌识别,身份证识别,银行卡识别,PaddleOCR+SpringBoot+Andr
|
2天前
|
Android开发
【亲测,安卓版】快速将网页网址打包成安卓app,一键将网页打包成app,免安装纯绿色版本,快速将网页网址打包成安卓apk
【亲测,安卓版】快速将网页网址打包成安卓app,一键将网页打包成app,免安装纯绿色版本,快速将网页网址打包成安卓apk
8 0
|
6天前
|
Java Maven Android开发
安卓项目使用阿里云镜像加速构建过程
安卓项目使用阿里云镜像加速构建过程
8 0
|
7天前
|
Java API Android开发
技术经验分享:Android源码笔记——Camera系统架构
技术经验分享:Android源码笔记——Camera系统架构
10 0
|
9天前
|
Java 开发工具 Android开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
在移动应用开发的广阔天地中,Android和iOS两大平台各自占据着半壁江山。本文将深入探讨这两个平台在开发过程中的关键差异点,包括编程语言、开发工具、用户界面设计、性能优化以及市场覆盖等方面。通过对这些关键因素的比较分析,旨在为开发者提供一个清晰的指南,帮助他们根据项目需求和目标受众做出明智的平台选择。
|
12天前
|
Java Android开发 Kotlin
Android面试题:App性能优化之Java和Kotlin常见的数据结构
Java数据结构摘要:ArrayList基于数组,适合查找和修改;LinkedList适合插入删除;HashMap1.8后用数组+链表/红黑树,初始化时预估容量可避免扩容。SparseArray优化查找,ArrayMap减少冲突。 Kotlin优化摘要:Kotlin的List用`listOf/mutableListOf`,Map用`mapOf/mutableMapOf`,支持操作符重载和扩展函数。序列提供懒加载,解构用于遍历Map,扩展函数默认参数增强灵活性。
16 0
|
1天前
|
JSON 前端开发 API
移动端---------app开发03----apicloud必须掌握的代码
移动端---------app开发03----apicloud必须掌握的代码
|
1天前
|
前端开发 开发者
移动端-------app开发02,了解apicloud功能和使用,真机测试
移动端-------app开发02,了解apicloud功能和使用,真机测试
|
2天前
4. 解决uni-app开发过程中view、image等标签出现诸如“出现错误:类型“{ class: string; }”的参数不能赋给类型“.......”
4. 解决uni-app开发过程中view、image等标签出现诸如“出现错误:类型“{ class: string; }”的参数不能赋给类型“.......”
8 0