Android 自定义带动画效果的开关按钮

自定义带动画效果的开关按钮
        一个自定义控制开关的控件,简单实用,效果特别棒。项目中常会用到,比那种用两张图片做按钮背景,selector选择的方式,视觉效果好很多。忘了是以前从哪找的了,本着开源的精神整理了一下分享出来。好了,废话不多说,先展示效果图:

 

Github 项目地址:https://github.com/xiaosong520/SwitchButtonDemo.git

步骤:

1.创建SwitchButton类添加到自己的项目中:
/**
 * @TODO<自定义选择开关按钮>
 * @author 小嵩
 * @date 2016-8-5 09:57:29
 */
public class SwitchButton extends View{
	/** 圆角大小*/
	private float radius;
	/** 开启颜色*/
	private int onColor = Color.parseColor("#4ebb7f");
	/** 关闭颜色*/
	private int offBorderColor = Color.parseColor("#dadbda");
	/** 灰色带颜色*/
	private int offColor = Color.parseColor("#ffffff");
	/** 手柄颜色*/
	private int spotColor = Color.parseColor("#ffffff");
	/** 边框颜色*/
	private int borderColor = offBorderColor;
	/** 画笔*/
	private Paint paint ;
	/** 开关状态*/
	private boolean toggleOn = false;
	/** 边框大小*/
	private int borderWidth = 2;
	/** 垂直中心*/
	private float centerY;
	/** 按钮的开始和结束位置*/
	private float startX, endX;
	/** 手柄X位置的最小和最大值*/
	private float spotMinX, spotMaxX;
	/**手柄大小 */
	private int spotSize ;
	/** 手柄X位置*/
	private float spotX;
	/** 关闭时内部灰色带高度*/
	private float offLineWidth;
	/** */
	private RectF rect = new RectF();
	/** 默认使用动画*/
	private boolean defaultAnimate = true;
	
	private OnToggleChanged listener;
	
	private SwitchButton(Context context) {
		super(context);
	}
	public SwitchButton(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		setup(attrs);
	}
	public SwitchButton(Context context, AttributeSet attrs) {
		super(context, attrs);
		setup(attrs);
	}

	public void setup(AttributeSet attrs) {
		paint = new Paint(Paint.ANTI_ALIAS_FLAG);
		paint.setStyle(Style.FILL);
		paint.setStrokeCap(Cap.ROUND);
				
		this.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View arg0) {
				toggle(defaultAnimate);
			}
		});
		
		TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.SwitchButton);
		offBorderColor = typedArray.getColor(R.styleable.SwitchButton_offBorderColor, offBorderColor);
		onColor = typedArray.getColor(R.styleable.SwitchButton_onColor, onColor);
		spotColor = typedArray.getColor(R.styleable.SwitchButton_spotColor, spotColor);
		offColor = typedArray.getColor(R.styleable.SwitchButton_offColor, offColor);
		borderWidth = typedArray.getDimensionPixelSize(R.styleable.SwitchButton_borderWidth, borderWidth);
		defaultAnimate = typedArray.getBoolean(R.styleable.SwitchButton_animate, defaultAnimate);
		typedArray.recycle();
		
		borderColor = offBorderColor;
	}
	
	public void toggle() {
		toggle(true);
	}
	
	public void toggle(boolean animate) {
		toggleOn = !toggleOn;
		takeEffect(animate);
		
		if(listener != null){
			listener.onToggle(toggleOn);
		}
	}
	
	public void toggleOn() {
		setToggleOn();
		if(listener != null){
			listener.onToggle(toggleOn);
		}
	}
	
	public void toggleOff() {
		setToggleOff();
		if(listener != null){
			listener.onToggle(toggleOn);
		}
	}
	
	/**
	 * 设置显示成打开样式,不会触发toggle事件
	 */
	public void setToggleOn() {
		setToggleOn(true);
	}
	
	/**
	 * @param animate
	 */
	public void setToggleOn(boolean animate){
		toggleOn = true;
		takeEffect(animate);
	}
	
	/**
	 * 设置显示成关闭样式,不会触发toggle事件
	 */
	public void setToggleOff() {
		setToggleOff(true);
	}
	
	public void setToggleOff(boolean animate) {
		toggleOn = false;
		takeEffect(animate);
	}
	
	private void takeEffect(boolean animate) {
		if(animate){
			slide();
		}else{
			calculateEffect(toggleOn ? 1 : 0);
		}
	}
	
	
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		
		final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
		final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
		
		int widthSize = MeasureSpec.getSize(widthMeasureSpec);
		int heightSize = MeasureSpec.getSize(heightMeasureSpec);
		
		Resources r = Resources.getSystem();
		if(widthMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.AT_MOST){
			widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, r.getDisplayMetrics());
			widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
		}
		
		if(heightMode == MeasureSpec.UNSPECIFIED || heightSize == MeasureSpec.AT_MOST){
			heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, r.getDisplayMetrics());
			heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
		}
		
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	}
	
	
	@Override
	protected void onLayout(boolean changed, int left, int top, int right,
			int bottom) {
		super.onLayout(changed, left, top, right, bottom);
		
		final int width = getWidth();
		final int height = getHeight();
		
		radius = Math.min(width, height) * 0.5f;
		centerY = radius;
		startX = radius;
		endX = width - radius;
		spotMinX = startX + borderWidth;
		spotMaxX = endX - borderWidth;
		spotSize = height - 4 * borderWidth;
		spotX = toggleOn ? spotMaxX : spotMinX;
		offLineWidth = 0;
	}
	
	private void slide(){
			Animation animation = new Animation() {
				@Override
				protected void applyTransformation(float interpolatedTime,
						Transformation t) {
					if(toggleOn){
						calculateEffect(interpolatedTime);
					}else{
						calculateEffect(1-interpolatedTime);
					}
				}
			};
			animation.setDuration(200);
			clearAnimation();
			startAnimation(animation);
	}

	private int clamp(int value, int low, int high) {
		return Math.min(Math.max(value, low), high);
	}

	
	@Override
	public void draw(Canvas canvas) {
		//
		rect.set(0, 0, getWidth(), getHeight());
		paint.setColor(borderColor);
		canvas.drawRoundRect(rect, radius, radius, paint);
		
		if(offLineWidth > 0){
			final float cy = offLineWidth * 0.5f;
			rect.set(spotX - cy, centerY - cy, endX + cy, centerY + cy);
			paint.setColor(offColor);
			canvas.drawRoundRect(rect, cy, cy, paint);
		}
		
		rect.set(spotX - 1 - radius, centerY - radius, spotX + 1.1f + radius, centerY + radius);
		paint.setColor(borderColor);
		canvas.drawRoundRect(rect, radius, radius, paint);
		
		final float spotR = spotSize * 0.5f;
		rect.set(spotX - spotR, centerY - spotR, spotX + spotR, centerY + spotR);
		paint.setColor(spotColor);
		canvas.drawRoundRect(rect, spotR, spotR, paint);
		
	}
	
	/**
	 * @param value
	 */
	private void calculateEffect(final double value) {
		final float mapToggleX = (float) mapValueFromRangeToRange(value, 0, 1, spotMinX, spotMaxX);
		spotX = mapToggleX;
		
		float mapOffLineWidth = (float) mapValueFromRangeToRange(1 - value, 0, 1, 10, spotSize);
		
		offLineWidth = mapOffLineWidth;
		
		final int fb = Color.blue(onColor);
		final int fr = Color.red(onColor);
		final int fg = Color.green(onColor);
		
		final int tb = Color.blue(offBorderColor);
		final int tr = Color.red(offBorderColor);
		final int tg = Color.green(offBorderColor);
		
		int sb = (int) mapValueFromRangeToRange(1 - value, 0, 1, fb, tb);
		int sr = (int) mapValueFromRangeToRange(1 - value, 0, 1, fr, tr);
		int sg = (int) mapValueFromRangeToRange(1 - value, 0, 1, fg, tg);
		
		sb = clamp(sb, 0, 255);
		sr = clamp(sr, 0, 255);
		sg = clamp(sg, 0, 255);
		
		borderColor = Color.rgb(sr, sg, sb);
		
		postInvalidate();
	}
	
	public interface OnToggleChanged{
		/**
		 * @param on
		 */
		public void onToggle(boolean on);
	}


	public void setOnToggleChanged(OnToggleChanged onToggleChanged) {
		listener = onToggleChanged;
	}
	
	public boolean isAnimate() {
		return defaultAnimate;
	}
	public void setAnimate(boolean animate) {
		this.defaultAnimate = animate;
	}
	
	/**
	   * Map a value within a given range to another range.
	   * @param value the value to map
	   * @param fromLow the low end of the range the value is within
	   * @param fromHigh the high end of the range the value is within
	   * @param toLow the low end of the range to map to
	   * @param toHigh the high end of the range to map to
	   * @return the mapped value
	   */
	  public static double mapValueFromRangeToRange(
	      double value,
	      double fromLow,
	      double fromHigh,
	      double toLow,
	      double toHigh) {
	    double fromRangeSize = fromHigh - fromLow;
	    double toRangeSize = toHigh - toLow;
	    double valueScale = (value - fromLow) / fromRangeSize;
	    return toLow + (valueScale * toRangeSize);
	  }
}



2.在项目res - values目录下 - 找到 attrs.xml 文件,在resource中间添加如下代码:
<declare-styleable name="SwitchButton">
    	<attr name="borderWidth" format="dimension">
    	</attr>
    	<attr name="offBorderColor" format="reference|color">
    	</attr>
    	<attr name="offColor" format="reference|color">
    	</attr>
    	<attr name="onColor" format="reference|color">
    	</attr>
    	<attr name="spotColor" format="reference|color">
    	</attr>
    	<attr name="animate" format="reference|boolean">
    	</attr>
    </declare-styleable>



3.在XML布局文件中使用控件(路径引用需改成SwitchButton类所在目录):
<com.ui.widget.view.SwitchButton
            android:id="@+id/switchButton"
            android:layout_width="45dp"
            android:layout_height="25dp"
            app:borderWidth="0.8dp"
            app:offBorderColor="@color/light_gray"
            app:offColor="@color/common_white"
            app:onColor="@color/common_light_blue"
            app:spotColor="@color/common_white"
            app:animate="true"
            android:layout_margin="20dp" />
 
    
   控件的属性介绍: 
    
      borderWidth  描边宽度
      offBorderColor 关闭状态下描边的颜色
      offColor 关闭状态的颜色
      onColor  打开状态的颜色
      spotColor 控件中间的圆点的颜色
      animate 是否打开动画效果

4.在Activity中使用:

4.1绑定控件:
如果使用ButterKinfe库的话:
@Bind(R.id.switchButton) SwitchButton switchButton;

findViewById的方式:
SwitchButton switchButton = (SwitchButton)findViewById(R.id.switchButton);

 4.2使用控件: 
    
switchButton.setToggleOn(false);//默认打开。如果参数传false,则打开页面初始化时不会有动画效果(改变状态还是会有动画) 
switchButton.setOnToggleChanged(new SwitchButton.OnToggleChanged(){      
    @Override      
    public void onToggle(boolean isOn) {      
      //处理自己的逻辑     
      showToast( "SwitchButton"+isOn);    
    } 
  });

 
    


5.没有第五步了,已经大功告成~ 有问题 欢迎call我,一起讨论~


你可能感兴趣的:(android,SwitchButton,自定义开关控件,动画效果开关按钮)