1. 程式人生 > >Android開發-基於百度地圖API開發仿滴滴出行APP介面的實現

Android開發-基於百度地圖API開發仿滴滴出行APP介面的實現

前 言

近年來,由於移動網際網路快速的發展以及基於移動裝置的APP的普及,移動網際網路改變了人們的生活方式。從線上的電子支付到線下的出行,移動網際網路是當今社會人們生活不可或缺的一部分,而線下出行的網約車的出現極大便利了人們的出行,雖然它飽受了很大的爭議,但不可否認的是網約車的出現是一次大膽創新的嘗試。而最早推出網約車的是一家美國矽谷的科技公司推出的Uber網約車打車軟體,中文譯作“優步”,目前國內做的比較好的網約車打車軟體是滴滴出行。那麼,今天就來談談如何實現基於百度地圖API開發仿滴滴出行APP介面的實現吧!

開發環境

Android Studio 3.1.2
JDK 1.8

開發前準備

編碼與實現

在Android Studio的\res\layout目錄下新建一個activity_take_tax.xml檔案作為打車軟體地圖顯示介面,佈局程式碼如下。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent" android:focusable="true" android:focusableInTouchMode="true">
<com.baidu.mapapi.map.MapView android:id="@+id/mv_dongdong" android:layout_width="match_parent" android:layout_height
="match_parent" android:layout_above="@+id/ll_route" android:clickable="true" />
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true"> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="2dp" android:orientation="horizontal"> <LinearLayout android:layout_width="wrap_content" android:layout_height="50dp" android:gravity="center_vertical" android:orientation="horizontal"> <ImageView android:layout_width="48dp" android:layout_height="48dp" android:paddingLeft="10dp" android:src="@drawable/icon_car" /> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center_vertical" android:paddingLeft="3dp" android:text="網約打車" android:textColor="#FF7F00" android:textSize="20sp" /> </LinearLayout> <Button android:id="@+id/onPopupMenuClick" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:onClick="onPopupMenuClick" android:text="地圖模式" android:textSize="16sp" /> <ImageButton android:id="@+id/onBicycle" android:layout_width="40dp" android:layout_height="40dp" android:background="@drawable/icon_bike" android:layout_below="@id/onPopupMenuClick" android:layout_alignParentRight="true" android:layout_margin="10dp" android:onClick="onBicycle" /> </RelativeLayout> </LinearLayout> <LinearLayout android:id="@+id/ll_route" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:background="@color/white" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="2dp" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="出發地:" android:textColor="@color/black" android:textSize="17sp" /> <EditText android:id="@+id/et_departure" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="@drawable/editext_selector" android:padding="5dp" android:text="當前位置" android:textColor="@color/black" android:textSize="17sp" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="2dp" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="目的地:" android:textColor="@color/black" android:textSize="17sp" /> <EditText android:id="@+id/et_destination" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="@drawable/editext_selector" android:padding="5dp" android:textColor="@color/black" android:textSize="17sp" /> </LinearLayout> <TextView android:id="@+id/tv_travel" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:text="準備出發" android:textColor="@color/black" android:textSize="15sp" /> <Button android:id="@+id/btn_travel" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:text="開始叫車" android:textColor="@color/black" android:textSize="17sp" /> </LinearLayout> </RelativeLayout>

以上佈局程式碼主要實現了顯示百度地圖的介面以及一些輸入文字框和操作按鈕的實現。

在Android Studio的app\src\main\java\目錄下新建一個TakeTaxActivity.java檔案作為處理百度地圖介面顯示以及相關的操作類的邏輯程式碼,邏輯程式碼如下。

package com.fukaimei.onlinecar;

import com.baidu.location.BDLocation;
import com.baidu.location.BDLocationListener;
import com.baidu.location.LocationClient;
import com.baidu.location.LocationClientOption;
import com.baidu.mapapi.SDKInitializer;
import com.baidu.mapapi.map.BaiduMap;
import com.baidu.mapapi.map.BitmapDescriptor;
import com.baidu.mapapi.map.BitmapDescriptorFactory;
import com.baidu.mapapi.map.MapStatus;
import com.baidu.mapapi.map.MapStatusUpdate;
import com.baidu.mapapi.map.MapStatusUpdateFactory;
import com.baidu.mapapi.map.MapView;
import com.baidu.mapapi.map.MarkerOptions;
import com.baidu.mapapi.map.MyLocationConfiguration;
import com.baidu.mapapi.map.MyLocationData;
import com.baidu.mapapi.map.OverlayOptions;
import com.baidu.mapapi.model.LatLng;
import com.fukaimei.onlinecar.BicycleSharing.BicycleActivity;
import com.fukaimei.onlinecar.alipay.task.AlipayTask;
import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.InitListener;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechSynthesizer;
import com.iflytek.cloud.SynthesizerListener;

import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.PopupMenu;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class TakeTaxActivity extends Activity implements OnClickListener {
    private final static String TAG = "TakeTaxActivity";
    private EditText et_departure;
    private EditText et_destination;
    private TextView tv_travel;
    private Button btn_travel;
    private int mStep = 0;
    private SpeechSynthesizer mCompose;
    private LatLng mUserPos;
    private LatLng mDriverPos;
    private BitmapDescriptor icon_car;
    private int mDelayTime = 10000;
    private boolean bFinish = false;
    PopupMenu popup = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SDKInitializer.initialize(getApplicationContext());
        setContentView(R.layout.activity_take_tax);
        // 獲取相應的動態許可權
        xPermissions();
        et_departure = (EditText) findViewById(R.id.et_departure);
        et_destination = (EditText) findViewById(R.id.et_destination);
        tv_travel = (TextView) findViewById(R.id.tv_travel);
        btn_travel = (Button) findViewById(R.id.btn_travel);
        btn_travel.setOnClickListener(this);
        initLocation();
        initVoiceSetting();
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btn_travel) {
            if (mStep == 0) {  //開始打車
                btn_travel.setTextColor(getResources().getColor(R.color.dark_grey));
                btn_travel.setEnabled(false);
                speaking("等待司機接單");
                mHandler.postDelayed(mAccept, mDelayTime);
            } else if (mStep == 1) {  //支付車費

                AlertDialog.Builder builder = new AlertDialog.Builder(TakeTaxActivity.this);
                builder.setIcon(R.drawable.icon_car);
                builder.setTitle("請選擇支付車費方式");
                final String[] pay = {"支付寶支付", "微信支付", "銀聯支付"};
                //    設定一個單項選擇下拉框
                /**
                 * 第一個引數指定我們要顯示的一組下拉單選框的資料集合
                 * 第二個引數代表索引,指定預設哪一個單選框被勾選上,0表示預設'支付寶支付' 會被勾選上
                 * 第三個引數給每一個單選項繫結一個監聽器
                 */
                builder.setSingleChoiceItems(pay, 0, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Toast.makeText(TakeTaxActivity.this, "您選擇的支付方式為:" + pay[which], Toast.LENGTH_SHORT).show();
                    }
                });
                builder.setPositiveButton("確定支付", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        String desc = String.format("從%s到%s的打車費", et_departure.getText().toString(), et_destination.getText().toString());
                        new AlipayTask(TakeTaxActivity.this, 1).execute("快滴打車-打車費", desc, "0.01");
                        bFinish = false;
                    }
                });
                builder.setNegativeButton("取消支付", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {

                    }
                });
                builder.show();
            }
            mStep++;
        }
    }

    private Handler mHandler = new Handler();
    private double latitude_offset;
    private double longitude_offset;
    private Runnable mAccept = new Runnable() {
        @Override
        public void run() {
            double latitude = mUserPos.latitude + getRandomDecimal();
            double longitude = mUserPos.longitude + getRandomDecimal();
            Log.d(TAG, "latitude=" + latitude + ", longitude=" + longitude);
            latitude_offset = latitude - mUserPos.latitude;
            longitude_offset = longitude - mUserPos.longitude;
            mDriverPos = new LatLng(latitude, longitude);
            rereshCar(mDriverPos);
            speaking("司機馬上過來");
            mHandler.postDelayed(mRefresh, mDelayTime / 100);
        }
    };

    private double getRandomDecimal() {
        return 0.05 - Math.random() * 200 % 10.0 / 100.0;
    }

    private Runnable mRefresh = new Runnable() {
        private int i = 0;

        @Override
        public void run() {
            if (i++ < 100) {
                double new_latitude = mUserPos.latitude + latitude_offset * (100 - i) / 100.0;
                double new_longitude = mUserPos.longitude + longitude_offset * (100 - i) / 100.0;
                rereshCar(new LatLng(new_latitude, new_longitude));
                mHandler.postDelayed(this, mDelayTime / 100);
            } else {
                speaking("快車已經到達,請上車");
                mHandler.postDelayed(mTravel, mDelayTime);
            }
        }
    };

    private Runnable mTravel = new Runnable() {
        private int i = 0;

        @Override
        public void run() {
            if (i++ < 100) {
                double new_latitude = mUserPos.latitude + latitude_offset * i / 100.0;
                double new_longitude = mUserPos.longitude + longitude_offset * i / 100.0;
                rereshCar(new LatLng(new_latitude, new_longitude));
                mHandler.postDelayed(this, mDelayTime / 100);
            } else {
                speaking("已經到達目的地,歡迎下次再來乘車");
                btn_travel.setTextColor(getResources().getColor(R.color.black));
                btn_travel.setEnabled(true);
                btn_travel.setText("支付車費");
                bFinish = true;
            }
        }
    };

    private void rereshCar(LatLng pos) {
        mMapLayer.clear();
        OverlayOptions ooCar = new MarkerOptions().draggable(false)
                .visible(true).icon(icon_car).position(pos);
        mMapLayer.addOverlay(ooCar);
    }

    private void initVoiceSetting() {
        mCompose = SpeechSynthesizer.createSynthesizer(this, mComposeInitListener);
        mCompose.setParameter(SpeechConstant.PARAMS, null);
        mCompose.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);
        mCompose.setParameter(SpeechConstant.VOICE_NAME, "xiaoyan");
        SharedPreferences shared = getSharedPreferences(VoiceSettingsActivity.PREFER_NAME, MODE_PRIVATE);
        mCompose.setParameter(SpeechConstant.SPEED, shared.getString("speed_preference", "50"));
        mCompose.setParameter(SpeechConstant.PITCH, shared.getString("pitch_preference", "50"));
        mCompose.setParameter(SpeechConstant.VOLUME, shared.getString("volume_preference", "50"));
        mCompose.setParameter(SpeechConstant.STREAM_TYPE, shared.getString("stream_preference", "3"));
        mCompose.setParameter(SpeechConstant.KEY_REQUEST_FOCUS, "true");
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (bFinish != true) {
            mStep = 0;
            mMapLayer.clear();
            tv_travel.setText("準備出發");
            btn_travel.setText("開始叫車");
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mCompose.stopSpeaking();
        mCompose.destroy();
    }

    private void speaking(String text) {
        tv_travel.setText(text);
        int code = mCompose.startSpeaking(text, mComposeListener);
        if (code != ErrorCode.SUCCESS) {
            Toast.makeText(this, "語音合成失敗,錯誤碼: " + code, Toast.LENGTH_SHORT).show();
        }
    }

    //初始化監聽
    private InitListener mComposeInitListener = new InitListener() {
        @Override
        public void onInit(int code) {
            if (code != ErrorCode.SUCCESS) {
                Toast.makeText(TakeTaxActivity.this, "語音初始化失敗,錯誤碼: " + code, Toast.LENGTH_SHORT).show();
            }
        }
    };

    private SynthesizerListener mComposeListener = new SynthesizerListener() {
        @Override
        public void onBufferProgress(int arg0, int arg1, int arg2, String arg3) {
        }

        @Override
        public void onCompleted(SpeechError arg0) {
        }

        @Override
        public void onEvent(int arg0, int arg1, int arg2, Bundle arg3) {
        }

        @Override
        public void onSpeakBegin() {
        }

        @Override
        public void onSpeakPaused() {
        }

        @Override
        public void onSpeakProgress(int arg0, int arg1, int arg2) {
        }

        @Override
        public void onSpeakResumed() {
        }
    };

    // 以下主要是定位用到的程式碼
    private MapView mMapView;
    private BaiduMap mMapLayer;
    private LocationClient mLocClient;
    private boolean isFirstLoc = true;// 是否首次定位
    private double m_latitude;
    private double m_longitude;
    //定點陣圖層顯示方式
    private MyLocationConfiguration.LocationMode locationMode;

    private void initLocation() {
        mMapView = (MapView) findViewById(R.id.mv_dongdong);
        mMapView.setVisibility(View.INVISIBLE);
        mMapLayer = mMapView.getMap();
        mMapLayer.setMyLocationEnabled(true);
        mLocClient = new LocationClient(this);
        mLocClient.registerLocationListener(new MyLocationListenner());
        LocationClientOption option = new LocationClientOption();
        option.setOpenGps(true);
        option.setCoorType("bd09ll");
        option.setScanSpan(1000);
        option.setIsNeedAddress(true);
        mLocClient.setLocOption(option);
        mLocClient.start();
        icon_car = BitmapDescriptorFactory.fromResource(R.drawable.car_small);
    }

    public class MyLocationListenner implements BDLocationListener {
        @Override
        public void onReceiveLocation(BDLocation location) {
            if (location == null || mMapView == null) {
                Log.d(TAG, "location is null or mMapView is null");
                return;
            }
            m_latitude = location.getLatitude();
            m_longitude = location.getLongitude();
            Log.d(TAG, "m_latitude=" + m_latitude + ", m_longitude=" + m_longitude);
            MyLocationData locData = new MyLocationData.Builder()
                    .accuracy(location.getRadius())
                    .direction(100).latitude(m_latitude).longitude(m_longitude).build();
            mMapLayer.setMyLocationData(locData);
            if (isFirstLoc) {
                isFirstLoc = false;
                mUserPos = new LatLng(m_latitude, m_longitude);
                MapStatus mMapStatus;//地圖當前狀態
                MapStatusUpdate mMapStatusUpdate;//地圖將要變化成的狀態
                mMapStatus = new MapStatus.Builder().overlook(-45).build();
                mMapStatusUpdate = MapStatusUpdateFactory.newMapStatus(mMapStatus);
                mMapLayer.setMapStatus(mMapStatusUpdate);
                MapStatusUpdate update = MapStatusUpdateFactory.newLatLngZoom(mUserPos, 18);
                mMapLayer.animateMapStatus(update);
                mMapView.setVisibility(View.VISIBLE);
                //定義Maker座標點
                LatLng point = new LatLng(m_latitude + 0.00056, m_longitude);
                //構建Marker圖示
                BitmapDescriptor bitmap = BitmapDescriptorFactory
                        .fromResource(R.drawable.car_small);
                //構建MarkerOption,用於在地圖上新增Marker
                OverlayOptions option = new MarkerOptions()
                        .position(point)
                        .icon(bitmap);
                //在地圖上新增Marker,並顯示
                mMapLayer.addOverlay(option);
                //定義Maker座標點
                LatLng point1 = new LatLng(m_latitude, m_longitude + 0.0003);

            }

        }

        public void onReceivePoi(BDLocation poiLocation) {
        }
    }

    public void getMyLocation() {
        LatLng latLng = new LatLng(m_latitude, m_longitude);
        MapStatusUpdate msu = MapStatusUpdateFactory.newLatLng(latLng);
        mMapLayer.setMapStatus(msu);
    }

    public void onPopupMenuClick(View v) {
        // 建立PopupMenu物件
        popup = new PopupMenu(this, v);
        // 將R.menu.menu_main選單資源載入到popup選單中
        getMenuInflater().inflate(R.menu.menu_main, popup.getMenu());
        // 為popup選單的選單項單擊事件繫結事件監聽器
        popup.setOnMenuItemClickListener(
                new PopupMenu.OnMenuItemClickListener() {
                    @Override
                    public boolean onMenuItemClick(MenuItem item) {
                        switch (item.getItemId()) {
                            case R.id.id_map_common:
                                mMapLayer.setMapType(BaiduMap.MAP_TYPE_NORMAL);
                                break;
                            case R.id.id_map_site:
                                mMapLayer.setMapType(BaiduMap.MAP_TYPE_SATELLITE);
                                break;
                            case R.id.id_map_traffic:
                                if (mMapLayer.isTrafficEnabled()) {
                                    mMapLayer.setTrafficEnabled(false);
                                    item.setTitle("實時交通(off)");
                                } else {
                                    mMapLayer.setTrafficEnabled(true);
                                    item.setTitle("實時交通(on)");
                                }
                                break;
                            case R.id.id_map_mlocation:
                                getMyLocation();
                                break;
                        }
                        return true;
                    }
                });
        popup.show();
    }

    public void onBicycle(View v) {
        Intent intent = new Intent(TakeTaxActivity.this, BicycleActivity.class);
        startActivity(intent);
    }

    // 定義獲取動態許可權
    private void xPermissions() {
        if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{
                    android.Manifest.permission.ACCESS_FINE_LOCATION}, 1);
        }
        if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{
                    android.Manifest.permission.ACCESS_COARSE_LOCATION}, 1);
        }
        if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.RECORD_AUDIO)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{
                    android.Manifest.permission.RECORD_AUDIO}, 2);
        }
        if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{
                    android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, 3);
        }
        if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{
                    android.Manifest.permission.READ_EXTERNAL_STORAGE}, 3);
        }
        if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{
                    android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS}, 3);
        }
    }

    /**
     * 重寫onRequestPermissionsResult方法
     * 獲取動態許可權請求的結果,再開啟定位
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            initLocation();
        } else {
//            Toast.makeText(this, "您拒絕了定位許可權", Toast.LENGTH_SHORT).show();
        }
        if (requestCode == 2 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

        } else {
//            Toast.makeText(this, "您拒絕了錄音許可權", Toast.LENGTH_SHORT).show();
        }
        if (requestCode == 3 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

        } else {
//            Toast.makeText(this, "您拒絕了訪問SD卡許可權", Toast.LENGTH_SHORT).show();
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

}

在以上的邏輯檔案程式碼中,initLocation()方法主要實現的是用來進行百度地圖定位的操作;xPermissions()方法主要實現的是對Android 6.0 以上的手機進行動態申請一些敏感的許可權,比如定位許可權等。

測試與執行截圖

這裡寫圖片描述 這裡寫圖片描述
這裡寫圖片描述 這裡寫圖片描述


Demo程式原始碼下載地址一(GitHub)
Demo程式原始碼下載地址二(碼雲)