1、创建MyToggleButton
public class MyToggleButton extends View {
public MyToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
2、在activity_main.xml布局中使用MyToggleButton,如下:
3、用面向对象的方式去设计MyToggleButton应该拥有哪些方法,修改MainActivity.java,如下:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyToggleButton toggleButton = (MyToggleButton) findViewById(R.id.toggle_button);
// 设置滑动开关的背景图片和滑块图片
toggleButton.setSwitchImage(R.drawable.slide_background, R.drawable.slide_icon);
// 设置滑动开发的状态
toggleButton.setState(false);
// 设置滑动开关的监听器
toggleButton.setOnStateChangedListener(new OnStateChangedListener() {
@Override
public void onStateChanged(boolean state) {
Toast.makeText(MainActivity.this, state ? "开" : "关", 0).show();
}
});
}
MyToggleButton.java如下:
public class MyToggleButton extends View {
private OnStateChangedListener listener;
private boolean state;
private Bitmap slideBackground;
private Bitmap slideIcon;
public MyToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* 设置开关图片
* @param slideBackgroundResId 滑块背景图片资源id
* @param slideIconResId 滑块图片资源id
*/
public void setSwitchImage(int slideBackgroundResId, int slideIconResId) {
slideBackground = BitmapFactory.decodeResource(getResources(), slideBackgroundResId);
slideIcon = BitmapFactory.decodeResource(getResources(), slideIconResId);
}
/** 设置滑块状态,开或者关 */
public void setState(boolean state) {
this.state = state;
}
/** 设置状态改变的监听器 */
public void setOnStateChangedListener(OnStateChangedListener listener) {
this.listener = listener;
}
/** 开关状态改变的监听器 */
public interface OnStateChangedListener {
/** 当开关状态发生改变时 */
void onStateChanged(boolean state);
}
}
运行查看效果,结果什么也看不见,因为我们没有对View进行mesure和draw的操作
1、获取滑块背景和滑块icon的宽高,如下红色为新增代码:
public void setSwitchImage(int slideBackgroundResId, int slideIconResId) {
slideBackground = BitmapFactory.decodeResource(getResources(), slideBackgroundResId);
slideIcon = BitmapFactory.decodeResource(getResources(), slideIconResId);
// 获取滑块背景图片宽高
backgroundWidth = slideBackground.getWidth();
backgroundHeight = slideBackground.getHeight();
// 获取滑块icon图片宽高
iconWidth = slideIcon.getWidth();
iconHeight = slideIcon.getHeight();
}
2、测量MyToggleButton大小,让其大小与滑块背景宽高一样,代码如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(backgroundWidth, backgroundHeight);
}
再次运行,还是看不到效果,因为的显示是通过onDraw方法画出来的。
View的显示都是通过onDraw画出来的,如下:
private int iconLeft;
protected void onDraw(Canvas canvas) {
// 画背景
int left = 0;
int top = 0;
canvas.drawBitmap(slideBackground, left, top, null);
// 画滑块
int iconLeft = 0;
canvas.drawBitmap(slideIcon, iconLeft, 0, null);
原理:滑块以手指触摸点作为其中心点显示,滑动时slideIcon需要重新画出来,画的时候改变其left坐标即可,如何计算left值呢?
滑动的时候计算滑块left = 触摸位置event.getX() – 滑块宽 / 2
原理了解后实现触摸的处理,如下:
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
// 滑动的时候计算滑块left = 触摸位置event.getX() – 滑块宽 / 2
iconLeft = (int) (event.getX() - iconWidth / 2);
break;
case MotionEvent.ACTION_UP:
break;
}
invalidate(); // 系统内部会重新调用onDraw方法
return true;
}
运行查看效果,slideIcon会根据手机的滑动而滑动,但是会滑超出范围
在计算iconLeft的时候要判断是否超出最小值和最大值,如果是往左滑,小值是0,如果是向右滑最大值是多少呢?分析如下:
滑块往右移动时,滑块left的最大值 = 背景宽 – 滑块宽
了解了原理后,修改onTouchEvnet方法,如下,红色为新增的代码:
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
// 滑动的时候计算滑块left = 触摸位置event.getX() – 滑块宽 / 2
iconLeft = (int) (event.getX() - iconWidth / 2);
// 预防超出范围
if (iconLeft < 0) {
// 往左移时最小值是0
iconLeft = 0;
} else if (iconLeft > backgroundWidth - iconWidth) {
// 滑块往右移动时,滑块left的最大值 = 背景宽 – 滑块宽
iconLeft = backgroundWidth - iconWidth;
}
break;
case MotionEvent.ACTION_UP:
break;
}
invalidate(); // 系统内部会重新调用onDraw方法
return true;
}
手指抬起时,滑块要么定位到开,要么定位到关的状态,要定位到最左边还是定位到最右边取决于滑块离左边比较近,还是离右边比较近,那如何知道离左边比较近还是离右边比较近呢?
手指松开时,计算滑块应该滑到最左边,还是滑到最右边: 如果手指抬起的位置 < 背景宽 / 2,把滑块滑到最左边,否则滑到最右边。
了解了原理之后,实现代码如下:
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
// 。。。以前的代码
break;
case MotionEvent.ACTION_UP:
/* 手指松开时,计算滑块应该滑到最左边,还是滑到最右边:
如果手指抬起的位置 < 背景宽 / 2,把滑块滑到最左边,否则滑到最右边 */
if (event.getX() < backgroundWidth / 2) {
iconLeft = 0;
} else {
iconLeft = backgroundWidth - iconWidth;
}
break;
}
invalidate(); // 系统内部会重新调用onDraw方法
return true;
}
通过setState方法可以让开关按钮直接滑到开或者关的状态,实现代码如下:
/** 设置滑块状态,开或者关 */
public void setState(boolean state) {
this.state = state;
if (state) {
// 如果是开的状态,则把滑块画到最右边
iconLeft = backgroundWidth - iconWidth;
} else {
// 如果是关的状态,则把滑块画到最左边
iconLeft = 0;
}
invalidate(); // 系统内部会重新调用onDraw方法
}
原理,用一个成员变量保存状态,当生产一个状态时就跟原来的状态比一下,如果不相同,则状态发生了改变。实现代码如下:
/** 设置滑块状态,开或者关 */
public void setState(boolean state) {
checkState(state);
if (state) {
// 如果是开的状态,则把滑块画到最右边
iconLeft = backgroundWidth - iconWidth;
} else {
// 如果是关的状态,则把滑块画到最左边
iconLeft = 0;
}
invalidate(); // 系统内部会重新调用onDraw方法
}
public void checkState(boolean currentState) {
if (this.state != currentState) { // 如果状态发生了改变
this.state = currentState;
if (listener != null) { // 如果有监听器
listener.onStateChanged(currentState);
}
}
}
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
// 。。。以前的代码
case MotionEvent.ACTION_UP:
/* 手指松开时,计算滑块应该滑到最左边,还是滑到最右边:
如果手指抬起的位置 < 背景宽 / 2,把滑块滑到最左边,否则滑到最右边 */
if (event.getX() < backgroundWidth / 2) {
iconLeft = 0;
checkState(false);
} else {
iconLeft = backgroundWidth - iconWidth;
checkState(true);
}
break;
}
invalidate(); // 系统内部会重新调用onDraw方法
return true;
}
1、 在values目录创建attrs.xml文件
format=”reference”代表是类型引用,比如@drawable/abc
2、 在根布局中声明命名空间
xaiozhuzhu是自定义的,res-auto:最好写上自己的包名
xmlns:xiaozhuzhu="http://schemas.android.com/apk/res-auto"
3、 在xml中使用自定义属性
4、 在代码中读取自定义属性
public MyToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);
String namespace = "http://schemas.android.com/apk/res-auto";
// 读取滑块背景和滑块icon属性
int slideBackgroundResId = attrs.getAttributeResourceValue(namespace, "slideBackground", -1);
int slideIconResId = attrs.getAttributeResourceValue(namespace, "slideIcon", -1);
if (slideBackgroundResId != -1 && slideIconResId != -1) {
setSwitchImage(slideBackgroundResId, slideIconResId);
}
// 读取开关状态属性
boolean isOpen = attrs.getAttributeBooleanValue(namespace, "state", false);
setState(isOpen);
}