可滑动的开关按钮

1、面向对象设计MyToggleButton的方法

1、创建MyToggleButton

public class MyToggleButton extends View {
	public MyToggleButton(Context context, AttributeSet attrs) {
		super(context, attrs);
	}	
}


2、activity_main.xml布局中使用MyToggleButton,如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="16dp" >

    <com.itheima.togglebutton.MyToggleButton
        android:id="@+id/toggle_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</RelativeLayout>


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进行mesuredraw操作

2onMeasure

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方法画出来的。

3onDraw

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);


 

4、滑动手指同时滑动滑块

原理:滑块手指触摸点作为其中心点显示,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;
}


5手指抬起时滑块定位

    

 

手指抬起时,块要么定位到开,要么定位关的状态定位到最左边定位到最右边取决于滑块离左边比较近,还是离右边比较近,那如何知道离边比较近还是离右边比较近呢?

 

手指松开时,计算滑块应该滑到最左边,还是滑到最右边: 如果手指抬起的位置 < 背景宽 / 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;
}


6setState方法

通过setState方法可以让开关按钮直接滑到开或者关的状态,实现代码如下:

/** 设置滑块状态,开或者关 */
public void setState(boolean state) {
	this.state = state;
	if (state) {
		// 如果是开的状态,则把滑块画到最右边
		iconLeft = backgroundWidth - iconWidth;
	} else {
		// 如果是关的状态,则把滑块画到最左边
		iconLeft = 0;
	}
	invalidate();	// 系统内部会重新调用onDraw方法
}


7、监听开关按钮状态

原理,用一个成员变量保存状态,当生产一个状态时就跟原来的状态比一下,如果不相同,则状态发生了改变。实现代码如下:

/** 设置滑块状态,开或者关 */
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;
}


8、自定义xml属性

1、 values目录创建attrs.xml文件

format=”reference”代表是类型引用,比如@drawable/abc

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyToggleButton">
        <attr name="slideBackground" format="reference" />
        <attr name="slideIcon" format="reference" />
        <attr name="state" format="boolean" />
    </declare-styleable>
</resources>



2、 在根布局中声明命名空间

xaiozhuzhu是自定义的,res-auto:最好写上自己的包名

xmlns:xiaozhuzhu="http://schemas.android.com/apk/res-auto"


3、 xml中使用自定义属性

<com.itheima.togglebutton.MyToggleButton
        android:id="@+id/toggle_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        itheima:slideBackground="@drawable/slide_background2"
        itheima:slideIcon="@drawable/slide_icon2"
        itheima:state="false"/>


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);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值