下载地址: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.CsSwitchxmlns: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作为底部的,用奇奇怪怪的图片概不负责~

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

  1. android的实现关注好友功能,android仿微信好友列表功能

    android studio实现微信好友列表功能,注意有一个jar包我没有放上来,请大家到MainActivity中的那个网址里面下载即可,然后把pinyin4j-2.5.0.jar复制粘贴到项目的a ...

  2. android加载h5页面加进度条,使用Android仿微信加载H5页面的进度条

    这篇文章主要为大家详细介绍了Android仿微信加载H5页面进度条,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 前言 Android中WebView打卡前端页面时受到网路环境,页面内容大小的影响 ...

  3. Android仿微信小视频录制功能(二)

    Android仿微信小视频录制功能(二) 接着上一篇,在完成了录制功能后,伟大的哲学家沃兹基索德曾经说过:"有录就有放.",那么紧接着就来实现播放功能,按照国际惯例,先上下效果图: ...

  4. php仿微信底部菜单,Android实现简单底部导航栏 Android仿微信滑动切换效果

    Android仿微信滑动切换最终实现效果: 大体思路: 1. 主要使用两个自定义View配合实现; 底部图标加文字为一个自定义view,底部导航栏为一个载体,根据需要来添加底部图标; 2. 底部导航栏 ...

  5. android仿微信的activity平滑水平切换动画,Android实现简单底部导航栏 Android仿微信滑动切换效果...

    Android实现简单底部导航栏 Android仿微信滑动切换效果 发布时间:2020-10-09 19:48:00 来源:脚本之家 阅读:96 作者:丶白泽 Android仿微信滑动切换最终实现效果 ...

  6. Android 仿微信小视频录制

    Android 仿微信小视频录制 WechatShortVideo和WechatShortVideo文章

  7. Android自定义弹窗模仿微信,Android仿微信右上角点击加号弹出PopupWindow

    本文实例为大家分享了Android仿微信右上角点击加号弹出展示的具体代码,供大家参考,具体内容如下 一.要弹出的布局,随便设计 android:layout_width="match_par ...

  8. android格式化时间中文版,Android 仿微信聊天时间格式化显示功能

    本文给大家分享android仿微信聊天时间格式化显示功能. 在同一年的显示规则: 如果是当天显示格式为 HH:mm 例:14:45 如果是昨天,显示格式为 昨天 HH:mm 例:昨天 13:12 如果 ...

  9. android滑动菜单图标,Android实现简单底部导航栏 Android仿微信滑动切换效果

    Android仿微信滑动切换最终实现效果: 大体思路: 1. 主要使用两个自定义View配合实现; 底部图标加文字为一个自定义view,底部导航栏为一个载体,根据需要来添加底部图标; 2. 底部导航栏 ...

最新文章

  1. 鹅厂机器人“穿着”轮滑鞋大玩前空翻,连人都不敢轻易尝试
  2. Android Sensor——传感器
  3. BlockChain:区块链/加密数字货币落地技术应用高质量相关文章
  4. vscode技巧、vscode教程、vscode使用技巧
  5. MyCat基本概念、配置文件及日志配置
  6. 146. LRU缓存机制
  7. 十法则打造安全无线局域网
  8. Maven scope中import的作用
  9. 自己动手破解斯凯Mrp游戏
  10. 产品的思路——来自腾讯张小龙的分享(全版)
  11. 安卓手机怎么root_手机怎么root
  12. c语言整形符号位_c语言无符号整型表示
  13. 【python】filetype根据内容推测文件类型
  14. matlab矩阵特征分解,用MATLAB实现矩阵分解
  15. win7关机一直卡在正在关机
  16. Vim查找、替换与删除常用命令
  17. 帝国CMS 7.2 WAP手机企业网站蓝色个性菜单整站模板
  18. C语言 - 巧解正数,负数以及零的按位取反
  19. 关于Monkey稳定性测试,这是我看到最详细的文章
  20. 富芮坤蓝牙FR801xH开发环境搭建

热门文章

  1. 苹果计算机的科学算法在哪里,iPhone计算器这么骚的吗?隐藏功能让苹果用户感觉被背叛了...
  2. 视频添加背景音乐侵权吗?
  3. 在markdown中使用LaTex数学公式
  4. EOS源码备忘-Push Transaction机制
  5. java combobox大小_java – JComboBox首选大小,选择空值但不在ComboBoxModel中
  6. 解决 Chrome首页 被 hao9.xzy15.com 劫持
  7. 计算机组成原理计编知识点,计算机组成原理考研计大纲详解
  8. Python爬取去哪网旅游景点保存到csv文件
  9. Oracle(甲骨文软件系统)公司简介
  10. [LeetCode]187. 重复的DNA序列(java实现)暴力 + 哈希