android自定义开关控件-SlideSwitch

1.效果
iphone上有开关控件,很漂亮,其实android4.0以后也有switch控件,但是只能用在4.0以后的系统中,这就失去了其使用价值,而且我觉得它的界面也不是很好看。最近看到了百度魔拍上面的一个控件,觉得很漂亮啊,然后反编译了下,尽管没有混淆过,但是还是不好读,然后就按照自己的想法写了个,功能和百度魔拍类似。

下面是百度魔拍的效果和SlideSwitch的效果

下载地址:https://github.com/singwhatiwanna/SlideSwitch

2.原理
继承自view类,override其onDraw函数,把两个背景图(一个灰的一个红的)和一个开关图(圆开关)通过canvas画出来;同时override其onTouchEvent函数,实现滑动效果;最后开启一个线程做动画,实现缓慢滑动的效果。

3.代码
//SlideSwitch.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
package com.example.hellojni;  

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;

/**
* SlideSwitch 仿iphone滑动开关组件,仿百度魔图滑动开关组件
* 组件分为三种状态:打开、关闭、正在滑动<br/>
* 使用方法:
* <pre>SlideSwitch slideSwitch = new SlideSwitch(this);
*slideSwitch.setOnSwitchChangedListener(onSwitchChangedListener);
*linearLayout.addView(slideSwitch);
</pre>
注:也可以加载在xml里面使用
* @author scott
*
*/
public class SlideSwitch extends View
{
public static final String TAG = "SlideSwitch";
public static final int SWITCH_OFF = 0;//关闭状态
public static final int SWITCH_ON = 1;//打开状态
public static final int SWITCH_SCROLING = 2;//滚动状态

//用于显示的文本
private String mOnText = "打开";
private String mOffText = "关闭";

private int mSwitchStatus = SWITCH_OFF;

private boolean mHasScrolled = false;//表示是否发生过滚动

private int mSrcX = 0, mDstX = 0;

private int mBmpWidth = 0;
private int mBmpHeight = 0;
private int mThumbWidth = 0;

private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

private OnSwitchChangedListener mOnSwitchChangedListener = null;

//开关状态图
Bitmap mSwitch_off, mSwitch_on, mSwitch_thumb;

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

public SlideSwitch(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}

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

//初始化三幅图片
private void init()
{
Resources res = getResources();
mSwitch_off = BitmapFactory.decodeResource(res, R.drawable.bg_switch_off);
mSwitch_on = BitmapFactory.decodeResource(res, R.drawable.bg_switch_on);
mSwitch_thumb = BitmapFactory.decodeResource(res, R.drawable.switch_thumb);
mBmpWidth = mSwitch_on.getWidth();
mBmpHeight = mSwitch_on.getHeight();
mThumbWidth = mSwitch_thumb.getWidth();
}

@Override
public void setLayoutParams(LayoutParams params)
{
params.width = mBmpWidth;
params.height = mBmpHeight;
super.setLayoutParams(params);
}

/**
* 为开关控件设置状态改变监听函数
* @param onSwitchChangedListener 参见 {@link OnSwitchChangedListener}
*/
public void setOnSwitchChangedListener(OnSwitchChangedListener onSwitchChangedListener)
{
mOnSwitchChangedListener = onSwitchChangedListener;
}

/**
* 设置开关上面的文本
* @param onText 控件打开时要显示的文本
* @param offText 控件关闭时要显示的文本
*/
public void setText(final String onText, final String offText)
{
mOnText = onText;
mOffText =offText;
invalidate();
}

/**
* 设置开关的状态
* @param on 是否打开开关 打开为true 关闭为false
*/
public void setStatus(boolean on)
{
mSwitchStatus = ( on ? SWITCH_ON : SWITCH_OFF);
}

@Override
public boolean onTouchEvent(MotionEvent event)
{
int action = event.getAction();
Log.d(TAG, "onTouchEvent x=" + event.getX());
switch (action) {
case MotionEvent.ACTION_DOWN:
mSrcX = (int) event.getX();
break;
case MotionEvent.ACTION_MOVE:
mDstX = Math.max( (int) event.getX(), 10);
mDstX = Math.min( mDstX, 62);
if(mSrcX == mDstX)
return true;
mHasScrolled = true;
AnimationTransRunnable aTransRunnable = new AnimationTransRunnable(mSrcX, mDstX, 0);
new Thread(aTransRunnable).start();
mSrcX = mDstX;
break;
case MotionEvent.ACTION_UP:
if(mHasScrolled == false)//如果没有发生过滑动,就意味着这是一次单击过程
{
mSwitchStatus = Math.abs(mSwitchStatus-1);
int xFrom = 10, xTo = 62;
if(mSwitchStatus == SWITCH_OFF)
{
xFrom = 62;
xTo = 10;
}
AnimationTransRunnable runnable = new AnimationTransRunnable(xFrom, xTo, 1);
new Thread(runnable).start();
}
else
{
invalidate();
mHasScrolled = false;
}
//状态改变的时候 回调事件函数
if(mOnSwitchChangedListener != null)
{
mOnSwitchChangedListener.onSwitchChanged(this, mSwitchStatus);
}
break;

default:
break;
}
return true;
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
}

@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
//绘图的时候 内部用到了一些数值的硬编码,其实不太好,
//主要是考虑到图片的原因,图片周围有透明边界,所以要有一定的偏移
//硬编码的数值只要看懂了代码,其实可以理解其含义,可以做相应改进。
mPaint.setTextSize(14);
mPaint.setTypeface(Typeface.DEFAULT_BOLD);

if(mSwitchStatus == SWITCH_OFF)
{
drawBitmap(canvas, null, null, mSwitch_off);
drawBitmap(canvas, null, null, mSwitch_thumb);
mPaint.setColor(Color.rgb(105, 105, 105));
canvas.translate(mSwitch_thumb.getWidth(), 0);
canvas.drawText(mOffText, 0, 20, mPaint);
}
else if(mSwitchStatus == SWITCH_ON)
{
drawBitmap(canvas, null, null, mSwitch_on);
int count = canvas.save();
canvas.translate(mSwitch_on.getWidth() - mSwitch_thumb.getWidth(), 0);
drawBitmap(canvas, null, null, mSwitch_thumb);
mPaint.setColor(Color.WHITE);
canvas.restoreToCount(count);
canvas.drawText(mOnText, 17, 20, mPaint);
}
else //SWITCH_SCROLING
{
mSwitchStatus = mDstX > 35 ? SWITCH_ON : SWITCH_OFF;
drawBitmap(canvas, new Rect(0, 0, mDstX, mBmpHeight), new Rect(0, 0, (int)mDstX, mBmpHeight), mSwitch_on);
mPaint.setColor(Color.WHITE);
canvas.drawText(mOnText, 17, 20, mPaint);

int count = canvas.save();
canvas.translate(mDstX, 0);
drawBitmap(canvas, new Rect(mDstX, 0, mBmpWidth, mBmpHeight),
new Rect(0, 0, mBmpWidth - mDstX, mBmpHeight), mSwitch_off);
canvas.restoreToCount(count);

count = canvas.save();
canvas.clipRect(mDstX, 0, mBmpWidth, mBmpHeight);
canvas.translate(mThumbWidth, 0);
mPaint.setColor(Color.rgb(105, 105, 105));
canvas.drawText(mOffText, 0, 20, mPaint);
canvas.restoreToCount(count);

count = canvas.save();
canvas.translate(mDstX - mThumbWidth / 2, 0);
drawBitmap(canvas, null, null, mSwitch_thumb);
canvas.restoreToCount(count);
}

}

public void drawBitmap(Canvas canvas, Rect src, Rect dst, Bitmap bitmap)
{
dst = (dst == null ? new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()) : dst);
Paint paint = new Paint();
canvas.drawBitmap(bitmap, src, dst, paint);
}

/**
* AnimationTransRunnable 做滑动动画所使用的线程
*/
private class AnimationTransRunnable implements Runnable
{
private int srcX, dstX;
private int duration;

/**
* 滑动动画
* @param srcX 滑动起始点
* @param dstX 滑动终止点
* @param duration 是否采用动画,1采用,0不采用
*/
public AnimationTransRunnable(float srcX, float dstX, final int duration)
{
this.srcX = (int)srcX;
this.dstX = (int)dstX;
this.duration = duration;
}

@Override
public void run()
{
final int patch = (dstX > srcX ? 5 : -5);
if(duration == 0)
{
SlideSwitch.this.mSwitchStatus = SWITCH_SCROLING;
SlideSwitch.this.postInvalidate();
}
else
{
Log.d(TAG, "start Animation: [ " + srcX + " , " + dstX + " ]");
int x = srcX + patch;
while (Math.abs(x-dstX) > 5)
{
mDstX = x;
SlideSwitch.this.mSwitchStatus = SWITCH_SCROLING;
SlideSwitch.this.postInvalidate();
x += patch;
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
mDstX = dstX;
SlideSwitch.this.mSwitchStatus = mDstX > 35 ? SWITCH_ON : SWITCH_OFF;
SlideSwitch.this.postInvalidate();
}
}

}

public static interface OnSwitchChangedListener
{
/**
* 状态改变 回调函数
* @param status SWITCH_ON表示打开 SWITCH_OFF表示关闭
*/
public abstract void onSwitchChanged(SlideSwitch obj, int status);
}

}

// layout.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#fdfdfd"
android:orientation="vertical"
android:paddingLeft="10dip"
android:paddingRight="10dip" >

<ImageView
android:id="@+id/imageView1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:src="@drawable/top" />

<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content" >

<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:text="网络构图"
android:textSize="15sp" />

<com.example.hellojni.SlideSwitch
android:id="@+id/slideSwitch1"
android:layout_width="116dip"
android:layout_height="46dip"
android:layout_alignParentRight="true"
android:layout_centerVertical="true" />
</RelativeLayout>

<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content" >

<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:text="保留原图"
android:textSize="15sp" />

<com.example.hellojni.SlideSwitch
android:id="@+id/slideSwitch2"
android:layout_width="116dip"
android:layout_height="46dip"
android:layout_alignParentRight="true"
android:layout_centerVertical="true" />
</RelativeLayout>

<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content" >

<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:text="拍照声音"
android:textSize="15sp" />

<com.example.hellojni.SlideSwitch
android:id="@+id/slideSwitch3"
android:layout_width="116px"
android:layout_height="46px"
android:layout_alignParentRight="true"
android:layout_centerVertical="true" />
</RelativeLayout>

<TextView
android:id="@+id/textViewTip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="TextView" />

</LinearLayout>