Demand: need to have the current positioning City, hot city, next according to the city Pinyin order, sliding process letters A,B,C They'll set the top and switch to each other. The right side has the choice of fast switching letter cities
design sketch:
Idea: because the upper part needs to be delimited, and a, B and C need to be set at the top during the RecyclerView sliding process, the CoordinatorLayout is adopted. Customize RecItemHeadDecoration to set a, B and C as the top.
Step 1: Layout
<?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" > <TextView android:id="@+id/tv_title" android:layout_width="match_parent" android:layout_height="48dp" android:text="Select city" android:gravity="center" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" android:textSize="20sp" android:background="@color/white" /> <android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/tv_title" app:layout_constraintBottom_toBottomOf="parent" > <!--Because the upper part has to be crossed, RecyclerView During sliding A,B,C Top, so CoordinatorLayout--> <android.support.design.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.design.widget.AppBarLayout android:id="@+id/abl_city" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/white"> <android.support.design.widget.CollapsingToolbarLayout android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <android.support.constraint.ConstraintLayout android: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"> <TextView android: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="Current positioning" android:textColor="@color/c_757575" android:textSize="12sp" /> <TextView android: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="Guangzhou (fake)" android:layout_marginTop="8dp" android:textSize="16sp" android:textColor="@color/c_33" android:textStyle="bold" /> <View android: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" /> <TextView android: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="Hot cities" android:textSize="12sp" android:textColor="@color/c_757575" /> <!--Hot cities, compatible, there may be many--> <com.zhy.view.flowlayout.TagFlowLayout android: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.RecyclerView android: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> <!--The distance between letters is determined by height, and it is self-adaptive--> <com.cong.coordinatorlayoutdemo.widget.QuickLocationBar android: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>
Step 2: some custom controls
/** * @author : congge * @date : 2020/5/8 11:56 * @desc :This control comes from Baidu **/ 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; /** * Radius of selected circle */ private Paint circlePaint; private String selectChars = "heat"; 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); // Set parameters related to paint paint.setColor(getResources().getColor(R.color.c_33)); paint.setAntiAlias(true); } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.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) {/ / the choose variable indicates the current displayed character position. If there is no touch, it is - 1 //paint.setColor(getResources().getColor(R.color.bg_653fac)); //paint.setFakeBoldText(true); //} // Calculate the drawing position of the character 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)); } // Draw characters on canvas canvas.drawText(characters.get(i), xPos, yPos, paint); paint.reset();// Don't forget to remake Paint every time you finish painting } } } @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 : Set letters * @author : congge on 2019/12/16 17:49 **/ public void setCharacters(ArrayList<String> characters ,Boolean hasHot){ if (hasHot){ this.characters.add("heat"); } this.characters.addAll(characters); invalidate(); } /** * @desc : Set selected letters * @author : congge on 2019/12/28 14:53 **/ public void setSelectCharacter(String character){ selectChars = character; invalidate(); } public String getSelectChars() { return selectChars; }}
Key: rectemheaddecoration class
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; } } /** * Set the gap around the layout of the Item * * @param outRect Rectangle that determines the value of the gap left top right bottom * @param view RecyclerView ChildView is the layout of each Item * @param parent RecyclerView Itself * @param state RecyclerView The various states of */ @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.We will draw the head we need at the first position of each group * * 2.There are two ways to draw the head office: * The first way: make room for the head of the Item, that is outRect.top . this method corresponds to that the current Item is the first Item grouped * The second way: make room for the bottom of the Item, that is outRect.bottpm . this method corresponds to that the current Item is the last Item in the current group * * How to choose this one? * 1.If the first Item needs to have a grouped layout, choose the first method * 2.You can choose the second way * * * This method sets the spacing for the Item. There are four properties that can set four spacing, left top right bottom. In short, if the height of the Item is 50dp, we set it in this method outRect.top = 40; * In other words, a gap of 40dp is added to the top of the Item area. In fact, the height of the Item is 50 + 40 = 90dp. This 40dp is used to draw the head layout we need * * 3.Here's the first way, so how to judge whether the current Item is the first Item grouped? * * We will mark the group in the data set in Item, that is, the tags belonging to the same group are the same, and the tags of different groups are different * If the current Item is a header layout, it must be compared with the previous Item's tag, because the tag value of each group header is different. If the previous tag is different from the current one, then the current one is the header of the next group * * a b c d e f g h i * * If a D G is the group header. A's tag = 1, B's tag = 2, c's tag = 3.... and so on, the previous Item is represented by preTag, and the initial value is - 1 * * If the current Item is a and the current tag = 1, then its previous Item is empty. That is to say, preTag is different from a's tag. Then a is the header of the group * If the current Item is b and the current tag = 1, then its previous preTag is a's tag = 1. It is found that it is the same group * If the current Item is d, and the current tag is 2, then its previous preTag is c's tag = 1. It is found that the previous tag is different from the current one, then the current one is the first header Item of the new group * */ //Remember this > = 0 if(adapterPosition - 1 >= 0) { RecBean.CityListBean nextBean = getBeanByPosition(adapterPosition - 1); if (nextBean == null) { return; } preTage = nextBean.getTage(); } Log.e("WANG","Current Position is " + adapterPosition +" Current Tage yes " +tage +" previous tage yes "+ preTage); if(preTage != tage){ outRect.top = headHeight; }else { outRect.top = lineHeight; } } /** * Draw * other than the Item content *, this method is executed before the content of the Item is drawn, * Therefore, if two drawing areas overlap, the drawing area of Item will cover the area drawn by this method * Generally with getItemOffsets to draw split lines * * @param c Canvas canvas * @param parent RecyclerView * @param state RecyclerView Status of */ @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDraw(c, parent, state); } /** * Draw * other than the Item content *, this method is implemented after the content of the Item is drawn, * So what this method draws will cover the content of the Item and display it on the Item * In general, getItemOffsets are used to draw group headers * * @param c Canvas canvas * @param parent RecyclerView * @param state RecyclerView Status of */ @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; /* When the list is sliding, RecyclerView will continue to load the items and reuse the layout. We need to redraw the layout of our header items in constant changes. This method will be called when each Item disappears or appears. Here we will draw the header area So in this method, we will traverse all visible items to re judge the group header and re draw 1.Judge the head layout and draw the head layout So we still need to judge which Item is the group header here. According to getItemOffsets, we need to compare it with the previous Item tag. But there is a problem that we can't get the Item layout or other things here. We can only go through all the displayed items In this way, we need to define the previous preTag ourselves, and then assign the tag to preTag. When the next Item's tag is the same as the previous preTag, we will continue to traverse without drawing the header layout. When the Item's tag is different from preTag, we will draw the layout 2.How to hover the head layout over the top It's the best to take an example to illustrate this problem. When we want to draw the head Item right at the top of the screen, we will continue to slide her head layout and gradually disappear, that is to say, the getTop distance of the Item will continue to be smaller than the height of the head we want to draw. When this happens, Let's take the getTop of the Item and the height of the head to a maximum value, so as to ensure that when the getTop is less than the height of the head, our head layout remains at the top 3.How to replace the next head when it comes When there is a head floating on the top, the two heads in the sliding list will definitely meet the current head. What we do here is to have a gradual effect when the floating head layout meets the next floating head layout. Then we are going to achieve this effect First of all, we need to determine when the next header will slide to the top of the screen. Here, we need to determine whether the next Item of Itme currently traversed has a header or the result of the comparison between the current tag and nextTag. If it is different, the next Item will have a header layout The gradient effect needs to have a gradient value. Let's think about it, * 1.Hover at the top first * * */ 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; } /** * The data in the list includes grouping information. At the beginning of each group, there will be a s tage field mark. set the data to */ 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; }}
This class has been modified by me. Please refer to the original documents of others https://www.jianshu.com/p/c0b131b679c0
Select city adapterselectcityadapter:
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) }}
Powerful Adapter library written by others under Baidu without BaseQuickAdapter
Step 3: use
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>?= null private var letterList = arrayListOf<String>() private var context: Context? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_select_city) context = this mLinearLayoutManager = LinearLayoutManager(this) rv_city.layoutManager = mLinearLayoutManager initViewListener() onCityData() } private fun initViewListener() { //Left A,B,C positioning qlb_letter.setOnTouchLitterChangedListener { if (behavior == null){ behavior = (abl_city.layoutParams as CoordinatorLayout.LayoutParams).behavior } if (it == "heat") { if (behavior is AppBarLayout.Behavior) { val appBarBehavior = behavior as AppBarLayout.Behavior appBarBehavior.topAndBottomOffset = 0 } //rv move to A moveToPosition(0) headDecoration?.setLastName("heat") } else { //Move head AppBarLayout distance if (behavior is AppBarLayout.Behavior) { val appBarBehavior = behavior as AppBarLayout.Behavior appBarBehavior.topAndBottomOffset = -cl_select_city_head.height } // The logic ABC... Is converted to the tag corresponding to the data group var toPosition = 0 for (i in letterList.indices) { if (it == letterList[i]) { toPosition = i + 1 break } } for (i in cityAdapter.data.indices) { if (cityAdapter.data[i].tage == toPosition) { toPosition = i break } } //This movement just stops when you see it //rv_city.scrollToPosition(toPosition) moveToPosition(toPosition) } } //Scroll through the events in the list, locate the position, and then place the position at the top rv_city.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) if (move) { move = false //Currently, scrolling is finished, that is, scrollToPosition is finished 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 != "heat"){ if (behavior == null){ behavior = (abl_city.layoutParams as CoordinatorLayout.LayoutParams).behavior } if (behavior is AppBarLayout.Behavior) { val appBarBehavior = behavior as AppBarLayout.Behavior if (abs(appBarBehavior.topAndBottomOffset) < cl_select_city_head.height){ qlb_letter.setSelectCharacter("heat") headDecoration?.setLastName("heat") } } } } }) } /** * @desc : City rolling roof * @author : congge on 2019/12/4 11:45 * n The location diagram corresponds to the following three judgments * n Here * -----------------firstItem * n Here * -----------------lastItem * n Here * **/ private fun moveToPosition(n: Int) { mIndex = n val firstItem = mLinearLayoutManager.findFirstVisibleItemPosition() val lastItem = mLinearLayoutManager.findLastVisibleItemPosition() if (n <= firstItem) { //It's already on the list (just invisible), roll to it, it's on top, it's pulling down. rv_city.scrollToPosition(n) } else if (n <= lastItem) { //Already in the visible list, already visible, may not be visible to the naked eye, but it is in the visible area. At this time, scrollToPosition does not work. Use scrollBy to scroll to the top distance from n to firstItem //Subtract 36dp to subtract the height of the letter item rv_city.scrollBy(0, rv_city.getChildAt(n - firstItem).top - UtilHelper.dip2px(context, 36f)) } else { //n has not yet appeared on the list, so roll to appear first, and then roll to the top through scrollBy rv_city.scrollToPosition(n) move = true } } /** * @desc : Set up a hot city * @author : congge on 2019/12/16 16:03 **/ private fun setHotCityData(hotCityData: List<String>?) { if (hotCityData.isNullOrEmpty()) { tv_hot_city_title.visibility = View.GONE tfl_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 TextView tv.text = bean return tv } } tfl_home_city?.adapter = tagAdapter tfl_home_city?.setOnTagClickListener { view, position, parent -> true } tv_hot_city_title.visibility = View.VISIBLE tfl_home_city.visibility = View.VISIBLE } } /** * @desc : Set city list * @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!! //Hot cities setHotCityData(cityAllBean.hot_city) val cityList = arrayListOf<RecBean.CityListBean>() var tagFirst = 1 for (cityItem in cityAllBean.city) { if (cityItem.citylist.isNotEmpty()) { //Gets the alphabet set. Only if the city list is not empty can it be added letterList.add(cityItem.letter) for (cityName in cityItem.citylist) { val cityBean = RecBean.CityListBean() cityBean.name = cityName //S tage each city for A,B,C... Sliding time zone cityBean.tage = tagFirst cityList.add(cityBean) } tagFirst++ } } headDecoration = RecItemHeadDecoration(context, letterList) //List data must be set to compare with 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 }}
The json file used, placed under assets, returned by the normal interface
{
"result": "1",
"type": "1",
"message": "request succeeded",
"data": {
"hot_city": [
"Beijing",
"Tianjin",
"Shanghai",
"Quzhou",
Bozhou,
"Guangzhou",
"Shenzhen",
Luzhou,
"Taipa Island"
],
"city": [
{
"letter": "A",
"citylist": [
"Anqing",
"Ankang",
"Anyang",
"Anshun",
"Macao Peninsula",
"Aksu",
"Altay",
"ABA",
"Alashan League",
"Ali,",
Anshan
]
},
{
"letter": "B",
"citylist": [
"Baoding",
"Baoshan",
"Baotou",
"Beijing",
"North Sea",
"Bortala",
"Baoji",
"Bazhong",
"Bayannur",
"Bayinguoleng",
"Benxi",
Bijie,
Binzhou,
"Baicheng",
"Baishan",
"Silver,",
"Baise",
"Bengbu"
]
},
{
"letter": "C",
"citylist": [
"Chongzuo",
Changzhou,
"Changde",
Chengdu,
"Chengde",
"Changji",
"Changdu",
"Sunrise,",
Chuxiong,
Chizhou,
"Cangzhou",
Chuzhou,
"Chaozhou",
"Chifeng",
"Chenzhou",
"Changchun",
"Changsha",
"Changzhi"
]
},
{
"letter": "D",
"citylist": [
"Dongguan",
"Dongying",
"Dandong",
"Daxinganling",
"Datong",
"Daqing",
"Dali",
"Dalian",
"Dingxi",
"Dehong",
"Texas,",
"Deyang",
"Dazhou",
"Diqing"
]
},
{
"letter": "E",
"citylist": [
"Enshi",
"Ordos",
Ezhou
]
},
{
"letter": "F",
"citylist": [
"Foshan",
"Fuzhou",
"Fushun",
"Fuzhou",
"Fuxin",
"Fuyang",
"Fangchenggang"
]
},
{
"letter": "G",
"citylist": [
"Guyuan",
"Guangyuan",
"Guang'an",
"Guangzhou",
"Golog",
Guilin,
Gannan,
"Ganzi",
"Guigang",
"Guiyang",
"Ganzhou",
"Kaohsiung"
]
},
{
"letter": "H",
"citylist": [
"Hefei",
"Hulunbuir",
"Hohhot",
"Hotan",
Hami,
"Harbin",
"Huaihua",
Huizhou,
Hangzhou,
Hanzhong,
"Rivers and pools",
"River source",
"Haidong",
"Haibei",
"Hainan",
"Haikou",
"Haixi",
"Huaibei",
"Huainan",
Huai'an,
Huzhou,
"Red River",
"Huawang hall",
"Hualien",
"Heze",
"Huludao",
"Hengshui",
"Hengyang",
"Hezhou",
Handan,
"Hebi",
"Hegang",
"Huanggang",
"Huangnan",
"Huangshan",
"Yellowstone",
"Heihe"
]
},
{
"letter": "I",
"citylist": []
},
{
"letter": "J",
"citylist": [
"Jiujiang",
"Kowloon",
"Jiamusi",
"Ji'an",
"Jilin",
Jiayi,
"Jiaxing",
"Jiayuguan",
"Keelung",
"Jieyang",
"Jinzhong",
"Jincheng",
Jingdezhen,
"Jiangmen",
Jinan,
Jining,
Jiaozuo,
Jingzhou,
Jingmen,
"Jiuquan",
"Jinhua",
Jinchang,
"Jinpu New Area",
"Golden Gate",
"Jinzhou",
"Jixi"
]
},
{
"letter": "K",
"citylist": [
"Kizilsu",
"Karamay",
"Kashi",
"Kaifeng",
"Kunming"
]
},
{
"letter": "L",
"citylist": [
"Liangjiang New Area",
"Linxia",
"Linfen",
"Linyi",
"Lincang",
"Lishui",
"Lijiang",
"Leshan",
"Lu'an",
"Liupanshui",
"Lanzhou",
"Liangshan",
"Luliang",
Loudi,
Langfang,
"Lhasa",
"Guest",
Lin Zhi,
"Liuzhou",
"Luoyang",
"Liaocheng",
Laiwu,
"Road bank reclamation",
"Ring Road island",
"Liaoyuan",
"Liaoyang",
"Lianyungang",
"Lianjiang",
"Longnan",
"Longyan"
]
},
{
"letter": "M",
"citylist": [
Meizhou,
Mudanjiang,
"Meishan",
"Mianyang",
"Miaoli",
"Maoming",
"Ma'anshan"
]
},
{
"letter": "N",
"citylist": [
"Neijiang",
"Nanjing",
Nanchong,
"Nanning",
"Nanping",
"Nantou",
Nanchang,
"Nantong",
"Nanyang",
"Ningde",
"Ningbo",
"Nujiang River",
"Naqu"
]
},
{
"letter": "O",
"citylist": []
},
{
"letter": "P",
"citylist": [
"Pingdong",
"Pingliang",
"Pingdingshan",
"Panzhihua",
"Pu'er",
"Penghu",
"Panjin",
Putian,
"Pingxiang"
]
},
{
"letter": "Q",
"citylist": [
"Qitaihe River",
"Qingyang",
"Qujing",
"Quanzhou",
"Qingyuan",
"Qinhuangdao",
Qinzhou,
"Qingdao",
"Qiandongnan",
"Qiannan",
"Southwest Guizhou",
"Qiqihar"
]
},
{
"letter": "R",
"citylist": [
"Shigatse",
"Sunshine"
]
},
{
"letter": "S",
"citylist": [
Sanya,
"Sanming",
"Sansha",
Sanmenxia,
"Shanghai",
"Shangrao",
"Shiyan",
"Shuangyashan",
"Shangqiu",
"Shangluo",
"Siping",
"Suzhou",
"Suqian",
"Shannan",
"Shuozhou",
"Songyuan",
"Shantou",
"Shanwei",
"Shenyang",
"Shenzhen",
"Shizuishan",
"Shijiazhuang",
Shaoxing,
"Suihua",
"Suzhou",
"Suining",
"Shaoyang",
"Suizhou",
"Shaoguan"
]
},
{
"letter": "T",
"citylist": [
"Taitung",
"Taichung",
"Taipei",
"Tainan",
"Taizhou",
Turpan,
"Tangshan",
"Tower city",
"Tianshui",
"Tianjin",
"Taiyuan",
"Peach garden",
"Tai'an",
"Taizhou",
"Tonghua",
"Tongliao",
"Tieling",
"Tongren",
Tongchuan,
"Tongling"
]
},
{
"letter": "U",
"citylist": []
},
{
"letter": "V",
"citylist": []
},
{
"letter": "W",
"citylist": [
"Ulanchab",
"Black Sea",
"Urumqi",
"Wu Zhong",
"Weihai",
"Wenshan",
"Wuxi",
"Wuzhou",
"Wuwei",
"Wuhan",
"Wenzhou",
"Weinan",
Weifang,
"Wuhu"
]
},
{
"letter": "X",
"citylist": [
"Xinyang",
"Xing'an League",
"Xiamen",
"Xianning",
"Xianyang",
"Filial piety",
"Xuancheng",
"Xuzhou",
Xinzhou,
"Xinxiang",
"Xinyu",
"Xinbei",
"New Territories",
"Hsinchu",
Xiangtan,
"Xiangxi",
Xiangyang,
Xishuangbanna,
"Xixian",
Xining,
"Xi'an",
"Xuchang",
"Xingtai",
"Xilingol League",
"Hong Kong Island"
]
},
{
"letter": "Y",
"citylist": [
"Cloud forest",
"Floating clouds",
"Yichun",
"Ili",
"Yilan",
"Yibin",
Yichang,
"Yichun",
"Yueyang",
"Yan'an",
"Yanbian",
Yangzhou,
"Yulin",
"Yongzhou",
Yantai,
"Yulin",
"Yushu",
"Yuxi",
"Yiyang",
"Yancheng",
"Yingkou",
"Yuncheng",
"Yinchuan",
Yangjiang,
"Yangquan",
"Ya'an",
"Yingtan"
]
},
{
"letter": "Z",
"citylist": [
"Zhongwei",
"Zhongshan",
Zhoukou,
"Zhangjiakou",
"Zhangjiajie",
Zhangye,
"Changhua",
"New counties directly under the central government",
"Zhaotong",
"Zaozhuang",
Zhuzhou,
Zibo,
"Zhanjiang",
"Zhangzhou",
"Zhuhai",
"Qiong county directly under the central government",
Zhaoqing,
"Zigong",
Zhoushan,
"Zhoushan New Area",
"Counties directly under the central government of Henan",
Ziyang,
"Zunyi",
Zhengzhou,
"Counties directly under the central government of Hubei Province",
"Chongqing",
"Zhenjiang",
Zhumadian
]
}
]
}
}
OK, basic replication can be used in the past, but each requirement is different. The key is rectemheaddecoration. If you have any questions, please add the group: 142739277 or add me QQ:893151960