Android自定义View之区块选择器

本文介绍了一个复杂的自定义Android View——区块选择器的实现过程。详细讲解了如何实现滑动刻度尺、惯性滑动、不可选区域、可选区域以及点击、移动、扩展等操作。此外,还讨论了状态变化的监听和功能完善。
摘要由CSDN通过智能技术生成

最近撸了一个自定义view,还是比较复杂的,感觉有必要分享下实现的过程。

效果

先来看下效果吧:

selectView.gif

我们来分析这个view需要实现哪些效果。
+ 首先它有一个刻度尺代表了时间段(也可以是别的什么),并且可以看到完整的刻度尺是比屏幕宽度大的,因此肯定需要可以左右滑动。
+ 其次,可以有不可选的区域(gif中灰色块)和选中的区域(gif中蓝色块),点击刻度的空白位置出现或者移动选中区域到点击位置。
+ 点击并拖动选中的区域可以移动,当移动到屏幕两边的时候,下层的刻度也能跟着移动。
+ 还可以点击并拖动选中区域右边的白色小圆改变选中区域的大小,同样到达屏幕边界时下层刻度跟着移动。
+ 当选中区域与不可选区域重叠时,选中区域变色。
+ 选中区域最小为1个刻度,当移动后手指抬起时,选中区域贴合刻度。
+ 最后还需要监听一些状态的变化,如是否重叠,选中区域改变的位置。

实现

刻度尺

别害怕有这么多的功能,我们一个一个来实现。首先是刻度尺,这个简单。由于完整的刻度尺是比屏幕宽度大的,因此我们先来了解几个概念:

未命名文件.png

这里手机屏幕的宽度是width,刻度尺的宽度的时maxWidth,我们其实只需要绘制手机屏幕可见的部分就可以了,这里的offset表示手机屏幕的左边与刻度尺左边的偏移量。

了解了这个概念,我们就来开始写吧,定义一个View,处理下构造都指向3个参数的那个,然后统一做初始化:

public class SelectView extends View {
   
    private final int DEFAULT_HEIGHT = dp2px(100);//wrap_content高度
    private Paint mPaint;

    public int dp2px(final float dpValue) {
        final float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    public SelectView(Context context) {
        this(context, null);
    }

    public SelectView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SelectView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        scroller = new OverScroller(context);
        init();
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setTextSize(textSize);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        width = widthSize;
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            height = DEFAULT_HEIGHT;//wrap_content的高
        }
        setMeasuredDimension(width, height);
    }
}

我们在onMeasure中处理了wrap_content的高度。然后在onSizeChanged中获取尺寸参数:

    private int width;//控件宽度
    private int height;//控件高度
    private int maxWidth;//最大内容宽度
    private int totalWidth;//刻度整体宽度(最后一个刻度的文字在刻度外)
    private int minOffset = 0;
    private int maxOffset;
    private int offset = minOffset;//可视区域左边界与整体内容左边界的偏移量

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        totalWidth = titles.length * space;
        maxWidth = totalWidth - space;
        maxOffset = totalWidth - width;
        if (maxOffset < 0) {
            maxOffset = 0;
        }
        areaTop = (1 - areaRate) * height;
    }

接着就开始绘制吧:

    private String[] titles = {
  "09:00", "09:30", "10:00", "10:30", "11:00",
            "11:30", "12:00", "12:30", "13:00", "13:30",
            "14:00", "14:30", "15:00", "15:30", "16:00",
            "16:30", "17:00", "17:30", "18:00"};
    private int space = dp2px(40);//刻度间隔
    private int lineWidth = dp2px(1);//刻度线的宽度
    private int textSize = dp2px(12);
    private int textMargin = dp2px(8);//文字与长刻度的margin值
    private int rate = 1;   //短刻度与长刻度数量的比例(>=1)
    private float lineRate = 0.4f;//短刻度与长刻度长度的比例(0.0~1.0)

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawLine(canvas);
    }

    private void drawLine(Canvas canvas) {
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(lineWidth);
        mPaint.setColor(Color.BLACK);
        canvas.drawLine(0, height, width, height, mPaint);
        for (int i = 0; i < titles.length; i++) {
            int position = i * space;
            if (position >= offset && position <= offset + width) {
  //判断是否可以显示在屏幕中
                int x &#
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值