简介
由于项目开发需要,所以做了一个自定义的开关按钮,样式类似于Android5.0中开关按钮,也有开启关闭的切换效果,在这里和大家分享一下,先看一下效果图如下:
看过效果图以后我们废话不多说,直接来看一下实现代码。
实现
这里主要是写了一个类去继承View,然后不断的对它进行重绘以显示出这种效果,该类ToggleButton.java的实现代码如下:
public class ToggleButton extends View implements View.OnClickListener{
private OnToggleChanged listener;
private int onColor = Color.parseColor("#4ebb7f");
private int offColor = Color.parseColor("#dadbda");
private boolean toggleOn = true;
private int BTN_WIDTH = 40;// 宽度
private int BTN_HEIGHT = 30;// 高度
private int CIRCLE_RADIUS = 8;// 圆的半径
private int LINE_WIDTH = 2;// 直线高度
private int circleX;// 圆心X轴坐标
private boolean changeCompelete = true;
private Resources r;
private TimerTask task;
private Timer timer;
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
if (msg.what == 1111) {
invalidate();
}
};
};
public ToggleButton(Context context) {
this(context, null, 0);
}
public ToggleButton(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ToggleButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
doInit();
}
public int getOnColor() {
return onColor;
}
public void setOnColor(int onColor) {
this.onColor = onColor;
invalidate();
}
public int getOffColor() {
return offColor;
}
public void setOffColor(int offColor) {
this.offColor = offColor;
invalidate();
}
public boolean isToggleOn() {
return toggleOn;
}
public void setToggleOn(boolean toggleOn) {
this.toggleOn = toggleOn;
if (toggleOn) {
circleX = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, BTN_WIDTH - CIRCLE_RADIUS, r.getDisplayMetrics());
}else {
circleX = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, CIRCLE_RADIUS, r.getDisplayMetrics());
}
invalidate();
}
private void doInit() {
r = Resources.getSystem();
setOnClickListener(this);
circleX = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, BTN_WIDTH - CIRCLE_RADIUS, r.getDisplayMetrics());
}
@Override
protected void onDraw(Canvas canvas) {
if (toggleOn) {
Paint linePaint = new Paint();
linePaint.setColor(onColor);
linePaint.setAntiAlias(true);
linePaint.setStyle(Style.FILL);
linePaint.setStrokeWidth(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, LINE_WIDTH, r.getDisplayMetrics()));
canvas.drawRect(0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, (BTN_HEIGHT - LINE_WIDTH) / 2, r.getDisplayMetrics()), TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, BTN_WIDTH, r.getDisplayMetrics()), TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, (BTN_HEIGHT + LINE_WIDTH) / 2, r.getDisplayMetrics()), linePaint);
Paint circlePaint = new Paint();
circlePaint.setColor(onColor);
circlePaint.setAntiAlias(true);
canvas.drawCircle(circleX, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, BTN_HEIGHT / 2, r.getDisplayMetrics()),TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, CIRCLE_RADIUS, r.getDisplayMetrics()), circlePaint);
} else {
Paint circlePaint = new Paint();
circlePaint.setColor(offColor);
circlePaint.setAntiAlias(true);
canvas.drawCircle(circleX,TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, BTN_HEIGHT / 2, r.getDisplayMetrics()),TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, CIRCLE_RADIUS, r.getDisplayMetrics()), circlePaint);
Paint linePaint = new Paint();
linePaint.setColor(offColor);
linePaint.setAntiAlias(true);
linePaint.setStyle(Style.FILL);
linePaint.setStrokeWidth(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, LINE_WIDTH, r.getDisplayMetrics()));
canvas.drawRect(0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, (BTN_HEIGHT - LINE_WIDTH) / 2, r.getDisplayMetrics()),TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, BTN_WIDTH, r.getDisplayMetrics()), TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, (BTN_HEIGHT + LINE_WIDTH) / 2, r.getDisplayMetrics()), linePaint);
}
}
@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);
if (widthMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.AT_MOST{
widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, BTN_WIDTH, r.getDisplayMetrics());
widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
}
if (heightMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.AT_MOST) {heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, BTN_HEIGHT,r.getDisplayMetrics());
heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public void onClick(View v) {
if (changeCompelete) {
if (toggleOn) {
toggleOn = false;
}else {
toggleOn = true;
}
changeCompelete = false;
task = new TimerTask() {
@Override
public void run() {
if (toggleOn) {
circleX++;
}else {
circleX--;
}
handler.sendEmptyMessage(1111);
if (circleX == TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, BTN_WIDTH - CIRCLE_RADIUS, r.getDisplayMetrics()) || circleX ==TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, CIRCLE_RADIUS, r.getDisplayMetrics())) {
changeCompelete = true;
timer.cancel();
}
}
};
timer = new Timer();
timer.schedule(task, 0, 2);
if (listener != null)
listener.onToggle(toggleOn);
}
}
public interface OnToggleChanged {
public void onToggle(boolean on);
}
public void setOnToggleChanged(OnToggleChanged onToggleChanged) {
listener = onToggleChanged;
}
}
这是整个自定义开关按钮的全部代码,其实非常简单,我来给大家解释一下具体思路,首先我们规定好按钮的宽度和高度,也就是代码中BTN_WIDTH、BTN_HEIGHT这两个值,当然这两个值你可以设置成任意值,并不一定是和我一样,然后是圆的半径CIRCLE_RADIUS。整个按钮其实是通过画一个直线和圆组合而成,而圆心的X轴坐标在最左边就是CIRCLE_RADIUS,在最右边是BTN_WIDTH-CIRCLE_RADIUS。然后我们想实现一个开关变换的动态效果时就不断改变圆心的X轴坐标的位置并不断对圆进行重绘,从而造成这么一种假象,好像圆在不断的移动。在开关切换时我们变换画笔的颜色,这些颜色当然也可以任意设置,整个思路大致就是这样。下面我们来继续来对它进行使用,就和使用Android自带的控件一样进行使用。
布局文件activity_main.xml:
主界面MainActivity.java:
public class MainActivity extends Activity{
private ToggleButton toggleButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toggleButton = (ToggleButton) findViewById(R.id.toggle_button);
toggleButton.setOnToggleChanged(new ToggleButton.OnToggleChanged() {
@Override
public void onToggle(boolean on) {
Toast.makeText(getApplicationContext(), "是否开启" + on, Toast.LENGTH_SHORT).show();
}
});
}
}
运行之后效果如上图所示,到这里自定义开关按钮就介绍完了。下面为了使这个控件有更多效果设置的空间,我再来和大家谈谈如何为自定义控件设置自定义属性,就以本文中的这个控件为例,我们为其加上一些自定义属性。
自定义控件的自定义属性
添加自定义属性文件attrs.xml(该文件放在项目的values目录下):
上述文件定义的自定义属性对应MainActivity.java中的如下几个变量:
private int onColor = Color.parseColor("#4ebb7f");// 开启颜色
private int offColor = Color.parseColor("#dadbda");// 关闭颜色
private int BTN_WIDTH = 40;// 宽度
private int BTN_HEIGHT = 30;// 高度
private int CIRCLE_RADIUS = 8;// 圆的半径
private int LINE_WIDTH = 2;// 直线高度
现在我们可以在布局文件中直接为自定义的开关控件设置这些属性,我就随便设置了一些值,更改后的布局文件activity_main.xml如下:
这里要注意在布局文件中加上:
xmlns:app="http://schemas.android.com/apk/res-auto"
加上了这一句代码才能使用自定义属性,并且为其命名为app,当然你也可以命名为其他的,然后就可以为自定义控件添加自定义属性了,你也可以为属性设置其他值。注意,虽然在布局文件中为控件设置了自定义属性,但这还不够,我门还需要在自定义控件的初始化中去获取这些值,所以我们更改上面的ToggleButton.java文件中的部分代码如下:
private void doInit(Context context, AttributeSet attrs, int defStyleAttr) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ToggleButton, defStyleAttr, 0);
onColor = a.getColor(R.styleable.ToggleButton_onColor, Color.parseColor("#4ebb7f"));
offColor = a.getColor(R.styleable.ToggleButton_offColor, Color.parseColor("#dadbda"));
BTN_WIDTH = a.getInteger(R.styleable.ToggleButton_btnWidth, 40);
BTN_HEIGHT = a.getInteger(R.styleable.ToggleButton_btnHeight, 30);
CIRCLE_RADIUS = a.getInteger(R.styleable.ToggleButton_circleRadius, 8);
LINE_WIDTH = a.getInteger(R.styleable.ToggleButton_lineHeight, 2);
r = Resources.getSystem();
setOnClickListener(this);
circleX = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, BTN_WIDTH - CIRCLE_RADIUS, r.getDisplayMetrics());
}
这里只是在doInit()方法中加入了几句代码,然后为该方法加入相应参数就可以了,至此自定义属性的工作就完成了,然后运行一下,来看一下运行效果吧:
ok,到这里所以介绍就结束了,代码中可能有不足之处,欢迎大家批评指正,共同学习!