Android(安卓)自定义View——自定义一个文本选择框
16lz
2021-01-26
项目地址:项目地址包含之前的内容
这种效果也算是比较常用的选择方式了。
- View的绘制流程
- 自定义View代码示例
View的绘制流程
//DecorView将会调用07-10 11:33:18.657 23998-23998/com.example.study E/CustomFrameLayout: requestLayout07-10 11:33:18.657 23998-23998/com.example.study E/CustomFrameLayout: requestLayout07-10 11:33:18.657 23998-23998/com.example.study E/CustomFrameLayout: CustomFrameLayout07-10 11:33:18.657 23998-23998/com.example.study E/CustomButton: CustomButton207-10 11:33:18.657 23998-23998/com.example.study E/CustomFrameLayout: requestLayout07-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: onMeasure07-10 11:33:18.677 23998-23998/com.example.study E/CustomFrameLayout: onMeasure//CustomFrameLayout的onLayout调用CustomButton的layout,然后由CustomButton的layout调用onLayout07-10 11:33:18.707 23998-23998/com.example.study E/CustomButton: onLayout07-10 11:33:18.707 23998-23998/com.example.study E/CustomButton: layout07-10 11:33:18.707 23998-23998/com.example.study E/CustomFrameLayout: onLayout//CustomFrameLayout的onDraw调用CustomButton的onDraw07-10 11:33:18.707 23998-23998/com.example.study E/CustomFrameLayout: onDraw07-10 11:33:18.707 23998-23998/com.example.study E/CustomButton: onDraw07-10 11:33:18.707 23998-23998/com.example.study E/CustomButton: onDraw//CustomFrameLayout的onMeasure调用CustomButton的onMeasure07-10 11:33:18.737 23998-23998/com.example.study E/CustomButton: onMeasure07-10 11:33:18.737 23998-23998/com.example.study E/CustomFrameLayout: onMeasure07-10 11:33:18.737 23998-23998/com.example.study E/CustomButton: onLayout07-10 11:33:18.737 23998-23998/com.example.study E/CustomButton: layout07-10 11:33:18.737 23998-23998/com.example.study E/CustomFrameLayout: onLayout07-10 11:33:18.737 23998-23998/com.example.study E/CustomFrameLayout: onDraw07-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方法。
自定义View代码示例
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(); }}
整体思路:
- 确定view的中心位置。mCenterY
- 根据view中心位置mCenterTextDefalutY ,确定第一个文本的中心位置。然后通过确定的文本高度和行间距,一次往下画出其它文本。
- 根据view的中心位置和文本高度确定两天线的位置。
- 事件处理,计算滑动距离,改变mCenterTextDefalutY (第一个文本中心位置)的值。
- 一些极限处理。
- 根据偏移的距离得到偏移了多少个高度,得到当前应该显示的正确位置。在UP的时候要进行检查。
其它知识点、Paint、canvas等。
更多相关文章
- Android中WebViewClient与WebChromClient两个类的区别
- android拍照显示缩略图
- 给 TextView 加上效果和事件响应
- Android原生调用mui里面的js如何实现
- 学习:Android生命周期
- Dalvik——基本Dalvik VM调用
- Android中Touch事件分发过程全解析
- android 系统重启关机 方法 非常好的一篇文章
- Android(安卓)Framework下StageFright框架流程解读