編輯:關於Android編程
今天來講講自定義單個控件,就拿開關按鈕來講講,相信大家見了非常多這樣的了,先看看效果:
我們可以看到一個很常見的開關按鈕,那就來分析分析。
首先:
這是由兩張圖片構成:
①一張為有開和關的背景圖片
②一張為控制開和關的滑動按鈕
第一步:
寫個類繼承View,並重寫幾個方法:
第一個為構造函數,重寫一個參數的函數和兩個參數的函數就夠了,因為兩個參數的函數能夠使用自定義屬性
第二個為控制控件的大小–>protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {}
第三個為繪制控件的方法–>protected void onDraw(Canvas canvas) {}
第二步:
將用戶指定的兩張圖片加載進來,這裡使用自定義屬性加載, 在values目錄下新建attrs.xml,在xml文件中指定自定義屬性名和自定義屬性的字段及值類型(即背景圖和滑塊圖)即可:
<?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>
各個字段的含義我在這就不講了,不懂的就去看看前幾篇《Android開發筆記之自定義組合控件》有講過,寫好就只需在構造函數中加載進來
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); //把值設置到相應組件上 backgroundBitmap = convertDrawable2BitmapSimple(switchBackground); switchSlide = convertDrawable2BitmapSimple(switchView_slide); }
不過要注意的是:
因為從自定義屬性中取到的是Drawable對象,而我們要的是一個Bitmap對象,所以我們先得把Drawable對象轉成Bitmap對象,其實有很多種方法來轉,這裡就介紹種最簡單的方法,借助與BitmapDrawable類:
//將Drawable轉成Bitmap public Bitmap convertDrawable2BitmapSimple(Drawable drawable) { BitmapDrawable bd= (BitmapDrawable)drawable; return bd.getBitmap(); }
第三步:
onMeasure方法來控制控件的大小,我們可以看到這個開關按鈕的大小就跟背景的大小一樣大,只需要設置為背景的大小:
//控制控件的大小 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (backgroundBitmap != null) { //控件大小設置為背景的大小 setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight()); }else { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } }
第四步:
既然這個開關按鈕需要通過點擊或移動來控制控件的開和關,所以就需要實現onTouchEvent方法,當然應該有三個事件會觸發:按下、移動和抬起的時候,每次點擊、移動或抬起都需要重繪,我們先來分析下滑塊的狀態有哪些(應該有四種狀態)一開始默認狀態為空:
1.點擊的時候
2.移動的時候
3.抬起的時候
4.空的時候(即什麼都沒干的時候)
先分析下點擊的時候的情況:
①當按下或移動的坐標大於滑塊寬度一半時將滑塊右移
②當按下或移動的坐標小於滑塊寬度一半時滑塊不動
注:防止滑塊移至背景外面,最大是滑塊右邊和背景右邊對齊(即最大離左邊為背景寬度-滑塊寬度)
再來看看移動的時候的情況應該是和點擊的時候是一樣的。
再來分析抬起的時候的情況:
①如果開關狀態是打開的就將滑塊移動至右邊
②如果開關狀態是關閉的就將滑塊移動至左邊
那怎麼判斷什麼時候是打開狀態和關閉狀態呢??
①抬起的坐標大於背景寬度一半的時候設為打開狀態
②抬起的坐標小於背景寬度坐標一 半的時候設為關閉狀態
再來分析下空的時候,可以發現它和抬起的時候的情況是一樣的。
第五步:
在onDraw方法中將背景和滑塊繪制出來。剛才分析了onTouchEvent方法,這次是一樣的,滑塊的四個狀態分別處理,前面onTouchEvent方法中滑塊的狀態改變,然後通過invalidate()方法來通知系統重繪。
第六步:
我們做這個自定義控件是為了讓用戶使用的,現在這個是沒有什麼用的,用戶用不了,所以可以通過設置監聽器來對外提供接口。
/** * switchView開關監聽接口 * * */ interface OnSwitchChangedListener { public void onSwitchChange(boolean isOpen); } /** * 設置 switchView狀態監聽 * */ public void setOnChangeListener(OnSwitchChangedListener listener) { switchListener = listener; }
這個監聽器中的boolean值需要賦值,那在什麼時候賦值呢,應該是在抬起或空的狀態的時候給它賦值,因為那個時候才真正確定開關按鈕是打開的還是關閉的。
第七步:
到這一步就是來使用了,在布局文件中把自定義的這個控件定義出來
<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應該都會用了,不會的去看看《android開發筆記之自定義組合控件》。
核心代碼:
布局文件
<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
/** * 自定義開關按鈕 * @author Administrator * */ public class SwitchView extends View { //背景圖片和滑塊圖片 private Bitmap backgroundBitmap,switchSlide; //畫筆 private Paint paint; //得到的x坐標(點擊、移動、抬起) private float currentX; //判斷開關是否打開的標記位 private boolean isOpen = false; //開關打開與關閉的監聽器 private OnSwitchChangedListener switchListener; //滑塊的四種狀態 public static final int STATE_DOWN = 1; //按下的時候 public static final int STATE_MOVE = 2; //移動的時候 public static final int STATE_UP = 3; //抬起的時候 public static final int STATE_NONE = 0; //空的時候(即什麼都沒干的時候) //標記狀態(默認為空狀態) 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); //把值設置到相應組件上 backgroundBitmap = convertDrawable2BitmapSimple(switchBackground); switchSlide = convertDrawable2BitmapSimple(switchView_slide); } //將Drawable轉成Bitmap public Bitmap convertDrawable2BitmapSimple(Drawable drawable) { BitmapDrawable bd = (BitmapDrawable)drawable; return bd.getBitmap(); } //控制控件的大小 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (backgroundBitmap != null) { //控件大小設置為背景的大小 setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight()); }else { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } //繪制控件的樣式 @Override protected void onDraw(Canvas canvas) { //背景為空的時候才將背景繪制 if (backgroundBitmap != null) { paint = new Paint(); //第一個參數表示需要畫的Bitmap //第二個參數表示Bitmap左邊離控件的左邊距 //第三個參數表示Bitmap上邊離控件的上邊距 //第四個參數表示畫筆 canvas.drawBitmap(backgroundBitmap, 0, 0, paint); } switch (state) { case STATE_DOWN: //按下和移動的觸發事件都一樣,都是將滑塊移動 case STATE_MOVE: //當按下或移動的坐標大於滑塊寬度一半時將滑塊右移 if (currentX > switchSlide.getWidth()/2f) { //讓滑塊向右滑動(重新繪制滑塊的位置) float left = currentX - switchSlide.getWidth()/2f; //防止滑塊移至背景外面,最大是滑塊右邊和背景右邊對齊(即最大離左邊為背景寬度-滑塊寬度) float maxLeft = backgroundBitmap.getWidth() - switchSlide.getWidth(); if (left > maxLeft) { left = maxLeft; } canvas.drawBitmap(switchSlide, left, 0, paint); //當按下或移動的坐標小於滑塊寬度一半時滑塊不動 }else if (currentX < switchSlide.getWidth()/2f) { //讓滑塊不動就可以了 canvas.drawBitmap(switchSlide, 0, 0, paint); } break; case STATE_NONE: //空或抬起的時候將滑塊至於左邊或右邊 case STATE_UP: //如果是打開的將滑塊移動至右邊,並將打開狀態傳至監聽器 if (isOpen) { if (switchListener != null) { switchListener.onSwitchChange(true); } canvas.drawBitmap(switchSlide, backgroundBitmap.getWidth() - switchSlide.getWidth(), 0, paint); //如果是關閉的將滑塊至於左邊,並將關閉狀態傳至監聽器 }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(); //將標記位修改成按下的狀態 state = STATE_DOWN; //通知系統重新繪制界面 invalidate();//在主線程 // postInvalidate();//在子線程 break; case MotionEvent.ACTION_MOVE: currentX = event.getX(); //將標記位修改為移動狀態 state = STATE_MOVE; invalidate(); break; case MotionEvent.ACTION_UP: currentX = event.getX(); //將標記為修改為抬起狀態 state = STATE_UP; //抬起的坐標大於背景寬度一半的時候設為打開狀態 if (currentX > backgroundBitmap.getWidth()/2f) { //滑塊在右邊,開啟 isOpen = true; //抬起的坐標小於背景寬度坐標一 半的時候設為關閉狀態 }else if (currentX < backgroundBitmap.getWidth()) { //滑塊在左邊,關閉 isOpen = false; } invalidate(); break; } return true; } /** * switchView開關監聽接口 * * */ interface OnSwitchChangedListener { public void onSwitchChange(boolean isOpen); } /** * 設置 switchView狀態監聽 * */ 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) { //打開開關的時候的邏輯 Toast.makeText(MainActivity.this, "開關打開了", Toast.LENGTH_LONG).show(); }else { //關閉開關的時候的邏輯 Toast.makeText(MainActivity.this, "開關關閉了", Toast.LENGTH_LONG).show(); } } }); } }
大家看起來這麼簡單的一個寫了這麼多,其實我們學習這個不是為了寫這個,比這個好的開源多的是,而是為了學習這種思路與思維,大家趕緊試試吧!
謝謝大家的閱讀,也希望大家可以繼續關注本站的更多精彩內容。
華為榮耀於8月1號下午正式發布了6.6吋大屏手機華為榮耀NOTE8,那麼想要購買新機的朋友是不是很想知道華為榮耀note8怎麼預約購買呢?下面小編就馬上帶來
一、前言新的一年又開始了,大家是否還記得去年年末的時候,我們還有一件事沒有做,那就是解析Android中編譯之後的classes.dex文件格式,我們在去年的時候已經介紹
有不少朋友為自己的手機鎖屏後設置了開鎖密碼,但如果忘記了手機解鎖圖案設置,不管是因為太過復雜,還是別的什麼原因,用上手機才是最重要的,下載吧小編就來分享一個
一、---框架---(1)新建布局,包括三個按鈕:播放、暫停、繼續播放,還有一個進度條(2)建立一個Service,其中有播放、暫停、繼續播放的方法(3)因為有進度條,所