绘制滚动条
区别选中与未选择文字
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 ); } } }
适配器子项视图
效果图
代码
<?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); } } );