先上一張gif,會議室預定時的時間選擇,整個控件自定義view方式實現,view控件左右滑動,手指擡起作慣性滑動, 選中單個時間能夠對單個選中區域進行拖, 選中多個區域 能夠點擊圖標進行左右滑動選擇 java
在自定義控件是須要重寫View的onMeasure()和 onDraw()方法 onMeasure方法主要肯定當前控件的寬高值,在這裏咱們要注意在規定寬高值是要匹配上系統的 match_parent 和wrap_content以及控件的padding值git
/**
* //寬高測量
*
* @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 值考慮上,測量的時候要規定控件的最大寬度要小於等於當前控件在父控件中所佔的最大值,不然超出會影響咱們自定義的滑動操做github
/**
* //高度測量
*
* @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
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事件裏面計算偏移距離可是,計算是要注意 左右滑動時偏移越界的問題api
@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中 實現慣性滑動ide
/**
* 慣性滑動
*
* @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的邊界要根據手指滑動區域 計算要繪製的起點終點.完整的觸摸代碼:spa
@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;
}
複製代碼