欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android自定義View實(shí)現(xiàn)開關(guān)按鈕

 更新時(shí)間:2016年11月02日 15:04:56   作者:Jaynm  
android 自定義view知識(shí)非常廣泛,難以讓人掌握。但是也是andoroid進(jìn)階學(xué)習(xí)的必經(jīng)之路。下面通過本文給大家介紹Android自定義View實(shí)現(xiàn)開關(guān)按鈕的知識(shí),非常不錯(cuò),感興趣的朋友一起看看吧

 前言:Android自定義View對(duì)于剛?cè)腴T乃至工作幾年的程序員來說都是非??謶值?,但也是Android進(jìn)階學(xué)習(xí)的必經(jīng)之路,平時(shí)項(xiàng)目中經(jīng)常會(huì)有一些苛刻的需求,我們可以在GitHub上找到各種各樣的效果,能用則用,不能用自己花功夫改改也能草草了事。不過隨著工作經(jīng)驗(yàn)和工作性質(zhì),越來越覺得自定義View是時(shí)候有必要自己花點(diǎn)功夫研究一下。

一、經(jīng)過這兩天的努力,自己也嘗試著寫了一個(gè)Demo,效果很簡(jiǎn)單,就是開關(guān)按鈕的實(shí)現(xiàn)。

可能有的人會(huì)說這效果so easy,找UI切三張圖就完事了,何必大費(fèi)周折自定義。你說的沒錯(cuò),不過這里只是用來學(xué)習(xí)自定義View來展示這么一個(gè)常見案例。

自定義View實(shí)現(xiàn)開關(guān)按鈕

自定義控件

1.為什么自定義View?

Android自身帶的控件不能滿足需求, 需要根據(jù)自己的需求定義控件.

2.Android 的界面繪制流程?

這里寫圖片描述 

onMeasure()——onLayout()——onDraw()方法都在Activity生命周期的onResume()方法之后執(zhí)行。

3.Android自定義View的方式?

集成View:View流程

onMeasure() (在這個(gè)方法里指定自己的寬高) -> onDraw() (繪制自己的內(nèi)容)

集成ViewGroup:ViewGroup流程

onMeasure() (指定自己的寬高, 所有子View的寬高)-> onLayout() (擺放所有子View) -> onDraw() (繪制內(nèi)容)

自定義View實(shí)現(xiàn)開關(guān)按鈕步驟:

寫個(gè)類繼承View,

拷貝包含包名的全路徑到xml中,

界面中找到該控件, 設(shè)置初始信息,

根據(jù)需求繪制界面內(nèi)容,

響應(yīng)用戶的觸摸事件,

創(chuàng)建一個(gè)狀態(tài)更新監(jiān)聽.

1.自定義ToggleView集成View,并且重新三個(gè)構(gòu)造方法。

注意:構(gòu)造方法為什么要重寫三個(gè)?

ToggleView(Context context)一個(gè)參數(shù)的構(gòu)造方法是用于代碼創(chuàng)建控件時(shí)調(diào)用的

ToggleView(Context context, AttributeSet attrs)用于在xml里使用, 可指定自定義屬性

ToggleView(Context context, AttributeSet attrs, int defStyle)用于在xml里使用, 可指定自定義屬性, 如果指定了樣式, 則走此構(gòu)造函數(shù)

我們?cè)赬ML中定義了背景圖片、開關(guān)按鈕圖片和開關(guān)默認(rèn)狀態(tài),要獲取在XML文件定義的屬性就在包含三個(gè)參數(shù)的構(gòu)造方法里用TypedArray類來獲取。

在attrs.xml聲明節(jié)點(diǎn)declare-styleable

<declare-styleable name="ToggleView">
<attr name="switch_background" format="reference" />
<attr name="slide_button" format="reference" />
<attr name="switch_state" format="boolean" />
</declare-styleable>
/**
* 用于在xml里使用, 可指定自定義屬性, 如果指定了樣式, 則走此構(gòu)造函數(shù)
* @param context
* @param attrs
* @param defStyle
*/
public ToggleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// 獲取配置的自定義屬性
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ToggleView, defStyle, 0);
int switchBackgroundResource = a.getResourceId(R.styleable.ToggleView_switch_background, -1);
int slideButtonResource = a.getResourceId(R.styleable.ToggleView_slide_button, -1);
mSwitchState = a.getBoolean(R.styleable.ToggleView_switch_state, false);
//獲取背景圖片和開關(guān)圖片后設(shè)置圖片,便于在onMeasure()方法中設(shè)置View寬和高,防止Null
setSwitchBackgroundResource(switchBackgroundResource);
setSlideButtonResource(slideButtonResource);
init();
}

2.自定義ToggleView集成View后,在XML文件里不要忘記添加命名空間

“xmlns:cb=”http://schemas.android.com/apk/res-auto””

然后將自定義View的完整路徑粘貼到XML中,這點(diǎn)類似于Android v4包下的ViewPager控件

以下便是demo中XML文件代碼:

設(shè)置開關(guān)背景圖片

- cb:switch_background=”@drawable/switch_background”

設(shè)置開關(guān)按鈕圖片

- cb:slide_button=”@drawable/slide_button”

設(shè)置開關(guān)默認(rèn)狀態(tài)

- cb:switch_state=”false”

這里寫圖片描述

3.界面中找到該控件, 設(shè)置初始信息

在Activity中通過findViewById方法找到自定義的View控件,和系統(tǒng)的組件操作沒區(qū)別。

private ToggleView toggleView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toggleView = (ToggleView) findViewById(R.id.toggleView);
// toggleView.setSwitchBackgroundResource(R.drawable.switch_background);
// toggleView.setSlideButtonResource(R.drawable.slide_button);
// toggleView.setSwitchState(true);
// 
// 設(shè)置開關(guān)更新監(jiān)聽
toggleView.setOnSwitchStateUpdateListener(new ToggleView.OnSwitchStateUpdateListener(){
@Override
public void onStateUpdate(boolean state) {
Toast.makeText(getApplicationContext(), "state: " + state, Toast.LENGTH_SHORT).show();
}
});
}

4.根據(jù)需求繪制界面內(nèi)容

已經(jīng)通過onMeasure()方法設(shè)置了View的寬度和高度,下面開始繪制的操作就全部在onDraw()方法中進(jìn)行,onDraw(Canvas canvas) 方法中canvas參數(shù):畫布, 畫板. 在上邊繪制的內(nèi)容都會(huì)顯示到界面上.

// 根據(jù)開關(guān)狀態(tài)boolean, 直接設(shè)置圖片位置
if(mSwitchState){// 開
int newLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth();
canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint);
}else {// 關(guān)
canvas.drawBitmap(slideButtonBitmap, 0, 0, paint);
}

開關(guān)打開時(shí),開關(guān)按鈕的位置在開關(guān)背景中的位置計(jì)算:

int newLeft = switchBackgroupBitmap.getWidth() - 

slideButtonBitmap.getWidth(); 背景的寬度-按鈕的寬度就是當(dāng)前開關(guān)按鈕所在的X軸上的位置點(diǎn)

開關(guān)關(guān)閉時(shí),當(dāng)前開關(guān)按鈕所在的X軸上的位置點(diǎn)=0

5.響應(yīng)用戶的觸摸事件

在完成以上3步操作后,你會(huì)發(fā)現(xiàn),只有在第一次進(jìn)入后XML初始化默認(rèn)開關(guān)狀態(tài)的boolean值才會(huì)有變化,此后點(diǎn)擊是沒有任何效果的,這個(gè)時(shí)候我們就要想辦法監(jiān)聽手勢(shì)事件,重寫onTouchEvent(MotionEvent event)方法,相信大多數(shù)朋友對(duì)這個(gè)方法并不陌生。

MotionEvent有三種狀態(tài):

MotionEvent.ACTION_DOWN: //按下屏幕
MotionEvent.ACTION_MOVE: //手指在屏幕上移動(dòng)
MotionEvent.ACTION_UP //離開屏幕

當(dāng)前需要考慮的問題是:

當(dāng)手指按下屏幕后MotionEvent.ACTION_DOWN(在當(dāng)前開關(guān)背景View中)開關(guān)的X軸位置應(yīng)該移動(dòng)到手指按下的位置;

當(dāng)手指在屏幕上移動(dòng)MotionEvent.ACTION_MOVE(在當(dāng)前開關(guān)背景View中)開關(guān)按鈕X軸應(yīng)該隨著手指移動(dòng)的位置改變;

當(dāng)手指離開屏幕后MotionEvent.ACTION_UP(在當(dāng)前開關(guān)背景View中)開關(guān)按鈕應(yīng)該判斷手指離開的位置是否是當(dāng)前背景的一半位置,如果X軸位置大于View背景寬度的1/2、那么應(yīng)該處于打開狀態(tài),如果X軸位置小于View背景寬度的1/2,那么應(yīng)該處于關(guān)閉狀態(tài)。

如圖所示:

這里寫圖片描述

private OnSwitchStateUpdateListener onSwitchStateUpdateListener;
// 重寫觸摸事件, 響應(yīng)用戶的觸摸.
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isTouchMode = true;
System.out.println("event: ACTION_DOWN: " + event.getX());
currentX = event.getX();
break;
case MotionEvent.ACTION_MOVE:
System.out.println("event: ACTION_MOVE: " + event.getX());
currentX = event.getX();
break;
case MotionEvent.ACTION_UP:
isTouchMode = false;
System.out.println("event: ACTION_UP: " + event.getX());
currentX = event.getX();
float center = switchBackgroupBitmap.getWidth() / 2.0f;
// 根據(jù)當(dāng)前按下的位置, 和控件中心的位置進(jìn)行比較. 
boolean state = currentX > center;
// 如果開關(guān)狀態(tài)變化了, 通知界面. 里邊開關(guān)狀態(tài)更新了.
if(state != mSwitchState && onSwitchStateUpdateListener != null){
// 把最新的boolean, 狀態(tài)傳出去了
onSwitchStateUpdateListener.onStateUpdate(state);
}
mSwitchState = state;
break;
default:
break;
}
// 重繪界面
invalidate(); // 會(huì)引發(fā)onDraw()被調(diào)用, 里邊的變量會(huì)重新生效.界面會(huì)更新
return true; // 消費(fèi)了用戶的觸摸事件, 才可以收到其他的事件.
}

注意:

以上監(jiān)聽onTouchEvent(MotionEvent

event)方法后還存在一個(gè)問題,不知道大家有沒有發(fā)現(xiàn),我們沒有設(shè)置開關(guān)按鈕的邊界值,什么意思呢?就是手指滑動(dòng)的時(shí)候左邊和右邊可以畫出當(dāng)前背景之外。

所以這里需要對(duì)左右兩邊的X軸位置進(jìn)行處理:

// Canvas 畫布, 畫板. 在上邊繪制的內(nèi)容都會(huì)顯示到界面上.
@Override
protected void onDraw(Canvas canvas) {
// 1. 繪制背景
canvas.drawBitmap(switchBackgroupBitmap, 0, 0, paint);
// 2. 繪制滑塊
if(isTouchMode){
// 根據(jù)當(dāng)前用戶觸摸到的位置畫滑塊
// 讓滑塊向左移動(dòng)自身一半大小的位置
float newLeft = currentX - slideButtonBitmap.getWidth() / 2.0f;
int maxLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth();
// 限定滑塊范圍
if(newLeft < 0){
newLeft = 0; // 左邊范圍
}else if (newLeft > maxLeft) {
newLeft = maxLeft; // 右邊范圍
}
canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint);
}else {
// 根據(jù)開關(guān)狀態(tài)boolean, 直接設(shè)置圖片位置
if(mSwitchState){// 開
int newLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth();
canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint);
}else {// 關(guān)
canvas.drawBitmap(slideButtonBitmap, 0, 0, paint);
}
}
}

6.創(chuàng)建一個(gè)狀態(tài)更新監(jiān)聽.

基本上所以工作已經(jīng)完成,這樣我們一個(gè)自定義View已經(jīng)大功告成了,當(dāng)你完成這個(gè)效果后,你可能會(huì)發(fā)現(xiàn)有點(diǎn)類似于CheckBox。既然類似于CheckBox,我們知道當(dāng)CheckBox點(diǎn)擊選中和取消選中的時(shí)候都會(huì)有ischecked()方法來獲取選中狀態(tài),所以我們這個(gè)自定義的開關(guān)按鈕自然不能少這個(gè)功能,否則我們?cè)诮缑嫔现挥行Ч故?,卻沒有邏輯處理的地方。

public interface OnSwitchStateUpdateListener{
// 狀態(tài)回調(diào), 把當(dāng)前狀態(tài)傳出去
void onStateUpdate(boolean state);
}
public void setOnSwitchStateUpdateListener(
OnSwitchStateUpdateListener onSwitchStateUpdateListener) {
this.onSwitchStateUpdateListener = onSwitchStateUpdateListener;
}

代碼很簡(jiǎn)單,寫一個(gè)接口,然后定義一個(gè)回調(diào)方法返回開關(guān)狀態(tài),需要注意的是,在手指離開屏幕的時(shí)候,我們需要判斷此次操作是否改變了開關(guān)的狀態(tài),如果沒有變化我們不做操作,如果跟上次狀態(tài)不同,則通知Activity狀態(tài)更改!

case MotionEvent.ACTION_UP:
isTouchMode = false;
System.out.println("event: ACTION_UP: " + event.getX());
currentX = event.getX(); 
float center = switchBackgroupBitmap.getWidth() / 2.0f;
// 根據(jù)當(dāng)前按下的位置, 和控件中心的位置進(jìn)行比較. 
boolean state = currentX > center;
// 如果開關(guān)狀態(tài)變化了, 通知界面. 里邊開關(guān)狀態(tài)更新了.
if(state != mSwitchState && onSwitchStateUpdateListener != null){
// 把最新的boolean, 狀態(tài)傳出去了
onSwitchStateUpdateListener.onStateUpdate(state);
}
mSwitchState = state;
break;

Activity中回調(diào)也是非常簡(jiǎn)單的,類似于Android系統(tǒng)為我們提供的setOnClickListener回調(diào)接口,之前一直用系統(tǒng)定義的監(jiān)聽接口,這次通過簡(jiǎn)單的一個(gè)自定義View,我們也可以給自己的View寫回調(diào)接口了。是不是覺得是件很開心的事情呢?

// 設(shè)置開關(guān)更新監(jiān)聽
toggleView.setOnSwitchStateUpdateListener(new ToggleView.OnSwitchStateUpdateListener(){
@Override
public void onStateUpdate(boolean state) {
Toast.makeText(getApplicationContext(), "state: " + state, Toast.LENGTH_SHORT).show();
}
});

以上所述是小編給大家介紹的Android自定義View實(shí)現(xiàn)開關(guān)按鈕,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!

相關(guān)文章

最新評(píng)論