需求:需要有当前定位城市,热门城市,下面按照城市首拼音排序,滑动的过程中字母A,B,C..会置顶互相切换。右侧有快速切换字母城市的选择
效果图:
思路:因为上部分要划走,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"
>
<TextView
android: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.ConstraintLayout
android: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.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="当前定位"
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="广州市(假的)"
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="热门城市"
android:textSize="12sp"
android:textColor="@color/c_757575"
/>
<!--热门城市,做好兼容,可能有很多-->
<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>
<!--字母之间的距离有高度决定,自定适应-->
<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>
步骤二:一些用的的自定义控件
/**
* @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 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) {// 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.
* */
//一定要记住这个 >= 0
if(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>?= 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() {
//左侧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.Behavior
appBarBehavior.topAndBottomOffset = 0
}
//rv移动到A
moveToPosition(0)
headDecoration?.setLastName("热")
} else {
//移动头部AppBarLayout距离
if (behavior is AppBarLayout.Behavior) {
val appBarBehavior = behavior as AppBarLayout.Behavior
appBarBehavior.topAndBottomOffset = -cl_select_city_head.height
}
// 逻辑ABC...转化为对应数据分组的tag
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
}
}
//这移动只是看到就停止了
//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.Behavior
if (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 = n
val 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.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 : 设置城市列表
* @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 = 1
for (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 = tagFirst
cityList.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文件,正常接口返回的
{
"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,