Android自定義控件之開(kāi)關(guān)按鈕學(xué)習(xí)筆記分享
今天來(lái)講講自定義單個(gè)控件,就拿開(kāi)關(guān)按鈕來(lái)講講,相信大家見(jiàn)了非常多這樣的了,先看看效果:

我們可以看到一個(gè)很常見(jiàn)的開(kāi)關(guān)按鈕,那就來(lái)分析分析。
首先:
這是由兩張圖片構(gòu)成:
①一張為有開(kāi)和關(guān)的背景圖片
②一張為控制開(kāi)和關(guān)的滑動(dòng)按鈕
第一步:
寫(xiě)個(gè)類(lèi)繼承View,并重寫(xiě)幾個(gè)方法:
第一個(gè)為構(gòu)造函數(shù),重寫(xiě)一個(gè)參數(shù)的函數(shù)和兩個(gè)參數(shù)的函數(shù)就夠了,因?yàn)閮蓚€(gè)參數(shù)的函數(shù)能夠使用自定義屬性
第二個(gè)為控制控件的大小–>protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {}
第三個(gè)為繪制控件的方法–>protected void onDraw(Canvas canvas) {}
第二步:
將用戶(hù)指定的兩張圖片加載進(jìn)來(lái),這里使用自定義屬性加載, 在values目錄下新建attrs.xml,在xml文件中指定自定義屬性名和自定義屬性的字段及值類(lèi)型(即背景圖和滑塊圖)即可:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="switchView_attrs">
<attr name="background" format="reference"></attr>
<attr name="slide" format="reference"></attr>
</declare-styleable>
</resources>
各個(gè)字段的含義我在這就不講了,不懂的就去看看前幾篇《Android開(kāi)發(fā)筆記之自定義組合控件》有講過(guò),寫(xiě)好就只需在構(gòu)造函數(shù)中加載進(jìn)來(lái)
public SwitchView(Context context, AttributeSet attrs) {
super(context, attrs);
//拿到自定義屬性
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.switchView_attrs);
//拿到自定義字段的值
Drawable switchBackground = ta.getDrawable(R.styleable.switchView_attrs_background);
Drawable switchView_slide = ta.getDrawable(R.styleable.switchView_attrs_slide);
//把值設(shè)置到相應(yīng)組件上
backgroundBitmap = convertDrawable2BitmapSimple(switchBackground);
switchSlide = convertDrawable2BitmapSimple(switchView_slide);
}
不過(guò)要注意的是:
因?yàn)閺淖远x屬性中取到的是Drawable對(duì)象,而我們要的是一個(gè)Bitmap對(duì)象,所以我們先得把Drawable對(duì)象轉(zhuǎn)成Bitmap對(duì)象,其實(shí)有很多種方法來(lái)轉(zhuǎn),這里就介紹種最簡(jiǎn)單的方法,借助與BitmapDrawable類(lèi):
//將Drawable轉(zhuǎn)成Bitmap
public Bitmap convertDrawable2BitmapSimple(Drawable drawable) {
BitmapDrawable bd= (BitmapDrawable)drawable;
return bd.getBitmap();
}
第三步:
onMeasure方法來(lái)控制控件的大小,我們可以看到這個(gè)開(kāi)關(guān)按鈕的大小就跟背景的大小一樣大,只需要設(shè)置為背景的大?。?/p>
//控制控件的大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (backgroundBitmap != null) {
//控件大小設(shè)置為背景的大小
setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight());
}else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
第四步:
既然這個(gè)開(kāi)關(guān)按鈕需要通過(guò)點(diǎn)擊或移動(dòng)來(lái)控制控件的開(kāi)和關(guān),所以就需要實(shí)現(xiàn)onTouchEvent方法,當(dāng)然應(yīng)該有三個(gè)事件會(huì)觸發(fā):按下、移動(dòng)和抬起的時(shí)候,每次點(diǎn)擊、移動(dòng)或抬起都需要重繪,我們先來(lái)分析下滑塊的狀態(tài)有哪些(應(yīng)該有四種狀態(tài))一開(kāi)始默認(rèn)狀態(tài)為空:
1.點(diǎn)擊的時(shí)候
2.移動(dòng)的時(shí)候
3.抬起的時(shí)候
4.空的時(shí)候(即什么都沒(méi)干的時(shí)候)
先分析下點(diǎn)擊的時(shí)候的情況:
①當(dāng)按下或移動(dòng)的坐標(biāo)大于滑塊寬度一半時(shí)將滑塊右移
②當(dāng)按下或移動(dòng)的坐標(biāo)小于滑塊寬度一半時(shí)滑塊不動(dòng)
注:防止滑塊移至背景外面,最大是滑塊右邊和背景右邊對(duì)齊(即最大離左邊為背景寬度-滑塊寬度)

再來(lái)看看移動(dòng)的時(shí)候的情況應(yīng)該是和點(diǎn)擊的時(shí)候是一樣的。
再來(lái)分析抬起的時(shí)候的情況:
①如果開(kāi)關(guān)狀態(tài)是打開(kāi)的就將滑塊移動(dòng)至右邊
②如果開(kāi)關(guān)狀態(tài)是關(guān)閉的就將滑塊移動(dòng)至左邊
那怎么判斷什么時(shí)候是打開(kāi)狀態(tài)和關(guān)閉狀態(tài)呢??
①抬起的坐標(biāo)大于背景寬度一半的時(shí)候設(shè)為打開(kāi)狀態(tài)
②抬起的坐標(biāo)小于背景寬度坐標(biāo)一 半的時(shí)候設(shè)為關(guān)閉狀態(tài)

再來(lái)分析下空的時(shí)候,可以發(fā)現(xiàn)它和抬起的時(shí)候的情況是一樣的。
第五步:
在onDraw方法中將背景和滑塊繪制出來(lái)。剛才分析了onTouchEvent方法,這次是一樣的,滑塊的四個(gè)狀態(tài)分別處理,前面onTouchEvent方法中滑塊的狀態(tài)改變,然后通過(guò)invalidate()方法來(lái)通知系統(tǒng)重繪。
第六步:
我們做這個(gè)自定義控件是為了讓用戶(hù)使用的,現(xiàn)在這個(gè)是沒(méi)有什么用的,用戶(hù)用不了,所以可以通過(guò)設(shè)置監(jiān)聽(tīng)器來(lái)對(duì)外提供接口。
/**
* switchView開(kāi)關(guān)監(jiān)聽(tīng)接口
*
* */
interface OnSwitchChangedListener {
public void onSwitchChange(boolean isOpen);
}
/**
* 設(shè)置 switchView狀態(tài)監(jiān)聽(tīng)
* */
public void setOnChangeListener(OnSwitchChangedListener listener) {
switchListener = listener;
}
這個(gè)監(jiān)聽(tīng)器中的boolean值需要賦值,那在什么時(shí)候賦值呢,應(yīng)該是在抬起或空的狀態(tài)的時(shí)候給它賦值,因?yàn)槟莻€(gè)時(shí)候才真正確定開(kāi)關(guān)按鈕是打開(kāi)的還是關(guān)閉的。
第七步:
到這一步就是來(lái)使用了,在布局文件中把自定義的這個(gè)控件定義出來(lái)
<com.example.custom.SwitchView
minguo:background="@drawable/switch_background"
minguo:slide="@drawable/slide_button_background"
android:id="@+id/switchView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

使用定義好的View應(yīng)該都會(huì)用了,不會(huì)的去看看《android開(kāi)發(fā)筆記之自定義組合控件》。
核心代碼:
布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:minguo="http://schemas.android.com/apk/res/com.example.custom"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.custom.MainActivity" >
<com.example.custom.SwitchView
minguo:background="@drawable/switch_background"
minguo:slide="@drawable/slide_button_background"
android:id="@+id/switchView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
自定義View: SwitchView.java
/**
* 自定義開(kāi)關(guān)按鈕
* @author Administrator
*
*/
public class SwitchView extends View {
//背景圖片和滑塊圖片
private Bitmap backgroundBitmap,switchSlide;
//畫(huà)筆
private Paint paint;
//得到的x坐標(biāo)(點(diǎn)擊、移動(dòng)、抬起)
private float currentX;
//判斷開(kāi)關(guān)是否打開(kāi)的標(biāo)記位
private boolean isOpen = false;
//開(kāi)關(guān)打開(kāi)與關(guān)閉的監(jiān)聽(tīng)器
private OnSwitchChangedListener switchListener;
//滑塊的四種狀態(tài)
public static final int STATE_DOWN = 1; //按下的時(shí)候
public static final int STATE_MOVE = 2; //移動(dòng)的時(shí)候
public static final int STATE_UP = 3; //抬起的時(shí)候
public static final int STATE_NONE = 0; //空的時(shí)候(即什么都沒(méi)干的時(shí)候)
//標(biāo)記狀態(tài)(默認(rèn)為空狀態(tài))
private int state = STATE_NONE;
public SwitchView(Context context) {
super(context,null);
}
public SwitchView(Context context, AttributeSet attrs) {
super(context, attrs);
//拿到自定義屬性
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.switchView_attrs);
//拿到自定義字段的值
Drawable switchBackground = ta.getDrawable(R.styleable.switchView_attrs_background);
Drawable switchView_slide = ta.getDrawable(R.styleable.switchView_attrs_slide);
//把值設(shè)置到相應(yīng)組件上
backgroundBitmap = convertDrawable2BitmapSimple(switchBackground);
switchSlide = convertDrawable2BitmapSimple(switchView_slide);
}
//將Drawable轉(zhuǎn)成Bitmap
public Bitmap convertDrawable2BitmapSimple(Drawable drawable) {
BitmapDrawable bd = (BitmapDrawable)drawable;
return bd.getBitmap();
}
//控制控件的大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (backgroundBitmap != null) {
//控件大小設(shè)置為背景的大小
setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight());
}else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
//繪制控件的樣式
@Override
protected void onDraw(Canvas canvas) {
//背景為空的時(shí)候才將背景繪制
if (backgroundBitmap != null) {
paint = new Paint();
//第一個(gè)參數(shù)表示需要畫(huà)的Bitmap
//第二個(gè)參數(shù)表示Bitmap左邊離控件的左邊距
//第三個(gè)參數(shù)表示Bitmap上邊離控件的上邊距
//第四個(gè)參數(shù)表示畫(huà)筆
canvas.drawBitmap(backgroundBitmap, 0, 0, paint);
}
switch (state) {
case STATE_DOWN: //按下和移動(dòng)的觸發(fā)事件都一樣,都是將滑塊移動(dòng)
case STATE_MOVE:
//當(dāng)按下或移動(dòng)的坐標(biāo)大于滑塊寬度一半時(shí)將滑塊右移
if (currentX > switchSlide.getWidth()/2f) {
//讓滑塊向右滑動(dòng)(重新繪制滑塊的位置)
float left = currentX - switchSlide.getWidth()/2f;
//防止滑塊移至背景外面,最大是滑塊右邊和背景右邊對(duì)齊(即最大離左邊為背景寬度-滑塊寬度)
float maxLeft = backgroundBitmap.getWidth() - switchSlide.getWidth();
if (left > maxLeft) {
left = maxLeft;
}
canvas.drawBitmap(switchSlide, left, 0, paint);
//當(dāng)按下或移動(dòng)的坐標(biāo)小于滑塊寬度一半時(shí)滑塊不動(dòng)
}else if (currentX < switchSlide.getWidth()/2f) {
//讓滑塊不動(dòng)就可以了
canvas.drawBitmap(switchSlide, 0, 0, paint);
}
break;
case STATE_NONE: //空或抬起的時(shí)候?qū)⒒瑝K至于左邊或右邊
case STATE_UP:
//如果是打開(kāi)的將滑塊移動(dòng)至右邊,并將打開(kāi)狀態(tài)傳至監(jiān)聽(tīng)器
if (isOpen) {
if (switchListener != null) {
switchListener.onSwitchChange(true);
}
canvas.drawBitmap(switchSlide,
backgroundBitmap.getWidth() - switchSlide.getWidth(), 0, paint);
//如果是關(guān)閉的將滑塊至于左邊,并將關(guān)閉狀態(tài)傳至監(jiān)聽(tīng)器
}else {
if (switchListener != null) {
switchListener.onSwitchChange(false);
}
canvas.drawBitmap(switchSlide, 0, 0, paint);
}
break;
default:
break;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
currentX = event.getX();
//將標(biāo)記位修改成按下的狀態(tài)
state = STATE_DOWN;
//通知系統(tǒng)重新繪制界面
invalidate();//在主線(xiàn)程
// postInvalidate();//在子線(xiàn)程
break;
case MotionEvent.ACTION_MOVE:
currentX = event.getX();
//將標(biāo)記位修改為移動(dòng)狀態(tài)
state = STATE_MOVE;
invalidate();
break;
case MotionEvent.ACTION_UP:
currentX = event.getX();
//將標(biāo)記為修改為抬起狀態(tài)
state = STATE_UP;
//抬起的坐標(biāo)大于背景寬度一半的時(shí)候設(shè)為打開(kāi)狀態(tài)
if (currentX > backgroundBitmap.getWidth()/2f) {
//滑塊在右邊,開(kāi)啟
isOpen = true;
//抬起的坐標(biāo)小于背景寬度坐標(biāo)一 半的時(shí)候設(shè)為關(guān)閉狀態(tài)
}else if (currentX < backgroundBitmap.getWidth()) {
//滑塊在左邊,關(guān)閉
isOpen = false;
}
invalidate();
break;
}
return true;
}
/**
* switchView開(kāi)關(guān)監(jiān)聽(tīng)接口
*
* */
interface OnSwitchChangedListener {
public void onSwitchChange(boolean isOpen);
}
/**
* 設(shè)置 switchView狀態(tài)監(jiān)聽(tīng)
* */
public void setOnChangeListener(OnSwitchChangedListener listener) {
switchListener = listener;
}
}
MainActivity.java
public class MainActivity extends Activity {
private SwitchView switchView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
switchView = (SwitchView) findViewById(R.id.switchView);
switchView.setOnChangeListener(new OnSwitchChangedListener() {
@Override
public void onSwitchChange(boolean isOpen) {
if (isOpen) {
//打開(kāi)開(kāi)關(guān)的時(shí)候的邏輯
Toast.makeText(MainActivity.this, "開(kāi)關(guān)打開(kāi)了", Toast.LENGTH_LONG).show();
}else {
//關(guān)閉開(kāi)關(guān)的時(shí)候的邏輯
Toast.makeText(MainActivity.this, "開(kāi)關(guān)關(guān)閉了", Toast.LENGTH_LONG).show();
}
}
});
}
}
大家看起來(lái)這么簡(jiǎn)單的一個(gè)寫(xiě)了這么多,其實(shí)我們學(xué)習(xí)這個(gè)不是為了寫(xiě)這個(gè),比這個(gè)好的開(kāi)源多的是,而是為了學(xué)習(xí)這種思路與思維,大家趕緊試試吧!
謝謝大家的閱讀,也希望大家可以繼續(xù)關(guān)注腳本之家的更多精彩內(nèi)容。
相關(guān)文章
Android自定義View仿支付寶芝麻信用分儀表盤(pán)
前幾天支付寶剛剛升級(jí)到v9.9,看了一眼里面的芝麻信用分,儀表盤(pán)挺好看的,所以想著來(lái)寫(xiě)一個(gè)這個(gè)版本的儀表盤(pán),不說(shuō)完全一模一樣,只是為了猜測(cè)支付寶在做這個(gè)的時(shí)候是如何設(shè)計(jì)的,在此記錄一下,有需要的可以參考借鑒。2016-09-09
http請(qǐng)求繞過(guò)Filter的實(shí)現(xiàn)實(shí)例
這篇文章主要介紹了http請(qǐng)求繞過(guò)Filter的實(shí)現(xiàn)實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-06-06
Android Activity之間的數(shù)據(jù)傳遞方法總結(jié)
這篇文章主要給大家總結(jié)介紹了關(guān)于Android Activity之間的數(shù)據(jù)傳遞方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)各位Android開(kāi)發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06
使用Composing?builds提升Android編譯速度
這篇文章主要介紹了使用Composing?builds提升Android編譯速度示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
Android 中 viewpager 滑動(dòng)指示器的實(shí)例代碼
本文通過(guò)實(shí)例代碼給大家介紹了android 中 viewpager 滑動(dòng)指示器,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-12-12
Android編程之在SD卡上進(jìn)行文件讀寫(xiě)操作實(shí)例詳解
這篇文章主要介紹了Android編程之在SD卡上進(jìn)行文件讀寫(xiě)操作的方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了Android的文件操作及針對(duì)SD卡的存取操作相關(guān)技巧,需要的朋友可以參考下2015-12-12
Android倒計(jì)時(shí)的開(kāi)始與停止 剩余時(shí)分秒的展示
這篇文章主要為大家詳細(xì)介紹了Android倒計(jì)時(shí)的開(kāi)始與停止,剩余時(shí)分秒的展示,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09

