参考链接
https://www.jianshu.com/p/1dc41a770f64
1效果
2 目的
学习onMeasure onDraw onTouchEvent等自定义view方法的使用
3 自定义view
public class LetterSideBar extends View {
private static final String TAG = "LetterSideBar";
String mLetters[] = new String[]{
"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 Paint mTextPaint, mFocusTextPaint;
private int currentTouchIndex = -1;
public LetterSideBar(Context context) {
this(context, null);
}
public LetterSideBar(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public LetterSideBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mTextPaint = initPaint(Color.BLUE);
mFocusTextPaint = initPaint(Color.RED);
}
private Paint initPaint(int color) {
//省略自定义属性
Paint paint = new Paint();
paint.setColor(color);
paint.setAntiAlias(true);
paint.setDither(true);
paint.setTextSize(sp2px(20));
return paint;
}
private float sp2px(int sp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
}
private Rect bounds = new Rect();
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int maxLetterWidth = 0;
int height = MeasureSpec.getSize(heightMeasureSpec);
for (String letter : mLetters) {//此处有改动 因为每个字母的宽度不等 所以我觉得不能以字母A的宽度作为控件宽度 而应该取最大的字母的宽度
mTextPaint.getTextBounds(letter, 0, 1, bounds);
maxLetterWidth = Math.max(maxLetterWidth, bounds.width());
}
setMeasuredDimension(maxLetterWidth + getPaddingEnd() + getPaddingStart(), height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int itemHeight = (getMeasuredHeight() - getPaddingTop() - getPaddingBottom()) / mLetters.length;
Paint.FontMetricsInt fontMetricsInt = mTextPaint.getFontMetricsInt();
int baseLine = (itemHeight - fontMetricsInt.bottom - fontMetricsInt.top) / 2;
int startX;//每个字母开始绘制的x点
int startY;//;//每个字母开始绘制的y起点
for (int i = 0; i < mLetters.length; i++) {
mTextPaint.getTextBounds(mLetters[i], 0, 1, bounds);
int currentTextWidth = bounds.width();
startX = (getMeasuredWidth() - currentTextWidth) / 2;
startY = getPaddingTop() + i * itemHeight + baseLine;
if (currentTouchIndex == i) {
canvas.drawText(mLetters[i], startX, startY, mFocusTextPaint);
} else {
canvas.drawText(mLetters[i], startX, startY, mTextPaint);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
//计算落在那个字母
int itemHeight = (getMeasuredHeight() - getPaddingTop() - getPaddingBottom()) / mLetters.length;
int index = (int) ((event.getY() - getPaddingTop()) / itemHeight);
if (index < 0 || index > 26) {
return true;
}
if (currentTouchIndex != index) {//优化 只有index发生变化才通知更新UI
currentTouchIndex = index;
mLetterSideBarTouchListener.onTouch(mLetters[index], true);
invalidate();
}
Log.d(TAG, "onTouchEvent: " + mLetters[index]);
break;
case MotionEvent.ACTION_UP:
//TODO 优化 松开的时候 高亮字母变色
mLetterSideBarTouchListener.onTouch("", false);
break;
}
return true;//view 消费事件
}
private LetterSideBarTouchListener mLetterSideBarTouchListener;
void setLetterSideBarListener(LetterSideBarTouchListener listener) {
mLetterSideBarTouchListener = listener;
}
interface LetterSideBarTouchListener {
void onTouch(String letter, boolean isDown);
}
}
4 mainActivity
public class MainActivity extends AppCompatActivity implements LetterSideBar.LetterSideBarTouchListener {
private TextView mTv;
private LetterSideBar mLetterSideBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTv = findViewById(R.id.tv);
mLetterSideBar = findViewById(R.id.letterSideBar);
mLetterSideBar.setLetterSideBarListener(this);
}
@Override
public void onTouch(final String letter, boolean isDown) {
if (isDown) {
mTv.setText(letter);
mTv.setVisibility(View.VISIBLE);
} else {
mTv.postDelayed(new Runnable() {
@Override
public void run() {
mTv.setText(letter);
mTv.setVisibility(View.GONE);
}
}, 200);
}
}
}
5 xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="50sp"
android:visibility="gone" />
<com.example.chj.lettersidebar.LetterSideBar
android:id="@+id/letterSideBar"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:padding="15dp" />
</RelativeLayout>
6 自定义View ViewGroup套路总结
6.1自定义View的套路:
6.1.1 自定义属性,获取自定义属性(达到配置的效果)
6.1.2 onMeasure()方法用于测量计算自己的宽高,如果继承系统的Button TextView一般不需要重写
6.1.3 onDraw() 绘制自己
6.1.4 onTouch() 处理交互
6.2 自定义ViewGroup的套路:
6.2.1 自定义属性,获取自定义属性(同上)
6.2.2 onMeasure() 方法,for循环测量子View,根据子View的宽高来计算自己的宽高(多了测量子view的部分)
6.2.3 onDraw() 一般不需要,默认情况下是不会调用,如果你要绘制需要实现dispatchDraw()方法,参见
https://blog.csdn.net/u011109881/article/details/110729417
6.2.4 onLayout() 用来摆放子View,必须实现的方法,注意排除是GONE的case
6.2.5 在很多情况下不会继承自ViewGroup ,往往是继承 系统已经提供好的ViewGroup 如 ViewPager ScrollView RelativeLayout