android 自定义控件 Switch

Switch?

 
 

Switch的使用场景非常广泛,它的功能和Checkbox几乎相同,但一般来说看起来更美观。虽然系统有自带的Switch控件,但是和很多app的自有风格并不搭配。

所以自定义实现一个Switch也是非常有用的。

**************************************************************************

 自定义实现Switch关键点?

总结一下,Switch的外观千差万别,但必备的属性即这几个:

(1)、记录一个Boolean值(只有两种状态)

(2)、外观可以直接显示当前状态

(3)、可以单击切换状态

(4)、可以触摸滑动切换状态(个人觉得就这一点儿和Checkbox有区别)

      观察源码其实可以发现系统自带的“Switch”和“Checkbox”都继承于“CompoundButton”。
  其实 CompoundButton已经有了Switch的最基本属性就是能够记录一个Boolean状态。那么我也通过继承CompoundButton来实现自定义的Switch。
(可以通过下方提供的源码看下运行效果)
 结构图如下(最终只显示红色圆圈部分):

*************************************************************************

具体实现?

  我觉得要实现这个Switch主要是以下几个问题:


(1)、怎样自定义属性以及传递属性值?(自定义控件应该都需要这一步的)

     如何实现这一步可以参照:
           http://blog.csdn.net/tianjf0514/article/details/7520988
     此处Switch是直接将定义属性和属性赋值放在同一个xml中的,通过一个Style来传递所有自定义属性值,所以在布局文件中没有新增该包的资源命名空间。
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="Switch">

        <!-- Drawable to use as the "thumb" that switches back and forth. -->
        <attr name="thumb" format="reference" />
        <!-- Drawable to use as the "track" that the switch thumb slides within. -->
        <attr name="track" format="reference" />
        <attr name="switchWidth" format="dimension" />
        <attr name="switchHeight" format="dimension" />
    </declare-styleable>

    <style name="DemoSwitch">
        <item name="track">@drawable/settings_close_bg</item>
        <item name="thumb">@drawable/settings_green_bg</item>
        <item name="switchWidth">320px</item>
        <item name="switchHeight">240px</item>
    </style>

</resources>


java代码中需要接受传递过来的参数,在构造方法中获取:
// 获取布局文件中配置的属性,生成一个TypedArray 对象
		TypedArray a = context.obtainStyledAttributes(attrs,
				R.styleable.Switch, defStyle, 0);

		// 通过获取的TypedArray 对象,得到具体每个配置的对象的值
		mThumbDrawable = a.getDrawable(R.styleable.Switch_thumb);
		mTrackDrawable = a.getDrawable(R.styleable.Switch_track);
		switchWidth = a.getDimensionPixelSize(R.styleable.Switch_switchWidth,
				120);
		switchHeight = a.getDimensionPixelSize(R.styleable.Switch_switchHeight,
				120);


(2)、怎样只显示图片中红色圆?

     其实要想只显示一个特殊图形区域,就可以使用canvas的修剪功能了。我们只需要在onDraw()方法中使用canvas的修剪功能即可。具体如何使用修剪功能,可以参考如下连接:
     http://blog.sina.com.cn/s/blog_4cd5d2bb0101g2la.html
       经过测试发现,在andoid 4.2的版本上,切割圆失败了,android4.4切割成功。

// 绘制switch中所有部分
	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);

		// Draw the switch
		canvas.save();
		mTrackDrawable.setBounds(mTrackLeft, mTrackTop, mTrackRight,
				mTrackBottom);
		final int thumbPos = (int) (mThumbPosition + 0.5f);
		int thumbLeft = thumbPos;
		int thumbRight = thumbPos + mThumbWidth;

		mThumbDrawable
				.setBounds(thumbLeft, mTrackTop, thumbRight, mTrackBottom);
		mPath.addCircle(mcircleX, mcircleY, mcircleR, Path.Direction.CW);
        //切显示部分的红色圆
		canvas.clipPath(mPath, Region.Op.REPLACE);

		//因为实现效果要显示thumb 在track中,所以需要先绘制thumb,后绘制track
		mThumbDrawable.draw(canvas);
		mTrackDrawable.draw(canvas);
		canvas.restore();
	}


     至于如何实现让滑动过来的thumb图片在track图片之上呢?只需要先绘制track图片,后绘制thumb图片就可以了

(3)、怎样让图片跟随手指移动?

首先是判定用户的行为是拖拽,此处实现方式为:

	case TOUCH_MODE_DOWN: {
				final float x = ev.getX();
				final float y = ev.getY();
				if (Math.abs(x - mTouchX) > mTouchSlop
						|| Math.abs(y - mTouchY) > mTouchSlop) {
					// 设置为拖动模式
					mTouchMode = TOUCH_MODE_DRAGGING;
					mTouchX = x;
					mTouchY = y;
					return true;
				}
				break;


处于拖拽模式时,只需要计算每次手指移动传递过来的水平方向差值,然后赋值给thumb的位置即可,
				// 计算thumb的新位置
				final float x = ev.getX();
				final float dx = x - mTouchX;
				float newPos = Math.min(mThumbRightRange,
						Math.max(mThumbPosition + dx, mThumbLeftRange));
				if (newPos != mThumbPosition) {
					mThumbPosition = newPos;
					mTouchX = x;
					// 刷新UI
					invalidate();
				}

当然,thumb的移动范围仅限于预定范围内

(4)、怎样实现自动滑动?

当触摸事件为“ACTION_UP”或“ACTION_CANCEL”时,我们就需要判定好当前状态了,如果thumb不处于当前状态的最终位置时,就需要自动滑过去。 此处我是采用了timer 和timerTask来实现。

        //自动滑动
	private void autoSlide(final int currentPosition, boolean checked) {

		// 单次的移动的改变量
		final int increment;
		final int finalPosition;
		if (checked) {
			increment = -2;
			finalPosition = mThumbLeftRange;
		} else {
			increment = 2;
			finalPosition = mThumbRightRange;
		}
		if (finalPosition == currentPosition) {
			if (mOnAutoSlideListener != null) {
				mOnAutoSlideListener.onAutoSlideDone();
			}
			return;
		}
		if (mTimer == null) {
			mTimer = new Timer();
		}
		if (autoSliding) {
			if (mTimerTask != null) {
				mTimerTask.cancel();
			}
		}
		autoSliding = true;
		mTimerTask = new TimerTask() {

			@Override
			public void run() {
				mThumbPosition = mThumbPosition + increment;
				if (mThumbPosition < mThumbLeftRange) {
					mThumbPosition = mThumbLeftRange;
					mTimerTask.cancel();
					autoSliding = false;
					if (mOnAutoSlideListener != null) {
						mOnAutoSlideListener.onAutoSlideDone();
					}
				}
				if (mThumbPosition > mThumbRightRange) {
					mThumbPosition = mThumbRightRange;
					mTimerTask.cancel();
					autoSliding = false;
					if (mOnAutoSlideListener != null) {
						mOnAutoSlideListener.onAutoSlideDone();
					}
				}
				postInvalidate();
			}
		};
		mTimer.schedule(mTimerTask, autoSlideDelay, autoSlidePeriod);
	}


理解了这几步应该就理解了这个自定义Switch。


***********************************************************************

缺陷?

(1)、因为配置使用的是像素大小,所以适配多种屏幕的效果很差,所以如果需要适配不同屏需要自己做更多的自适应处理。

(2)、当Switch放置在本身也需要监听手指动作的父控件上时, 会出现抢夺现象。这个需要在Switch中做更多的处理,主要是
    使用到这个方法 “requestDisallowInterceptTouchEvent”。
 
 
 

***********************************************************************

源码下载 ?

android自定义控件Switch







猜你喜欢

转载自blog.csdn.net/a953210725/article/details/44202873