项目地址:项目地址包含之前的内容

这种效果也算是比较常用的选择方式了。

  • 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();    }}

整体思路:

  1. 确定view的中心位置。mCenterY
  2. 根据view中心位置mCenterTextDefalutY ,确定第一个文本的中心位置。然后通过确定的文本高度和行间距,一次往下画出其它文本。
  3. 根据view的中心位置和文本高度确定两天线的位置。
  4. 事件处理,计算滑动距离,改变mCenterTextDefalutY (第一个文本中心位置)的值。
  5. 一些极限处理。
  6. 根据偏移的距离得到偏移了多少个高度,得到当前应该显示的正确位置。在UP的时候要进行检查。

其它知识点、Paint、canvas等。

更多相关文章

  1. Android中WebViewClient与WebChromClient两个类的区别
  2. android拍照显示缩略图
  3. 给 TextView 加上效果和事件响应
  4. Android原生调用mui里面的js如何实现
  5. 学习:Android生命周期
  6. Dalvik——基本Dalvik VM调用
  7. Android中Touch事件分发过程全解析
  8. android 系统重启关机 方法 非常好的一篇文章
  9. Android(安卓)Framework下StageFright框架流程解读

随机推荐

  1. Android(安卓)-- Toolbar跟随ListView滑
  2. Android使用NDK进行联网签名认证
  3. android主线程中Looper.loop()为什么不会
  4. AES Crypto 在 Android(安卓)P(Android(安
  5. android中SimpleCursorAdapter _id错误的
  6. android——ListView功能的实现
  7. 总结系列-Android的文件系统
  8. android 笔记handler
  9. Android(安卓)SDK下载和更新失败的解决方
  10. android 图片设置圆角