本文转自:http://blog.csdn.net/swust_chenpeng/article/details/19967501

我将原文的控件进行了一些修改,去掉了原来控件的外边框,只留下重要的遮罩、背景和滑块。并且可以在布局文件中预览(预览效果不是太好,凑合看看还可以)。自己修改了下监听器,增加了一些方法。总之目前已经和官方的控件差不多了。重要的是可以自定义控件的大小了!

上面粉红色的那个就是我们自定义的控件了,下面的两个是用的官方的控件,自己改样式。基本处于没用的级别。

好了,现在我们开始讲自定义控件添加入代码中。

首先,定义attrs.xml文件

layout_width   控件的宽度,必须为确定数值的单位,不能用match_parent/wrap_content
layout_height  控件的高度,必须为确定数值的单位,不能用match_parent/wrap_content
mask          控件的遮罩,在这个遮罩范围内的图片才予以显示,可以实现圆角
track         控件的轨道背景图
thumb        控件的滑块图片

这里的5个属性必须同时指定,否则会出问题!

mask.png

 track.png

thumb.png(这里可以看见这原点做的时候,应该做到和轨道一个长度,并且正好能在轨道的中间位置)

效果要达到这样能完美显示 →   ╮(╯▽╰)╭这种事情就交给美工妹子去做吧,程序员就好好地编程吧~

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3.  
  4. <declare-styleable name="SwitchButton">
  5. <attr name="android:layout_width"/>
  6. <attr name="android:layout_height"/>
  7. <attr name="mask" format="reference"/>
  8. <attr name="android:track"/>
  9. <attr name="android:thumb"/>
  10. </declare-styleable>
  11.  
  12. </resources>

然后,我们写这个控件的类  SwitchButton.java

  1. package com.kale.switchbutton;
  2.  
  3. import android.content.Context;
  4. import android.content.res.TypedArray;
  5. import android.graphics.Bitmap;
  6. import android.graphics.Canvas;
  7. import android.graphics.Matrix;
  8. import android.graphics.Paint;
  9. import android.graphics.PorterDuff.Mode;
  10. import android.graphics.PorterDuffXfermode;
  11. import android.graphics.Rect;
  12. import android.graphics.RectF;
  13. import android.graphics.drawable.BitmapDrawable;
  14. import android.util.AttributeSet;
  15. import android.view.MotionEvent;
  16. import android.view.View;
  17. import android.view.View.OnClickListener;
  18.  
  19. /**
  20. * @author:swust_chenpeng
  21. * 本文转自:http://blog.csdn.net/swust_chenpeng/article/details/19967501
  22. * @tips : 白色圆点:switch_btn_pressed;
  23. * 左白右红的长条:switch_bottom;
  24. * 无色长条:switch_frame;
  25. * 黑色长条:switch_mask.
  26. * @date :2014-7-20
  27. */
  28. public class SwitchButton extends View implements OnClickListener {
  29. private Bitmap mSwitchTrack, mSwitchThumb, mSwitchMask;
  30. private float mCurrentX = ;// 当前的x坐标
  31. private boolean mSwitchOn = true;// 开关默认是开着的
  32. private int mMoveLength;// 最大移动距离
  33. private float mLastX = ;// 第一次按下的有效区域
  34.  
  35. private Rect mDest = null;// 绘制的目标区域大小
  36. private Rect mSrc = null;// 截取源图片的大小
  37. private int mDeltX = ;// 移动的偏移量
  38.  
  39. private int width, height;
  40.  
  41. private Paint mPaint = null;
  42. private OnCheckedChangeListener mListener = null;
  43. private boolean mFlag = false;
  44.  
  45. private float scaleWidth,scaleHeight;//图片缩放的比例
  46.  
  47. public SwitchButton(Context context) {
  48. this(context, null);
  49. }
  50.  
  51. public SwitchButton(Context context, AttributeSet attrs) {
  52. this(context, attrs, );
  53.  
  54. }
  55.  
  56. public SwitchButton(Context context, AttributeSet attrs, int defStyle) {
  57. super(context, attrs, defStyle);
  58. init(context, attrs);
  59. }
  60.  
  61. /**
  62. * 初始化相关资源
  63. */
  64. private void init(Context context, AttributeSet attrs) {
  65. TypedArray mTypedArray = context.obtainStyledAttributes(attrs,
  66. R.styleable.SwitchButton);
  67.  
  68. width = (int) mTypedArray.getDimension(
  69. R.styleable.SwitchButton_android_layout_width, );
  70. height = (int) mTypedArray.getDimension(
  71. R.styleable.SwitchButton_android_layout_height,);
  72.  
  73. // 遮罩
  74. mSwitchMask = getSrcBitmap(mTypedArray, R.styleable.SwitchButton_mask);
  75. // 背景(轨道的背景)
  76. mSwitchTrack = getSrcBitmap(mTypedArray, R.styleable.SwitchButton_android_track);
  77. // 滑块
  78. mSwitchThumb = getSrcBitmap(mTypedArray, R.styleable.SwitchButton_android_thumb);
  79.  
  80. // 计算缩放的比例
  81. scaleWidth = ((float) width / mSwitchMask.getWidth());
  82. scaleHeight = ((float) height / mSwitchMask.getHeight());
  83.  
  84. mSwitchMask = getScaleBitmap(mSwitchMask);
  85. mSwitchTrack = getScaleBitmap(mSwitchTrack);
  86. mSwitchThumb = getScaleBitmap(mSwitchThumb);
  87.  
  88. setOnClickListener(this);
  89. setOnTouchListener(new OnTouchListener() {
  90. @Override
  91. public boolean onTouch(View v, MotionEvent event) {
  92. return false;
  93. }
  94. });
  95. // 可以移动的长度
  96. mMoveLength = mSwitchTrack.getWidth() - mSwitchMask.getWidth();
  97. // 创建一个矩形对象(目标区域的大小),通过使用四个整数来初始化矩形左上角的横坐标、纵坐标以及矩形的高度、宽度
  98. mDest = new Rect(, , mSwitchMask.getWidth(),mSwitchMask.getHeight());
  99. // 创建一个矩形对象,矩形左上角的横坐标、纵坐标以及矩形的宽度、高度均为零。这是默认的构造函数
  100. mSrc = new Rect();
  101. mPaint = new Paint();
  102. mPaint.setAntiAlias(true);// 消除锯齿
  103. mPaint.setAlpha();// 设置透明度
  104. mPaint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
  105. }
  106.  
  107. // 从资源中获取图片对象
  108. private Bitmap getSrcBitmap(TypedArray mTypedArray,int index) {
  109. BitmapDrawable tempBit;
  110. tempBit = (BitmapDrawable)mTypedArray.getDrawable(index);
  111. return tempBit.getBitmap();
  112. }
  113.  
  114. // 将原图,按照布局文件中的控件长度来缩放
  115. private Bitmap getScaleBitmap(Bitmap bitmap) {
  116. Matrix matrix = new Matrix();
  117. if (isInEditMode()) {
  118. return bitmap;
  119. }
  120. matrix.postScale(scaleWidth, scaleHeight);
  121. bitmap = Bitmap.createBitmap(bitmap, , , bitmap.getWidth(),
  122. bitmap.getHeight(), matrix, true);
  123. return bitmap;
  124. }
  125.  
  126. // 该方法指定该控件在屏幕上的大小,这里要计算一下控件的实际大小,然后调用setMeasuredDimension来设置
  127. @Override
  128. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  129. setMeasuredDimension(width, height);
  130. }
  131.  
  132. @Override
  133. protected void onDraw(Canvas canvas) {
  134. super.onDraw(canvas);
  135. if (mDeltX > || mDeltX == && mSwitchOn) {
  136. if (mSrc != null) {
  137. mSrc.set(mMoveLength - mDeltX, ,
  138. mSwitchTrack.getWidth() - mDeltX, mSwitchMask.getHeight());
  139. }
  140. }
  141. else if (mDeltX < || mDeltX == && !mSwitchOn) {
  142. if (mSrc != null) {
  143. mSrc.set(-mDeltX, , mSwitchMask.getWidth() - mDeltX,
  144. mSwitchMask.getHeight());
  145. }
  146. }
  147.  
  148. // 这儿是离屏缓冲,自己感觉类似双缓冲机制吧
  149. int count = canvas.saveLayer(new RectF(mDest), null,
  150. Canvas.MATRIX_SAVE_FLAG
  151. | Canvas.CLIP_SAVE_FLAG
  152. | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG
  153. | Canvas.FULL_COLOR_LAYER_SAVE_FLAG
  154. | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
  155.  
  156. canvas.drawBitmap(mSwitchTrack, mSrc, mDest, null);
  157. canvas.drawBitmap(mSwitchTrack, mSrc, mDest, null);
  158. canvas.drawBitmap(mSwitchMask, , , mPaint);
  159. canvas.drawBitmap(mSwitchThumb, mSrc, mDest, null);
  160. //这个判断,用来让这个控件可以在布局文件中预览
  161. if (isInEditMode()) {
  162. return ;
  163. }
  164. canvas.restoreToCount(count);
  165.  
  166. }
  167.  
  168. @Override
  169. public boolean onTouchEvent(MotionEvent event) {
  170. switch (event.getAction()) {
  171. case MotionEvent.ACTION_DOWN:
  172. mLastX = event.getX();
  173. break;
  174. case MotionEvent.ACTION_MOVE:
  175. mCurrentX = event.getX();
  176. mDeltX = (int) (mCurrentX - mLastX);
  177. // 如果开关开着向左滑动,或者开关关着向右滑动(这时候是不需要处理的)
  178. if ((mSwitchOn && mDeltX < ) || (!mSwitchOn && mDeltX > )) {
  179. mFlag = true;
  180. mDeltX = ;
  181. }
  182.  
  183. if (Math.abs(mDeltX) > mMoveLength) {
  184. mDeltX = mDeltX > ? mMoveLength : -mMoveLength;
  185. }
  186. invalidate();
  187. return true;
  188. case MotionEvent.ACTION_UP:
  189. if (Math.abs(mDeltX) > && Math.abs(mDeltX) < mMoveLength / ) {
  190. mDeltX = ;
  191. invalidate();
  192. return true;
  193. } else if (Math.abs(mDeltX) > mMoveLength /
  194. && Math.abs(mDeltX) <= mMoveLength) {
  195. mDeltX = mDeltX > ? mMoveLength : -mMoveLength;
  196. mSwitchOn = !mSwitchOn;
  197. if (mListener != null) {
  198. mListener.onCheckedChanged(this, mSwitchOn);
  199. }
  200. invalidate();
  201. mDeltX = ;
  202. return true;
  203. } else if (mDeltX == && mFlag) {
  204. // 这时候得到的是不需要进行处理的,因为已经move过了
  205. mDeltX = ;
  206. mFlag = false;
  207. return true;
  208. }
  209. return super.onTouchEvent(event);
  210. default:
  211. break;
  212. }
  213. invalidate();
  214. return super.onTouchEvent(event);
  215. }
  216.  
  217. public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
  218. mListener = listener;
  219. }
  220.  
  221. public interface OnCheckedChangeListener {
  222. public void onCheckedChanged(SwitchButton button, boolean isChecked);
  223. }
  224.  
  225. @Override
  226. public void onClick(View v) {
  227. mDeltX = mSwitchOn ? mMoveLength : -mMoveLength;
  228. mSwitchOn = !mSwitchOn;
  229. if (mListener != null) {
  230. mListener.onCheckedChanged(this, mSwitchOn);
  231. }
  232. invalidate();// 重绘
  233. mDeltX = ;
  234. }
  235.  
  236. public Boolean isChecked() {
  237. return mSwitchOn;
  238. }
  239.  
  240. public void setChecked(Boolean checked) {
  241. mSwitchOn = checked;
  242. invalidate();
  243. }
  244.  
  245. }

最后,在布局文件中使用它。(记得写上命名空间)

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. xmlns:pv="http://schemas.android.com/apk/res/com.kale.switchbutton"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. android:orientation="vertical" >
  7.  
  8. <com.kale.switchbutton.SwitchButton
  9. android:id="@+id/switch_id"
  10. android:layout_width="80dp"
  11. android:layout_height="34dp"
  12. android:layout_marginTop="20dp"
  13. android:layout_gravity="center_horizontal"
  14. android:thumb="@drawable/switch_thumb"
  15. android:track="@drawable/switch_bottom"
  16. pv:mask="@drawable/switch_mask" />
  17.  
  18. <Switch
  19. android:layout_width="wrap_content"
  20. android:layout_height="wrap_content"
  21. android:layout_marginTop="20dp"
  22. android:layout_gravity="center_horizontal"
  23. android:switchMinWidth="40dp"
  24. android:switchPadding="30dp"
  25. android:padding="5dp"
  26. android:text="自定义风格"
  27. android:textAppearance="?android:attr/textAppearanceMedium"
  28. android:textOff=" "
  29. android:textOn=" "
  30. android:thumb="@drawable/tt_button"
  31. android:track="@drawable/tt_path" />
  32.  
  33. <Switch
  34. android:id="@+id/switch1"
  35. android:layout_marginTop="20dp"
  36. android:layout_gravity="center_horizontal"
  37. android:layout_width="wrap_content"
  38. android:layout_height="wrap_content"/>
  39.  
  40. </LinearLayout>

源码下载:http://download.csdn.net/detail/shark0017/7659623

Android 自定义的开关按钮——SwitchButton的更多相关文章

  1. 自定义的开关按钮——SwitchButton

    本文转自:http://blog.csdn.net/swust_chenpeng/article/details/19967501 我将原文的控件进行了一些修改,去掉了原来控件的外边框,只留下重要的遮 ...

  2. Android 自定义View合集

    自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/ ...

  3. android 自定义动画

    android自定义动画注意是继承Animation,重写里面的initialize和applyTransformation,在initialize方法做一些初始化的工作,在applyTransfor ...

  4. Android自定义View 画弧形,文字,并增加动画效果

    一个简单的Android自定义View的demo,画弧形,文字,开启一个多线程更新ui界面,在子线程更新ui是不允许的,但是View提供了方法,让我们来了解下吧. 1.封装一个抽象的View类   B ...

  5. Android自定义View4——统计图View

    1.介绍 周末在逛慕课网的时候,看到了一张学习计划报告图,详细记录了自己一周的学习情况,天天都是0节课啊!正好在学习Android自定义View,于是就想着自己去写了一个,这里先给出一张慕课网的图,和 ...

  6. (转)[原] Android 自定义View 密码框 例子

    遵从准则 暴露您view中所有影响可见外观的属性或者行为. 通过XML添加和设置样式 通过元素的属性来控制其外观和行为,支持和重要事件交流的事件监听器 详细步骤见:Android 自定义View步骤 ...

  7. Android 自定义View (五)——实践

    前言: 前面已经介绍了<Android 自定义 view(四)-- onMeasure 方法理解>,那么这次我们就来小实践下吧 任务: 公司现有两个任务需要我完成 (1)监测液化天然气液压 ...

  8. Android 自定义 view(四)—— onMeasure 方法理解

    前言: 前面我们已经学过<Android 自定义 view(三)-- onDraw 方法理解>,那么接下我们还需要继续去理解自定义view里面的onMeasure 方法 推荐文章: htt ...

  9. Android 自定义 view(三)—— onDraw 方法理解

    前言: 上一篇已经介绍了用自己定义的属性怎么简单定义一个view<Android 自定义view(二) -- attr 使用>,那么接下来我们继续深究自定义view,下一步将要去简单理解自 ...

随机推荐

  1. POJ 3664 Election Time 题解

    这道题网上非常多人都会说easy,水题之类的话,只是我看了下说这种话的人的程序,能够说他们的程序都不及格! 为什么呢?由于他们的程序都是使用简单的二次排序水过(大概你能搜索到的多是这种程序).那样自然 ...

  2. iOS开发UI调试神器----Reveal

    做iOS的开发,UI是非常非常重要的一环.调试时我们一般用模拟器,提交前用真机做測试.用模拟器来调试UI效果尽管快捷方便,但有时仍然希望有更强大的工具来帮助分析UI,尤其是专注在UI的效果调试时.近期 ...

  3. NOIP2017提高组模拟赛5 (总结)

    NOIP2017提高组模拟赛5 (总结) 第一题 最远 奶牛们想建立一个新的城市.它们想建立一条长度为N (1 <= N <= 1,000,000)的 主线大街,然后建立K条 (2 < ...

  4. bzoj5204: [CodePlus 2018 3 月赛]投票统计(离散化+暴力)

    5204: [CodePlus 2018 3 月赛]投票统计 题目:传送门 题解: 谢谢niang老师的一道sui题 离散化之后直接搞啊(打完之后还错了...) 代码: #include<cst ...

  5. .net core @Html 自定义属性中包含特殊符号解决

    最近自己在练手项目用到了VUE 绑定属性的时候发现 有: -符号 这样显然是不支持的.之前发现 v-on  这种-符号也是不支持的 但是可用 @v_on 替代.可是找遍了所有资料也没找到:转义符 当时 ...

  6. ServiceStack.Redis之IRedisClient<第三篇>【转】

    事实上,IRedisClient里面的很多方法,其实就是Redis的命令名.只要对Redis的命令熟悉一点就能够非常快速地理解和掌握这些方法,趁着现在对Redis不是特别了解,我也对着命令来了解一下这 ...

  7. BigDecimal相除异常

    使用两个BigDecimal类型的数字做除法运算时,出现了一个如下的异常信息: 1 java.lang.ArithmeticException: Non-terminating decimal exp ...

  8. C++之指针与引用,函数和数组

    ]={,,}; //ptr是指针,该指针类型是int[3] ]=&arr; cout << **ptr << endl;//第一次解指针时得到数组地址,第二次解指针取数 ...

  9. perl脚本去除文件中重复数据

    今天第一天写博客,写的不好请大家多多指教,废话不多说了,干货送上: ############################################################# #!/u ...

  10. Python学习笔记(二):字符串类型

    在上一篇随笔(https://www.cnblogs.com/g-qiang/p/10448813.html)中,说到 Python 有六种标准数据类型,而数字类型和字符串类型又是其中基本的数据类型. ...