Android自定义滚动条——城市列表
效果视频
![](https://img-blog.csdnimg.cn/c6bcaf083cd84a35b14b231dcefe80d7.gif#pic_center)
绘制滚动条
区别选中与未选择文字
private void init() {
mLetterList = Arrays.asList(INDEX_STRING);
mTextNormalColor = Color.parseColor("#000000");
mTextSelectedColor = Color.parseColor("#ff0000");
}
绘制等高间距
将一个布局高度绘制成26等份,对应26个字母
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int height = getHeight();
int width = getWidth();
int singleHeight = height / mLetterList.size(); // 获取每一个字母的高度
for (int i = 0; i < mLetterList.size(); i++) {
mPaint.setColor(mTextNormalColor);
mPaint.setTypeface(Typeface.DEFAULT);
mPaint.setAntiAlias(true);
mPaint.setTextSize(32);
// 选中的状态
if (i == mChoose) {
mPaint.setColor(mTextSelectedColor);
mPaint.setFakeBoldText(true);
}
// x 坐标等于中间-字符串宽度的一半
float xPos = width / 2.0f - mPaint.measureText(mLetterList.get(i)) / 2;
float yPos = singleHeight * i + singleHeight / 2.0f;
canvas.drawText(mLetterList.get(i), xPos, yPos, mPaint);
mPaint.reset(); // 重置画笔
}
滑动事件监听
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getAction();
final float y = event.getY(); // 点击 y 坐标
// 点击 y 坐标所占总高度的比例*b数组的长度就等于点击 b 中的个数
final int c = (int) (y / getHeight() * mLetterList.size());
switch (action) {
case MotionEvent.ACTION_UP:
if (mOnTextPositionChangedListener != null) {
mOnTextPositionChangedListener.onActionUp();
}
mChoose = -1;
invalidate();
break;
default:
if (mChoose != c) {
if (c >= 0 && c < mLetterList.size()) {
if (mOnTouchLetterChangedListener != null) {
mOnTouchLetterChangedListener.onTouchingLetterChanged(mLetterList.get(c));
}
mChoose = c;
invalidate();
}
}
if (mOnTextPositionChangedListener != null && y >= 0 && y <= getHeight()) {
mOnTextPositionChangedListener.onTextPositionChanged(mLetterList.get(mChoose), (int) y);
}
break;
}
return true;
}
解析承载城市数据的XML文件
下载XML文件
从网上下载一个包含全部城市、城市代码、城市拼音首字母的文件,可以是xml格式亦或json格式
文件下载链接
解析文件
从xml文件中取出城市数据,按城市名称、城市代码、城市首字母三个信息为一组,实例化实体类对象,并保存到列表中
private void InitCityInfo(){
AllCityList = new ArrayList<>( );
String[] cityArray = getResources().getStringArray(R.array.city);
// 侧边栏的索引字母
ArrayList<String> indexList = new ArrayList<>();
String curGroup = "0";
for (int i = 0; i < cityArray.length; i += 3) {
CityInfo cityInfo = new CityInfo(cityArray[i], cityArray[i + 1], cityArray[i + 2],
false, false);
if (!cityInfo.getGroup().equals(curGroup)) {
// 如果当前城市的 group 信息与保存的不一致, 那么就是该 group 的第一个
cityInfo.setIsFirstInGroup(true);
// 同时将该 group 信息添加到索引中
indexList.add(cityInfo.getGroup());
// 它的上一个城市就是上一个 group 的最后一个
if (i > 0) {
AllCityList.get(AllCityList.size() - 1).setIsLastInGroup(true);
}
curGroup = cityInfo.getGroup();
}
AllCityList.add(cityInfo);
}
//AllCitiesScrollBar.setIndexText(indexList);
}
适配器
建立适配器类
将解析出的城市数据,添加到RecyclerView子项中
public class AllCitiesReclcyerView extends RecyclerView.Adapter<AllCitiesReclcyerView.ViewHolder> {
private List<CityInfo> mCityInfo;
public AllCitiesReclcyerView(List<CityInfo> mCityInfo){
this.mCityInfo = mCityInfo;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from( parent.getContext() ).inflate( R.layout.allcities_recyclerview_item,null,false );
return new ViewHolder( view );
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
CityInfo cityInfo = mCityInfo.get( position );
holder.CityName.setText( cityInfo.getCityName());
}
@Override
public int getItemCount() {
return mCityInfo.size();
}
public int getPositionForSection(char section) {
for (int i = 0; i < getItemCount(); i++) {
String group = mCityInfo.get(i).getGroup();
char firstChar = group.charAt(0);
if (firstChar == section) {
return i;
}
}
return -1;
}
class ViewHolder extends RecyclerView.ViewHolder{
private TextView CityName;
public ViewHolder(@NonNull View itemView) {
super( itemView );
CityName = itemView.findViewById( R.id.CityName );
}
}
}
适配器子项视图
效果图
![](https://img-blog.csdnimg.cn/c874d1fef01841a8a773f03a26471123.png)
代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/AllCities"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<com.example.ChooseCity.SideBar
android:id="@+id/AllCitiesScrollBar"
android:layout_width="20dp"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginTop="25dp"
android:background="@drawable/sidebar_style"/>
<!-- 提示文字 -->
<TextView
android:id="@+id/CityTipsText"
android:layout_width="36dp"
android:layout_height="36dp"
android:gravity="center"
android:textSize="24sp"
android:textColor="#C5C2C2"
android:background="@drawable/scrollbar_tipstext_style"
android:layout_toLeftOf="@+id/AllCitiesScrollBar"
android:visibility="invisible"/>
</RelativeLayout>
适配器绑定
View AllCities = LayoutInflater.from( City.this).inflate( R.layout.choosecity_recyclerview1,null);
AllCities_RecyclerView = AllCities.findViewById( R.id.AllCities );
AllCitiesScrollBar = AllCities.findViewById( R.id.AllCitiesScrollBar );
CityTipsText = AllCities.findViewById( R.id.CityTipsText );
LinearLayoutManager manager1 = new LinearLayoutManager( City.this );
AllCities_RecyclerView.setLayoutManager( manager1 );
AllCitiesReclcyerView allCitiesReclcyerView = new AllCitiesReclcyerView( AllCityList );
AllCities_RecyclerView.setAdapter( allCitiesReclcyerView );
//分割线
此语句作用为:为每一个子项之间添加一个分割线
AllCities_RecyclerView.addItemDecoration(new CityItemDecoration(AllCityList));
单向绑定
通过滑动滚动条,相应的改变RecyclerView子项位置;并对滑到的滚动条字母进行突出显示
AllCities_RecyclerView.addItemDecoration(new CityItemDecoration(AllCityList));
AllCitiesScrollBar.setOnTouchLetterChangedListener( new SideBar.OnTouchingLetterChangedListener() {
@Override
public void onTouchingLetterChanged(String s) {
int position = allCitiesReclcyerView.getPositionForSection(s.charAt(0));
if (position != -1) {
manager1.scrollToPositionWithOffset(position, 0);
}
}
} );
AllCitiesScrollBar.setOnTextPositionChangedListener( new SideBar.OnTextPositionChangedListener() {
@Override
public void onTextPositionChanged(String c, int y) {
CityTipsText.setVisibility(View.VISIBLE);
int sideBarY = AllCitiesScrollBar.getTop();
int topMargin = sideBarY + y - CityTipsText.getHeight() / 2;
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) CityTipsText.getLayoutParams();
lp.topMargin = topMargin;
CityTipsText.setLayoutParams(lp);
CityTipsText.setText(c);
}
@Override
public void onActionUp() {
CityTipsText.setVisibility(View.INVISIBLE);
}
} );