近日在工作中,有需要到一个滚动的数值选择器,主要是滑动的时候的文字动画过渡效果计算。起初预想使用 ListView 实现,后发现需要 Adapter 和 ListView 的共同协助,高耦合,不易使用。而后转为全部自定义,自己处理触摸事件等。效果如下:
具体实现过程如下:
1. 先在构造函数中获得自定义属性
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.NumPickView);
for (int i = 0; i < ta.getIndexCount(); i++) {
int index = ta.getIndex(i);
switch (index) {
case R.styleable.NumPickView_totalNum:
int total = ta.getInteger(index, 24);
for (int j = 0; j < total; j++) {
if (j < 10) {
mData.add("0" + String.valueOf(j));
} else {
mData.add(String.valueOf(j));
}
}
break;
case R.styleable.NumPickView_showNum:
mShowNum = ta.getInteger(index, 6);
break;
case R.styleable.NumPickView_textColor:
mTextColor = ta.getColor(index, mTextColor);
}
}
ta.recycle();
init();
2. 初始化各个使用工具,这里的工具重要的是 ValueAnimator 计算过渡时的改变值,ArgbEvaluator 计算不同位置文字的颜色值。
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextAlign(Paint.Align.CENTER);
mRect = new Rect();
mArgvEvlauator = new ArgbEvaluator();
mValueAnimator = new ValueAnimator();
mValueAnimator.setDuration(300);
mValueAnimator.setInterpolator(new LinearInterpolator());
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
if (abs(pivot) > mUnitHeight) {
return;
}
pivot = value;
mScale = min(1, abs(pivot / mUnitHeight));
invalidate();
}
});
mValueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {}
@Override
public void onAnimationEnd(Animator animation) {
if (mStatus == Status.UP && pivot != 0) {
mCurrentPostion = clamp(mCurrentPostion + 1);
} else if (mStatus == Status.DOWN && pivot != 0) {
mCurrentPostion = clamp(mCurrentPostion - 1);
}
invalidate();
pivot = 0;
mStatus = Status.IDEL;
mScale = 0;
if (mListener != null) {
mListener.onSelected(mCurrentPostion);
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
3. 在 onMeasure 函数里面取得需要的参数,特别的是由高度计算出单位高度 mUnitHeight,使得之后的计算依靠这个为参照。
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mHeight = getMeasuredHeight();
mWidth = getMeasuredWidth();
middleHeight = mHeight / 2;
middleWidht = mWidth / 2;
mUnitHeight = (mHeight - getPaddingTop() + getPaddingBottom()) / mShowNum;
textSize = mUnitHeight / 2;
textStep = mUnitHeight / 9;
4. 核心代码一:在 onDraw 里面画出文字
//画选中字体
drawText(canvas, mData.get(mCurrentPostion), 0, 1);
//画除中间外上下字体
int num = mShowNum / 2;
for (int i = 1; i <= num; i++) {
drawText(canvas, mData.get(clamp(mCurrentPostion + i)), i, 1);
drawText(canvas, mData.get(clamp(mCurrentPostion - i)), i, -1);
}
5. 核心代码二: 在 onTouchEvent 里面处理触摸事件,这里的关键是 pivot 的取值:滑动就取滑动距离,也就是字体下一次画的时候需要偏移量,但是偏移到下个 Item 的时候就要归零即:在新的 Item,旧偏移量对于新 Item 来说是零的。
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downY = event.getY();
mValueAnimator.cancel();
break;
case MotionEvent.ACTION_MOVE:
pivot = event.getY() - downY;
if (pivot > 0) {
//向下
mStatus = Status.DOWN;
if (abs(pivot) > mUnitHeight) {
mCurrentPostion = clamp(mCurrentPostion - 1);
downY = event.getY();
pivot = 0;
} else {
invalidate();
}
} else {
//向上
mStatus = Status.UP;
if (abs(pivot) > mUnitHeight) {
mCurrentPostion = clamp(mCurrentPostion + 1);
downY = event.getY();
pivot = 0;
} else {
invalidate();
}
}
mScale = min(1, abs(pivot / mUnitHeight));
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (pivot == 0) {
//把点击事件统一为滑动事件处理,简化流程。
pivot = 0.00001f;
}
if (abs(pivot) > mUnitHeight / 2) {
//需要过渡
int rest = (int) abs(mUnitHeight / 2 - pivot);
if (mStatus == Status.UP) {
mValueAnimator.setFloatValues(pivot, -rest);
} else if (mStatus == Status.DOWN) {
//这里需要注意
mValueAnimator.setFloatValues(pivot, (int) pivot + rest + mUnitHeight / 2);
}
} else {
//过渡失败,返回原数值,所以终点都是 0
if (mStatus == Status.UP) {
mValueAnimator.setFloatValues(pivot, 0);
} else if (mStatus == Status.DOWN) {
mValueAnimator.setFloatValues(pivot, 0);
}
}
if (mValueAnimator.getValues() == null || mValueAnimator.getValues().length == 0) {
return false;
}
mValueAnimator.start();
break;
}
return true;
总结:总体来说这个实现是不难的,主要就是观察分析滑动的时候的上下字体变化规律,然后自己以此设定不同的滑动规则来应对不同的变化效果。主体代码和设计思想已经出来,读者如有自己个性化需要也可以在这基础上修改便可。 源码移步GitHub。自学小记,无伤大雅。