簡介
由于項目開發需要,所以做了一個自定義的開關按鈕,樣式類似于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:
<?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()方法中加入了幾句代碼,然后為該方法加入相應參數就可以了,至此自定義屬性的工作就完成了,然后運行一下,來看一下運行效果吧:
ok,到這里所以介紹就結束了,代碼中可能有不足之處,歡迎大家批評指正,共同學習!