[Android]仿微信开关按钮:)扁平化简洁风

来源:互联网 发布:怎么修复u盘里的数据 编辑:程序博客网 时间:2024/05/29 04:25

下载地址: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);}@Overridepublic void setEnabled(boolean enabled) {super.setEnabled(enabled);}@Overrideprotected 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);}}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// 测量控件的最大宽高,裁定画布的最大宽高setMeasuredDimension(mSwtichWidth + X_OFFSET,Math.max(mMeasuredHeight + Y_OFFSET, mThumbHeight + Y_OFFSET));}@Overridepublic 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 {@Overridepublic void run() {performClick();}}@Overridepublic boolean performClick() {startAnimator(mChecked);if (mOnClickListener != null) {mOnClickListener.onClick(CsSwitch.this);}return true;}/** * 延迟设定开关值,保证动画流畅 *  * @param checked */private void setCheckedDelay(final boolean checked) {postDelayed(new Runnable() {@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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作为底部的,用奇奇怪怪的图片概不负责~











0 0