先上一张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;
}
网友评论