andorid 自定义开关控件
1,实现效果
运用知识,onMeasure 测量,onDraw 绘制, 接口回调
2,实现逻辑
【1】创建一个类 继承view 实现空参构造
public class ToogleView extends View {
public ToogleView(Context context, AttributeSet attrs) {
}
}
【2】布局中全名使用
-
com.xiaoshuai.toogleview.ToogleView 包名类名调用
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<com.xiaoshuai.toogleview.ToogleView
android:id="@+id/toogleView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
</RelativeLayout>
【3】onMeasure 自己进行测量和图片一样宽和一样高
-
BitmapFactory在ToogleView 构造中获取背景图片的宽和高,获取滑动块的高和宽
private Bitmap mtoogleViewbg;
private Bitmap mSlideBg;
//[1]先获取开关的背景图片和滑动块的图片(bitmap)
mtoogleViewbg = BitmapFactory.decodeResource(getResources(), R.drawable.toogle_background);
mSlideBg = BitmapFactory.decodeResource(getResources(), R.drawable.toogle_slidebg);
onMeasure测量当前view的大小
//测量当前view的大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//这个view大小和背景图片宽高一样
setMeasuredDimension(mtoogleViewbg.getWidth(), mtoogleViewbg.getHeight());
}
【4】onDraw 方法中画出控件
/**在这个方法里面对当前view进行绘制**/
@Override
protected void onDraw(Canvas canvas) {
//[1]画开关的背景
canvas.drawBitmap(mtoogleViewbg, 0, 0, null);
//[2]画开关的滑块
canvas.drawBitmap(mSlideBg, 0, 0, null);
}
【5】设置开关的状态
-
MainActivity中 onCreate 方法设置
//[2]设置开关的状态
// toogleView.setToogleViewState(true);
-
实现对应的逻辑
//设置开关的状态
public void setToogleViewState(boolean b) {
}
【6】自定义接口方法回调内部里面内部的实现逻辑叫调用者自己完成(写到这操作没有效果)
-
设置开关的监听方法,要传入一个接口对象,定义一个成员变量接收调用对象
private OnToogleViewListener mToogleViewListener;
/**设置开关的监听方法**/
public void setOnToogleViewListener(OnToogleViewListener l){
this.mToogleViewListener = l;
}
定义监听器对象
/**
* 定义监听器对象
* **/
public interface OnToogleViewListener{
//当开关的状态发生改变的时候调用
void toogleState(boolean result);
}
-
MainActivity 调用接口
toogleView.setOnToogleViewListener(new OnToogleViewListener() {
@Override
public void toogleState(boolean result) {
if (result) {
Toast.makeText(getApplicationContext(), "开", 0).show();
}else {
Toast.makeText(getApplicationContext(), "关", 0).show();
}
}
});
【7】移动效果的处理
-
在移动的时候调用invalidate方法重新绘制ondraw方法
/**代表滑块左边界的值**/
private float slideLeftPosition;
//[2]画开关的滑块
canvas.drawBitmap(mSlideBg, slideLeftPosition, 0, null);
-
在构造方法中,给设置边界线为了不叫他滑动出去,滑动的最大距离是left,最小距离是0
/**代表滑动块left的最大边界**/
private float slideLeftMaxSize;
//[2]算出滑块left最大边界值
slideLeftMaxSize = mtoogleViewbg.getWidth() - mSlideBg.getWidth();
-
不断更改滑动块的X轴的位置
downX = moveX; 不重新设置按下每次滑动会多一段距离
/**处理当前view的事件**/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN://按下
//[1]当手指按下的时候 获取按下x的坐标
downX = event.getX();
break;
case MotionEvent.ACTION_MOVE: //移动
//[2]当手指移动获取移动的距离
float moveX = event.getX();
float distanceX = moveX - downX;
slideLeftPosition+=distanceX;
//[3]对边界进行判断
if (slideLeftPosition <=0) {
slideLeftPosition = 0;
}else if (slideLeftPosition >=slideLeftMaxSize) {
slideLeftPosition = slideLeftMaxSize;
}
downX = moveX;
break;
case MotionEvent.ACTION_UP: //抬起
break;
}
invalidate();// --->ondraw方法会执行
return true; //处理事件 消费事件
}
【8】抬起开关弹动效果处理
case MotionEvent.ACTION_UP: //抬起
//当手指抬起时候 滑动块是往左移动还是往右移动
//算出滑动块的中心点位置 = slideLeftPosition + 滑动块宽度的一半 < 背景宽度的一半 就往左移动 否则往右移动
if (slideLeftPosition + mSlideBg.getWidth() /2 < mtoogleViewbg.getWidth()/2 ) {
slideLeftPosition = 0;
}else {
slideLeftPosition = slideLeftMaxSize;
}
}
break;
【9】开关功能实现(接口回调)
-
定义一个isHandup默认为false ,在ACTION_UP执行时返回true,onDraw 方法中进行处理
case MotionEvent.ACTION_UP: //抬起
isHandup = true;
//当手指抬起时候 滑动块是往左移动还是往右移动
//算出滑动块的中心点位置 = slideLeftPosition + 滑动块宽度的一半 < 背景宽度的一半 就往左移动 否则往右移动
if (slideLeftPosition + mSlideBg.getWidth() /2 < mtoogleViewbg.getWidth()/2 ) {
slideLeftPosition = 0;
}else {
slideLeftPosition = slideLeftMaxSize;
}
break;
-
定义一个变量记录isOpen 记录开关真实的状态,默认为false
/**在这个方法里面对当前view进行绘制**/
@Override
protected void onDraw(Canvas canvas) {
//[1]画开关的背景
canvas.drawBitmap(mtoogleViewbg, 0, 0, null);
//[2]画开关的滑块
canvas.drawBitmap(mSlideBg, slideLeftPosition, 0, null);
//[3]如果是手指抬起 实现开关的功能
if (isHandup) {
isHandup = false;
//[4]获取开关当前的状态
boolean isOpenTemp = slideLeftPosition > 0;
if(isOpen != isOpenTemp && mToogleViewListener!=null){
//[5]条件满足 触发回调方法
mToogleViewListener.toogleState(isOpenTemp);
isOpen = isOpenTemp;
}
}
}
接口回调流程:
A类创建方法,方法中变量是接口并且成员变量引用,接口实现一个没有具体的方法,B类调用方法传入接口引用,自己实现具体方法内容。当满足某些条件时,A类中成员变量调用接口方法回调。B类引用指向A类方法。
【10】点击滑动块以外的地方,判断是滑动事件还是点击事件。开关转变
-
ACTION_DOWN中获取按下的坐标,和时间
case MotionEvent.ACTION_DOWN://按下
//[1]当手指按下的时候 获取按下x的坐标
downX = event.getX();
downY = event.getY();
//[1.1]记录一下时间 按下的时间
startTime = System.currentTimeMillis();
break;
case MotionEvent.ACTION_UP: //抬起
isHandup = true;
//[4]判断用户是点击操作还是 按下移动操作抬起操作
float upX = event.getX();
long endtime = System.currentTimeMillis()-startTime;
System.out.println("~~~~~~:"+endtime);
if (Math.abs(event.getX()-downX)<5 && Math.abs(event.getY()-downY)<5 && System.currentTimeMillis()-startTime<200) {
//如果点击滑动块以为的地方条件成立说明是点击事件
if (upX > mSlideBg.getWidth()) {
//条件满足 开关是开
slideLeftPosition = slideLeftMaxSize;
}else {
slideLeftPosition = 0;
}
}else {
//当手指抬起时候 滑动块是往左移动还是往右移动
//算出滑动块的中心点位置 = slideLeftPosition + 滑动块宽度的一半 < 背景宽度的一半 就往左移动 否则往右移动
if (slideLeftPosition + mSlideBg.getWidth() /2 < mtoogleViewbg.getWidth()/2 ) {
slideLeftPosition = 0;
}else {
slideLeftPosition = slideLeftMaxSize;
}
}
break;
【11】自定义属性
-
values 创建attres 文件,name="ToogleView" 和控件名字一致,
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ToogleView">
<attr name="toogleState" format="boolean" />
</declare-styleable>
</resources>
-
获取当前命名空间,设置自定义属性
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:itheima="http://schemas.android.com/apk/res/com.xiaoshuai.toogleview"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<com.ithiema.toogleview.ToogleView
android:id="@+id/toogleView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
xiaoshuai:toogleState="true"
android:layout_centerInParent="true" />
</RelativeLayout>
-
在ToogleView构造方法中获取到自己定义的属性
//[3]获取布局中定义的属性值
String namespace = "http://schemas.android.com/apk/res/com.xiaoshuai.toogleview";
boolean toogleState = attrs.getAttributeBooleanValue(namespace, "toogleState", false);
//[4]设置开关的状态
setToogleViewState(toogleState);
-
设置方法
//设置开关的状态
public void setToogleViewState(boolean b) {
isHandup = true;
if (b) {
slideLeftPosition = slideLeftMaxSize;
}else {
slideLeftPosition = 0;
}
}