1. 程式人生 > >Android開發實現選擇城市介面,可根據拼音、首字母進行搜尋

Android開發實現選擇城市介面,可根據拼音、首字母進行搜尋

短短的國慶8天假期一眨眼就過去了,下次長假只有等到過年了,本寶寶不開心。既然已經開始工作了,就要好好多學習點新知識,來提高自己的程式碼能力,今天帶大家去實現簡易的選擇城市介面,並且可以根據城市首字母或者拼音搜尋。先來看下我的效果圖:

 

在寫程式碼之前先準備好一個城市列表的json檔案以及去百度或者高德申請定位功能,Demo在文章最後。(Demo裡沒有寫定位功能,需要自己實現哦~)

實現方法也比較簡單,我就簡單的給大家說一下,整個城市列表是用的一個ListView,方便我們後面監聽它的滾動狀態,右邊的快速定位欄是自定義View,程式碼比較簡單,我就直接放程式碼了。不知道大家發現沒有,有些字母開頭的城市是沒有的,比如 I,O,U,V等等,為了節約空間我就把那幾個字母去掉了,有的軟體是把26個字母全部保留了。

(其實都不影響,看你個人吧,我反正是有強迫症的。)注意,在自定義View的時候三種構造方法都要寫上,千萬不要偷懶!!!哎,都是淚  o(╥﹏╥)o

public class LetterListView extends View {

    OnTouchingLetterChangedListener onTouchingLetterChangedListener;
    public static String[] b = {"定位", "熱門", "A", "B", "C", "D", "E", "F", "G", "H", "J", "K",
            "L", "M", "N", "P", "Q", "R", "S", "T", "W", "X", "Y", "Z"};
    int choose = -1;
    Paint paint = new Paint();
    boolean showBkg = false;
    private Context mContext;

    public LetterListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.mContext = context;
    }

    public LetterListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
    }

    public LetterListView(Context context) {
        super(context);
        this.mContext = context;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (showBkg) {
            canvas.drawColor(Color.parseColor("#40000000"));
        }
        int height = getHeight();
        int width = getWidth();
        int singleHeight = height / b.length;
        for (int i = 0; i < b.length; i++) {
            paint.setColor(Color.parseColor("#50B3DA"));
            paint.setTextSize(DisplayUtil.sp2px(mContext, 12));
            paint.setAntiAlias(true);
            float xPos = width / 2 - paint.measureText(b[i]) / 2;
            float yPos = singleHeight * i + singleHeight;
            canvas.drawText(b[i], xPos, yPos, paint);
            paint.reset();
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        final int action = event.getAction();
        final float y = event.getY();
        final int oldChoose = choose;
        final OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener;
        final int c = (int) (y / getHeight() * b.length);
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                showBkg = true;
                if (oldChoose != c && listener != null) {
                    if (c >= 0 && c < b.length) {
                        listener.onTouchingLetterChanged(b[c]);
                        choose = c;
                        invalidate();
                    }
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (oldChoose != c && listener != null) {
                    if (c >= 0 && c < b.length) {
                        listener.onTouchingLetterChanged(b[c]);
                        choose = c;
                        invalidate();
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                showBkg = false;
                choose = -1;
                invalidate();
                break;
        }
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }

    public void setOnTouchingLetterChangedListener(
            OnTouchingLetterChangedListener onTouchingLetterChangedListener) {
        this.onTouchingLetterChangedListener = onTouchingLetterChangedListener;
    }

    public interface OnTouchingLetterChangedListener {
        void onTouchingLetterChanged(String s);
    }

}

使用的時候設定好寬度和高度:
<com.kairui.kyb.ui.view.widget.LetterListView
            android:id="@+id/total_city_letters_lv"
            android:layout_width="25dp"
            android:layout_height="match_parent"
            android:layout_alignParentRight="true"
            android:layout_marginRight="2dp"
            android:layout_marginTop="7dp"
            android:layout_marginBottom="7dp"/>
接下來就是關鍵的地方了,咳咳~

一、 既然左邊用到了ListView,那就少不了介面卡和城市模型。

①、城市模型:其中拼音和首字母是方便使用者搜尋的時候進行篩選,CityCode你們可以不用去管,我主要是用來請求資料。

public class CityEntity {
    private String name;
    private String key;
    private String pinyin;  //全拼
    private String first;   //首字母
    private String cityCode;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getPinyin() {
        return pinyin;
    }

    public void setPinyin(String pinyin) {
        this.pinyin = pinyin;
    }

    public String getFirst() {
        return first;
    }

    public void setFirst(String first) {
        this.first = first;
    }

    public String getCityCode() {
        return cityCode;
    }

    public void setCityCode(String cityCode) {
        this.cityCode = cityCode;
    }
}

②、全部城市列表的介面卡:我這裡將全部城市列表分成了三種類型,第一種就是當前定位城市的佈局,第二種就是熱門城市的佈局,(當然了,熱門城市你們可以從後臺獲取,我圖方便就寫死在本地了),第三種就是全部城市的佈局了
/**
     * 總城市介面卡
     */
    private class CityListAdapter extends BaseAdapter {
        private Context context;

        private List<CityEntity> totalCityList;
        private List<CityEntity> hotCityList;
        private LayoutInflater inflater;
        final int VIEW_TYPE = 3;

        CityListAdapter(Context context,
                        List<CityEntity> totalCityList,
                        List<CityEntity> hotCityList) {
            this.context = context;
            this.totalCityList = totalCityList;
            this.hotCityList = hotCityList;
            inflater = LayoutInflater.from(context);

            alphaIndexer = new HashMap<>();

            for (int i = 0; i < totalCityList.size(); i++) {
                // 當前漢語拼音首字母
                String currentStr = totalCityList.get(i).getKey();

                String previewStr = (i - 1) >= 0 ? totalCityList.get(i - 1).getKey() : " ";
                if (!previewStr.equals(currentStr)) {
                    String name = getAlpha(currentStr);
                    alphaIndexer.put(name, i);
                }
            }
        }

        @Override
        public int getViewTypeCount() {
            return VIEW_TYPE;
        }

        @Override
        public int getItemViewType(int position) {
            return position < 2 ? position : 2;
        }

        @Override
        public int getCount() {
            return totalCityList == null ? 0 : totalCityList.size();
        }

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

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

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            final TextView curCityNameTv;
            ViewHolder holder;
            int viewType = getItemViewType(position);
            if (viewType == 0) { // 定位
                convertView = inflater.inflate(R.layout.select_city_location_item, null);

                LinearLayout noLocationLl = (LinearLayout) convertView.findViewById(R.id.cur_city_no_data_ll);
                TextView getLocationTv = (TextView) convertView.findViewById(R.id.cur_city_re_get_location_tv);
                curCityNameTv = (TextView) convertView.findViewById(R.id.cur_city_name_tv);

                if (TextUtils.isEmpty(locationCity)) {
                    noLocationLl.setVisibility(View.VISIBLE);
                    curCityNameTv.setVisibility(View.GONE);
                    getLocationTv.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            initLocation();
                        }
                    });
                } else {
                    noLocationLl.setVisibility(View.GONE);
                    curCityNameTv.setVisibility(View.VISIBLE);

                    curCityNameTv.setText(locationCity);
                    curCityNameTv.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            if (!locationCity.equals(UserConstant.curSelCity)) {
                                //設定城市程式碼
                                String cityCode = "";
                                for (CityEntity cityEntity : AppCache.getInstance().getCurCityList()) {
                                    if (cityEntity.getName().equals(locationCity)) {
                                        cityCode = cityEntity.getCityCode();
                                        break;
                                    }
                                }
                                showSetCityDialog(locationCity, cityCode);
                            } else {
                                ToastUtils.show("當前定位城市" + curCityNameTv.getText().toString());
                            }
                        }
                    });
                }
            } else if (viewType == 1) { //熱門城市
                convertView = inflater.inflate(R.layout.recent_city_item, null);
                GridView hotCityGv = (GridView) convertView.findViewById(R.id.recent_city_gv);
                hotCityGv.setAdapter(new HotCityListAdapter(context, this.hotCityList));
                hotCityGv.setOnItemClickListener(new AdapterView.OnItemClickListener() {

                    @Override
                    public void onItemClick(AdapterView<?> parent, View view,
                                            int position, long id) {
                        CityEntity cityEntity = hotCityList.get(position);
                        showSetCityDialog(cityEntity.getName(), cityEntity.getCityCode());
                    }
                });
            } else {
                if (null == convertView) {
                    holder = new ViewHolder();
                    convertView = inflater.inflate(R.layout.city_list_item_layout, null);
                    ViewBinder.bind(holder, convertView);
                    convertView.setTag(holder);
                } else {
                    holder = (ViewHolder) convertView.getTag();
                }

                CityEntity cityEntity = totalCityList.get(position);
                holder.cityKeyTv.setVisibility(View.VISIBLE);
                holder.cityKeyTv.setText(getAlpha(cityEntity.getKey()));
                holder.cityNameTv.setText(cityEntity.getName());

                if (position >= 1) {
                    CityEntity preCity = totalCityList.get(position - 1);
                    if (preCity.getKey().equals(cityEntity.getKey())) {
                        holder.cityKeyTv.setVisibility(View.GONE);
                    } else {
                        holder.cityKeyTv.setVisibility(View.VISIBLE);
                    }
                }
            }

            return convertView;
        }

        private class ViewHolder {
            @Bind(R.id.city_name_tv)
            TextView cityNameTv;
            @Bind(R.id.city_key_tv)
            TextView cityKeyTv;
        }
    }
1)、當前定位城市佈局:定位失敗的話顯示出來,並且提示使用者去開啟GPS
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/white"
    android:orientation="vertical">

    <TextView
        android:id="@+id/city_item_hint_tv"
        android:layout_width="match_parent"
        android:layout_height="25dp"
        android:background="@color/mainGray"
        android:gravity="center_vertical"
        android:paddingLeft="20dp"
        android:paddingRight="20dp"
        android:drawablePadding="5dp"
        android:drawableLeft="@drawable/ic_location"
        android:text="當前定位城市"
        android:textColor="@color/gray_9"
        android:textSize="14sp" />

    <TextView
        android:id="@+id/cur_city_name_tv"
        android:layout_width="90dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:layout_marginTop="10dp"
        android:background="@drawable/shape_gray_border_pres_style"
        android:ellipsize="end"
        android:gravity="center"
        android:textColor="@color/mainColor"
        android:textSize="14sp"
        android:visibility="gone" />

    <LinearLayout
        android:id="@+id/cur_city_no_data_ll"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="5dp"
        android:layout_marginTop="5dp"
        android:orientation="horizontal">

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            android:layout_weight="1"
            android:text="無法獲取您的定位地址" />

        <TextView
            android:id="@+id/cur_city_re_get_location_tv"
            android:layout_width="120dp"
            android:layout_height="35dp"
            android:layout_marginLeft="30dp"
            android:layout_marginRight="30dp"
            android:background="@drawable/round_btn_pres_style"
            android:gravity="center"
            android:text="重新獲取"
            android:textColor="@color/white" />

    </LinearLayout>
</LinearLayout>
2)、熱門城市佈局:記住,這裡直接使用GridView的話只會顯示一行資料,需要繼承GridView並重寫OnMeasure方法,網上有很多案例,就不放程式碼了。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/white"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="25dp"
        android:background="@color/mainGray"
        android:drawableLeft="@drawable/ic_hot"
        android:drawablePadding="5dp"
        android:gravity="center_vertical"
        android:paddingLeft="20dp"
        android:paddingRight="20dp"
        android:text="熱門城市"
        android:textColor="@color/gray_9"/>

    <com.kairui.kyb.ui.view.widget.ScrollWithGridView
        android:id="@+id/recent_city_gv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="30dp"
        android:layout_marginTop="10dp"
        android:horizontalSpacing="10dp"
        android:listSelector="@android:color/transparent"
        android:numColumns="3"
        android:verticalSpacing="10dp" />

</LinearLayout>

3)、全部城市的單行佈局:每繪製一條資料判斷前一個數據的Key是否與現在的相同,true則不顯示  city_key_tv,保證同一個Key的城市只有一個顯示。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/recycler_bg"
    android:orientation="vertical">

    <TextView
        android:id="@+id/city_key_tv"
        android:layout_width="match_parent"
        android:layout_height="25dp"
        android:background="@color/mainGray"
        android:gravity="center_vertical"
        android:paddingLeft="32dp"
        android:paddingRight="32dp"
        android:textColor="@color/gray_9"
        android:textSize="14sp" />

    <TextView
        android:id="@+id/city_name_tv"
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:gravity="center_vertical"
        android:paddingLeft="20dp"
        android:paddingRight="20dp"
        android:textColor="@color/gray_6"
        android:textSize="15sp" />

    <View
        style="@style/default_line"
        android:layout_marginLeft="@dimen/common_20"
        android:layout_marginRight="@dimen/common_20" />

</LinearLayout>

③、熱門城市列表介面卡:比較簡單,不過多說明。
 /**
     * 熱門城市介面卡
     */
    private class HotCityListAdapter extends BaseAdapter {

        private List<CityEntity> cityEntities;
        private LayoutInflater inflater;

        HotCityListAdapter(Context mContext, List<CityEntity> cityEntities) {
            this.cityEntities = cityEntities;
            inflater = LayoutInflater.from(mContext);
        }

        @Override
        public int getCount() {
            return cityEntities == null ? 0 : cityEntities.size();
        }

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

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

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
            if (null == convertView) {
                holder = new ViewHolder();
                convertView = inflater.inflate(R.layout.city_list_grid_item_layout, null);
                ViewBinder.bind(holder, convertView);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            CityEntity cityEntity = cityEntities.get(position);
            holder.cityNameTv.setText(cityEntity.getName());

            return convertView;
        }

        private class ViewHolder {
            @Bind(R.id.city_list_grid_item_name_tv)
            TextView cityNameTv;
        }
    }

二、初始化首字母提示框,並且設定ListVIew的滾動監聽以及自定義View的Touch事件
    /**
     * 初始化漢語拼音首字母彈出提示框
     */
    private void initOverlay() {
        mReady = true;
        LayoutInflater inflater = LayoutInflater.from(this);
        overlay = (TextView) inflater.inflate(R.layout.overlay, null);
        overlay.setVisibility(View.INVISIBLE);
        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_APPLICATION,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
                PixelFormat.TRANSLUCENT);
        WindowManager windowManager = (WindowManager) this
                .getSystemService(Context.WINDOW_SERVICE);
        windowManager.addView(overlay, lp);
    }
private class LetterListViewListener implements
            LetterListView.OnTouchingLetterChangedListener {

        @Override
        public void onTouchingLetterChanged(final String s) {
            isScroll = false;
            if (alphaIndexer.get(s) != null) {
                int position = alphaIndexer.get(s);
                totalCityLv.setSelection(position);
                overlay.setText(s);
                overlay.setVisibility(View.VISIBLE);
                handler.removeCallbacks(overlayThread);
                // 延遲讓overlay為不可見
                handler.postDelayed(overlayThread, 700);
            }
        }
    }

    /**
     * 設定overlay不可見
     */
    private class OverlayThread implements Runnable {
        @Override
        public void run() {
            overlay.setVisibility(View.GONE);
        }
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (scrollState == SCROLL_STATE_TOUCH_SCROLL
                || scrollState == SCROLL_STATE_FLING) {
            isScroll = true;
        } else {
            isScroll = false;
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
                         int visibleItemCount, int totalItemCount) {
        if (!isScroll) {
            return;
        }

        if (mReady) {
            String key = getAlpha(totalCityList.get(firstVisibleItem).getKey());
            overlay.setText(key);
            overlay.setVisibility(View.VISIBLE);
            handler.removeCallbacks(overlayThread);
            // 延遲讓overlay為不可見
            handler.postDelayed(overlayThread, 700);
        }
    }

其中最主要關鍵的地方就是以上這些了

最後我就放上demo的下載地址了,Demo點我

就是這些了,祝大家第一天工作愉快~