1.背景
项目有时候需要选择城市,来跳转到不同的业务逻辑中。所以有必要对各个业务线提供一个公共的组件。
2.思路
很多时候,城市列表有几块逻辑,城市列表,热门城市,当前城市,甚至历史记录。但是具体什么模块,又不清楚业务需要。所以需要抽取。
1.整个列表多为多布局存在,但是这里不是普通的多布局,是经过包装适配器的形式来做的。
2.内容的处理,如当前位置是一个特别的布局结构,所以作为一个单独的布局,剩下的就和列表类似,所以可以作为普通的布局结构,只是需要区分数据的类别而已。
3.忽略每个条目内部城市的排列形式,大家基于业务不同,item的形式不同。
4.注意每个分类的组标签,如 A B,这里使用itemDecroation来做的,需要了解相关间隔线的逻辑。
5.字母的快速索引使用自定义view,来绘制每个字母出来,同时处理点击和滑动的事件逻辑。且和列表进行交互。
3.code
3.1 城市列表
mCitylistlayout = findViewById(R.id.cityListLayout);
//添加当前位置数据和热门、普通数据列表
mCitylistlayout.addCurrLocation(cityBean, R.layout.item_city);
mCitylistlayout.addCitySpecialData("热门", hotlist);
mCitylistlayout.addCityList(allList);
mCitylistlayout.setItemClickListener(new CityListLayout.ItemClickListener() {
@Override
public void headerViewClick(CityBean cityBean) {
Toast.makeText(MainActivity2.this, "" + cityBean.toString(), Toast.LENGTH_SHORT).show();
}
@Override
public void flowItemClick(CityBean cityBean) {
Toast.makeText(MainActivity2.this, "" + cityBean.toString(), Toast.LENGTH_SHORT).show();
}
});
适配器相关逻辑
if (mAdapter == null) {
//处理普通数据的适配器
mAdapter = new BaseCityAdapter(getContext());
}
//处理头部数据的适配器,eg: 当前位置
mHeaderAdapter = new Header_FooterWrapperAdapter(mAdapter) {
@Override
protected void onBindHeaderHolder(ViewHolder holder, int headerPos, int layoutId, CityBean cityBean) {
holder.setText(R.id.location, cityBean.getcName());
holder.getView(R.id.location).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (itemClickListener!=null){
itemClickListener.headerViewClick(cityBean);
}
}
});
}
};
mHeaderAdapter.addHeaderView(layoutid, currCityBean);
//添加普通数据列表
mAdapter.setDataMap(hashMap);
//添加自定义间隔线,在此处是字母组名
addItemDecoration();
//如果没有头部适配器,说明没有添加头部类型
if (mHeaderAdapter == null) {
recyclerView.setAdapter(mAdapter);
} else {
recyclerView.setAdapter(mHeaderAdapter);
}
3.2 字母组名
private void addItemDecoration() {
//添加自定义分割线----此处是字母组名
mDecoration = new SuspensionDecoration(getContext(), hashMap);
if (mHeaderAdapter != null) {
mDecoration.setHeaderViewCount(mHeaderAdapter.getHeaderViewCount());
}
recyclerView.addItemDecoration(mDecoration);
//如果add两个,那么按照先后顺序,依次渲染。
recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL));
}
public SuspensionDecoration(Context context, HashMap<String, List<CityBean>> hashMap) {
super();
this.hashMap = hashMap;
mPaint = new Paint();
mBounds = new Rect();
mTitleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, context.getResources().getDisplayMetrics());
mTitleFontSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, context.getResources().getDisplayMetrics());
mPaint.setTextSize(mTitleFontSize);
mPaint.setAntiAlias(true);
mInflater = LayoutInflater.from(context);
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams();
int position = params.getViewLayoutPosition();
position -= getHeaderViewCount();
//pos为1,size为1,1>0? true
if (hashMap == null || hashMap.isEmpty() || position > hashMap.size() - 1 || position < 0 /**|| !hashMap.get(position).isShowSuspension()**/) {
continue;//越界
}
if (position >=0) {
if (position == 0) {
drawTitleArea(c, left, right, child, params, position);
} else {
drawTitleArea(c, left, right, child, params, position);
}
}
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int position = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
position -= getHeaderViewCount();
if (hashMap == null || hashMap.isEmpty() || position > hashMap.size() - 1) {
//pos为1,size为1,1>0? true
return;//越界
}
if (position >= 0) {
String suspensionFirstWord = getSuspensionFirstWord(position);
if (position == 0) {
outRect.set(0, mTitleHeight, 0, 0);
} else if (!suspensionFirstWord.equals(getSuspensionFirstWord(position - 1))) {
outRect.set(0, mTitleHeight, 0, 0);
}
}
}
@Override
public void onDrawOver(Canvas c, final RecyclerView parent, RecyclerView.State state) {
//最后调用 绘制在最上层
int pos = ((LinearLayoutManager) (parent.getLayoutManager())).findFirstVisibleItemPosition();
pos -= getHeaderViewCount();
if (hashMap == null || hashMap.isEmpty() || pos > hashMap.size() - 1 || pos < 0) {
return;//越界
}
String tag = getSuspensionFirstWord(pos);
View child = parent.findViewHolderForLayoutPosition(pos + getHeaderViewCount()).itemView;
boolean flag = false;//定义一个flag,Canvas是否位移过的标志
if (null != tag && !tag.equals(getSuspensionFirstWord(pos + 1))) {
//当前第一个可见的Item的tag,不等于其后一个item的tag,说明悬浮的View要切换了
if (child.getHeight() + child.getTop() < mTitleHeight) {
c.save();
flag = true;
c.translate(0, child.getHeight() + child.getTop() - mTitleHeight);
}
}
mPaint.setColor(COLOR_TITLE_BG);
c.drawRect(parent.getPaddingLeft(), parent.getPaddingTop(), parent.getRight() - parent.getPaddingRight(), parent.getPaddingTop() + mTitleHeight, mPaint);
mPaint.setColor(COLOR_TITLE_FONT);
mPaint.getTextBounds(tag, 0, tag.length(), mBounds);
c.drawText(tag, child.getPaddingLeft() + defaultPaddingLeft, parent.getPaddingTop() + mTitleHeight - (mTitleHeight / 2 - mBounds.height() / 2), mPaint);
if (flag)
c.restore();
}
代码参考:https://github.com/cts33/CityListView