项目地址:项目地址包含之前的内容
这种效果也算是比较常用的选择方式了。
//DecorView将会调用
07-10 11:33:18.657 23998-23998/com.example.study E/CustomFrameLayout: requestLayout
07-10 11:33:18.657 23998-23998/com.example.study E/CustomFrameLayout: requestLayout
07-10 11:33:18.657 23998-23998/com.example.study E/CustomFrameLayout: CustomFrameLayout
07-10 11:33:18.657 23998-23998/com.example.study E/CustomButton: CustomButton2
07-10 11:33:18.657 23998-23998/com.example.study E/CustomFrameLayout: requestLayout
07-10 11:33:18.657 23998-23998/com.example.study E/CustomFrameLayout: requestLayout
//CustomFrameLayout的onMeasure调用
07-10 11:33:18.677 23998-23998/com.example.study E/CustomButton: onMeasure
07-10 11:33:18.677 23998-23998/com.example.study E/CustomFrameLayout: onMeasure
//CustomFrameLayout的onLayout调用CustomButton的layout,然后由CustomButton的layout调用onLayout
07-10 11:33:18.707 23998-23998/com.example.study E/CustomButton: onLayout
07-10 11:33:18.707 23998-23998/com.example.study E/CustomButton: layout
07-10 11:33:18.707 23998-23998/com.example.study E/CustomFrameLayout: onLayout
//CustomFrameLayout的onDraw调用CustomButton的onDraw
07-10 11:33:18.707 23998-23998/com.example.study E/CustomFrameLayout: onDraw
07-10 11:33:18.707 23998-23998/com.example.study E/CustomButton: onDraw
07-10 11:33:18.707 23998-23998/com.example.study E/CustomButton: onDraw
//CustomFrameLayout的onMeasure调用CustomButton的onMeasure
07-10 11:33:18.737 23998-23998/com.example.study E/CustomButton: onMeasure
07-10 11:33:18.737 23998-23998/com.example.study E/CustomFrameLayout: onMeasure
07-10 11:33:18.737 23998-23998/com.example.study E/CustomButton: onLayout
07-10 11:33:18.737 23998-23998/com.example.study E/CustomButton: layout
07-10 11:33:18.737 23998-23998/com.example.study E/CustomFrameLayout: onLayout
07-10 11:33:18.737 23998-23998/com.example.study E/CustomFrameLayout: onDraw
07-10 11:33:18.737 23998-23998/com.example.study E/CustomButton: onDraw
CustomFrameLayout继承自FrameLayout,CustomButton继承自Button。而且只有CustomButton是CustomFrameLayout子view。
ViewGroup中的onMeasure、onLayout、onDraw。会遍历自己的子view分别调用onMeasure、layout、onDraw方法。
public class CustomView extends View {
private static final String TAG = "CustomView";
/**
* 文字之间的间隔
*/
private final int TEXT_LINE_SPACE = 40;
/**
* 文字和线之间的间隔
*/
private final int LINE_SPACE = TEXT_LINE_SPACE / 2;
/**
* 默认的文字大小
*/
private final float DEFAULT_TEXT_SIZE = 32;
/**
* 标题
*/
private List<String> mTitles;
/**
* 画线的画笔
*/
private Paint mLinePaint;
/**
* 画标题的画笔
*/
private Paint mTitlePaint;
/**
* view的中心点
*/
int mCenterX;
int mCenterY;
/**
* 文本中心点初始值Y
*/
int mCenterTextDefalutY;
/**
* 文本的中心点Y
*/
int mCenterTextY;
/**
* 当前文本的下标
*/
private int currentIndex;
/**
* 文本高度
*/
private int titleHeight;
/**
* 文本和行间距的高度
*/
private int textTotalHeight;
/**
* 是否允许滑动超出范围
*/
private boolean disAllowOutTopOrBottom = true;
/**
* 当前手指所在的坐标
*/
float x;
float y;
/**
* @param context
*/
public CustomView(Context context) {
this(context, null);
}
public CustomView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mTitles = new ArrayList<>();
for (int i = 0; i < 12; i++) {
mTitles.add("第 " + i + " 个标题");
}
//划线的画笔
mLinePaint = new Paint();
mLinePaint.setColor(Color.GRAY);
//画标题的画笔
mTitlePaint = new Paint();
mTitlePaint.setTextSize(DEFAULT_TEXT_SIZE);
mTitlePaint.setTextAlign(Paint.Align.CENTER);
//文本高度
titleHeight = (int) DEFAULT_TEXT_SIZE;
//文本和行间距高度
textTotalHeight = titleHeight + TEXT_LINE_SPACE;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int viewWidth = MeasureSpec.getSize(widthMeasureSpec);
int viewHeight = MeasureSpec.getSize(heightMeasureSpec);
mCenterX = viewWidth / 2;
mCenterY = viewHeight / 2;
mCenterTextDefalutY = mCenterY;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.e(TAG, "onMeasure");
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Log.e(TAG, "onLayout");
super.onLayout(changed, left, top, right, bottom);
}
@Override
public void layout(int l, int t, int r, int b) {
Log.e(TAG, "layout");
super.layout(l, t, r, b);
}
@Override
protected void onDraw(Canvas canvas) {
Log.e(TAG, "onDraw");
super.onDraw(canvas);
//没有数据就返回
if (mTitles.size() == 0) {
return;
}
if (currentIndex < 0) {
currentIndex = 0;
}
if (currentIndex >= mTitles.size()) {
currentIndex = mTitles.size() - 1;
}
//整体的偏移量
int offset = mCenterY - mCenterTextDefalutY;
//偏移了多少个文本的高度
int i = offset / textTotalHeight;
int i1 = offset % (textTotalHeight);
//如果超过余下的,大于一个高度,就加一
if (i1 > textTotalHeight / 2) {
i++;
}
currentIndex = i;
Log.e("index", "currentIndex: " + currentIndex);
mCenterTextY = mCenterTextDefalutY;
for (String title : mTitles) {
int index = mTitles.indexOf(title);
if (index == currentIndex) {
//当前文本设置颜色
mTitlePaint.setColor(Color.BLACK);
} else {
//其它文本设置颜色
mTitlePaint.setColor(Color.GRAY);
}
canvas.drawText(title, mCenterX, mCenterTextY + (float) titleHeight / 4, mTitlePaint);
if (title.equals(mTitles.get(mTitles.size() - 1))) {
break;
}
mCenterTextY += titleHeight + TEXT_LINE_SPACE;
}
//上面的线
drawTopLine(canvas, titleHeight);
drawBottomLine(canvas, titleHeight);
}
/**
* 绘制上方的横线
*
* @param canvas
* @param titleHeight
*/
private void drawTopLine(Canvas canvas, float titleHeight) {
float startX = 0;
float startY = mCenterY - titleHeight / 2 - LINE_SPACE;
float stopX = mCenterX * 2;
canvas.drawLine(startX, startY, stopX, startY, mLinePaint);
}
/**
* 绘制下方的横线
*
* @param canvas
* @param titleHeight
*/
private void drawBottomLine(Canvas canvas, float titleHeight) {
float startX = 0;
float startY = mCenterY + titleHeight / 2 + LINE_SPACE;
float stopX = mCenterX * 2;
canvas.drawLine(startX, startY, stopX, startY, mLinePaint);
}
/**
* 获取文本区域所在的矩形区域,帮助获取绘制范围宽高
*
* @param title
* @return
*/
private Rect getTextBounds(String title) {
Rect bounds = new Rect();
mTitlePaint.getTextBounds(title, 0, title.length(), bounds);
return bounds;
}
private int getTextWidth(String title) {
Rect textBounds = getTextBounds(title);
return textBounds.right - textBounds.left;
}
private int getTextHeight(String title) {
Rect textBounds = getTextBounds(title);
return textBounds.bottom - textBounds.top;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.e(TAG, "dispatchTouchEvent" + " action: " + event.getAction());
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG, "onTouchEvent");
if (event.getAction() == MotionEvent.ACTION_DOWN) {
x = event.getX();
y = event.getY();
return true;
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
//和上次比划过的距离
float v = event.getY() - y;
y = event.getY();
if (checkIsAllowOut(v)) {
return true;
}
mCenterTextDefalutY += v;
invalidate();
return true;
}
if (event.getAction() == MotionEvent.ACTION_UP) {
//防止滑出了第一条
if (mCenterTextDefalutY - mCenterY >= 0) {
//默认的初始文本中心和view中心相同或者大于就是在第一条
mCenterTextDefalutY = mCenterY;
invalidate();
return true;
}
//防止滑出了最后一条
if (mCenterTextY - mCenterY <= 0) {
mCenterTextDefalutY = mCenterY - ((mTitles.size() - 1) * textTotalHeight);
invalidate();
return true;
}
mCenterTextDefalutY = mCenterY - (currentIndex * textTotalHeight);
invalidate();
return true;
}
return super.onTouchEvent(event);
}
/**
* 检查是否允许超出顶部和底部
*
* @param v
* @return
*/
private boolean checkIsAllowOut(float v) {
if (!disAllowOutTopOrBottom) {
return false;
}
if (v > 0) {
//已经显示的是第一条,禁止下滑
if (mCenterTextDefalutY - mCenterY >= 0) {
//默认的初始文本中心和view中心相同或者大于就是在第一条
mCenterTextDefalutY = mCenterY;
invalidate();
return true;
}
}
if (v < 0) {
//已经显示的是最后一条,禁止上滑
Log.e(TAG, "mCenterTextY: " + mCenterTextY);
Log.e(TAG, "mCenterY: " + mCenterY);
Log.e(TAG, "mCenterTextDefalutY: " + mCenterTextDefalutY);
if (mCenterTextY - mCenterY <= 0) {
mCenterTextDefalutY = mCenterY - ((mTitles.size() - 1) * textTotalHeight);
invalidate();
return true;
}
}
return false;
}
@Override
public boolean performClick() {
Log.e(TAG, "performClick");
return super.performClick();
}
}
整体思路:
其它知识点、Paint、canvas等。