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);
        init(context);
    }

    public LetterListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public LetterListView(Context context) {
        super(context);
        init(context);
    }
    
    private void init(Context context){
        this.mContext = context;
        paint.setColor(Color.parseColor("#50B3DA"));
        paint.setTextSize(DisplayUtil.sp2px(mContext, 12));
        paint.setAntiAlias(true);
    }

    @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++) {
            float xPos = width / 2 - paint.measureText(b[i]) / 2;
            float yPos = singleHeight * i + singleHeight;
            canvas.drawText(b[i], xPos, yPos, paint);
        }
    }

    @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点我

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

-------------------------------

2019-10-09更新

CSDN当初上传设置的最低1分,现在居然涨到50了,,,过分。现在传到了百度云,  提取码:l7ip

欢迎自取,好用的话,不放点个关注。谢谢~~

评论 49
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

斯普润丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值