自定义控件--自定义ToggleButton

  1. 自定义类继承View,并复写三个构造方法
  2. 在构造方法中对背景图片,按钮图片,按钮滑动最大距离进行初始化,设置点击事件
  3. 在点击事件中,对开关状态进行反向操作,并使用invalidate(),重新调用onDraw(),在onDraw()中改变按钮图片的位置,实现开关状态的效果
  4. 实现触摸事件,(注意要调用父类的触摸事件,将事件继续向下转发,在处理结束后要返回true已表示事件已经被消耗)在第一次按下按下控件的时候记录初始的X的坐标,在移动过程中不断的根据X坐标的偏移量刷新视图,在抬起动作中对X坐标进行判断,超过最大滑动距离的一半,滑动到最大距离,不超过,滑动到最小0的位置上
  5. bug解决

bug描述

在移动按钮过程中,不管是否移动超过最大滑动距离的一半,都会导致按钮状态往反方向变化.

bug原因

当前view在响应触摸事件的同时,又响应的点击事件,原因是调用super.onTouchEvent(event);,按照安卓系统的事件传递机制,在触摸事件响应时将触摸事件传递给点击事件,使得在滑动按钮过程中按照触摸事件响应,在抬起时,又响应了点击事件,点击事件导致按钮开关状态的反向.

bug解决

增加开关变量,用于表示在抬起时,点击事件对按钮开关状态的控制权,true表示点击事件获得对按钮开关状态的控制权,false表示点击事件放弃按钮开关状态的控制权

在触摸事件处理中:

按下时将开关置为true,表示此时点击事件与触摸事件都有权控制按钮的开关状态

移动时,如果移动距离超过一定值,触摸事件接管,将开关置为false,让点击事件放弃控制权

抬起时,对开关进行判断,如果触摸事件有控制权,则刷新按钮状态,否则什么也不做,交给点击事件去处理

 

 

代码实现

注:里面有两张图片,需要导入到工程中

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class MyToggleButton extends View implements View.OnClickListener {

	private Bitmap backgroundBitmap;
	private Bitmap slideBitmap;
	private Paint paint;
	// 图片距离左边的距离
	private float maxSlide;
	/**
	 * 距离左边的最大距离
	 */
	private int maxLeft ;
	/**
	 * 按钮的开关状态 true:开 flase:关
	 */
	private boolean isStatus = true;

	// 测量view
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// 设置测量的值宽和高,采用背景图片的宽高
		setMeasuredDimension(backgroundBitmap.getWidth(),
				backgroundBitmap.getHeight());
	}

	// 绘制View
	@Override
	protected void onDraw(Canvas canvas) {
                // 画出背景
		canvas.drawBitmap(backgroundBitmap, 0, 0, paint);
                //画出滑动按钮
		canvas.drawBitmap(slideBitmap, maxSlide, 0, paint);
	}
	

	private void initView() {
		paint = new Paint();
		paint.setColor(Color.RED);
		// 设置抗锯齿
		paint.setAntiAlias(true);
                //初始化背景图片
		backgroundBitmap = BitmapFactory.decodeResource(getResources(),
				R.drawable.switch_background);
                //初始化按钮图片
		slideBitmap = BitmapFactory.decodeResource(getResources(),
				R.drawable.slide_button);
                //设置按钮距离左边最大滑动距离
		maxLeft = backgroundBitmap.getWidth() - slideBitmap.getWidth();
		// 设置点击事件
		 setOnClickListener(this);
	}

	//第一次按下的X轴坐标
	private float startX;
	//第一次按下的X轴坐标的历史记录
	private float lastX;
	/**点击事件是否生效,如果生效滑动事件就无效
	 * true生效
	 * false无效
	 */
	private boolean isClicked = false;
	@Override
	public boolean onTouchEvent(MotionEvent event) {
                //调用父类的方法,将点击事件进行转发
		super.onTouchEvent(event);
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:// 按下
			//1.第一次按下坐标
			lastX = startX = event.getX();
			isClicked = true;
			break;

		case MotionEvent.ACTION_MOVE:// 移动
			//2.来到新的X轴
			float newX = event.getX();
			//3.计算偏移量
			float dX = newX - startX;		
			//4.更新控件的位置
			maxSlide += dX;
			if(Math.abs(event.getX()-lastX)>5){
				isClicked = false;
			}
			flushView();
			//5.重新记录X轴坐标
			startX = event.getX();
			break;

		case MotionEvent.ACTION_UP:// 离开
			
			if(!isClicked){	
				if(maxSlide >maxLeft/2){
					//开
					isStatus = true;
				}else if(maxSlide <=maxLeft/2){
					//关
					isStatus = false;
				}
				flushStatus();
			}
	
			break;
		}
		return true;
	}

	private void flushView() {
		//屏蔽非法值
		if(maxSlide <=0){
			maxSlide = 0;
		}
		
		if(maxSlide >=maxLeft){
			maxSlide = maxLeft;
		}
		//导致onDraw执行
		invalidate();
	}

	// 设置自己的样式的时候用到
	public MyToggleButton(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		initView();
	}

	// Android系统通过布局文件使用全类名使用控件的时候,使用这个构造方法对控件进行实例化
	public MyToggleButton(Context context, AttributeSet attrs) {
		super(context, attrs);
		initView();
	}

	// 在代码中使用创建对象的时候使用
	public MyToggleButton(Context context) {
		super(context);
		initView();
	}

	@Override
	public void onClick(View v) {
		if(isClicked){
			isStatus = !isStatus;
			flushStatus();
		}	
	}

	private void flushStatus() {
		
		if (isStatus) {
			// 开
			maxSlide = maxLeft;
		} else {
			// 关
			maxSlide = 0;
		}

		flushView();
	}

}

posted on 2014-12-25 09:10  fujianyi  阅读(899)  评论(0编辑  收藏  举报