Android自定義開關按鈕ToggleButton

簡介

由于項目開發需要,所以做了一個自定義的開關按鈕,樣式類似于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,到這里所以介紹就結束了,代碼中可能有不足之處,歡迎大家批評指正,共同學習!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 內容抽屜菜單ListViewWebViewSwitchButton按鈕點贊按鈕進度條TabLayout圖標下拉刷新...
    皇小弟閱讀 46,901評論 22 665
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,466評論 25 708
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,251評論 4 61
  • 如果這個世界真有奇跡,那也只是努力的另外一個名字。遇不到真愛,是因為你活得還不夠漂亮,只有當你的臉能夠閃爍出迷人的...
    王莉Lydia閱讀 277評論 0 1
  • 時光如梭,一轉眼你長大成了一個男子漢,伴隨著你的成長,我們有過辛苦,煩惱,但更多是感受了一家人在一起快樂和...
    勿忘心安whh閱讀 444評論 0 2