1. ToggleButton项目地址
2. rebound项目地址
3. 本地下载
4. 相关参考
android中onMeasure初看,深入理解布局之一!
Android 自定义View onMeasure方法的实现
ANDROID自定义视图——onMeasure,MeasureSpec源码 流程 思路详解
Android视图绘制流程完全解析,带你一步步深入了解View(二)
昨天学习了ToggleButton,一个挺好看的开关。并且自己重新实现了一遍。感觉还是很不错的。今早又重新复习了自定义View的相关知识,包括绘制的流程及回调函数的作用。想到不知怎样才能达到一个高度,有些失落。
这个View的效果图是这样的(图来自这里):
看起来很舒服,但是是怎样实现的呢?实现起来还是需要思考的。结合项目的源码,可以把这个View分解为几部分。这也提供了一种思路,要实现一个复杂漂亮的控件,是一层一层效果叠加起来。第一层是最里边的RoundRect,它的效果是由灰色变成青色(手机里还没了解哪个制作gif软件好用)。但是这个变化的过程是如何产生的,这也是整个View最核心的部分。包括滑动过程的控制。作者使用了一个开源的库,是由Facebook团队开发维护的rebound。它的作用是模拟物理的弹簧力的效果,想象一下,一根弹簧固定立起来,把手压下去,当手放开时,弹簧的伸缩变化。即是rebound所实现的,这里涉及到两个系数,就是张力tension和摩擦力friction。rebound的使用可以参考项目的例子或者作者的写法。先是创建Spring实例:
mBaseSpringSystem = SpringSystem.create();
mSpring = mBaseSpringSystem.createSpring();
mSpring.setSpringConfig(SpringConfig.fromOrigamiTensionAndFriction(50, 7));
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mSpring.addListener(mSpringListener);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mSpring.removeListener(mSpringListener);
}
mWhileLTScale = (int) SpringUtil.mapValueFromRangeToRange(currentValue, 0, 1, 0, mWhileLTScaleWidth);
int width = getWidth();
int height = getHeight();
mWhileLeftMargin = (int) mOffBorderWidth;
mWhileTopMargin = (int) mOffBorderWidth;
mWhileRightMargin = (int) (width - mOffBorderWidth);
mWhileBottomMargin = (int) (height - mOffBorderWidth);
mWhileLTScaleWidth = width - (width-(int)mOffBorderWidth*2)/3 - width/15;
mWhileRBScaleWidth = (height-(int)mOffBorderWidth*2)/4;
mRadius = (Math.min(width, height) ) / 2;
mRadius2 = mRadius - mOffBorderWidth;
mRadius3 = mRadius2 - mOffBorderWidth;
mRectMoveFillLeft = mOffBorderWidth;
mRectMoveFillTop = mOffBorderWidth;
mRectMoveFillRight = mRectMoveFillLeft + mRadius2 * 2;
mRectMoveFillBottom = mOffBorderWidth + mRadius2 * 2;
mRectMoveMax = width - mRadius2*2 - mOffBorderWidth;
现在,我们看第二层绘画,即是一个比第一层图像小一个BorderWidth的内RountRect,并且颜色为白色。打开开关时,它会同时移动到距右边的三分之一,并且缩小。它的移动、缩放的值也是在spring的监听回调函数中改变。第三层是一个圆形的RoundRect,它从左边移动到右边,并且颜色渐变。第四层动画是一个比第三层小一个BorderWidth的圆形RoundRect,颜色为固定的色值,这里为白色,并且同样从左边移动到右边。最后,如果把这四层效果一同绘制,就是上面ToggleButton的效果了。当实现完成后,发现并不是很难,给了自己信心。下面贴几段代码:
①获取自定义的参数值
TypedArray typedArray = null;
try {
typedArray = context.obtainStyledAttributes(set, R.styleable.ToggleButton);
mOnColor = typedArray.getColor(R.styleable.ToggleButton_onColor, mOnColor);
mOffColor = typedArray.getColor(R.styleable.ToggleButton_offColor, mOffColor);
mOffBorderWidth = typedArray.getDimension(R.styleable.ToggleButton_offBorderWidth, mOffBorderWidth);
mOffBorderColor = typedArray.getColor(R.styleable.ToggleButton_offBorderColor, mOffBorderColor);
mTension = typedArray.getInteger(R.styleable.ToggleButton_tension, mTension);
mResistance = typedArray.getInteger(R.styleable.ToggleButton_resistance, mResistance);
} finally {
if (typedArray != null ) {
typedArray.recycle();
}
}
private class SpringListener extends SimpleSpringListener {
@Override
public void onSpringUpdate(Spring spring) {
super.onSpringUpdate(spring);
double currentValue = spring.getCurrentValue();
changeEffect(currentValue );
log("currentValue: " + currentValue );
}
private void changeEffect(double currentValue) {
//得到变化的背景颜色值
int or = Color.red(mOffColor);
int og = Color.green(mOffColor);
int ob = Color.blue(mOffColor);
int dr = Color.red(mOnColor);
int dg = Color.green(mOnColor);
int db = Color.blue(mOnColor);
int cr = (int) SpringUtil.mapValueFromRangeToRange(1-currentValue, 0, 1, dr, or);
int cg = (int) SpringUtil.mapValueFromRangeToRange(1-currentValue, 0, 1, dg, og);
int cb = (int) SpringUtil.mapValueFromRangeToRange(1-currentValue, 0, 1, db, ob);
cr = modify(cr);
cg = modify(cg);
cb = modify(cb);
mCurrentColor = Color.rgb(cr, cg, cb);
//改变第二层效果,即白色的内框移动缩小
mWhileLTScale = (int) SpringUtil.mapValueFromRangeToRange(currentValue, 0, 1, 0, mWhileLTScaleWidth);
mWhileRBScale = (int) SpringUtil.mapValueFromRangeToRange(currentValue, 0, 1, 0, mWhileRBScaleWidth);
log("mWhileLTScale:" + mWhileLTScale + ", mWhileRBScale:" + mWhileRBScale );
//移动的圆圈,第三层
mCurRectMove = SpringUtil.mapValueFromRangeToRange(currentValue, 0, 1, 0, mRectMoveMax);
if (Looper.myLooper() == Looper.getMainLooper() ) {
invalidate();
} else {
postInvalidate();
}
}
private int modify(int cr) {
int t = cr > 0 ? cr : 0;
t = t < 255 ? t : 255;
return t;
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
log("onDraw()");
mPaint.setColor(mCurrentColor);
mOutRect.set(0, 0, getWidth(), getHeight());
canvas.drawRoundRect(mOutRect, mRadius, mRadius, mPaint);
mPaint.setColor(Color.parseColor("#FCFCFC") ); //有质感的白色
// mOutRect.set(mWhileLeftMargin+mWhileLTScale, mWhileTopMargin+mWhileLTScale, mWhileRightMargin-mWhileRBScale, mWhileBottomMargin-mWhileRBScale);
int left = mWhileLeftMargin+mWhileLTScale;
int top = mWhileTopMargin+mWhileRBScale;
int right = mWhileRightMargin-mWhileRBScale;
int bottom = mWhileBottomMargin-mWhileRBScale;
float r2 = mRadius-mOffBorderWidth-mWhileRBScale;
log("zb", "mWhileLTScale:" + mWhileLTScale + ", mWhileRBScale:" + mWhileRBScale);
log("zb", "left: " + left + ", top: " + top + ", right: " + right + ", bottom :" + bottom );
mOutRect.set(left, top, right, bottom);
canvas.drawRoundRect(mOutRect, r2, r2, mPaint);
//画第三部分,一个移动变化的圆圈
mPaint.setColor(mCurrentColor);
mOutRect.set(mRectMoveFillLeft+(float)mCurRectMove, mRectMoveFillTop, mRectMoveFillRight+(float)mCurRectMove, mRectMoveFillBottom);
canvas.drawRoundRect(mOutRect, mRadius, mRadius, mPaint);
//画第四部分,最里面的移动的白色圆圈
mPaint.setColor(Color.WHITE);
mOutRect.set(mRectMoveFillLeft+(float)mCurRectMove+mOffBorderWidth, mRectMoveFillTop+mOffBorderWidth, mRectMoveFillRight+(float)mCurRectMove-mOffBorderWidth, mRectMoveFillBottom-mOffBorderWidth);
canvas.drawRoundRect(mOutRect, mRadius3, mRadius3, mPaint);
}