Android Study 之玩轉(zhuǎn)高德地圖二部曲[實(shí)現(xiàn)POI檢索附近]

LZ-Says:前段時(shí)間更新了一篇關(guān)于高德地圖的簡(jiǎn)單使用,其中主要包含實(shí)現(xiàn)地圖顯示以及定位簡(jiǎn)單功能,具體文章如下:

Android Study 之玩轉(zhuǎn)高德地圖一部曲[實(shí)現(xiàn)顯示地圖以及定位功能]

寫(xiě)這個(gè)的<font color=#FF0000>目的</font>有倆個(gè),一個(gè)是對(duì)自己的一個(gè)<font color=#FF0000>記錄,想看的時(shí)候隨時(shí)能點(diǎn)擊看看</font>;二就是針對(duì)和LZ一樣的人群,<font color=#FF0000>簡(jiǎn)單快捷高效實(shí)現(xiàn)地圖開(kāi)發(fā),我走過(guò)的坑,在坑上浪費(fèi)的時(shí)間,大家看了之后就能多少避免一些。</font>

寫(xiě)文章,方便自己,共享他人,相互學(xué)習(xí),相互交流,共同進(jìn)步,何樂(lè)不為?

本文意在快速實(shí)現(xiàn)高德地圖POI檢索附近,具體官方文檔以及官方GitHub都已經(jīng)有相關(guān)demo,LZ只是把自己的開(kāi)發(fā)過(guò)程敘述下而已,希望大家都有自己的收獲~

下面為大家展示先最終實(shí)現(xiàn)效果:

這里寫(xiě)圖片描述

POI簡(jiǎn)介

POI,又稱(chēng)Point of Interest(興趣點(diǎn))。在地圖表達(dá)中,一個(gè) POI 可代表一棟大廈、一家商鋪、一處景點(diǎn)等等。通過(guò)POI搜索,完成找餐館、找景點(diǎn)、找?guī)鹊鹊墓δ堋?/p>

主要細(xì)化為4個(gè)部分,如下:

  • 關(guān)鍵字檢索POI
    根據(jù)關(guān)鍵字檢索適用于在某個(gè)城市搜索某個(gè)名稱(chēng)相關(guān)的POI,例如:查找北京市的“肯德基”。
    <font color=#FF0000>注意:
    1、關(guān)鍵字未設(shè)置城市信息(默認(rèn)為全國(guó)搜索)時(shí),如果涉及多個(gè)城市數(shù)據(jù)返回,僅會(huì)返回建議城市,請(qǐng)根據(jù)APP需求,選取城市進(jìn)行搜索。
    2、不設(shè)置POI的類(lèi)別,默認(rèn)返回“餐飲服務(wù)”、“商務(wù)住宅”、“生活服務(wù)”這三種類(lèi)別的POI,下方提供了POI分類(lèi)碼表,請(qǐng)按照列表內(nèi)容設(shè)置希望檢索的POI類(lèi)型。(建議使用POI類(lèi)型的代碼進(jìn)行檢索)

  • 周邊檢索POI
    適用于搜索某個(gè)位置附近的POI,可設(shè)置POI的類(lèi)別,具體查詢(xún)所在位置的餐飲類(lèi)、住宅類(lèi)POI,例如:查找天安門(mén)附近的廁所等等場(chǎng)景。
    <font color=#FF0000>與關(guān)鍵字檢索的唯一區(qū)別需要通過(guò) PoiSearch 的 setBound 方法設(shè)置圓形查詢(xún)范圍

  • 多邊形內(nèi)檢索的POI
    不同于周邊搜索,周邊搜索是一個(gè)圓形范圍,而多邊形搜索的范圍是一個(gè)多邊形,適用于在搜索某個(gè)不規(guī)則區(qū)域的POI查詢(xún),例如:查找中關(guān)村范圍內(nèi)的停車(chē)場(chǎng)。

  • ID檢索POI
    通過(guò)關(guān)鍵字檢索、周邊檢索以及多邊形檢索,或者任意形式得到的高德POI ID信息,可通過(guò)ID檢索來(lái)獲取POI完整詳細(xì)信息。

  • 輸入內(nèi)容自動(dòng)提示
    輸入提示是指根據(jù)用戶(hù)輸入的關(guān)鍵詞,給出相應(yīng)的提示信息,將最有可能的搜索詞呈現(xiàn)給用戶(hù),以減少用戶(hù)輸入信息,提升用戶(hù)體驗(yàn)。如:輸入“方恒”,提示“方恒國(guó)際中心A座”,“方恒購(gòu)物中心”等

  • 道路沿途檢索POI
    從搜索 SDK 3.5.0 版本開(kāi)始支持,可查詢(xún)路徑沿途的加油站、ATM、汽修店、廁所

接下來(lái),帶大家一起玩轉(zhuǎn)POI檢索附近~讓你欲罷不能,so easy~

話不多說(shuō),開(kāi)干

既然要實(shí)現(xiàn)POI檢索附近,那首要肯定是引入依賴(lài),如下:

compile 'com.amap.api:search:latest.integration'

接下來(lái),為大家展示下我們實(shí)現(xiàn)后的界面效果,方便我們?nèi)?xiě)我們的layout文件,如下圖:

這里寫(xiě)圖片描述

在中間部分,也就是上圖中 住宅區(qū) 學(xué)校 樓宇 商場(chǎng) 部分,高德官方自定義了一個(gè)控件,直接繼承于RadioGroup,代碼如下:

package cn.hlq.gaodemapdemo.weight;

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RadioGroup;

import cn.hlq.gaodemapdemo.R;

/**
 * Created by HLQ on 2017/5/15
 */

public class SegmentedGroup extends RadioGroup {

    private int mMarginDp;
    private Resources resources;
    private int mTintColor;
    private int mCheckedTextColor = Color.WHITE;
    private LayoutSelector mLayoutSelector;
    private Float mCornerRadius;

    public SegmentedGroup(Context context) {
        super(context);
        resources = getResources();
        mTintColor = resources.getColor(R.color.color_android_indicator_text);
        mMarginDp = (int) getResources().getDimension(R.dimen.dp_1);
        mCornerRadius = getResources().getDimension(R.dimen.dp_5);
        mLayoutSelector = new LayoutSelector(mCornerRadius);
    }

    /* Reads the attributes from the layout */
    private void initAttrs(AttributeSet attrs) {
        TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(
                attrs,
                R.styleable.SegmentedGroup,
                0, 0);

        try {
            mMarginDp = (int) typedArray.getDimension(
                    R.styleable.SegmentedGroup_sc_border_width,
                    getResources().getDimension(R.dimen.dp_1));

            mCornerRadius = typedArray.getDimension(
                    R.styleable.SegmentedGroup_sc_corner_radius,
                    getResources().getDimension(R.dimen.dp_5));

            mTintColor = typedArray.getColor(
                    R.styleable.SegmentedGroup_sc_tint_color,
                    getResources().getColor(R.color.color_android_indicator_text));

            mCheckedTextColor = typedArray.getColor(
                    R.styleable.SegmentedGroup_sc_checked_text_color,
                    getResources().getColor(android.R.color.white));

        } finally {
            typedArray.recycle();
        }
    }

    public SegmentedGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        resources = getResources();
        mTintColor = resources.getColor(R.color.color_android_indicator_text);
        mMarginDp = (int) getResources().getDimension(R.dimen.dp_1);
        mCornerRadius = getResources().getDimension(R.dimen.dp_5);
        initAttrs(attrs);
        mLayoutSelector = new LayoutSelector(mCornerRadius);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        //Use holo light for default
        updateBackground();
    }

    public void setTintColor(int tintColor) {
        mTintColor = tintColor;
        updateBackground();
    }

    public void setTintColor(int tintColor, int checkedTextColor) {
        mTintColor = tintColor;
        mCheckedTextColor = checkedTextColor;
        updateBackground();
    }

    public void updateBackground() {
        int count = super.getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            updateBackground(child);

            // If this is the last view, don't set LayoutParams
            if (i == count - 1) break;

            LayoutParams initParams = (LayoutParams) child.getLayoutParams();
            LayoutParams params = new LayoutParams(initParams.width, initParams.height, initParams.weight);
            // Check orientation for proper margins
            if (getOrientation() == LinearLayout.HORIZONTAL) {
                params.setMargins(0, 0, -mMarginDp, 0);
            } else {
                params.setMargins(0, 0, 0, -mMarginDp);
            }
            child.setLayoutParams(params);
        }
    }

    private void updateBackground(View view) {
        int checked = mLayoutSelector.getSelected();
        int unchecked = mLayoutSelector.getUnselected();
        //Set text color
        ColorStateList colorStateList = new ColorStateList(new int[][]{
                {android.R.attr.state_pressed},
                {-android.R.attr.state_pressed, -android.R.attr.state_checked},
                {-android.R.attr.state_pressed, android.R.attr.state_checked}},
                new int[]{Color.GRAY, mTintColor, mCheckedTextColor});
        ((Button) view).setTextColor(colorStateList);

        //Redraw with tint color
        Drawable checkedDrawable = resources.getDrawable(checked).mutate();
        Drawable uncheckedDrawable = resources.getDrawable(unchecked).mutate();
        ((GradientDrawable) checkedDrawable).setColor(mTintColor);
        ((GradientDrawable) checkedDrawable).setStroke(mMarginDp, mTintColor);
        ((GradientDrawable) uncheckedDrawable).setStroke(mMarginDp, mTintColor);
        //Set proper radius
        ((GradientDrawable) checkedDrawable).setCornerRadii(mLayoutSelector.getChildRadii(view));
        ((GradientDrawable) uncheckedDrawable).setCornerRadii(mLayoutSelector.getChildRadii(view));

        //Create drawable
        StateListDrawable stateListDrawable = new StateListDrawable();
        stateListDrawable.addState(new int[]{-android.R.attr.state_checked}, uncheckedDrawable);
        stateListDrawable.addState(new int[]{android.R.attr.state_checked}, checkedDrawable);

        //Set button background
        if (Build.VERSION.SDK_INT >= 16) {
            view.setBackground(stateListDrawable);
        } else {
            view.setBackgroundDrawable(stateListDrawable);
        }
    }

    /*
     * This class is used to provide the proper layout based on the view.
     * Also provides the proper radius for corners.
     * The layout is the same for each selected left/top middle or right/bottom button.
     * float tables for setting the radius via Gradient.setCornerRadii are used instead
     * of multiple xml drawables.
     */
    private class LayoutSelector {

        private int children;
        private int child;
        private final int SELECTED_LAYOUT = R.drawable.shape_gaode_check;
        private final int UNSELECTED_LAYOUT = R.drawable.shape_gaode_uncheck;

        private float r;    //this is the radios read by attributes or xml dimens
        private final float r1 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP
                , 0.1f, getResources().getDisplayMetrics());    //0.1 dp to px
        private final float[] rLeft;    // left radio button
        private final float[] rRight;   // right radio button
        private final float[] rMiddle;  // middle radio button
        private final float[] rDefault; // default radio button
        private final float[] rTop;     // top radio button
        private final float[] rBot;     // bot radio button
        private float[] radii;          // result radii float table

        public LayoutSelector(float cornerRadius) {
            children = -1; // Init this to force setChildRadii() to enter for the first time.
            child = -1; // Init this to force setChildRadii() to enter for the first time
            r = cornerRadius;
            rLeft = new float[]{r, r, r1, r1, r1, r1, r, r};
            rRight = new float[]{r1, r1, r, r, r, r, r1, r1};
            rMiddle = new float[]{r1, r1, r1, r1, r1, r1, r1, r1};
            rDefault = new float[]{r, r, r, r, r, r, r, r};
            rTop = new float[]{r, r, r, r, r1, r1, r1, r1};
            rBot = new float[]{r1, r1, r1, r1, r, r, r, r};
        }

        private int getChildren() {
            return SegmentedGroup.this.getChildCount();
        }

        private int getChildIndex(View view) {
            return SegmentedGroup.this.indexOfChild(view);
        }

        private void setChildRadii(int newChildren, int newChild) {

            // If same values are passed, just return. No need to update anything
            if (children == newChildren && child == newChild)
                return;

            // Set the new values
            children = newChildren;
            child = newChild;

            // if there is only one child provide the default radio button
            if (children == 1) {
                radii = rDefault;
            } else if (child == 0) { //left or top
                radii = (getOrientation() == LinearLayout.HORIZONTAL) ? rLeft : rTop;
            } else if (child == children - 1) {  //right or bottom
                radii = (getOrientation() == LinearLayout.HORIZONTAL) ? rRight : rBot;
            } else {  //middle
                radii = rMiddle;
            }
        }

        /* Returns the selected layout id based on view */
        public int getSelected() {
            return SELECTED_LAYOUT;
        }

        /* Returns the unselected layout id based on view */
        public int getUnselected() {
            return UNSELECTED_LAYOUT;
        }

        /* Returns the radii float table based on view for Gradient.setRadii()*/
        public float[] getChildRadii(View view) {
            int newChildren = getChildren();
            int newChild = getChildIndex(view);
            setChildRadii(newChildren, newChild);
            return radii;
        }
    }
}

同時(shí)我們也需要去創(chuàng)建自定義控件的樣式文件,方便我們對(duì)其屬性進(jìn)行拓展,如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="SegmentedGroup">
        <attr name="sc_corner_radius" format="dimension"/>
        <attr name="sc_border_width" format="dimension"/>
        <attr name="sc_tint_color" format="color"/>
        <attr name="sc_checked_text_color" format="color"/>
    </declare-styleable>

</resources>

接下來(lái),去編寫(xiě)我們的布局文件,如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/color_while"
    android:orientation="vertical"
    tools:context="cn.hlq.gaodemapdemo.map.PoiSearchActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="@dimen/dp_5"
        android:focusable="true"
        android:orientation="horizontal">

        <AutoCompleteTextView
            android:id="@+id/id_gaode_location_poi_search"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="@drawable/shape_circle_while_bg"
            android:completionThreshold="1"
            android:dropDownVerticalOffset="1.0dip"
            android:focusable="true"
            android:gravity="center_vertical"
            android:hint="@string/string_gaode_location_search_hint"
            android:imeOptions="actionDone"
            android:inputType="text|textAutoComplete"
            android:maxLength="20"
            android:padding="@dimen/dp_5"
            android:singleLine="true"
            android:textColor="@color/color_c6"
            android:textColorHint="@color/color_c9"
            android:textSize="@dimen/sp_14"/>
    </LinearLayout>

    <com.amap.api.maps.MapView
        android:id="@+id/id_gaode_location_map"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_0"
        android:layout_weight="1.2"/>

    <cn.hlq.gaodemapdemo.weight.SegmentedGroup
        xmlns:segmentedgroup="http://schemas.android.com/apk/res-auto"
        android:id="@+id/id_gaode_location_segmented_group"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="4dp"
        segmentedgroup:sc_border_width="1dp"
        segmentedgroup:sc_corner_radius="2dp">

        <RadioButton
            android:id="@+id/id_gaode_location_uptown"
            style="@style/style_gaode_search_title"
            android:checked="true"
            android:text="@string/string_gaode_location_title_uptown"/>

        <RadioButton
            android:id="@+id/id_gaode_location_school"
            style="@style/style_gaode_search_title"
            android:text="@string/string_gaode_location_title_school"/>

        <RadioButton
            android:id="@+id/id_gaode_location_building"
            style="@style/style_gaode_search_title"
            android:text="@string/string_gaode_location_title_building"/>

        <RadioButton
            android:id="@+id/id_gaode_location_shopping"
            style="@style/style_gaode_search_title"
            android:text="@string/string_gaode_location_title_shopping"/>

    </cn.hlq.gaodemapdemo.weight.SegmentedGroup>

    <ListView
        android:id="@+id/id_gaode_location_list"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_0"
        android:layout_weight="1"
        android:overScrollMode="never"
        android:scrollbars="none"/>

</LinearLayout>

最下方的搜索結(jié)果,大家可直接把其看定為一個(gè)ListView,既然是ListView了,肯定會(huì)有item布局,仔細(xì)觀看圖,item布局文件是不是so-easy呢?

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@color/color_while"
                android:gravity="center_vertical"
                android:orientation="vertical"
                android:padding="@dimen/dp_15">

    <ImageView
        android:id="@+id/id_gaode_location_search_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_centerVertical="true"
        android:background="@drawable/img_gaode_location_search_icon"
        android:scaleType="fitXY"/>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="@dimen/dp_5"
        android:layout_toEndOf="@+id/id_gaode_location_search_icon"
        android:layout_toStartOf="@+id/id_gaode_location_search_confirm_icon"
        android:orientation="vertical">

        <TextView
            android:id="@+id/id_gaode_location_search_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/color_c6"
            android:textSize="@dimen/sp_14"/>

        <TextView
            android:id="@+id/id_gaode_location_search_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/color_c9"
            android:textSize="@dimen/sp_14"/>

    </LinearLayout>

    <ImageView
        android:id="@+id/id_gaode_location_search_confirm_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:background="@drawable/icon_gaode_location_confirm_icon"
        android:scaleType="fitXY"/>

</RelativeLayout>

item布局文件設(shè)置完畢后,緊接著,肯定要完善咱的Adapter類(lèi)嘍,不多說(shuō),上碼:

package cn.hlq.gaodemapdemo.adapter;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.amap.api.services.core.PoiItem;

import java.util.ArrayList;
import java.util.List;

import cn.hlq.gaodemapdemo.R;

/**
 * Created by HLQ on 2017/5/15
 */

public class GaoDeSearchResultAdapter extends BaseAdapter {

    private List<PoiItem> data;
    private Context context;

    private int selectedPosition = 0;

    public GaoDeSearchResultAdapter(Context context) {
        this.context = context;
        data = new ArrayList<>();
    }

    public void setData(List<PoiItem> data) {
        this.data = data;
    }

    public void setSelectedPosition(int selectedPosition) {
        this.selectedPosition = selectedPosition;
    }

    public int getSelectedPosition() {
        return selectedPosition;
    }

    @Override
    public int getCount() {
        return data.size();
    }

    @Override
    public Object getItem(int position) {
        return data.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder;
        if (convertView == null) {
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.item_gaode_location_search_info, parent, false);
            viewHolder = new ViewHolder(convertView);
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }
        viewHolder.bindView(position);
        return convertView;
    }

    class ViewHolder {
        TextView textTitle;
        TextView textSubTitle;
        ImageView imageCheck;

        public ViewHolder(View view) {
            textTitle = (TextView) view.findViewById(R.id.id_gaode_location_search_title);
            textSubTitle = (TextView) view.findViewById(R.id.id_gaode_location_search_content);
            imageCheck = (ImageView) view.findViewById(R.id.id_gaode_location_search_confirm_icon);
        }

        public void bindView(int position) {
            if (position >= data.size())
                return;
            PoiItem poiItem = data.get(position);
            textTitle.setText(poiItem.getTitle());
            textSubTitle.setText(poiItem.getCityName() + poiItem.getAdName() + poiItem.getSnippet());
            imageCheck.setVisibility(position == selectedPosition ? View.VISIBLE : View.INVISIBLE);
            textSubTitle.setVisibility((position == 0 && poiItem.getPoiId().equals("regeo")) ? View.GONE : View.VISIBLE);
        }
    }
}

前期準(zhǔn)備工作完善之后,接下來(lái)就是我們的重中之重了。

開(kāi)始前,不妨讓我們?nèi)ダ砝硭悸罚米屛覀兏奖憧旖莸娜]碼。其實(shí)個(gè)人感覺(jué),重要的也就是下面?zhèn)z個(gè)部分,大體了解下,結(jié)合開(kāi)發(fā)文檔以及提供示例,差不多就可以開(kāi)搞了~

step 1:初始化地圖,包括初始化地圖相關(guān)屬性設(shè)置以及地圖的監(jiān)聽(tīng)事件;

step 2:輸入內(nèi)容自動(dòng)提示,我們所需要的步驟如下:

1、繼承 InputtipsListener 監(jiān)聽(tīng);
2、構(gòu)造 InputtipsQuery 對(duì)象,通過(guò)
InputtipsQuery(java.lang.String keyword, java.lang.String city)
3、構(gòu)造 Inputtips 對(duì)象,并設(shè)置監(jiān)聽(tīng);
4、調(diào)用 PoiSearch 的 requestInputtipsAsyn() 方法發(fā)送請(qǐng)求;
5、通過(guò)回調(diào)接口 onGetInputtips解析返回的結(jié)果,獲取輸入提示返回的信息。
其中我們需要注意的點(diǎn)如下:
<font color=#FF0000> a 、由于提示中會(huì)出現(xiàn)相同的關(guān)鍵字,但是這些關(guān)鍵字所在區(qū)域不同,使用時(shí)可以通過(guò) tipList.get(i).getDistrict() 獲得區(qū)域,也可以在提示時(shí)在關(guān)鍵字后加上區(qū)域。
b、當(dāng) Tip 的 getPoiID() 返回空,并且 getPoint() 也返回空時(shí),表示該提示詞不是一個(gè)真實(shí)存在的 POI,這時(shí)區(qū)域、經(jīng)緯度參數(shù)都是空的,此時(shí)可根據(jù)該提示詞進(jìn)行POI關(guān)鍵詞搜索
c、當(dāng) Tip 的 getPoiID() 返回不為空,但 getPoint() 返回空時(shí),表示該提示詞是一個(gè)公交線路名稱(chēng),此時(shí)用這個(gè)id進(jìn)行公交線路查詢(xún)。
d、當(dāng) Tip 的 getPoiID() 返回不為空,且 getPoint() 也不為空時(shí),表示該提示詞一個(gè)真實(shí)存在的POI,可直接顯示在地圖上。

package cn.hlq.gaodemapdemo.map;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Point;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.Interpolator;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.ListView;
import android.widget.RadioGroup;

import com.amap.api.location.AMapLocation;
import com.amap.api.location.AMapLocationClient;
import com.amap.api.location.AMapLocationClientOption;
import com.amap.api.location.AMapLocationListener;
import com.amap.api.maps.AMap;
import com.amap.api.maps.CameraUpdateFactory;
import com.amap.api.maps.LocationSource;
import com.amap.api.maps.MapView;
import com.amap.api.maps.model.BitmapDescriptorFactory;
import com.amap.api.maps.model.CameraPosition;
import com.amap.api.maps.model.LatLng;
import com.amap.api.maps.model.Marker;
import com.amap.api.maps.model.MarkerOptions;
import com.amap.api.maps.model.animation.Animation;
import com.amap.api.maps.model.animation.TranslateAnimation;
import com.amap.api.services.core.AMapException;
import com.amap.api.services.core.LatLonPoint;
import com.amap.api.services.core.PoiItem;
import com.amap.api.services.geocoder.GeocodeResult;
import com.amap.api.services.geocoder.GeocodeSearch;
import com.amap.api.services.geocoder.RegeocodeQuery;
import com.amap.api.services.geocoder.RegeocodeResult;
import com.amap.api.services.help.Inputtips;
import com.amap.api.services.help.InputtipsQuery;
import com.amap.api.services.help.Tip;
import com.amap.api.services.poisearch.PoiResult;
import com.amap.api.services.poisearch.PoiSearch;

import java.util.ArrayList;
import java.util.List;

import cn.hlq.gaodemapdemo.R;
import cn.hlq.gaodemapdemo.adapter.GaoDeSearchResultAdapter;
import cn.hlq.gaodemapdemo.weight.SegmentedGroup;

/**
 * create by heliquan at 2017年5月10日14:39:10
 * 實(shí)現(xiàn)高德地圖POI檢索附近
 */
public class PoiSearchActivity extends Activity implements LocationSource,
        AMapLocationListener, GeocodeSearch.OnGeocodeSearchListener, PoiSearch.OnPoiSearchListener {

    private PoiSearchActivity self = this;

    private SegmentedGroup mSegmentedGroup;
    private AutoCompleteTextView searchText;
    private AMap aMap;
    private MapView mapView;
    private Marker locationMarker;
    private AMapLocationClient mlocationClient;
    private LatLonPoint searchLatlonPoint;
    private AMapLocationClientOption mLocationOption;
    private GeocodeSearch geocoderSearch;
    // Poi查詢(xún)條件類(lèi)
    private PoiSearch.Query query;
    private PoiSearch poiSearch;
    private PoiItem firstItem;
    private OnLocationChangedListener mListener;

    // 當(dāng)前頁(yè)面,從0開(kāi)始計(jì)數(shù)
    private int currentPage = 0;
    private boolean isfirstinput = true;
    private boolean isItemClickAction;
    private boolean isInputKeySearch;
    private String inputSearchKey;
    private String searchKey = "";
    private String[] items = {"住宅區(qū)", "學(xué)校", "樓宇", "商場(chǎng)"};
    private String searchType = items[0];
    // 記錄用戶(hù)點(diǎn)擊定位地址
    private String saveClickLocationAddress = "";
    // poi數(shù)據(jù)
    private List<PoiItem> poiItems;
    private List<Tip> autoTips;
    private List<PoiItem> resultData;

    private ListView listView;

    private GaoDeSearchResultAdapter searchResultAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
        setContentView(R.layout.activity_poi_search);
        mapView = (MapView) findViewById(R.id.id_gaode_location_map);
        mapView.onCreate(savedInstanceState);
        initGaoDeMapListener();
        initView();
        resultData = new ArrayList<>();
    }

    protected void initView() {
        listView = (ListView) findViewById(R.id.id_gaode_location_list);
        searchResultAdapter = new GaoDeSearchResultAdapter(self);
        listView.setAdapter(searchResultAdapter);
        listView.setOnItemClickListener(onItemClickListener);
        mSegmentedGroup = (SegmentedGroup) findViewById(R.id.id_gaode_location_segmented_group);
        mSegmentedGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                searchType = items[0];
                switch (checkedId) {
                    case R.id.id_gaode_location_uptown:
                        searchType = items[0];
                        break;
                    case R.id.id_gaode_location_school:
                        searchType = items[1];
                        break;
                    case R.id.id_gaode_location_building:
                        searchType = items[2];
                        break;
                    case R.id.id_gaode_location_shopping:
                        searchType = items[3];
                        break;
                }
                geoAddress();
            }
        });
        searchText = (AutoCompleteTextView) findViewById(R.id.id_gaode_location_poi_search);
        searchText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                String newText = s.toString().trim();
                if (newText.length() > 0) {
                    InputtipsQuery inputquery = new InputtipsQuery(newText, "北京");
                    Inputtips inputTips = new Inputtips(self, inputquery);
                    inputquery.setCityLimit(true);
                    inputTips.setInputtipsListener(inputtipsListener);
                    inputTips.requestInputtipsAsyn();
                }
            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });
        searchText.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (autoTips != null && autoTips.size() > position) {
                    Tip tip = autoTips.get(position);
                    searchPoi(tip);
                }
            }
        });
        geocoderSearch = new GeocodeSearch(this);
        geocoderSearch.setOnGeocodeSearchListener(this);
        hideSoftKey(searchText);
    }

    /**
     * 初始化高德地圖監(jiān)聽(tīng)
     */
    private void initGaoDeMapListener() {
        if (aMap == null) {
            aMap = mapView.getMap();
            setUpMap();
        }
        aMap.setOnCameraChangeListener(new AMap.OnCameraChangeListener() {
            @Override
            public void onCameraChange(CameraPosition cameraPosition) {
            }

            @Override
            public void onCameraChangeFinish(CameraPosition cameraPosition) {
                if (!isItemClickAction && !isInputKeySearch) {
                    geoAddress();
                    startJumpAnimation();
                }
                searchLatlonPoint = new LatLonPoint(cameraPosition.target.latitude, cameraPosition.target.longitude);
                isInputKeySearch = false;
                isItemClickAction = false;
            }
        });
        aMap.setOnMapLoadedListener(new AMap.OnMapLoadedListener() {
            @Override
            public void onMapLoaded() {
                addMarkerInScreenCenter(null);
            }
        });
    }

    /**
     * 設(shè)置一些amap的屬性
     */
    private void setUpMap() {
        aMap.getUiSettings().setZoomControlsEnabled(false);
        // 設(shè)置地圖默認(rèn)的指南針是否顯示
        aMap.getUiSettings().setCompassEnabled(true);
        // 設(shè)置定位監(jiān)聽(tīng)
        aMap.setLocationSource(this);
        // 設(shè)置默認(rèn)定位按鈕是否顯示
        aMap.getUiSettings().setMyLocationButtonEnabled(true);
        // 設(shè)置為true表示顯示定位層并可觸發(fā)定位,false表示隱藏定位層并不可觸發(fā)定位,默認(rèn)是false
        aMap.setMyLocationEnabled(true);
        aMap.setMyLocationType(AMap.LOCATION_TYPE_LOCATE);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mapView.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        mapView.onPause();
        deactivate();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        mapView.onSaveInstanceState(outState);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mapView.onDestroy();
        if (null != mlocationClient) {
            mlocationClient.onDestroy();
        }
    }

    /**
     * 定位成功后回調(diào)函數(shù)
     */
    @Override
    public void onLocationChanged(AMapLocation amapLocation) {
        if (mListener != null && amapLocation != null) {
            if (amapLocation != null
                    && amapLocation.getErrorCode() == 0) {
                mListener.onLocationChanged(amapLocation);
                LatLng curLatlng = new LatLng(amapLocation.getLatitude(), amapLocation.getLongitude());
                aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(curLatlng, 17f));
                searchLatlonPoint = new LatLonPoint(curLatlng.latitude, curLatlng.longitude);
                isInputKeySearch = false;
                searchText.setText("");
            }
        }
    }

    /**
     * 激活定位
     */
    @Override
    public void activate(OnLocationChangedListener listener) {
        mListener = listener;
        if (mlocationClient == null) {
            mlocationClient = new AMapLocationClient(this);
            mLocationOption = new AMapLocationClientOption();
            // 設(shè)置定位監(jiān)聽(tīng)
            mlocationClient.setLocationListener(this);
            // 設(shè)置為高精度定位模式
            mLocationOption.setOnceLocation(true);
            mLocationOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy);
            // 設(shè)置定位參數(shù)
            mlocationClient.setLocationOption(mLocationOption);
            // 此方法為每隔固定時(shí)間會(huì)發(fā)起一次定位請(qǐng)求,為了減少電量消耗或網(wǎng)絡(luò)流量消耗,
            // 注意設(shè)置合適的定位時(shí)間的間隔(最小間隔支持為2000ms),并且在合適時(shí)間調(diào)用stopLocation()方法來(lái)取消定位請(qǐng)求
            // 在定位結(jié)束后,在合適的生命周期調(diào)用onDestroy()方法
            // 在單次定位情況下,定位無(wú)論成功與否,都無(wú)需調(diào)用stopLocation()方法移除請(qǐng)求,定位sdk內(nèi)部會(huì)移除
            mlocationClient.startLocation();
        }
    }

    /**
     * 停止定位
     */
    @Override
    public void deactivate() {
        mListener = null;
        if (mlocationClient != null) {
            mlocationClient.stopLocation();
            mlocationClient.onDestroy();
        }
        mlocationClient = null;
    }

    /**
     * 響應(yīng)逆地理編碼
     */
    public void geoAddress() {
        searchText.setText("");
        // 第一個(gè)參數(shù)表示一個(gè)Latlng,第二參數(shù)表示范圍多少米,第三個(gè)參數(shù)表示是火系坐標(biāo)系還是GPS原生坐標(biāo)系
        RegeocodeQuery query = new RegeocodeQuery(searchLatlonPoint, 200, GeocodeSearch.AMAP);
        geocoderSearch.getFromLocationAsyn(query);
    }

    /**
     * 開(kāi)始進(jìn)行poi搜索
     */
    protected void doSearchQuery() {
        currentPage = 0;
        // 第一個(gè)參數(shù)表示搜索字符串,第二個(gè)參數(shù)表示poi搜索類(lèi)型,第三個(gè)參數(shù)表示poi搜索區(qū)域(空字符串代表全國(guó))
        query = new PoiSearch.Query(searchKey, searchType, "");
        query.setCityLimit(true);
        // 設(shè)置每頁(yè)最多返回多少條poiitem
        query.setPageSize(20);
        query.setPageNum(currentPage);
        if (searchLatlonPoint != null) {
            poiSearch = new PoiSearch(this, query);
            poiSearch.setOnPoiSearchListener(this);
            poiSearch.setBound(new PoiSearch.SearchBound(searchLatlonPoint, 1000, true));
            poiSearch.searchPOIAsyn();
        }
    }

    @Override
    public void onRegeocodeSearched(RegeocodeResult result, int rCode) {
        if (rCode == AMapException.CODE_AMAP_SUCCESS) {
            if (result != null && result.getRegeocodeAddress() != null
                    && result.getRegeocodeAddress().getFormatAddress() != null) {
                String address = result.getRegeocodeAddress().getProvince() + result.getRegeocodeAddress().getCity() + result.getRegeocodeAddress().getDistrict() + result.getRegeocodeAddress().getTownship();
                firstItem = new PoiItem("regeo", searchLatlonPoint, address, address);
                doSearchQuery();
            }
        }
    }

    @Override
    public void onGeocodeSearched(GeocodeResult geocodeResult, int i) {
    }

    /**
     * POI搜索結(jié)果回調(diào)
     *
     * @param poiResult  搜索結(jié)果
     * @param resultCode 錯(cuò)誤碼
     */
    @Override
    public void onPoiSearched(PoiResult poiResult, int resultCode) {
        if (resultCode == AMapException.CODE_AMAP_SUCCESS) {
            if (poiResult != null && poiResult.getQuery() != null) {
                if (poiResult.getQuery().equals(query)) {
                    poiItems = poiResult.getPois();
                    if (poiItems != null && poiItems.size() > 0) {
                        updateListview(poiItems);
                    }
                }
            }
        }
    }

    /**
     * 更新列表中的item
     *
     * @param poiItems
     */
    private void updateListview(List<PoiItem> poiItems) {
        resultData.clear();
        searchResultAdapter.setSelectedPosition(0);
        resultData.add(firstItem);
        resultData.addAll(poiItems);
        searchResultAdapter.setData(resultData);
        searchResultAdapter.notifyDataSetChanged();
    }


    @Override
    public void onPoiItemSearched(PoiItem poiItem, int i) {

    }

    AdapterView.OnItemClickListener onItemClickListener = new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            if (position != searchResultAdapter.getSelectedPosition()) {
                PoiItem poiItem = (PoiItem) searchResultAdapter.getItem(position);
                LatLng curLatlng = new LatLng(poiItem.getLatLonPoint().getLatitude(), poiItem.getLatLonPoint().getLongitude());
                // 滯空
                saveClickLocationAddress = "";
                saveClickLocationAddress = poiItem.getCityName() + poiItem.getAdName() + poiItem.getSnippet();
                isItemClickAction = true;
                aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(curLatlng, 16f));
                searchResultAdapter.setSelectedPosition(position);
                searchResultAdapter.notifyDataSetChanged();
            }
        }
    };

    private void addMarkerInScreenCenter(LatLng locationLatLng) {
        LatLng latLng = aMap.getCameraPosition().target;
        Point screenPosition = aMap.getProjection().toScreenLocation(latLng);
        locationMarker = aMap.addMarker(new MarkerOptions()
                .anchor(0.5f, 0.5f)
                .icon(BitmapDescriptorFactory.fromResource(R.drawable.img_gaode_location_purple_pin)));
        // 設(shè)置Marker在屏幕上,不跟隨地圖移動(dòng)
        locationMarker.setPositionByPixels(screenPosition.x, screenPosition.y);
    }

    public int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * 屏幕中心marker 跳動(dòng)
     */
    public void startJumpAnimation() {
        if (locationMarker != null) {
            // 根據(jù)屏幕距離計(jì)算需要移動(dòng)的目標(biāo)點(diǎn)
            final LatLng latLng = locationMarker.getPosition();
            Point point = aMap.getProjection().toScreenLocation(latLng);
            point.y -= dip2px(this, 50);
            LatLng target = aMap.getProjection()
                    .fromScreenLocation(point);
            // 使用TranslateAnimation,填寫(xiě)一個(gè)需要移動(dòng)的目標(biāo)點(diǎn)
            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)));
                    }
                }
            });
            // 整個(gè)移動(dòng)所需要的時(shí)間
            animation.setDuration(600);
            // 設(shè)置動(dòng)畫(huà)
            locationMarker.setAnimation(animation);
            // 開(kāi)始動(dòng)畫(huà)
            locationMarker.startAnimation();
        }
    }

    Inputtips.InputtipsListener inputtipsListener = new Inputtips.InputtipsListener() {
        @Override
        public void onGetInputtips(List<Tip> list, int rCode) {
            if (rCode == AMapException.CODE_AMAP_SUCCESS) {// 正確返回
                autoTips = list;
                List<String> listString = new ArrayList<String>();
                for (int i = 0; i < list.size(); i++) {
                    listString.add(list.get(i).getName());
                }
                ArrayAdapter<String> aAdapter = new ArrayAdapter<String>(
                        getApplicationContext(),
                        R.layout.item_gaode_location_autotext, listString);
                searchText.setAdapter(aAdapter);
                aAdapter.notifyDataSetChanged();
                if (isfirstinput) {
                    isfirstinput = false;
                    searchText.showDropDown();
                }
            }
        }
    };

    /**
     * POI查詢(xún)
     *
     * @param result
     */
    private void searchPoi(Tip result) {
        try {
            isInputKeySearch = true;
            inputSearchKey = result.getName();//getAddress(); // + result.getRegeocodeAddress().getCity() + result.getRegeocodeAddress().getDistrict() + result.getRegeocodeAddress().getTownship();
            searchLatlonPoint = result.getPoint();
            firstItem = new PoiItem("tip", searchLatlonPoint, inputSearchKey, result.getAddress());
            firstItem.setCityName(result.getDistrict());
            firstItem.setAdName("");
            resultData.clear();
            searchResultAdapter.setSelectedPosition(0);
            aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(searchLatlonPoint.getLatitude(), searchLatlonPoint.getLongitude()), 16f));
            hideSoftKey(searchText);
            doSearchQuery();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void hideSoftKey(View view) {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.showSoftInput(view, InputMethodManager.SHOW_FORCED);
        imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
    }

    /**
     * 發(fā)送存儲(chǔ)的用戶(hù)點(diǎn)擊定位地址信息
     *
     * @return
     */
    private Intent sendLocationAddress() {
        Intent resultIntent = new Intent();
        resultIntent.putExtra("saveClickLocationAddress", saveClickLocationAddress);
        return resultIntent;
    }

}

結(jié)束

其他具體使用詳情,大家可根據(jù)官方提供地址查閱:
http://lbs.amap.com/dev/demo#/?tags=android

個(gè)人感覺(jué)地圖開(kāi)發(fā),或者說(shuō)是類(lèi)似這樣采用第三方服務(wù)的時(shí)候,我們只需要嚴(yán)格按照開(kāi)發(fā)文檔進(jìn)行就差不多了,當(dāng)然有點(diǎn)三方服務(wù)比較坑,那就需要我們曲線救國(guó)了~

本篇適合快速實(shí)現(xiàn)功能,以及和LZ一樣的小白觀看~

祝編碼無(wú)bug~

github查看地址如下:

https://github.com/HLQ-Struggle/GaoDeMapDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評(píng)論 6 540
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,275評(píng)論 3 428
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 177,904評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,633評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,368評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,736評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評(píng)論 3 446
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,919評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,481評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,235評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,427評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,656評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,348評(píng)論 1 294
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,160評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,380評(píng)論 2 379

推薦閱讀更多精彩內(nèi)容