ToggleButton自定义View学习

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);
	}
	

其中,传入的mSpringListener监听对象继承了SimpleSpringListener,关键在于它的onSpringUdate(Spring spring)方法,我们要重写它的方法。并且进行映射。其中关键的函数是:

mWhileLTScale = (int) SpringUtil.mapValueFromRangeToRange(currentValue, 0, 1, 0, mWhileLTScaleWidth);

它传入了从spring中获取的currentValue,映射的起始范围,和映射的结束范围。返回的值是物理的弹簧力学模型。仔细观察上面开关中间的滑动圆形,可以看到它的伸缩性。我们应该去它的 文档说明网站,里边有介绍如何使用,和一个非常形象的演示系统。便于理解其中的效果。其中,我想到了之前没有深入学习的属性动画中的插值器,这个应该也是做同样的事情。等下要去学习弄懂。这样最重要的事情别人就帮我们做好了。而我们现在,只要在监听回调函数中,进行draw绘画所需属性的修改,就可以完成这个自定义View。但是其中,还是有一些涉及到计算的事情,不能搞错。这也是较为重要和繁琐的工作。比如,我们要计算长和宽、可以匹配的最大范围值,外RountRect的半径,内RountRect的半径等:

		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;

还有,这段代码应该放在哪个地方。onMeasure和onLayout的作用。当然,这个在给出的参考地址中都有讲解。我们把它放在onLayout函数中,其中相关的只是要获取到getWidth()和getHeight(),其它的都只是纯计算。

现在,我们看第二层绘画,即是一个比第一层图像小一个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();
			}
		}

②Spring中的监听回调:

	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;
		}

	}

③onDraw方法中的绘制过程:

	@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);
	}




你可能感兴趣的:(Android)