SidebarView
前言
最近公司项目用到像通讯那样的A-Z的选择器,网上有很多的案例自己也想实现一下,以此记录一下。
下面看代码的实现:
- 先定义属性
<declare-styleable name="SidebarView">
<attr name="sidebar_TextColor" format="color" />
<attr name="sidebar_TextSelectColor" format="color" />
<attr name="sidebar_TextSize" format="dimension" />
<attr name="sidebar_TextPadding" format="dimension" />
<attr name="sidebar_Data" format="reference" /> // string-arry 资源id 在res 创建strings
</declare-styleable>
<string-array name="az">
<item>A</item>
<item>B</item>
<item>C</item>
<item>D</item>
<item>E</item>
<item>F</item>
<item>G</item>
<item>H</item>
<item>I</item>
<item>J</item>
<item>K</item>
<item>L</item>
<item>M</item>
<item>N</item>
<item>O</item>
<item>P</item>
<item>Q</item>
<item>R</item>
<item>S</item>
<item>T</item>
<item>U</item>
<item>V</item>
<item>W</item>
<item>X</item>
<item>Y</item>
<item>Z</item>
</string-array>
- 自定义View 继承View 重写onMeasure() onDraw() onTouchEvent() 代码简单 直接上代码
package com.ui.lirary;
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.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
public class SidebarView extends View {
private final Paint paint;
private int textColor = Color.BLACK;
private int textSelectColor = Color.BLUE;
private int textHeight;
private int textWidth;
private final int textPadding;
private final String[] data;
private int selectIndex = -1;
private OnSelectCallback onSelectCallback;
private TextView toastView;
public void setOnSelectCallback(OnSelectCallback onSelectCallback) {
this.onSelectCallback = onSelectCallback;
}
public void setToastView(TextView toastView) {
this.toastView = toastView;
}
public SidebarView(Context context) {
this(context, null);
}
public SidebarView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SidebarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
float textSize = context.getResources().getDimension(R.dimen.sidebar_text_size);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SidebarView);
textColor = array.getColor(R.styleable.SidebarView_sidebar_TextColor, textColor);
textSelectColor = array.getColor(R.styleable.SidebarView_sidebar_TextSelectColor, textSelectColor);
textSize = array.getDimension(R.styleable.SidebarView_sidebar_TextSize, textSize);
textPadding = array.getDimensionPixelOffset(R.styleable.SidebarView_sidebar_TextPadding, 0);
int resourcesId = array.getResourceId(R.styleable.SidebarView_sidebar_Data, R.array.az);
array.recycle();
data = context.getResources().getStringArray(resourcesId);
paint = new Paint();
paint.setTextSize(textSize);
paint.setColor(textColor);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthModel = MeasureSpec.getMode(widthMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int heightModel = MeasureSpec.getMode(heightMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
measureText();
int w = textWidth + getPaddingStart() + getPaddingEnd();
int h = textHeight + getPaddingTop() + getPaddingBottom() + textPadding * (data.length < 1 ? 0 : data.length - 1);
if (widthModel == MeasureSpec.EXACTLY && heightModel == MeasureSpec.EXACTLY) {
setMeasuredDimension(width, height);
} else if (widthModel == MeasureSpec.EXACTLY && heightModel == MeasureSpec.AT_MOST) {
setMeasuredDimension(width, h);
} else if (widthModel == MeasureSpec.AT_MOST && heightModel == MeasureSpec.EXACTLY) {
setMeasuredDimension(w, height);
} else {
setMeasuredDimension(w, h);
}
}
private void measureText() {
for (String datum : data) {
Rect rect = new Rect();
paint.getTextBounds(datum, 0, datum.length(), rect);
int w = rect.width();
if (w > textWidth) {
textWidth = w;
}
textHeight += rect.height();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int itemHeight = textHeight / data.length;
float h = itemHeight / 2.0f + getPaddingTop();
for (int i = 0; i < data.length; i++) {
String text = data[i];
if (i == selectIndex) {
paint.setColor(textSelectColor);
} else {
paint.setColor(textColor);
}
float itemWidth = paint.measureText(text);
float x = (getWidth() - getPaddingStart() - getPaddingEnd() - itemWidth) / 2 + getPaddingStart();
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
//dy的计算公式是固定的 (bottom 绝对值 + top 绝对值)/2 减去 bottom 由于 top 是负数 所以直接减法可以了
float dy = ((fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom);
//基线 等于 item y轴中间点 + dy
float baseLine = h + dy;
canvas.drawText(text, x, baseLine, paint);
h += itemHeight + textPadding;
}
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
final int action = event.getAction();
final float y = event.getY();
final int oldChoose = selectIndex;
// 点击y坐标所占总高度的比例乘数组的长度就等于当前选中的索引.
final int c = (int) (y / getHeight() * data.length);
if (action == MotionEvent.ACTION_UP) {
if (toastView != null) {
toastView.setVisibility(GONE);
}
invalidate();
} else {
if (toastView != null) {
toastView.setVisibility(VISIBLE);
}
if (oldChoose != c) {
if (c >= 0 && c < data.length) {
selectIndex = c;
if (onSelectCallback != null) {
onSelectCallback.onCallback(data[selectIndex]);
}
invalidate();
}
}
}
return true;
}
public interface OnSelectCallback {
void onCallback(String s);
}
}