在平时使用的手机的时候,我们经常会看到这样的按钮,用来改变某种状态,Android上叫ToggleButton,但是系统默认的按钮很难看。现在都流行滑动来改变状态,就像IOS上实现类似功能的按钮。最近在GitHub上看到了有个开源的项目,就研究了一下,实现了相同的功能,效果还是不错的。下面来介绍一下。
实现这个按钮是使用了自定义View,关于自定义View,之前也写过几篇博客,今天算是用来练练手。废话不多说,先来分析一下,需要哪些自定义的属性
1.按钮的颜色
2.按钮的形状,分为圆形和矩形
3.按钮的状态,即开或者关
定义三个属性,color_theme,shape,isOpen,贴一下XML文件
<resources>
<attr name="themeColor" format="color" />
<attr name="isOpen" format="boolean" />
<attr name="shape" format="integer" />
<declare-styleable name="SlideSwitch">
<attr name="themeColor" />
<attr name="isOpen" />
<attr name="shape" />
</declare-styleable>
</resources>
然后来分析一下实现流程,首先OnMeasure,分为矩形和圆形,在OnDraw方法中进行绘制,首先先绘制一个backRect作为整个按钮的滑动背景,然后根据形状在backRect上面是绘制矩形还是圆形。对于手指移动时按钮跟着滑动,可以定义一个frontRect_left变量来改变按钮和背景左边的距离,从而实现滑动。下面来具体实现一下。
onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = measureDimension(280,widthMeasureSpec);
int height = measureDimension(140, heightMeasureSpec);
if(shape == SHAPE_CIRCLE){
if(width <height)
width = height*2;
}
setMeasuredDimension(width, height);
initDrawingVal();
}
private int measureDimension(int i, int widthMeasureSpec) {
int result;
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
if(specMode == MeasureSpec.EXACTLY){
result =specSize;
}else{
result = i;
if(specMode == MeasureSpec.AT_MOST){
result = Math.min(result, specSize);
}
}
return result;
}
onDraw
一些初始化
private void initDrawingVal() {
int width = getMeasuredWidth();
int height= getMeasuredHeight();
backRect = new Rect(0,0,width,height);
min_left = RIM_SIZE;
if(shape == SHAPE_RECT)
max_left = width/2;
else
max_left = width-(height-2*RIM_SIZE);
if(isOpen){
frontRect_left = max_left;
alpha =255;
}else{
frontRect_left = RIM_SIZE;
alpha=0;
}
frontRect_left_begin = frontRect_left;
}
@Override
protected void onDraw(Canvas canvas) {
if(shape == SHAPE_RECT){
paint.setColor(Color.GRAY);
canvas.drawRect(backRect, paint);
paint.setColor(color_theme);
paint.setAlpha(alpha);
canvas.drawRect(backRect, paint);
frontRect = new Rect(frontRect_left,RIM_SIZE,frontRect_left+getMeasuredWidth()/2,getMeasuredHeight()-RIM_SIZE);
paint.setColor(Color.WHITE);
canvas.drawRect(frontRect, paint);
}else{
//圆形
int radius;
radius = backRect.height()/2-RIM_SIZE;
paint.setColor(Color.GRAY);
canvas.drawRoundRect(new RectF(backRect), radius, radius, paint);
paint.setColor(color_theme);
paint.setAlpha(alpha);
canvas.drawRoundRect(new RectF(backRect), radius, radius, paint);
frontRect = new Rect(frontRect_left, RIM_SIZE, frontRect_left+backRect.height()-2*RIM_SIZE, backRect.height()-RIM_SIZE);
paint.setColor(Color.WHITE);
canvas.drawRoundRect(new RectF(frontRect), radius, radius, paint);
}
}
监听onTouch事件
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
eventStartX = (int)event.getRawX();
break;
case MotionEvent.ACTION_MOVE:
eventLastX = (int)event.getRawX();
diffX = eventLastX-eventStartX;
int tem = diffX+frontRect_left_begin;
tem = (tem>max_left?max_left:tem);
tem = tem<min_left?min_left:tem;
if(tem >=min_left&&tem<=max_left){
frontRect_left = tem;
alpha = (int)(255*(float)tem/(float)max_left);
invalidateView();
}
break;
case MotionEvent.ACTION_UP:
int wholeX = (int)(event.getX()-eventStartX);
frontRect_left_begin = frontRect_left;
boolean toRight = frontRect_left>max_left/2?true:false;
//点击事件,改变状态
if(Math.abs(wholeX)<3){
toRight =!toRight;
}
moveToDest(toRight);
break;
}
return true;
}
当我们手指up的时候,滑块需要自己滑动到制定是位置并且动态的改变alpha,可以开一个线程,动态的改变frontRect_left实现,并通过handle来通知listener改变。
private void moveToDest(final boolean toRight) {
final Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
if(msg.what ==1){
listener.open();
}else{
listener.close();
}
}
};
new Thread(new Runnable() {
@Override
public void run() {
if(toRight){
while(frontRect_left <=max_left){
alpha = (int)(255*(float)frontRect_left/(float)max_left);
invalidateView();
frontRect_left+=3;
try {
Thread.sleep(3);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
alpha = 255;
frontRect_left = max_left;
isOpen = true;
if(listener !=null)
handler.sendEmptyMessage(1);
frontRect_left_begin = max_left;
}else{
while(frontRect_left >=min_left){
alpha = (int)(255*(float)frontRect_left/(float)max_left);
invalidateView();
frontRect_left -=3;
try {
Thread.sleep(3);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
alpha = 0;
frontRect_left = min_left;
isOpen = false;
if(listener!=null)
handler.sendEmptyMessage(0);
frontRect_left_begin = min_left;
}
}
})
.start();
}
然后暴漏出几个方法给外部调用。
public void setState(boolean isopen){
this.isOpen = isopen;
invalidateView();
initDrawingVal();
if(listener!=null){
listener.open();
}else{
listener.close();
}
}
public void setShapeType(int shapeType){
this.shape = shapeType;
}
再定义一个listener来监听状态的改变
public interface SlideListener{
public void open();
public void close();
}
public void setSlideListener(SlideListener listener){
this.listener = listener;
}
最后贴一下mainactivity的代码
public class MainActivity extends Activity implements SlideListener {
TextView txt;
SlideSwitch slide;
SlideSwitch slide2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
slide = (SlideSwitch) findViewById(R.id.swit);
slide2 = (SlideSwitch) findViewById(R.id.swit2);
slide.setState(false);
txt = (TextView) findViewById(R.id.txt);
slide.setSlideListener(this);
}
@Override
public void open() {
// TODO Auto-generated method stub
txt.setText(" is opend ");
}
@Override
public void close() {
// TODO Auto-generated method stub
txt.setText(" is close ");
}
}