Android自定义控件之状态开关

效果图大概类似于这样,这是打开的状态:

Android自定义控件之状态开关_第1张图片


关闭的状态:

嗯,就这样,简单,但是很实用。


Switch开关状态的获取:

drawableSwitch = (DrawableSwitch) findViewById(R.id.drawableSwitch);
drawableSwitch.setListener(new DrawableSwitch.MySwitchStateChangeListener()
{
@Override
public void mySwitchStateChanged(boolean isSwitchOn)
{
Log.d("TAG","mySwitchStateChanged:isSwitchOn="+ isSwitchOn);
}
});

这里的MySwitchStateChangeListener是switch控件内部定义的监听器,用于监听switch的状态是打开还是关闭。


下面来说说绘制思路:

从外形来看,一个switch控件是由三部分组成的:(1)左边的一个圆;(2)中间一个矩形;(3)右边一个圆形。其中在打开状态时左边的圆和中间的矩形颜色相同,矩形被右边的圆给覆盖掉了一部分;在关闭状态时中间的矩形左边被左边的圆覆盖掉了一部分;文字则绘制在矩形中。

所以,绘制流程是这样的:

(1)定义一个变量来标识switch的状态是开还是关:

private boolean isSwitchOn; //开关的状态

(2)获取自定义属性——>重写onMeasure()方法——>重写onDraw()方法,在onDraw()方法里根据isSwitchOn的状态绘制不同的图形;

(3)定义一个内部接口,用来监听switch控件的状态更改事件。当然,要给自定义控件设置OnClickListener,在用户点击控件时改变isSwitchOn的值,然后重绘界面,同时把isSwitchOn的值传给自定义的监听器就可以了。

全部代码如下:

1、attrs.xml




    
        
        
        
        
        
        
        


2、DrawableSwitch.java

package com.ctgu.drawableswitch;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Paint.FontMetrics;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

import com.orhanobut.logger.Logger;

/**
 * 一个简单的带提示文字的状态开关
 */
public class DrawableSwitch extends View
{
	private float radius; // 圆的半径
	private int switchOnColor; // 开关打开时的背景颜色
	private int switchOffColor; // 开关关闭时的背景颜色
	private int circleColor; // 圆的填充颜色
	private int textColor; // 文字颜色
	private boolean isSwitchOn; // 开关的状态

	private Paint paint; // 画笔
	private RectF rect; // 画中间的矩形

	private MySwitchStateChangeListener listener;

	public DrawableSwitch(Context context)
	{
		super(context);
	}

	public DrawableSwitch(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		init(attrs, context);
	}

	public DrawableSwitch(Context context, AttributeSet attrs, int defStyleAttr)
	{
		super(context, attrs, defStyleAttr);
		init(attrs, context);
	}

	/**
	 * 初始化操作,获得自定义的属性值
	 * 
	 * @param attrs
	 *            属性集合
	 * @param context
	 *            上下文
	 */
	private void init(AttributeSet attrs, Context context)
	{
		Logger.d("init(),  isSwitchOn=" + isSwitchOn);

		TypedArray ta = context.obtainStyledAttributes(attrs,
				R.styleable.drawableSwitch);
		int n = ta.getIndexCount();
		for (int i = 0; i < n; i++)
		{
			int attr = ta.getIndex(i);
			switch (attr)
			// 获得自定义的属性值
			{
				case R.styleable.drawableSwitch_radius:
					radius = ta.getDimensionPixelSize(attr, (int) TypedValue
							.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10,
									getResources().getDisplayMetrics())); // 圆的半径
					break;
				case R.styleable.drawableSwitch_switchOnColor:
					switchOnColor = ta.getColor(attr, Color.GREEN); // 开关打开时控件的底色
					break;
				case R.styleable.drawableSwitch_switchOffColor:
					switchOffColor = ta.getColor(attr, Color.GRAY); // 开关关闭时控件的底色
					break;
				case R.styleable.drawableSwitch_circleColor:
					circleColor = ta.getColor(attr, Color.WHITE); // 圆的颜色,默认为白色
					break;
				case R.styleable.drawableSwitch_textColor:
					textColor = ta.getColor(attr, Color.BLACK); // 文字颜色,默认为黑色
					break;
				case R.styleable.drawableSwitch_isSwitchOn:
					isSwitchOn = ta.getBoolean(attr, false); // 控件的开关状态
					break;
			}
		}
		ta.recycle();
		paint = new Paint(); // 画笔对象
		rect = new RectF();// 中间的矩形

		this.setOnClickListener(new OnClickListener()
		{
			@Override
			public void onClick(View v)
			{
				changeStatus();
			}
		});
	}

	/**
	 * 实现更改开关状态,此处需要重绘界面
	 */
	protected void changeStatus()
	{
		Logger.d("changeStatus...");
		isSwitchOn = !isSwitchOn;

		if (listener != null)
		{
			listener.mySwitchStateChanged(isSwitchOn); // 监听开关状态更改事件
		}
		this.postInvalidate(); // 状态更改之后还要更新一下界面
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
	{
		// 系统帮我们测量的高度和宽度都是MATCH_PARNET,当我们设置明确的宽度和高度时,
		// 系统帮我们测量的结果就是我们设置的结果,当我们设置为WRAP_CONTENT,或者MATCH_PARENT时
		// 系统帮我们测量的结果就是MATCH_PARENT的长度。
		// 所以,当设置了WRAP_CONTENT时,我们需要自己进行测量,即重写onMesure方法”
		// 重写之前先了解MeasureSpec的specMode,一共三种类型:
		// MeasureSpec.EXACTLY 一般是设置了明确的值或者是MATCH_PARENT
		// MeasureSpec.AT_MOST 表示子布局限制在一个最大值内,一般为WARP_CONTENT
		// MeasureSpec.UNSPECIFIED表示子布局想要多大就多大,很少使用

		int width = 0;
		int height = 0;
		// 设置宽度
		int specMode = MeasureSpec.getMode(widthMeasureSpec);
		int specSize = MeasureSpec.getSize(widthMeasureSpec);
		switch (specMode)
		{
			case MeasureSpec.EXACTLY: // 明确指定
				width = specSize;
				break;
			case MeasureSpec.AT_MOST:
				width = getPaddingLeft() + getPaddingRight();
				break;
		}

		// 设置高度
		specMode = MeasureSpec.getMode(heightMeasureSpec);
		specSize = MeasureSpec.getSize(heightMeasureSpec);
		switch (specMode)
		{
			case MeasureSpec.EXACTLY:
				height = specSize;
				break;
			case MeasureSpec.AT_MOST:
				height = width / 20;
				break;
		}
		Logger.d("onMeasure(),width=" + width + ",height=" + height);
		setMeasuredDimension(width, height);
	}

	/**
	 * 绘制操作的具体实现
	 */
	@Override
	protected void onDraw(Canvas canvas)
	{
		// super.onDraw(canvas);
		// 图形的绘制:左边一个半圆,中间一个矩形,右边一个半圆。实际绘制时,分两种情况:switchOn和switchOff
		// switchOn:先绘制左边的整个圆,然后再绘制矩形,然后改变颜色,绘制右边的圆,中间的矩形被右边的圆覆盖了一部分
		// switchOff:先绘制中间的矩形,再绘制右边的圆,右边的圆的颜色和矩形相同,然后再更改颜色,绘制左边的圆
		Logger.d("onDraw(),  isSwitchOn=" + isSwitchOn);

		float switchWidth = 3.0f * radius;
		float switchHeight = 2.0f * radius;

		paint.setAntiAlias(true);// 设置抗锯齿
		if (isSwitchOn == true)
		{ // 打开时的状态
			paint.setColor(switchOnColor);
			paint.setStyle(Paint.Style.FILL); // 设置为填充圆
			canvas.drawCircle(radius, radius, radius, paint);// 画左边的圆

			rect.set(radius, 0, radius + switchWidth, switchHeight);
			canvas.drawRect(rect, paint); // 画中间的矩形

			paint.setColor(circleColor); // 改一下圆的填充色
			canvas.drawCircle(radius + switchWidth, radius, radius, paint); // 画右边的圆

			paint.setColor(textColor); // 文字颜色设置为布局文件里的值
			paint.setTextSize(24); // 设置文字大小
			String text = "打开";
			FontMetrics fontMetrics = paint.getFontMetrics();
			float fontHeight = fontMetrics.bottom - fontMetrics.top;
			float baseline = radius + fontHeight / 2 - fontMetrics.bottom; // 计算文字绘制时的baseline
			canvas.drawText(text, radius, baseline, paint); // 绘制文字,//打开时不需要获取绘制文字的初始x坐标,直接为radius
		}
		else
		{ // 关闭时的状态
			paint.setColor(switchOffColor);
			paint.setStyle(Paint.Style.FILL);
			rect.set(radius, 0, radius + switchWidth, switchHeight);
			canvas.drawRect(rect, paint); // 先画中间的矩形

			canvas.drawCircle(radius + switchWidth, radius, radius, paint); // 再画右边的圆

			paint.setColor(circleColor);
			canvas.drawCircle(radius, radius, radius, paint); // 再画左边的圆

			paint.setColor(textColor); // 文字颜色设置为布局文件里的值
			paint.setTextSize(24); // 设置文字大小
			String text = "关闭";
			float textWidth = paint.measureText(text); // 关闭时需获取文字宽度
			FontMetrics fontMetrics = paint.getFontMetrics();
			float fontHeight = fontMetrics.bottom - fontMetrics.top;
			float baseline = radius + fontHeight / 2 - fontMetrics.bottom; // 计算文字绘制时的baseline
			canvas.drawText(text, switchWidth + radius - textWidth, baseline,
					paint); // 绘制文字,文字绘制的初始x坐标为右圆的圆心x坐标减去文字的宽度
		}
	}

	/**
	 * 获得控件的开启状态
	 * 
	 * @return isSwitchOn 开关的状态
	 */
	public boolean isSwitchOn()
	{
		return isSwitchOn;
	}

	/**
	 * 设置控件的开关状态
	 * 
	 * @param isSwitchOn
	 *            开关的状态
	 */
	public void setSwitchOn(boolean isSwitchOn)
	{
		this.isSwitchOn = isSwitchOn;
	}

	/**
	 * 设置监听器,监听控件开关状态是否改变
	 * 
	 * @param myListener
	 *            外部传入的监听器,用来监听控件的开关状态
	 */
	public void setListener(MySwitchStateChangeListener myListener)
	{
		this.listener = myListener;
	}

	/**
	 * 定义的一个内部接口,用来监听控件状态更改事件
	 */
	public interface MySwitchStateChangeListener
	{
		public void mySwitchStateChanged(boolean isSwitchOn);
	}
}

3、activity_main.xml



    


4、MainActivity.java

package com.ctgu.drawableswitch;

import com.orhanobut.logger.Logger;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

public class MainActivity extends Activity
{
	private DrawableSwitch drawableSwitch;

	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		drawableSwitch = (DrawableSwitch) findViewById(R.id.drawableSwitch);
		drawableSwitch
				.setListener(new DrawableSwitch.MySwitchStateChangeListener()
				{
					@Override
					public void mySwitchStateChanged(boolean isSwitchOn)
					{
						Logger.d("mySwitchStateChanged:isSwitchOn="
								+ isSwitchOn);
					}
				});
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu)
	{
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item)
	{
		// Handle action bar item clicks here. The action bar will
		// automatically handle clicks on the Home/Up button, so long
		// as you specify a parent activity in AndroidManifest.xml.
		int id = item.getItemId();
		if (id == R.id.action_settings)
		{
			return true;
		}
		return super.onOptionsItemSelected(item);
	}
}


代码都贴上去了,就不传附件了。

你可能感兴趣的:(自定义控件)