Android仿美团选择城市

2024-05-05 06:18

本文主要是介绍Android仿美团选择城市,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

需求:需要有当前定位城市,热门城市,下面按照城市首拼音排序,滑动的过程中字母A,B,C…会置顶互相切换。右侧有快速切换字母城市的选择
效果图:

选择城市.jpg

字母置顶.jpg

思路:因为上部分要划走,RecyclerView滑动过程中要A,B,C置顶,所以采用CoordinatorLayout。自定义RecItemHeadDecoration做A,B,C置顶。

步骤一:布局
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextViewandroid:id="@+id/tv_title"android:layout_width="match_parent"android:layout_height="48dp"android:text="选择城市"android:gravity="center"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toTopOf="parent"android:textSize="20sp"android:background="@color/white"/><android.support.constraint.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="0dp"app:layout_constraintTop_toBottomOf="@id/tv_title"app:layout_constraintBottom_toBottomOf="parent"><!--因为上部分要划走,RecyclerView滑动过程中要A,B,C置顶,所以采用CoordinatorLayout--><android.support.design.widget.CoordinatorLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><android.support.design.widget.AppBarLayoutandroid:id="@+id/abl_city"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@color/white"><android.support.design.widget.CollapsingToolbarLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"app:layout_scrollFlags="scroll|exitUntilCollapsed"><android.support.constraint.ConstraintLayoutandroid:id="@+id/cl_select_city_head"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingBottom="15dp"app:layout_collapseMode="pin"xmlns:zhy="http://schemas.android.com/tools"><TextViewandroid:id="@+id/tv_city_location_title"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintTop_toTopOf="parent"app:layout_constraintLeft_toLeftOf="parent"android:layout_marginTop="10dp"android:layout_marginLeft="16dp"android:text="当前定位"android:textColor="@color/c_757575"android:textSize="12sp"/><TextViewandroid:id="@+id/tv_city_location"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintTop_toBottomOf="@id/tv_city_location_title"app:layout_constraintLeft_toLeftOf="@id/tv_city_location_title"android:gravity="center"android:drawablePadding="8dp"android:text="广州市(假的)"android:layout_marginTop="8dp"android:textSize="16sp"android:textColor="@color/c_33"android:textStyle="bold"/><Viewandroid:id="@+id/v_line1"android:layout_width="match_parent"android:layout_height="8dp"android:background="@color/c_f2efef"app:layout_constraintTop_toBottomOf="@id/tv_city_location"app:layout_constraintLeft_toLeftOf="parent"android:layout_marginTop="15dp"/><TextViewandroid:id="@+id/tv_hot_city_title"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintLeft_toLeftOf="@id/tv_city_location_title"app:layout_constraintTop_toBottomOf="@id/v_line1"android:layout_marginTop="10dp"android:text="热门城市"android:textSize="12sp"android:textColor="@color/c_757575"/><!--热门城市,做好兼容,可能有很多--><com.zhy.view.flowlayout.TagFlowLayoutandroid:id="@+id/tfl_home_city"android:layout_width="match_parent"android:layout_height="wrap_content"app:layout_constraintTop_toBottomOf="@id/tv_hot_city_title"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"android:layout_marginRight="16dp"android:layout_marginLeft="12dp"android:layout_marginTop="8dp"zhy:max_select="1" /></android.support.constraint.ConstraintLayout></android.support.design.widget.CollapsingToolbarLayout></android.support.design.widget.AppBarLayout><android.support.v7.widget.RecyclerViewandroid:id="@+id/rv_city"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/white"app:layout_behavior="@string/appbar_scrolling_view_behavior"/></android.support.design.widget.CoordinatorLayout><!--字母之间的距离有高度决定,自定适应--><com.cong.coordinatorlayoutdemo.widget.QuickLocationBarandroid:id="@+id/qlb_letter"android:layout_width="24dp"android:layout_height="450dp"app:layout_constraintTop_toTopOf="parent"app:layout_constraintRight_toRightOf="parent"android:layout_marginRight="2dp"android:layout_marginTop="86dp"/>
</android.support.constraint.ConstraintLayout></android.support.constraint.ConstraintLayout>
步骤二:一些用的的自定义控件
/*** @author :congge* @date : 2020/5/8 11:56* @desc :这控件百度来的**/
public class QuickLocationBar extends View {
private List<String> characters = new ArrayList<>();private int choose = -1;
private Paint paint = new Paint();
private OnTouchLetterChangedListener mOnTouchLetterChangedListener;
private TextView mTextDialog;/*** 选择的圆的半径*/
private Paint circlePaint;
private String selectChars = "热";public QuickLocationBar(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}public QuickLocationBar(Context context, AttributeSet attrs) {super(context, attrs);init();
}public QuickLocationBar(Context context) {super(context);}public void setOnTouchLitterChangedListener(OnTouchLetterChangedListener onTouchLetterChangedListener) {this.mOnTouchLetterChangedListener = onTouchLetterChangedListener;
}public void setTextDialog(TextView dialog) {this.mTextDialog = dialog;
}private void init(){circlePaint = new Paint();circlePaint.setAntiAlias(true);circlePaint.setColor(getResources().getColor(R.color.c_0091ff));circlePaint.setStyle(Paint.Style.FILL);// 对paint进行相关的参数设置paint.setColor(getResources().getColor(R.color.c_33));paint.setAntiAlias(true);
}@Override
protected void onDraw(Canvas canvas) {// TODO Auto-generated method stubsuper.onDraw(canvas);int width = getWidth();int height = getHeight();if (characters.size() > 0){int singleHeight = height / characters.size();for (int i = 0; i < characters.size(); i++) {paint.setTextSize(150*(float) width/320);
//if (i == choose) {// choose变量表示当前显示的字符位置,若没有触摸则为-1
//paint.setColor(getResources().getColor(R.color.bg_653fac));
//paint.setFakeBoldText(true);
//}// 计算字符的绘制的位置float xPos = width / 2 - paint.measureText(characters.get(i)) / 2;float yPos = singleHeight * i + singleHeight;if (selectChars.equals(characters.get(i))){canvas.drawCircle(xPos+ paint.measureText(characters.get(i)) / 2, yPos-singleHeight/4,width/3,circlePaint);paint.setColor(Color.WHITE);} else {paint.setColor(getResources().getColor(R.color.c_33));}// 在画布上绘制字符canvas.drawText(characters.get(i), xPos, yPos, paint);paint.reset();// 每次绘制完成后不要忘记重制Paint}}}@Override
public boolean dispatchTouchEvent(MotionEvent event) {int action = event.getAction();float y = event.getY();int c = (int) (y / getHeight() * characters.size());switch (action) {case MotionEvent.ACTION_UP:choose = -1;//setBackgroundColor(0x0000);invalidate();if (mTextDialog != null) {mTextDialog.setVisibility(View.GONE);}break;case MotionEvent.ACTION_DOWN:case MotionEvent.ACTION_MOVE://setBackgroundColor(getResources().getColor(R.color.bg_653fac));if (choose != c) {if (c >= 0 && c < characters.size()) {if (mOnTouchLetterChangedListener != null) {mOnTouchLetterChangedListener.touchLetterChanged(characters.get(c));}if (mTextDialog != null) {mTextDialog.setText(characters.get(c));mTextDialog.setVisibility(View.VISIBLE);}Toast.makeText(getContext(),characters.get(c),Toast.LENGTH_SHORT).show();choose = c;selectChars = characters.get(c);invalidate();}}break;}return true;
}public interface OnTouchLetterChangedListener {public void touchLetterChanged(String s);
}/**
* @desc : 设置字母
* @author : congge on 2019/12/16 17:49
**/
public void setCharacters(ArrayList<String> characters ,Boolean hasHot){if (hasHot){this.characters.add("热");}this.characters.addAll(characters);invalidate();}/**
* @desc : 设置选择的字母
* @author : congge on 2019/12/28 14:53
**/
public void setSelectCharacter(String character){selectChars = character;invalidate();
}public String getSelectChars() {return selectChars;
}} 

关键:RecltemHeadDecoration类

public class RecItemHeadDecoration extends RecyclerView.ItemDecoration {private List<RecBean.CityListBean> citiList;
private Context context;
private int headHeight ;
private int lineHeight;
private Paint paint;
private Rect rectOver;
private List<String> index;
private ChangeTagNameListener changeTagNameListener;
private String lastName = "";public RecItemHeadDecoration(Context context, List<String> index) {this.context = context;headHeight = dip2px(36);lineHeight = dip2px(1);if(paint == null){paint = new Paint();paint.setAntiAlias(true);paint.setTextSize(dip2px(15));rectOver = new Rect();this.index = index;}
}/*** 设置Item的布局四周的间隙.** @param outRect 确定间隙Left  Top Right Bottom 的数值的矩形.* @param view    RecyclerView的ChildView也就是每个Item的的布局.* @param parent  RecyclerView本身.* @param state   RecyclerView的各种状态.*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {super.getItemOffsets(outRect, view, parent, state);if (citiList == null || citiList.size() == 0) {return;}int adapterPosition = parent.getChildAdapterPosition(view);RecBean.CityListBean beanByPosition = getBeanByPosition(adapterPosition);if(beanByPosition == null){return;}int preTage = -1;int tage = beanByPosition.getTage();/** 1.我们要在每组的第一个位置绘制我们需要的头部.** 2.绘制头部局有两种方式:*   第一种方式:给Item 的头部留出空间,也就是outRect.top.该种方式对应的就是当前的Item就是分组的第一个Item.*   第二种方式:给Item 的底部留出空间也就是outRect.bottpm.该种方式对应的就是当前的Item是当前分组的最后一个Item.**   这个该怎么选择呢?*   1.如果第一个Item需要有分组的布局,那就选择第一种方式.*   2.其他可以选择第二种方式.***   该方法是给Item设置间距的,有四个属性可以设置四个间距,Left  Top Right Bottom.简单来说如果Item 的高度是50dp 我们再该方法里面设置了outRect.top = 40;*   也就是给Item区域的顶部多出了40dp的间隙,那么实际上该Item显示出来的高度为 50 + 40 = 90dp.正好这个40dp用来绘制我们所需要的头布局.** 3.这里拿第一种方式,那么怎么判断当前的Item是不是分组的第一个Item呢?**   我们再Item的设置的数据里面做好分组的标记,即属于同一组的tag都一样,不同组tag都不一样.*   当前Item为头布局的话就要跟前一个Item 的tag比较了,因为每个分组头部的tag的值都是不一样的,如果前一个的Tag跟当前的不一样那么,当前就是下个分组的头部.**   a  b c    d e f   g h i**   如果 a  d  g  是分组的头部的 .a的tag = 1 , b的tag = 2, c 的tag = 3....等等 ,前一个Item 用 preTag 来表示 ,初始值为 -1.**   假如当前的Item为a,当前tag = 1,那么它前一个Item为空,也就是发现preTag和a的tag不一样,那么a就是分组的头部.*   假如当前的Item为b,当前tag = 1,那么它前一个preTag 也就是a的tag = 1,发现一样那就是是同一组的.*   假如当前的Item为d,当前tag = 2,那么它前一个preTag 也就是c的tag = 1,发现前一个的tag跟当前的不一样,那么当前的就是新分组的第一个头部Item.* *///一定要记住这个 >= 0if(adapterPosition - 1 >= 0) {RecBean.CityListBean nextBean = getBeanByPosition(adapterPosition - 1);if (nextBean == null) {return;}preTage = nextBean.getTage();}Log.e("WANG","当前的Position is " + adapterPosition +" 当前的Tage 是  " +tage  +"   前一个 tage  是  "+ preTage);if(preTage != tage){outRect.top = headHeight;}else {outRect.top = lineHeight;}}/*** 绘制*除Item内容*以外的东西,这个方法是再****Item的内容绘制之前****执行的,* 所以呢如果两个绘制区域重叠的话,Item的绘制区域会覆盖掉该方法绘制的区域.* 一般配合getItemOffsets来绘制分割线等.** @param c      Canvas 画布* @param parent RecyclerView* @param state  RecyclerView的状态*/
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {super.onDraw(c, parent, state);
}/*** 绘制*除Item内容*以外的东西,这个方法是在****Item的内容绘制之后****才执行的,* 所以该方法绘制的东西会将Item的内容覆盖住,既显示在Item之上.* 一般配合getItemOffsets来绘制分组的头部等.** @param c      Canvas 画布* @param parent RecyclerView* @param state  RecyclerView的状态*/
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {super.onDrawOver(c, parent, state);if(citiList == null || citiList.size() == 0){return;}int parentLeft = parent.getPaddingLeft();int parentRight = parent.getWidth() - parent.getPaddingRight();int childCount = parent.getChildCount();int tag = -1;int preTag;/*当列表滑动的时候RecyclerView会不断的加载之后的Item,布局发生复用,我们要在不断的变化中去重新绘制我们的头部Item的布局.这个方法当每个Item消失或者出现的时候都会被调用,我们在这里去绘制头部的区域.所以在该方法里面我们会遍历所有可见的Item去重新判断分组的头布,去重新绘制.1.判断头布局绘制头布局.那么我们在这里呢还是需要判重新去判断哪个Item是分组的头部.按照getItemOffsets里面的我们需要跟之前的Item的tag作比较.但是有个问题就是我们再这里并不能拿到Item的布局或者别的东西,只能遍历所有已经显示的Item.这样的话我们的前一个preTag就需要我们自己去定义,然后把tag赋值给preTag,当遍历到下个Item的Tag跟之前的preTag一样的话,那就继续遍历不去绘制头布局,当遍历到Item的tag跟preTag不一样的时候就去绘制有布局.2.怎么让头布局悬停在顶部.这个问题其实拿一个例子去说明是最好的了,当我们要绘制头部的Item正好出现在屏幕的顶部的时候,我们继续滑动她的头布局就会渐渐的消失,也就是Item的getTop距离会不断的小于我们要绘制的头部的高度,当出现这种情况的时候,我们就让Item的getTop和头部的高度中去一个最大值.这样就好保证当getTop小于头部的高度的时候我们的头部布局一直留在顶部.3.下个头部来的时候怎么替换呢.当顶部悬浮的有一个头部的时候,我们滑动列表俩个头部肯定会和当前的头部相遇.我们再这里做的是当悬浮的头布局跟下个悬浮的头布局相遇的时候有个渐变的效果.那么我们就要来实现这个效果了.首先我们要判断下个头部什么时候滑动到屏幕顶部,我们这里就需要判断当前遍历到的Itme的下个Item时候有头部,还是当前的tag跟nextTag比较的结果,如果不同的话那下个Item就是有头布局的.那个渐变的效果需要有一个渐变值.我们想想啊,* 1.先做到顶部悬停.** */for (int i = 0; i <childCount; i++) {View childView = parent.getChildAt(i);if(childView == null){continue;}int adapterPosition = parent.getChildAdapterPosition(childView);int top = childView.getTop();int bottom = childView.getBottom() ;preTag = tag;if(adapterPosition >= citiList.size()){break;}tag = citiList.get(adapterPosition).getTage();if(preTag == tag){continue;}String name = index.get((tag - 1 ) < 0 ? 0 : (tag -1));int height = Math.max(top,headHeight);if(adapterPosition + 1 < citiList.size()){int nextTag = citiList.get(adapterPosition + 1).getTage();if(tag != nextTag){height = bottom;}}paint.setColor(context.getResources().getColor(R.color.c_f2efef));c.drawRect(parentLeft,height - headHeight,parentRight,height,paint);paint.setColor(context.getResources().getColor(R.color.c_757575));paint.getTextBounds(name, 0, name.length(), rectOver);c.drawText(name, dip2px(10), height - (headHeight - rectOver.height()) / 2, paint);if (!lastName.equals(name) && changeTagNameListener != null && top<headHeight){changeTagNameListener.changeName(name);lastName = name;}}}public interface ChangeTagNameListener{void changeName(String name);
}public void setChangeTagNameListener(ChangeTagNameListener changeTagNameListener) {this.changeTagNameListener = changeTagNameListener;
}private RecBean.CityListBean getBeanByPosition(int position) {if (position < citiList.size()) {RecBean.CityListBean citiListBean = citiList.get(position);return citiListBean;}return null;
}/*** 列表的数据包括分组信息 ,每个组的开始会有个tage字段标记.通过set方法把数据给设置进去*/
public void setCitiList(List<RecBean.CityListBean> citiList) {this.citiList = citiList;
}public int dip2px(float dpValue) {float scale = context.getResources().getDisplayMetrics().density;return (int) (dpValue * scale + 0.5f);
}public void setLastName(String lastName) {this.lastName = lastName;
}}

这个类被我修改过,可参考原始的别人文档https://www.jianshu.com/p/c0b131b679c0

选择城市适配器SelectCityAdapter:

class SelectCityAdapter(layoutResId: Int, data: List<RecBean.CityListBean>? = null) : BaseQuickAdapter<RecBean.CityListBean, BaseViewHolder>(layoutResId, data){override fun convert(helper: BaseViewHolder, item:RecBean.CityListBean) {helper.setText(R.id.tv_city,item.name)}}

没有BaseQuickAdapter的,百度下,别人写的强大的Adapter库

步骤三:使用
class MSelectCityActivity : AppCompatActivity(){private lateinit var cityAdapter: SelectCityAdapter
private var headDecoration: RecItemHeadDecoration? = null
private lateinit var mLinearLayoutManager: LinearLayoutManager
private var mIndex = 0
private var move = false
private var  behavior:Behavior<View>?= nullprivate var letterList = arrayListOf<String>()
private var context: Context? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_select_city)context = thismLinearLayoutManager = LinearLayoutManager(this)rv_city.layoutManager = mLinearLayoutManagerinitViewListener()onCityData()
}private fun initViewListener() {//左侧A,B,C定位qlb_letter.setOnTouchLitterChangedListener {if (behavior == null){behavior = (abl_city.layoutParams as CoordinatorLayout.LayoutParams).behavior}if (it == "热") {if (behavior is AppBarLayout.Behavior) {val appBarBehavior = behavior as AppBarLayout.BehaviorappBarBehavior.topAndBottomOffset = 0}//rv移动到AmoveToPosition(0)headDecoration?.setLastName("热")} else {//移动头部AppBarLayout距离if (behavior is AppBarLayout.Behavior) {val appBarBehavior = behavior as AppBarLayout.BehaviorappBarBehavior.topAndBottomOffset = -cl_select_city_head.height}// 逻辑ABC...转化为对应数据分组的tagvar toPosition = 0for (i in letterList.indices) {if (it == letterList[i]) {toPosition = i + 1break}}for (i in cityAdapter.data.indices) {if (cityAdapter.data[i].tage == toPosition) {toPosition = ibreak}}//这移动只是看到就停止了//rv_city.scrollToPosition(toPosition)moveToPosition(toPosition)}}//列表滚动事件,定位出position,再把position置顶rv_city.addOnScrollListener(object : RecyclerView.OnScrollListener() {override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {super.onScrolled(recyclerView, dx, dy)if (move) {move = false//当前已经滚完了即scrollToPosition执行完val n = mIndex - mLinearLayoutManager.findFirstVisibleItemPosition()if (0 <= n && n < rv_city.childCount) {rv_city.scrollBy(0, rv_city.getChildAt(n).top - UtilHelper.dip2px(context, 36f))}}if (qlb_letter.selectChars != "热"){if (behavior == null){behavior = (abl_city.layoutParams as CoordinatorLayout.LayoutParams).behavior}if (behavior is AppBarLayout.Behavior) {val appBarBehavior = behavior as AppBarLayout.Behaviorif (abs(appBarBehavior.topAndBottomOffset) < cl_select_city_head.height){qlb_letter.setSelectCharacter("热")headDecoration?.setLastName("热")}}}}})}/*** @desc : 城市滚动置顶* @author : congge on 2019/12/4 11:45* n所在位置示意图对应下面三个判断*            n在这*  -----------------firstItem*            n在这*  -----------------lastItem*            n在这***/
private fun moveToPosition(n: Int) {mIndex = nval firstItem = mLinearLayoutManager.findFirstVisibleItemPosition()val lastItem = mLinearLayoutManager.findLastVisibleItemPosition()if (n <= firstItem) {//已经在列表上面(只是看不见),滚到它,它就置顶了,相当于拉下来。rv_city.scrollToPosition(n)} else if (n <= lastItem) {//已经处于可见列表,已经可见,可能肉眼看不见,但确实处于可见区域。这时用scrollToPosition已不起作用。用scrollBy滚到n到firstItem的top距离//减去36dp是减去字母item的高度rv_city.scrollBy(0, rv_city.getChildAt(n - firstItem).top - UtilHelper.dip2px(context, 36f))} else {//n还没出现在列表上,所以要先滚到出现,再通过scrollBy滚到顶部rv_city.scrollToPosition(n)move = true}
}/*** @desc : 设置热门城市* @author : congge on 2019/12/16 16:03**/
private fun setHotCityData(hotCityData: List<String>?) {if (hotCityData.isNullOrEmpty()) {tv_hot_city_title.visibility = View.GONEtfl_home_city.visibility = View.GONE} else {val tagAdapter = object : TagAdapter<String>(hotCityData) {override fun getView(parent: FlowLayout, position: Int, bean: String): View {val tv = View.inflate(context, R.layout.active_hot_city_item, null) as TextViewtv.text = beanreturn tv}}tfl_home_city?.adapter = tagAdaptertfl_home_city?.setOnTagClickListener { view, position, parent ->true}tv_hot_city_title.visibility = View.VISIBLEtfl_home_city.visibility = View.VISIBLE}}/*** @desc : 设置城市列表* @author : congge on 2019/12/16 15:48**/private fun onCityData() {val cityAllNewBean:CityAllNewBean = UtilHelper.JsonToObject(UtilHelper.getJson(context!!,"city.json"),CityAllNewBean::class.java)val cityAllBean: CityAllBean = cityAllNewBean.data!!//热门城市setHotCityData(cityAllBean.hot_city)val cityList = arrayListOf<RecBean.CityListBean>()var tagFirst = 1for (cityItem in cityAllBean.city) {if (cityItem.citylist.isNotEmpty()) {//获取字母集合,只有城市列表不为空,才添加letterList.add(cityItem.letter)for (cityName in cityItem.citylist) {val cityBean = RecBean.CityListBean()cityBean.name = cityName//为每个城市打上tage,用于A,B,C...滑动时区分置顶cityBean.tage = tagFirstcityList.add(cityBean)}tagFirst++}}headDecoration = RecItemHeadDecoration(context, letterList)//必须设置列表数据与getTag对比headDecoration?.setCitiList(cityList)headDecoration?.setChangeTagNameListener {qlb_letter.setSelectCharacter(it)}rv_city.addItemDecoration(headDecoration!!)qlb_letter.setCharacters(letterList,!cityAllBean.hot_city.isNullOrEmpty())cityAdapter = SelectCityAdapter(R.layout.active_city_item, cityList)rv_city.adapter = cityAdapter
}}

用的的json文件,放在assets下,正常接口返回的

{
“result”: “1”,
“type”: “1”,
“message”: “请求成功”,
“data”: {
“hot_city”: [
“北京”,
“天津”,
“上海”,
“衢州”,
“亳州”,
“广州”,
“深圳”,
“泸州”,
“氹仔岛”
],
“city”: [
{
“letter”: “A”,
“citylist”: [
“安庆”,
“安康”,
“安阳”,
“安顺”,
“澳门半岛”,
“阿克苏”,
“阿勒泰”,
“阿坝”,
“阿拉善盟”,
“阿里”,
“鞍山”
]
},
{
“letter”: “B”,
“citylist”: [
“保定”,
“保山”,
“包头”,
“北京”,
“北海”,
“博尔塔拉”,
“宝鸡”,
“巴中”,
“巴彦淖尔”,
“巴音郭楞”,
“本溪”,
“毕节”,
“滨州”,
“白城”,
“白山”,
“白银”,
“百色”,
“蚌埠”
]
},
{
“letter”: “C”,
“citylist”: [
“崇左”,
“常州”,
“常德”,
“成都”,
“承德”,
“昌吉”,
“昌都”,
“朝阳”,
“楚雄”,
“池州”,
“沧州”,
“滁州”,
“潮州”,
“赤峰”,
“郴州”,
“长春”,
“长沙”,
“长治”
]
},
{
“letter”: “D”,
“citylist”: [
“东莞”,
“东营”,
“丹东”,
“大兴安岭”,
“大同”,
“大庆”,
“大理”,
“大连”,
“定西”,
“德宏”,
“德州”,
“德阳”,
“达州”,
“迪庆”
]
},
{
“letter”: “E”,
“citylist”: [
“恩施”,
“鄂尔多斯”,
“鄂州”
]
},
{
“letter”: “F”,
“citylist”: [
“佛山”,
“抚州”,
“抚顺”,
“福州”,
“阜新”,
“阜阳”,
“防城港”
]
},
{
“letter”: “G”,
“citylist”: [
“固原”,
“广元”,
“广安”,
“广州”,
“果洛”,
“桂林”,
“甘南”,
“甘孜”,
“贵港”,
“贵阳”,
“赣州”,
“高雄”
]
},
{
“letter”: “H”,
“citylist”: [
“合肥”,
“呼伦贝尔”,
“呼和浩特”,
“和田”,
“哈密”,
“哈尔滨”,
“怀化”,
“惠州”,
“杭州”,
“汉中”,
“河池”,
“河源”,
“海东”,
“海北”,
“海南”,
“海口”,
“海西”,
“淮北”,
“淮南”,
“淮安”,
“湖州”,
“红河”,
“花王堂”,
“花莲”,
“菏泽”,
“葫芦岛”,
“衡水”,
“衡阳”,
“贺州”,
“邯郸”,
“鹤壁”,
“鹤岗”,
“黄冈”,
“黄南”,
“黄山”,
“黄石”,
“黑河”
]
},
{
“letter”: “I”,
“citylist”: []
},
{
“letter”: “J”,
“citylist”: [
“九江”,
“九龙”,
“佳木斯”,
“吉安”,
“吉林”,
“嘉义”,
“嘉兴”,
“嘉峪关”,
“基隆”,
“揭阳”,
“晋中”,
“晋城”,
“景德镇”,
“江门”,
“济南”,
“济宁”,
“焦作”,
“荆州”,
“荆门”,
“酒泉”,
“金华”,
“金昌”,
“金普新区”,
“金门”,
“锦州”,
“鸡西”
]
},
{
“letter”: “K”,
“citylist”: [
“克孜勒苏”,
“克拉玛依”,
“喀什”,
“开封”,
“昆明”
]
},
{
“letter”: “L”,
“citylist”: [
“两江新区”,
“临夏”,
“临汾”,
“临沂”,
“临沧”,
“丽水”,
“丽江”,
“乐山”,
“六安”,
“六盘水”,
“兰州”,
“凉山”,
“吕梁”,
“娄底”,
“廊坊”,
“拉萨”,
“来宾”,
“林芝”,
“柳州”,
“洛阳”,
“聊城”,
“莱芜”,
“路氹填海”,
“路环岛”,
“辽源”,
“辽阳”,
“连云港”,
“连江”,
“陇南”,
“龙岩”
]
},
{
“letter”: “M”,
“citylist”: [
“梅州”,
“牡丹江”,
“眉山”,
“绵阳”,
“苗栗”,
“茂名”,
“马鞍山”
]
},
{
“letter”: “N”,
“citylist”: [
“内江”,
“南京”,
“南充”,
“南宁”,
“南平”,
“南投”,
“南昌”,
“南通”,
“南阳”,
“宁德”,
“宁波”,
“怒江”,
“那曲”
]
},
{
“letter”: “O”,
“citylist”: []
},
{
“letter”: “P”,
“citylist”: [
“屏东”,
“平凉”,
“平顶山”,
“攀枝花”,
“普洱”,
“澎湖”,
“盘锦”,
“莆田”,
“萍乡”
]
},
{
“letter”: “Q”,
“citylist”: [
“七台河”,
“庆阳”,
“曲靖”,
“泉州”,
“清远”,
“秦皇岛”,
“钦州”,
“青岛”,
“黔东南”,
“黔南”,
“黔西南”,
“齐齐哈尔”
]
},
{
“letter”: “R”,
“citylist”: [
“日喀则”,
“日照”
]
},
{
“letter”: “S”,
“citylist”: [
“三亚”,
“三明”,
“三沙”,
“三门峡”,
“上海”,
“上饶”,
“十堰”,
“双鸭山”,
“商丘”,
“商洛”,
“四平”,
“宿州”,
“宿迁”,
“山南”,
“朔州”,
“松原”,
“汕头”,
“汕尾”,
“沈阳”,
“深圳”,
“石嘴山”,
“石家庄”,
“绍兴”,
“绥化”,
“苏州”,
“遂宁”,
“邵阳”,
“随州”,
“韶关”
]
},
{
“letter”: “T”,
“citylist”: [
“台东”,
“台中”,
“台北”,
“台南”,
“台州”,
“吐鲁番”,
“唐山”,
“塔城”,
“天水”,
“天津”,
“太原”,
“桃园”,
“泰安”,
“泰州”,
“通化”,
“通辽”,
“铁岭”,
“铜仁”,
“铜川”,
“铜陵”
]
},
{
“letter”: “U”,
“citylist”: []
},
{
“letter”: “V”,
“citylist”: []
},
{
“letter”: “W”,
“citylist”: [
“乌兰察布”,
“乌海”,
“乌鲁木齐”,
“吴忠”,
“威海”,
“文山”,
“无锡”,
“梧州”,
“武威”,
“武汉”,
“温州”,
“渭南”,
“潍坊”,
“芜湖”
]
},
{
“letter”: “X”,
“citylist”: [
“信阳”,
“兴安盟”,
“厦门”,
“咸宁”,
“咸阳”,
“孝感”,
“宣城”,
“徐州”,
“忻州”,
“新乡”,
“新余”,
“新北”,
“新界”,
“新竹”,
“湘潭”,
“湘西”,
“襄阳”,
“西双版纳”,
“西咸”,
“西宁”,
“西安”,
“许昌”,
“邢台”,
“锡林郭勒盟”,
“香港岛”
]
},
{
“letter”: “Y”,
“citylist”: [
“云林”,
“云浮”,
“伊春”,
“伊犁”,
“宜兰”,
“宜宾”,
“宜昌”,
“宜春”,
“岳阳”,
“延安”,
“延边”,
“扬州”,
“榆林”,
“永州”,
“烟台”,
“玉林”,
“玉树”,
“玉溪”,
“益阳”,
“盐城”,
“营口”,
“运城”,
“银川”,
“阳江”,
“阳泉”,
“雅安”,
“鹰潭”
]
},
{
“letter”: “Z”,
“citylist”: [
“中卫”,
“中山”,
“周口”,
“张家口”,
“张家界”,
“张掖”,
“彰化”,
“新直辖县”,
“昭通”,
“枣庄”,
“株洲”,
“淄博”,
“湛江”,
“漳州”,
“珠海”,
“琼直辖县”,
“肇庆”,
“自贡”,
“舟山”,
“舟山新区”,
“豫直辖县”,
“资阳”,
“遵义”,
“郑州”,
“鄂直辖县”,
“重庆”,
“镇江”,
“驻马店”
]
}
]
}
}

好了,基本复制过去就能用,但是每个需求都不一样,关键是RecltemHeadDecoration,有问题请加群:142739277或者加我QQ:893151960

这篇关于Android仿美团选择城市的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/960974

相关文章

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

Android自定义Scrollbar的两种实现方式

《Android自定义Scrollbar的两种实现方式》本文介绍两种实现自定义滚动条的方法,分别通过ItemDecoration方案和独立View方案实现滚动条定制化,文章通过代码示例讲解的非常详细,... 目录方案一:ItemDecoration实现(推荐用于RecyclerView)实现原理完整代码实现

Android App安装列表获取方法(实践方案)

《AndroidApp安装列表获取方法(实践方案)》文章介绍了Android11及以上版本获取应用列表的方案调整,包括权限配置、白名单配置和action配置三种方式,并提供了相应的Java和Kotl... 目录前言实现方案         方案概述一、 androidManifest 三种配置方式

Android WebView无法加载H5页面的常见问题和解决方法

《AndroidWebView无法加载H5页面的常见问题和解决方法》AndroidWebView是一种视图组件,使得Android应用能够显示网页内容,它基于Chromium,具备现代浏览器的许多功... 目录1. WebView 简介2. 常见问题3. 网络权限设置4. 启用 JavaScript5. D

Android如何获取当前CPU频率和占用率

《Android如何获取当前CPU频率和占用率》最近在优化App的性能,需要获取当前CPU视频频率和占用率,所以本文小编就来和大家总结一下如何在Android中获取当前CPU频率和占用率吧... 最近在优化 App 的性能,需要获取当前 CPU视频频率和占用率,通过查询资料,大致思路如下:目前没有标准的

基于Python实现多语言朗读与单词选择测验

《基于Python实现多语言朗读与单词选择测验》在数字化教育日益普及的今天,开发一款能够支持多语言朗读和单词选择测验的程序,对于语言学习者来说无疑是一个巨大的福音,下面我们就来用Python实现一个这... 目录一、项目概述二、环境准备三、实现朗读功能四、实现单词选择测验五、创建图形用户界面六、运行程序七、

前端知识点之Javascript选择输入框confirm用法

《前端知识点之Javascript选择输入框confirm用法》:本文主要介绍JavaScript中的confirm方法的基本用法、功能特点、注意事项及常见用途,文中通过代码介绍的非常详细,对大家... 目录1. 基本用法2. 功能特点①阻塞行为:confirm 对话框会阻塞脚本的执行,直到用户作出选择。②

Android开发中gradle下载缓慢的问题级解决方法

《Android开发中gradle下载缓慢的问题级解决方法》本文介绍了解决Android开发中Gradle下载缓慢问题的几种方法,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、网络环境优化二、Gradle版本与配置优化三、其他优化措施针对android开发中Gradle下载缓慢的问

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后