会议室预约效果 时间选择

先上一张gif,会议室预约时的时间选择,整个控件自定义view方式实现,view控件左右滑动,手指抬起做惯性滑动, 选中单个时间可以对单个选中区域进行拖, 选中多个区域 可以点击图标进行左右滑动选择

4.gif

代码分析:

在自定义控件是需要重写View的onMeasure()和 onDraw()方法 onMeasure方法主要确定当前控件的宽高值,在这里我们要注意在规定宽高值是要匹配上系统的 match_parent 和wrap_content以及控件的padding值

/**

 * //宽高测量

 *

 * @param defaultHeight

 * @param measureSpec

 * @return

 */

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    final int minimumWidth = getSuggestedMinimumWidth();

    final int minimumHeight = getSuggestedMinimumHeight();

    int width = measureWidth(minimumWidth, widthMeasureSpec);

    int height = measureHeight(minimumHeight, heightMeasureSpec);

    setMeasuredDimension(width, height);

    mMeasuredWidth = getMeasuredWidth();

    mMeasuredHeight = getMeasuredHeight();

    limitOffset = mDataArea.length * areaWidth - mMeasuredWidth;

}

这里在测量高度宽度是要注意将padding 值考虑上,测量的时候要规定控件的最大宽度要小于等于当前控件在父控件中所占的最大值,否则超出会影响我们自定义的滑动操作

/**
 * //高度测量
 *
 * @param defaultHeight
 * @param measureSpec
 * @return
 */
private int measureWidth(int defaultWidth, int measureSpec) {
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    //需要绘制的最大宽度
    int defaultWidth1 = (int) (mDataArea.length * areaWidth + getPaddingLeft() + getPaddingRight());
    switch (specMode) {
     //超出最大宽度要限定
        case MeasureSpec.AT_MOST://wrap
            defaultWidth = defaultWidth1 > getMeasuredWidth() ? getMeasuredWidth() : defaultWidth1;
            break;
        case MeasureSpec.EXACTLY://match 和精确数值
            defaultWidth = defaultWidth1 > specSize ? specSize : defaultWidth1;
            break;
        case MeasureSpec.UNSPECIFIED:
            defaultWidth = Math.max(defaultWidth, specSize);
        default:
            break;
    }
    return defaultWidth;
}

/**
 * //高度测量
 *
 * @param defaultHeight
 * @param measureSpec
 * @return
 */
private int measureHeight(int defaultHeight, int measureSpec) {

    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
        case MeasureSpec.AT_MOST:
            defaultHeight = (int) (-mTimePaint.ascent() + mTimePaint.descent()) * 6 + getPaddingTop() + getPaddingBottom();
            break;
        case MeasureSpec.EXACTLY:
            defaultHeight = specSize;
            break;
        case MeasureSpec.UNSPECIFIED:
            defaultHeight = Math.max(mTextsize * 6, specSize);
            break;
        default:
            break;
    }
    return defaultHeight;


}

接下来要绘制展示的内容,先将文字部分和分割线进行绘制,绘制好之后只能显示一屏幕的数据, 此时我们要考虑的是如何让控件滑动起来,初步的想法是记录手指按下到抬起的滑动距离 对onDraw()中的画布做左右平移操作来实现滑动效果

        canvas.save();

    //平移画布实现滑动效果 mOffset + mTempset这个是左右平移的偏移距离

    canvas.translate(mOffset + mTempset, 0);

    for (int i = 0; i < mDataArea.length; i++) {

        if (i != 0) {

            canvas.drawLine(areaWidth * i + getPaddingLeft(),

                    getPaddingTop(),

                    areaWidth * i + getPaddingLeft(),

                    mMeasuredHeight - getPaddingBottom(),

                    mLinePaint);

        }

      canvas.drawText(mDataArea[i],

      areaWidth * i + 10 + getPaddingLeft(),

      getPaddingTop() + (mMeasuredHeight - getPaddingTop() - getPaddingBottom()) / 4 + mTextsize / 2,

      mTimePaint);

    }

    canvas.restore();

我们在touch事件里面计算偏移距离但是,计算是要注意 左右滑动时偏移越界的问题

      @Override

public boolean onTouch(View v, MotionEvent event) {

    switch (event.getAction()) {

        case MotionEvent.ACTION_DOWN:

            startX = event.getX();

            break;

        case MotionEvent.ACTION_MOVE:

            //滚动整个控件

            if (isScrollArea) {

                float stopX = event.getX();

                mTempset = (stopX - startX);

                //防止内容划出控件

                float offset = -(limitOffset + getPaddingLeft() + getPaddingRight());

                //右边界判断

                if (mOffset + mTempset <= offset && mTempset <= 0) {

                    mOffset = offset;

                    mTempset = 0;

                } else if (mOffset + mTempset >= 0 && mTempset >= 0) {

                    //左边界判断

                    mOffset = 0;

                    mTempset = 0;

                }

            } else {

            }

            break;

        case MotionEvent.ACTION_UP:

        case MotionEvent.ACTION_CANCEL:

            if (isScrollArea) {

                mOffset += mTempset;

                mTempset = 0;

            break;

        default:

            break;

    }

    invalidate();

    return true;

}

要想 手指抬起有惯性滑动 需要知道抬起手指是的滑动加速度,幸运的是谷歌api已经提供了 加速度获取的方法,需要在ontouch 前边使用VelocityTracker将event时间传入

    //加速度计算

    if (null == mVelocityTracker) {

        mVelocityTracker = VelocityTracker.obtain();//手指抬起之后的速度变化

    }

    mVelocityTracker.computeCurrentVelocity(200);

    mVelocityTracker.addMovement(event);

在ACTION_UP中获取到加速度并且清除前边使用VelocityTracker传入的事件 int xVelocity = (int) mVelocityTracker.getXVelocity();

                setxVelocity(xVelocity);

                mVelocityTracker.clear();`

                然后在用valueanimation  DecelerateInterpolator  将变动数值 添加到偏移量 mOffset中 实现惯性滑动

  /**

  * 惯性滑动

  *

* @param xVelocity

*/

protected void setxVelocity(int xVelocity) {

    if (Math.abs(xVelocity) < 20) {

        return;

    }

    if (mAnimatorRunning != null && mAnimatorRunning.isRunning()) {

        return;

    }

    mAnimatorRunning = ValueAnimator.ofInt(0, xVelocity / 20).setDuration(Math.abs(xVelocity / 10));

    mAnimatorRunning.setInterpolator(new DecelerateInterpolator());

    mAnimatorRunning.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

        @Override

        public void onAnimationUpdate(ValueAnimator animation) {

            mOffset += (int) animation.getAnimatedValue();

            mTempset = 0;

            //防止内容滑出控件

            float offset = -(limitOffset + getPaddingLeft() + getPaddingRight());

            if (mOffset + mTempset <= offset) {

                mOffset = offset;

            } else if (mOffset + mTempset >= 0) {

                mOffset = 0;

            }

            invalidate();

        }

    });

    mAnimatorRunning.addListener(new AnimatorListenerAdapter() {

        @Override

        public void onAnimationEnd(Animator animation) {

            invalidate();

        }

    });

    mAnimatorRunning.start();

}

接下来要做 选中 拖拽功能 这些都要在 触摸事件里做 ,滑动跟点击要区分开 控件的上半部分做滑动下半部分做点击 所以再ACTION_DOWN中 区分 滑动区域和选中的功能区域,选中区域要用Rect 来限制rect的边界要根据手指滑动区域 计算要绘制的起点终点.完整的触摸代码:

    @Override

public boolean onTouch(View v, MotionEvent event) {

    //加速度计算

    if (null == mVelocityTracker) {

        mVelocityTracker = VelocityTracker.obtain();//手指抬起之后的速度变化

    }

    mVelocityTracker.computeCurrentVelocity(200);

    mVelocityTracker.addMovement(event);

    switch (event.getAction()) {

        case MotionEvent.ACTION_DOWN:

            startX = event.getX();

            //是否点击在控件上部  区分左右滑动 和点击选中区域

            if (event.getY() < getPaddingTop() + (mMeasuredHeight - getPaddingTop() - getPaddingBottom()) / 2) {

                isScrollArea = true;

            } else {

                isScrollArea = false;

            }

            //点击位置在画布上的绝对位置

            float currentPos = Math.abs(mOffset) + event.getX();

            //点击的位置是否是图片 要做点击图片 重新选择区域的操作

            if (currentPos > mRect.right - mBitmap.getWidth() / 2 && currentPos < mRect.right + mBitmap.getWidth() / 2) {

                isClickImg = true;

            } else {

                isClickImg = false;

            }

            //是否点击在选中区域 并且不在图片上  做单击选中

            if (currentPos > mRect.left && currentPos < mRect.right && mRect.width() == areaWidth) {

                isClickContent = true;

            } else {

                isClickContent = false;

            }

            //临时记录上次选中的起始位置,在点击图片左右选的时候 记录起点 在左选是  出事起点 应该是终点值

            if (mTempStartOffset == 0) {

                mTempStartOffset = mStartClickOffset;

            }

            break;

        case MotionEvent.ACTION_MOVE:

            //滚动整个控件

            if (isScrollArea) {

                float stopX = event.getX();

                mTempset = (stopX - startX);

                //防止内容滑出控件

                float offset = -(limitOffset + getPaddingLeft() + getPaddingRight());

                //右边界判断

                if (mOffset + mTempset <= offset && mTempset <= 0) {

                    mOffset = offset;

                    mTempset = 0;

                } else if (mOffset + mTempset >= 0 && mTempset >= 0) {

                    //左边界判断

                    mOffset = 0;

                    mTempset = 0;

                }

            } else {

                //点击图片  左右选择区域

                if (isClickImg) {

                    //右滑

                    float endClickOffset = Math.abs(mOffset) + Math.abs(event.getX());

                    if (endClickOffset > mTempStartOffset) {

                        //在开始值 右侧滑动

                        mEndClickOffset = endClickOffset;

                        mStartClickOffset = mTempStartOffset;

                        //右侧边界

                        if (endClickOffset >= mDataArea.length * areaWidth) {

                            mEndClickOffset = mDataArea.length * areaWidth;

                        }

                        isDrawLeft = false;

                    } else {

                        //开始值左侧滑动

                        //终点是初始化的起点

                        mEndClickOffset = mTempStartOffset;

                        //起点跟随手指

                        mStartClickOffset = endClickOffset;

                        if (mStartClickOffset <= getPaddingLeft()) {

                            mStartClickOffset = 0;

                        }

                        isDrawLeft = true;

                    }

                } else {

                    //点击不是图片的地方,做点击选中取消选中 或者点击拖拽单个选中区域

                    //左滑 右滑

                    if (isClickContent) {

                        mStartClickOffset = mTempStartOffset + (event.getX() - startX);

                        // 向前限制 拖拽区域起始值要大于控件0位置

                        if (mStartClickOffset <= 0) {

                            mStartClickOffset = 0;

                        }

                        //向后限制  防止滑块划出结束阈值

                        //防止内容划出控件

                        if (mStartClickOffset > mDataArea.length * areaWidth - areaWidth) {

                            mStartClickOffset = mDataArea.length * areaWidth - areaWidth;

                        }

                        //开始位置不变终点改变

                        mEndClickOffset = mStartClickOffset + areaWidth;

                    }

                }

            }

            break;

        case MotionEvent.ACTION_UP:

        case MotionEvent.ACTION_CANCEL:

            if (isScrollArea) {

            //滑动偏移计算

                mOffset += mTempset;

                mTempset = 0;

            } else {

                if (isClickImg) {

                    //右滑

                    int currentPos2 = (int) (Math.abs(mOffset) + Math.abs(event.getX()));

                    //当前位置与起点比较

                    //在起点右侧滑动

                    if (currentPos2 > mTempStartOffset) {

                        int v1 = (int) ((int) (currentPos2 / areaWidth) * areaWidth);

                        mEndClickOffset = currentPos2 % areaWidth > (areaWidth / 2) ? v1 + areaWidth : v1;

                        mStartClickOffset = mTempStartOffset;

                        if (mEndClickOffset - mStartClickOffset < areaWidth) {

                            mEndClickOffset = mStartClickOffset + areaWidth;

                        }

                        //右侧边界

                        if (mEndClickOffset > mDataArea.length * areaWidth) {

                            mEndClickOffset = mDataArea.length * areaWidth;

                        }

                    } else {

                        //在起点左侧

                        //终点是初始化的起点

                        mEndClickOffset = mTempStartOffset;

                        //起点跟随手指

                        int v1 = (int) ((int) (currentPos2 / areaWidth) * areaWidth);

                        mStartClickOffset = currentPos2 % areaWidth > (areaWidth / 2) ? v1 + areaWidth : v1;

                        //滑动小于一个偏移量

                        if (mEndClickOffset - mStartClickOffset < areaWidth) {

                            mStartClickOffset = mEndClickOffset - areaWidth;

                        }

                    }

                } else {

                    float startClickOffset = (int) ((Math.abs(mOffset) + Math.abs(event.getX())) / areaWidth) * areaWidth;

                    float endClickOffset = startClickOffset + areaWidth;

                    //点击事件 或者点击选中区域进行左滑操作

                    float v1 = event.getX() - startX;

                    if (Math.abs(v1) < 30) {

                        //点击数据啊in

                        if (startClickOffset == mStartClickOffset && endClickOffset == mEndClickOffset) {

                            mStartClickOffset = 0;

                            mEndClickOffset = 0;

                        } else {

                            mStartClickOffset = startClickOffset;

                            //防止内容滑出控件

                            if (mStartClickOffset > mDataArea.length * areaWidth - areaWidth) {

                                mStartClickOffset = mDataArea.length * areaWidth - areaWidth;

                            }

                            //越界判断

                            mEndClickOffset = mStartClickOffset + areaWidth;

                        }

                    } else if (isClickContent) {

                        mStartClickOffset = startClickOffset;

                        if (mStartClickOffset <= 0) {

                            mStartClickOffset = 0;

                        }

                        //向后限制

                        //防止内容划出控件

                        if (mStartClickOffset > mDataArea.length * areaWidth - areaWidth) {

                            mStartClickOffset = mDataArea.length * areaWidth - areaWidth;

                        }

                        //越界判断

                        mEndClickOffset = mStartClickOffset + areaWidth;

                    }

                }

            }

            //获取加速度做惯性滑动

            if (isScrollArea) {

                int xVelocity = (int) mVelocityTracker.getXVelocity();

                setxVelocity(xVelocity);

                mVelocityTracker.clear();

            }

            选中区间回调

            if (!isScrollArea && mOnSelectAreaLienter != null) {

                setOnlisenter();

            }

            startX = 0;

            isScrollArea = false;

            mTempStartOffset = 0;

            isClickContent = false;

            isDrawLeft = false;

            break;

        default:

            break;

    }

    invalidate();

    return true;

}

完整代码: github地址

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355

推荐阅读更多精彩内容