自定义控件实用控件自定义view

Android自定义开关按钮ToggleButton

2016-07-13  本文已影响3609人  iflymoon

简介

由于项目开发需要,所以做了一个自定义的开关按钮,样式类似于Android5.0中开关按钮,也有开启关闭的切换效果,在这里和大家分享一下,先看一下效果图如下:

togglebutton.gif

看过效果图以后我们废话不多说,直接来看一下实现代码。

实现

这里主要是写了一个类去继承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:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"    
  android:layout_height="match_parent">    
  <me.my.togglebutton.ToggleButton        
    android:layout_width="wrap_content"        
    android:layout_height="wrap_content"        
    android:layout_centerInParent="true"        
    android:id="@+id/toggle_button"/>
</RelativeLayout>

主界面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目录下):

<?xml version="1.0" encoding="utf-8"?>
<resources>    
  <declare-styleable name="ToggleButton">        
    <attr name="onColor" format="color" />        
    <attr name="offColor" format="color" />        
    <attr name="btnWidth" format="integer" />        
    <attr name="btnHeight" format="integer" />        
    <attr name="circleRadius" format="integer" />        
    <attr name="lineHeight" format="integer" />    
  </declare-styleable>
</resources>

上述文件定义的自定义属性对应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如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto" 
  android:layout_width="match_parent"    
  android:layout_height="match_parent">    
  <me.my.togglebutton.ToggleButton        
    android:layout_width="wrap_content"        
    android:layout_height="wrap_content"        
    android:layout_centerInParent="true"        
    app:offColor="#ff0000"        
    app:onColor="#00ff00"  
    app:btnWidth="80"
    app:btnHeight="60"
    app:circleRadius="15"
    app:lineHeight="4"      
    android:id="@+id/toggle_button"/>
</RelativeLayout>

这里要注意在布局文件中加上:

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()方法中加入了几句代码,然后为该方法加入相应参数就可以了,至此自定义属性的工作就完成了,然后运行一下,来看一下运行效果吧:

togglebutton2.gif

ok,到这里所以介绍就结束了,代码中可能有不足之处,欢迎大家批评指正,共同学习!

上一篇下一篇

猜你喜欢

热点阅读