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"
>
<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,

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260