Android开发常见的需求: 按拼音排序
Android常见需求,按照汉字首字母排序,从A~Z,右侧自定义一个字母列表,手指可以滑动选择,并和RecyclerView联动的效果。 如图: 左侧RecyclerView建议使用多布局类型,以后好做头部悬停效果。右侧要自定义View来实现,触摸滑动刷新界面了。
首先,计算View的宽高和字母所占的空间高度;
1、计算View的宽度
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int wMode = MeasureSpec.getMode(widthMeasureSpec);
int wSize = MeasureSpec.getSize(widthMeasureSpec);
int hMode = MeasureSpec.getMode(heightMeasureSpec);
int hSize = MeasureSpec.getSize(heightMeasureSpec);
int measureWidth = 0, measureHeight = 0;
Rect indexBounds = new Rect();
String index;
for (int i = 0; i < firstLetters.size(); i++) {
index = firstLetters.get(i);
mPaint.getTextBounds(index, 0, index.length(), indexBounds);
measureWidth = Math.max(indexBounds.width(), measureWidth);
}
switch (wMode) {
case MeasureSpec.EXACTLY:
measureWidth = wSize;
break;
case MeasureSpec.AT_MOST:
measureWidth = Math.min(measureWidth, wSize);
break;
case MeasureSpec.UNSPECIFIED:
break;
}
}
2、先算出整个View的高度,再除以字母个数,就是每个字母所用的平均空间高度,
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int wMode = MeasureSpec.getMode(widthMeasureSpec);
int wSize = MeasureSpec.getSize(widthMeasureSpec);
int hMode = MeasureSpec.getMode(heightMeasureSpec);
int hSize = MeasureSpec.getSize(heightMeasureSpec);
int measureWidth = 0, measureHeight = 0;
Rect indexBounds = new Rect();
String index;
for (int i = 0; i < firstLetters.size(); i++) {
index = firstLetters.get(i);
mPaint.getTextBounds(index, 0, index.length(), indexBounds);
measureWidth = Math.max(indexBounds.width(), measureWidth);
measureHeight = Math.max(indexBounds.height(), measureHeight);
}
measureHeight *= firstLetters.size();
measureHeight += (getPaddingBottom() + getPaddingTop());
switch (hMode) {
case MeasureSpec.EXACTLY:
measureHeight = hSize;
break;
case MeasureSpec.AT_MOST:
measureHeight = Math.min(measureHeight, hSize);
break;
case MeasureSpec.UNSPECIFIED:
break;
}
}
高度算出来后,可以在 onSizeChanged() 方法里面获取;
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
if (mIsHalfWidth){
mCircleRadius = mWidth / 2F;
}
if (null == firstLetters || firstLetters.isEmpty()) {
return;
}
computeGapHeight();
}
获取高度后,除去字母个数可得每个字母所占平均高度:
private void computeGapHeight() {
mCellHeight = (mHeight - getPaddingTop() - getPaddingBottom()) / firstLetters.size();
}
绘制字母和小圆圈
在onDraw() 方法里吗进行绘制。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int paddingTop = getPaddingTop();
String letter;
for (int i = 0; i < firstLetters.size(); i++) {
if (choosePos == i) {
mPaint.setColor(mSelectTextColor);
canvas.drawCircle(mWidth / 2F, paddingTop + mCellHeight / 2F + mCellHeight * i, mCircleRadius, mCirclePaint);
} else {
mPaint.setColor(mUnSelectTextColor);
}
letter = firstLetters.get(i);
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
float baseLine =(mCellHeight - fontMetrics.bottom - fontMetrics.top) / 2F;
canvas.drawText(letter, mWidth / 2F - mPaint.measureText(letter) / 2F,
paddingTop + baseLine + mCellHeight * i, mPaint);
}
}
然后写OnTouchEvent 事件
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
float y = event.getY();
int indexSelected = (int) ((y - getPaddingTop()) / mCellHeight);
if (indexSelected < 0) {
indexSelected = 0;
} else if (indexSelected >= firstLetters.size()) {
indexSelected = firstLetters.size() - 1;
}
if (choosePos != indexSelected) {
choosePos = indexSelected;
invalidate();
if (mOnSelectItemListener != null) {
mOnSelectItemListener.onItemSelect(firstLetters.get(choosePos));
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mOnReleaseListener != null) {
mOnReleaseListener.onRelease();
}
break;
}
return true;
}
最后就是和RecyclerView联动了.
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class MySlideBarView extends View {
private String[] mLetters = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"};
private final int mDefaultSelectTextColor = Color.parseColor("#FFFFFF");
private final int mDefaultUnSelectTextColor = Color.parseColor("#202020");
private final int mDefaultSelectBgColor = Color.RED;
private final float mDefaultLetterSize = 14;
private int mWidth, mHeight;
private int mCellHeight;
private OnSelectItemListener mOnSelectItemListener;
private OnReleaseListener mOnReleaseListener;
private Paint mPaint, mCirclePaint;
private Rect mTextRect;
private int mSelectTextColor;
private int mUnSelectTextColor;
private int mSelectTextBgColor;
private float mCircleRadius;
private boolean mIsHalfWidth;
private float mCorner;
private int mBgColor;
private float mLetterSize;
private List<String> firstLetters ;
private int choosePos;
public MySlideBarView(Context context) {
this(context, null);
}
public MySlideBarView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MySlideBarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MySlideBarView, defStyleAttr, 0);
mSelectTextColor = typedArray.getColor(R.styleable.MySlideBarView_slb_select_txt_color, mDefaultSelectTextColor);
mUnSelectTextColor = typedArray.getColor(R.styleable.MySlideBarView_slb_un_select_txt_color, mDefaultUnSelectTextColor);
mLetterSize = typedArray.getDimensionPixelSize(R.styleable.MySlideBarView_slb_letter_size,
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, mDefaultLetterSize, getResources().getDisplayMetrics()));
mSelectTextBgColor = typedArray.getColor(R.styleable.MySlideBarView_slb_select_text_bg_color, mDefaultSelectBgColor);
mBgColor = typedArray.getColor(R.styleable.MySlideBarView_slb_bg_color, Color.GRAY);
mCorner = typedArray.getDimension(R.styleable.MySlideBarView_slb_corner, 15f);
mCircleRadius = typedArray.getDimension(R.styleable.MySlideBarView_slb_circle_radius,0f);
mIsHalfWidth = typedArray.getBoolean(R.styleable.MySlideBarView_slb_is_circle_radius_equals_width,true);
typedArray.recycle();
firstLetters = Arrays.asList(mLetters);
mTextRect = new Rect();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(mUnSelectTextColor);
mPaint.setTextSize(mLetterSize);
mCirclePaint.setColor(mSelectTextBgColor);
float[] outerRadii = {mCorner, mCorner, mCorner, mCorner, mCorner, mCorner, mCorner, mCorner};
RoundRectShape roundRectShape = new RoundRectShape(outerRadii, null, null);
ShapeDrawable drawable = new ShapeDrawable(roundRectShape);
drawable.getPaint().setColor(mBgColor);
drawable.getPaint().setAntiAlias(true);
drawable.getPaint().setStyle(Paint.Style.FILL);
if (getBackground() == null) {
setBackground(drawable);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int wMode = MeasureSpec.getMode(widthMeasureSpec);
int wSize = MeasureSpec.getSize(widthMeasureSpec);
int hMode = MeasureSpec.getMode(heightMeasureSpec);
int hSize = MeasureSpec.getSize(heightMeasureSpec);
int measureWidth = 0, measureHeight = 0;
Rect indexBounds = new Rect();
String index;
for (int i = 0; i < firstLetters.size(); i++) {
index = firstLetters.get(i);
mPaint.getTextBounds(index, 0, index.length(), indexBounds);
measureWidth = Math.max(indexBounds.width(), measureWidth);
measureHeight = Math.max(indexBounds.height(), measureHeight);
}
measureHeight *= firstLetters.size();
measureHeight += (getPaddingBottom() + getPaddingTop());
switch (wMode) {
case MeasureSpec.EXACTLY:
measureWidth = wSize;
break;
case MeasureSpec.AT_MOST:
measureWidth = Math.min(measureWidth, wSize);
break;
case MeasureSpec.UNSPECIFIED:
break;
}
switch (hMode) {
case MeasureSpec.EXACTLY:
measureHeight = hSize;
break;
case MeasureSpec.AT_MOST:
measureHeight = Math.min(measureHeight, hSize);
break;
case MeasureSpec.UNSPECIFIED:
break;
}
setMeasuredDimension(measureWidth, measureHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int paddingTop = getPaddingTop();
String letter;
for (int i = 0; i < firstLetters.size(); i++) {
letter = firstLetters.get(i);
mPaint.getTextBounds(letter, 0, letter.length(), mTextRect);
int textWidth = mTextRect.width();
int textHeight = mTextRect.height();
float baseLine = paddingTop + (mCellHeight / 2.0f + textHeight / 2.0f + mCellHeight * i);
if (choosePos == i) {
mPaint.setColor(mSelectTextColor);
canvas.drawCircle(mWidth / 2F, paddingTop + mCellHeight / 2F + mCellHeight * i, mCircleRadius, mCirclePaint);
} else {
mPaint.setColor(mUnSelectTextColor);
}
canvas.drawText(letter, mWidth / 2F - textWidth / 2F,
baseLine , mPaint);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
if (mIsHalfWidth){
mCircleRadius = mWidth / 2F;
}
if (null == firstLetters || firstLetters.isEmpty()) {
return;
}
computeGapHeight();
}
private void computeGapHeight() {
mCellHeight = (mHeight - getPaddingTop() - getPaddingBottom()) / firstLetters.size();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
float y = event.getY();
int indexSelected = (int) ((y - getPaddingTop()) / mCellHeight);
if (indexSelected < 0) {
indexSelected = 0;
} else if (indexSelected >= firstLetters.size()) {
indexSelected = firstLetters.size() - 1;
}
if (choosePos != indexSelected) {
choosePos = indexSelected;
invalidate();
if (mOnSelectItemListener != null) {
mOnSelectItemListener.onItemSelect(firstLetters.get(choosePos));
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mOnReleaseListener != null) {
mOnReleaseListener.onRelease();
}
break;
}
return true;
}
public void setData(List<String> data) {
firstLetters = new ArrayList<>();
firstLetters.addAll(data);
requestLayout();
computeGapHeight();
}
public interface OnSelectItemListener {
void onItemSelect(String selectLetter);
}
public interface OnReleaseListener{
void onRelease();
}
public void setOnSelectItemListener(OnSelectItemListener mOnSelectItemListener) {
this.mOnSelectItemListener = mOnSelectItemListener;
}
public void setOnReleaseListener(OnReleaseListener mOnReleaseListener) {
this.mOnReleaseListener = mOnReleaseListener;
}
public void invalidateByWord(String word) {
for (int i = 0; i < firstLetters.size(); i++) {
if (firstLetters.get(i).equals(word) && choosePos != i) {
choosePos = i;
invalidate();
}
}
}
public void setSelectTextColor(int mSelectTextColor) {
this.mSelectTextColor = mSelectTextColor;
}
public void setUnSelectTextColor(int mUnSelectTextColor) {
this.mUnSelectTextColor = mUnSelectTextColor;
}
public void setSelectBgColor(int mSelectBgColor) {
this.mSelectTextBgColor = mSelectBgColor;
}
}
自定义属性:
<declare-styleable name="MySlideBarView">
<!-- 选中时,文字颜色 -->
<attr name="slb_select_txt_color" format="color|reference" />
<!-- 非选中时,文字颜色 -->
<attr name="slb_un_select_txt_color" format="color|reference" />
<!-- 选中时,滑动条背景颜色 -->
<attr name="slb_select_text_bg_color" format="color|reference" />
<!-- 字母大小 -->
<attr name="slb_letter_size" format="dimension" />
<!-- View的背景色 -->
<attr name="slb_bg_color" format="color|reference" />
<!-- 背景圆角半径 -->
<attr name="slb_corner" format="dimension" />
<!-- 小圆的半径 -->
<attr name="slb_circle_radius" format="dimension" />
<!-- 小圆的半径等于宽度一半 默认true-->
<attr name="slb_is_circle_radius_equals_width" format="boolean" />
</declare-styleable>
RecyclerView设置滚动监听事件
recycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int scrollState) {
super.onScrollStateChanged(recyclerView, scrollState);
mScrollState = scrollState;
if (scrollState == RecyclerView.SCROLL_STATE_IDLE) {
isClick = false;
}
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (mScrollState != -1) {
if (!isClick) {
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
int firstItemPosition = 0;
if (layoutManager instanceof LinearLayoutManager) {
LinearLayoutManager linearManager = (LinearLayoutManager) layoutManager;
firstItemPosition = linearManager.findFirstVisibleItemPosition();
}
mySlideBarView.invalidateByWord(sectionAuthors.get(firstItemPosition).header);
}
if (mScrollState == RecyclerView.SCROLL_STATE_IDLE) {
mScrollState = -1;
}
}
}
});
mySlideBarView.setOnSelectItemListener(new MySlideBarView.OnSelectItemListener() {
@Override
public void onItemSelect(String selectLetter) {
for (int i = 0; i < sectionAuthors.size(); i++) {
if (sectionAuthors.get(i).header.equals(selectLetter)) {
isClick = true;
recycler.smoothScrollToPosition(i);
break;
}
}
}
});
布局文件
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:overScrollMode="never" />
<com.widget.slidebar.MySlideBarView
android:id="@+id/slide_bar"
android:layout_width="20dp"
android:layout_height="395dp"
android:layout_gravity="end"
android:layout_marginTop="56dp"
app:slb_bg_color="#EAEAEA"
app:slb_circle_radius="7dp"
app:slb_corner="9dp"
app:slb_is_circle_radius_equals_width="false"
app:slb_letter_size="10sp"
app:slb_select_text_bg_color="@color/_78303B" />
</FrameLayout>
|