[Android]仿微信開關按鈕:)扁平化簡潔風

下載地址:https://github.com/ChenSiLiang/Android.git


最近在做界面,發現Android自帶的開關控件有點醜,好看的一個還需要比較高的SDK版本。


所以,打算寫了一個開關控件。


發現微信的開關比較簡潔大方,與Android的扁平風格相宜得章。


拋磚引玉,與君共勉。


思路:

1.使用一張圖片作爲小滑塊,然後以小滑塊的雙倍長度作爲描繪的底部(添加偏移量作爲填補縫隙),開和關的背景顏色可以自定義。如果用圖片作爲底部感覺更加損耗性能,感覺不需要這樣做。

2.大家注意到滑動的時候背景的顏色是會根據滑動的距離變化的。這個細節我覺得應該可以只描繪一層底部就可以了。但是因爲灰色和綠色的值的轉化之間與滑動距離的關係比較難找,鄙人數學基礎可差可差了,可恥大一的時候高數沒學好。

耍了個小聰明:),描繪了兩層,底層是灰色的,第二層是綠色的(最上面是小滑塊)。滑動的距離與第二層的透明度成正比關係。這樣滑動的時候就可以看見灰色和綠色的轉換了。如果有大神請告訴LZ如何用一層實現這個效果。


註釋寫得比較清楚。


有很多地方當時寫的時候沒注意容易吃大虧。


public class CsSwitch extends CheckBox {
	/**
	 * 是否打開
	 */
	private boolean mChecked = false;
	/**
	 * 是否正在滑動
	 */
	private boolean mIsMoving = false;
	/**
	 * 是否正在顯示動畫效果
	 */
	private boolean mAnimating = false;
	/**
	 * 確保開關只激活一次
	 */
	private boolean mBroadcasting = false;
	/**
	 * 底部圖片高
	 */
	private int mThumbHeight = 0;
	/**
	 * 畫布高度
	 */
	private int mMeasuredHeight = 0;
	/**
	 * 底部圖片寬
	 */
	private int mSwtichWidth = 0;
	/**
	 * 小滑塊的寬度
	 */
	private int mThumbWidth = 0;
	/**
	 * 點擊範圍
	 */
	private int mTouchSlop = 0;

	private static final int DEFAULT_HEIGHT = 46;
	/**
	 * 點擊事件過時的時間
	 */
	private long mClickTimeout = 0;
	/**
	 * 首次按下的x軸座標
	 */
	private float mFristDownX = 0;
	/**
	 * 首次按下的Y軸座標
	 */
	private float mFirstDownY = 0;
	/**
	 * 小滑塊的初始位置
	 */
	private float mInitPos = 0;
	/**
	 * 小滑塊當前x軸位置
	 */
	private float mCurXPos = 0;
	/**
	 * 小滑塊爲開時的位置
	 */
	private float mOnPos = 0;
	/**
	 * 小滑塊爲關時候的位置
	 */
	private float mOffPos = 0;
	/**
	 * 動畫的矢量速度
	 */
	private float mAnimatorVelocity = 0;
	/**
	 * 動畫位置
	 */
	private float mAnimationPosition = 0;
	/**
	 * 矢量速度
	 */
	private float mVelocity = 10;
	/**
	 * 小滑塊圖片
	 */
	private Bitmap mThumbBitmap = null;
	/**
	 * 小滑塊資源id
	 */
	private int mThumbResource = 0;
	/**
	 * 父控件
	 */
	private ViewParent mParent = null;
	/**
	 * 畫筆
	 */
	private Paint mPaint = null;
	/**
	 * 點擊事件
	 */
	private OnClickListener mOnClickListener;
	/**
	 * 狀態改變監聽器
	 */
	private OnCheckedChangeListener mOnCheckedChangeListener = null;
	/**
	 * 底部矩形
	 */
	private RectF mSwitchFRect = null;
	/**
	 * 默認綠色
	 */
	private static int sDefaultColorGreen = 0;
	/**
	 * 自定義綠色
	 */
	private int mCheckedColor = 0;
	/**
	 * 默認灰色
	 */
	private static int sDefaultColorGray = 0;
	/**
	 * 自定義灰色
	 */
	private int mUncheckedColor = 0;
	/**
	 * X軸增量
	 */
	private static final int X_OFFSET = 6;
	/**
	 * 一半X軸增量
	 */
	private static final int HALF_X_OFFSET = X_OFFSET / 4;
	/**
	 * Y軸增量
	 */
	private static final int Y_OFFSET = 4;
	/**
	 * 一半Y軸增量
	 */
	private static final int HALT_Y_OFFSET = Y_OFFSET / 2;

	/**
	 * 透明度的最大值
	 */
	private static final int MAX_ALPHA = 255;
	/**
	 * 圓角矩形的弧度
	 */
	private static final float RECF_RADIUS = 13;
	/**
	 * 開關狀態改變的延遲時間
	 */
	private static final long CHECKED_DELAY_TIME = 10;
	/**
	 * 調用點擊時間的Runnable對象
	 */
	private PerformClick mPerformClick = null;
	/**
	 * 記錄滑動是否打開
	 */
	private boolean mTurningOn = false;

	public boolean isChecked() {
		return mChecked;
	}

	public OnCheckedChangeListener getOnCheckedChangeListener() {
		return mOnCheckedChangeListener;
	}

	public void setOnCheckedChangeListener(OnCheckedChangeListener Listener) {
		this.mOnCheckedChangeListener = Listener;
	}

	/**
	 * 初始化時調用
	 */
	public void setChecked(boolean checked) {
		if (mChecked != checked) {
			mChecked = checked;
			Log.e("sysout", "setChecked:" + checked);
			mPaint.setColor(checked ? mCheckedColor : mUncheckedColor);
			if (mBroadcasting) {
				return;
			}
			// 設置調用onCheckedChanged時的狀態
			super.setChecked(checked);
			// 初始化時會給mBroadcasting附真值
			mBroadcasting = true;
			if (mOnCheckedChangeListener != null) {
				mOnCheckedChangeListener.onCheckedChanged(CsSwitch.this,
						checked);
			}
			mBroadcasting = false;
		}
	}

	public OnClickListener getOnClickListener() {
		return mOnClickListener;
	}

	public void setOnClickListener(OnClickListener onClickListener) {
		this.mOnClickListener = onClickListener;
	}

	public CsSwitch(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init(context, attrs, defStyle);
	}

	/**
	 * 初始化
	 * 
	 * @param context
	 */
	private void init(Context context, AttributeSet attrs, int defStyle) {
		Resources resources = context.getResources();
		// 顏色
		sDefaultColorGreen = resources.getColor(R.color.open_green);
		sDefaultColorGray = resources.getColor(R.color.closed_gray);

		TypedArray ta = context.obtainStyledAttributes(attrs,
				R.styleable.CsSwitch, defStyle, 0);
		mCheckedColor = ta.getColor(R.styleable.CsSwitch_checked_color,
				sDefaultColorGreen);
		mUncheckedColor = ta.getColor(R.styleable.CsSwitch_unchecked_color,
				sDefaultColorGray);
		mThumbResource = ta.getResourceId(R.styleable.CsSwitch_thumb,
				R.drawable.ic_switch_thumb);
		// 小滑塊圖片
		mThumbBitmap = BitmapFactory.decodeResource(resources, mThumbResource);
		mMeasuredHeight = mThumbBitmap.getHeight();
		// 得到小滑塊圖片的寬高
		mThumbHeight = DEFAULT_HEIGHT;
		mThumbWidth = mThumbBitmap.getWidth();
		mSwtichWidth = mThumbWidth * 2 + X_OFFSET;
		// 記錄打開位置
		mOnPos = mSwtichWidth / 2;
		// 點擊範圍
		mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
		// 點擊事件
		mClickTimeout = ViewConfiguration.getPressedStateDuration()
				+ ViewConfiguration.getTapTimeout();
		// 開關底部矩形
		mSwitchFRect = new RectF(0, 0, mSwtichWidth, mThumbHeight + Y_OFFSET);

		// 設置畫筆
		mPaint = new Paint();
		mPaint.setColor(mChecked ? mCheckedColor : mUncheckedColor);
		mPaint.setStyle(Style.FILL_AND_STROKE);
		mPaint.setDither(true);
		// 使用抗鋸齒
		mPaint.setAntiAlias(true);
		mPaint.setFilterBitmap(true);
		ta.recycle();
	}

	public CsSwitch(Context context, AttributeSet attrs) {
		this(context, attrs, android.R.attr.checkboxStyle);
	}

	public CsSwitch(Context context) {
		this(context, null);
	}

	@Override
	public void setEnabled(boolean enabled) {
		super.setEnabled(enabled);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		// 在畫布上繪畫圖形
		if (mIsMoving) {
			mPaint.setColor(mUncheckedColor);
			canvas.drawRoundRect(mSwitchFRect, RECF_RADIUS, RECF_RADIUS, mPaint);
			mPaint.setColor(mCheckedColor);
			double absPos = Math.min(Math.abs(mCurXPos), mThumbWidth);
			int alpha = (int) (absPos / mThumbWidth * MAX_ALPHA);
			mPaint.setAlpha(alpha);
			canvas.drawRoundRect(mSwitchFRect, RECF_RADIUS, RECF_RADIUS, mPaint);
			mPaint.setAlpha(MAX_ALPHA);
			canvas.drawBitmap(mThumbBitmap, mCurXPos + HALF_X_OFFSET,
					HALT_Y_OFFSET, mPaint);
		} else {
			canvas.drawRoundRect(mSwitchFRect, RECF_RADIUS, RECF_RADIUS, mPaint);
			canvas.drawBitmap(mThumbBitmap, mCurXPos + HALF_X_OFFSET,
					HALT_Y_OFFSET, mPaint);
		}
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// 測量控件的最大寬高,裁定畫布的最大寬高
		setMeasuredDimension(mSwtichWidth + X_OFFSET,
				Math.max(mMeasuredHeight + Y_OFFSET, mThumbHeight + Y_OFFSET));
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		float x = event.getX();
		float y = event.getY();
		// x,y距離
		int disX = (int) (x - mFristDownX);
		int disY = (int) (y - mFirstDownY);
		// x,y絕對值
		int absDisX = Math.abs(disX);
		int absDisY = Math.abs(disY);
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			mIsMoving = false;
			mFristDownX = x;
			mFirstDownY = y;
			mParent = getParent();
			if (mParent != null) {
				mParent.requestDisallowInterceptTouchEvent(true);
			}
			mInitPos = mChecked ? mOnPos : mOffPos;
			break;
		case MotionEvent.ACTION_MOVE:
			mIsMoving = true;
			mCurXPos = mInitPos + event.getX() - mFristDownX;
			// 超出按鈕位置時小滑塊的位置
			if (mCurXPos >= mOnPos) {
				// 開
				mCurXPos = mOnPos;
				mPaint.setColor(mCheckedColor);
			} else if (mCurXPos <= 0) {
				// 關
				mCurXPos = mOffPos;
				mPaint.setColor(mUncheckedColor);
			}
			mTurningOn = mCurXPos > ((mOnPos - mOffPos) / 3);
			break;
		case MotionEvent.ACTION_UP:
			mIsMoving = false;
			long time = event.getEventTime() - event.getDownTime();
			// 響應點擊事件
			if (absDisX < mTouchSlop && absDisY < mTouchSlop
					&& time < mClickTimeout) {
				if (mPerformClick == null) {
					mPerformClick = new PerformClick();
				}
				if (!post(mPerformClick)) {
					performClick();
				}
			} else {
				startAnimator(!mTurningOn);
			}
			break;
		default:
			break;
		}
		// 重繪
		invalidate();
		return isEnabled();
	}

	private class PerformClick implements Runnable {

		@Override
		public void run() {
			performClick();
		}
	}

	@Override
	public boolean performClick() {
		startAnimator(mChecked);
		if (mOnClickListener != null) {
			mOnClickListener.onClick(CsSwitch.this);
		}
		return true;
	}

	/**
	 * 延遲設定開關值,保證動畫流暢
	 * 
	 * @param checked
	 */
	private void setCheckedDelay(final boolean checked) {
		postDelayed(new Runnable() {

			@Override
			public void run() {
				setChecked(checked);
			}
		}, CHECKED_DELAY_TIME);
	}

	private void stopAnimator() {
		mAnimating = false;
	}

	/**
	 * 動畫+設置開關值.
	 * 
	 * @param checked
	 *            開關值
	 */
	private void startAnimator(final boolean checked) {
		// 用更平滑的動畫過渡
		mAnimating = true;
		mAnimationPosition = mCurXPos;
		mAnimatorVelocity = checked ? -mVelocity : mVelocity;
		new SwitchAnimator().run();
	}

	/**
	 * 點擊開關動畫
	 */
	private class SwitchAnimator implements Runnable {
		@Override
		public void run() {
			if (!mAnimating) {
				return;
			}
			mAnimationPosition += mAnimatorVelocity;
			if (mAnimatorVelocity > 0) {
				// 打開
				mPaint.setColor(mCheckedColor);
			} else {
				// 關閉
				mPaint.setColor(mUncheckedColor);
			}
			if (mAnimationPosition >= mOnPos) {
				stopAnimator();
				mAnimationPosition = mOnPos;
				setCheckedDelay(true);
			} else if (mAnimationPosition <= 0) {
				stopAnimator();
				setCheckedDelay(false);
				mAnimationPosition = mOffPos;
			}
			mCurXPos = mAnimationPosition;
			invalidate();
			// 循環播放動畫
			SwitchAnimatorController.sendMsg(this);
		}
	}

下面是動畫實現:

/**
 * 動畫控制循環播放
 */
public class SwitchAnimatorController {
	private static AnimatorHandler mHandler = new AnimatorHandler();

	private static final long DELAY_TIME = 10;

	public static void sendMsg(Runnable runnable) {
		Message msg = new Message();
		msg.obj = runnable;
		mHandler.sendMessageDelayed(msg, DELAY_TIME);
	}

	private static class AnimatorHandler extends Handler {
		@Override
		public void handleMessage(Message msg) {
			if (msg.obj != null) {
				((Runnable) msg.obj).run();
			}
		}
	}
}



因爲是做成庫文件的形式,可以在佈局文件裏這樣使用:

    <com.csl.myswitch.CsSwitch
        xmlns:yourname="http://schemas.android.com/apk/res-auto"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />


yourname可以自己修改成自己的名字


yourname:checked_color 
yourname:unchecked_color 
yourname:thumb

這樣可以聲明開關爲打開/關閉時背景顏色,

小滑塊的圖片等。

因爲是用小滑塊的寬度*2作爲底部的,用奇奇怪怪的圖片概不負責~











發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章