編輯:關於Android編程
前言:Android自定義View對於剛入門乃至工作幾年的程序員來說都是非常恐懼的,但也是Android進階學習的必經之路,平時項目中經常會有一些苛刻的需求,我們可以在GitHub上找到各種各樣的效果,能用則用,不能用自己花功夫改改也能草草了事。不過隨著工作經驗和工作性質,越來越覺得自定義View是時候有必要自己花點功夫研究一下。
一、經過這兩天的努力,自己也嘗試著寫了一個Demo,效果很簡單,就是開關按鈕的實現。
可能有的人會說這效果so easy,找UI切三張圖就完事了,何必大費周折自定義。你說的沒錯,不過這裡只是用來學習自定義View來展示這麼一個常見案例。
自定義控件
1.為什麼自定義View?
Android自身帶的控件不能滿足需求, 需要根據自己的需求定義控件.
2.Android 的界面繪制流程?
onMeasure()——onLayout()——onDraw()方法都在Activity生命周期的onResume()方法之後執行。
3.Android自定義View的方式?
集成View:View流程
onMeasure() (在這個方法裡指定自己的寬高) -> onDraw() (繪制自己的內容)
集成ViewGroup:ViewGroup流程
onMeasure() (指定自己的寬高, 所有子View的寬高)-> onLayout() (擺放所有子View) -> onDraw() (繪制內容)
自定義View實現開關按鈕步驟:
寫個類繼承View,
拷貝包含包名的全路徑到xml中,
界面中找到該控件, 設置初始信息,
根據需求繪制界面內容,
響應用戶的觸摸事件,
創建一個狀態更新監聽.
1.自定義ToggleView集成View,並且重新三個構造方法。
注意:構造方法為什麼要重寫三個?
ToggleView(Context context)一個參數的構造方法是用於代碼創建控件時調用的
ToggleView(Context context, AttributeSet attrs)用於在xml裡使用, 可指定自定義屬性
ToggleView(Context context, AttributeSet attrs, int defStyle)用於在xml裡使用, 可指定自定義屬性, 如果指定了樣式, 則走此構造函數
我們在XML中定義了背景圖片、開關按鈕圖片和開關默認狀態,要獲取在XML文件定義的屬性就在包含三個參數的構造方法裡用TypedArray類來獲取。
在attrs.xml聲明節點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裡使用, 可指定自定義屬性, 如果指定了樣式, 則走此構造函數 * @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); //獲取背景圖片和開關圖片後設置圖片,便於在onMeasure()方法中設置View寬和高,防止Null setSwitchBackgroundResource(switchBackgroundResource); setSlideButtonResource(slideButtonResource); init(); }
2.自定義ToggleView集成View後,在XML文件裡不要忘記添加命名空間
“xmlns:cb=”http://schemas.android.com/apk/res-auto””
然後將自定義View的完整路徑粘貼到XML中,這點類似於Android v4包下的ViewPager控件
以下便是demo中XML文件代碼:
設置開關背景圖片
- cb:switch_background=”@drawable/switch_background”
設置開關按鈕圖片
- cb:slide_button=”@drawable/slide_button”
設置開關默認狀態
- cb:switch_state=”false”
3.界面中找到該控件, 設置初始信息
在Activity中通過findViewById方法找到自定義的View控件,和系統的組件操作沒區別。
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); // // 設置開關更新監聽 toggleView.setOnSwitchStateUpdateListener(new ToggleView.OnSwitchStateUpdateListener(){ @Override public void onStateUpdate(boolean state) { Toast.makeText(getApplicationContext(), "state: " + state, Toast.LENGTH_SHORT).show(); } }); }
4.根據需求繪制界面內容
已經通過onMeasure()方法設置了View的寬度和高度,下面開始繪制的操作就全部在onDraw()方法中進行,onDraw(Canvas canvas) 方法中canvas參數:畫布, 畫板. 在上邊繪制的內容都會顯示到界面上.
// 根據開關狀態boolean, 直接設置圖片位置 if(mSwitchState){// 開 int newLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth(); canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint); }else {// 關 canvas.drawBitmap(slideButtonBitmap, 0, 0, paint); }
開關打開時,開關按鈕的位置在開關背景中的位置計算:
int newLeft = switchBackgroupBitmap.getWidth() -
slideButtonBitmap.getWidth(); 背景的寬度-按鈕的寬度就是當前開關按鈕所在的X軸上的位置點
開關關閉時,當前開關按鈕所在的X軸上的位置點=0
5.響應用戶的觸摸事件
在完成以上3步操作後,你會發現,只有在第一次進入後XML初始化默認開關狀態的boolean值才會有變化,此後點擊是沒有任何效果的,這個時候我們就要想辦法監聽手勢事件,重寫onTouchEvent(MotionEvent event)方法,相信大多數朋友對這個方法並不陌生。
MotionEvent有三種狀態:
MotionEvent.ACTION_DOWN: //按下屏幕
MotionEvent.ACTION_MOVE: //手指在屏幕上移動
MotionEvent.ACTION_UP //離開屏幕
當前需要考慮的問題是:
當手指按下屏幕後MotionEvent.ACTION_DOWN(在當前開關背景View中)開關的X軸位置應該移動到手指按下的位置;
當手指在屏幕上移動MotionEvent.ACTION_MOVE(在當前開關背景View中)開關按鈕X軸應該隨著手指移動的位置改變;
當手指離開屏幕後MotionEvent.ACTION_UP(在當前開關背景View中)開關按鈕應該判斷手指離開的位置是否是當前背景的一半位置,如果X軸位置大於View背景寬度的1/2、那麼應該處於打開狀態,如果X軸位置小於View背景寬度的1/2,那麼應該處於關閉狀態。
如圖所示:
private OnSwitchStateUpdateListener onSwitchStateUpdateListener; // 重寫觸摸事件, 響應用戶的觸摸. @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; // 根據當前按下的位置, 和控件中心的位置進行比較. boolean state = currentX > center; // 如果開關狀態變化了, 通知界面. 裡邊開關狀態更新了. if(state != mSwitchState && onSwitchStateUpdateListener != null){ // 把最新的boolean, 狀態傳出去了 onSwitchStateUpdateListener.onStateUpdate(state); } mSwitchState = state; break; default: break; } // 重繪界面 invalidate(); // 會引發onDraw()被調用, 裡邊的變量會重新生效.界面會更新 return true; // 消費了用戶的觸摸事件, 才可以收到其他的事件. }
注意:
以上監聽onTouchEvent(MotionEvent
event)方法後還存在一個問題,不知道大家有沒有發現,我們沒有設置開關按鈕的邊界值,什麼意思呢?就是手指滑動的時候左邊和右邊可以畫出當前背景之外。
所以這裡需要對左右兩邊的X軸位置進行處理:
// Canvas 畫布, 畫板. 在上邊繪制的內容都會顯示到界面上. @Override protected void onDraw(Canvas canvas) { // 1. 繪制背景 canvas.drawBitmap(switchBackgroupBitmap, 0, 0, paint); // 2. 繪制滑塊 if(isTouchMode){ // 根據當前用戶觸摸到的位置畫滑塊 // 讓滑塊向左移動自身一半大小的位置 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 { // 根據開關狀態boolean, 直接設置圖片位置 if(mSwitchState){// 開 int newLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth(); canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint); }else {// 關 canvas.drawBitmap(slideButtonBitmap, 0, 0, paint); } } }
6.創建一個狀態更新監聽.
基本上所以工作已經完成,這樣我們一個自定義View已經大功告成了,當你完成這個效果後,你可能會發現有點類似於CheckBox。既然類似於CheckBox,我們知道當CheckBox點擊選中和取消選中的時候都會有ischecked()方法來獲取選中狀態,所以我們這個自定義的開關按鈕自然不能少這個功能,否則我們在界面上只有效果展示,卻沒有邏輯處理的地方。
public interface OnSwitchStateUpdateListener{ // 狀態回調, 把當前狀態傳出去 void onStateUpdate(boolean state); } public void setOnSwitchStateUpdateListener( OnSwitchStateUpdateListener onSwitchStateUpdateListener) { this.onSwitchStateUpdateListener = onSwitchStateUpdateListener; }
代碼很簡單,寫一個接口,然後定義一個回調方法返回開關狀態,需要注意的是,在手指離開屏幕的時候,我們需要判斷此次操作是否改變了開關的狀態,如果沒有變化我們不做操作,如果跟上次狀態不同,則通知Activity狀態更改!
case MotionEvent.ACTION_UP: isTouchMode = false; System.out.println("event: ACTION_UP: " + event.getX()); currentX = event.getX(); float center = switchBackgroupBitmap.getWidth() / 2.0f; // 根據當前按下的位置, 和控件中心的位置進行比較. boolean state = currentX > center; // 如果開關狀態變化了, 通知界面. 裡邊開關狀態更新了. if(state != mSwitchState && onSwitchStateUpdateListener != null){ // 把最新的boolean, 狀態傳出去了 onSwitchStateUpdateListener.onStateUpdate(state); } mSwitchState = state; break;
Activity中回調也是非常簡單的,類似於Android系統為我們提供的setOnClickListener回調接口,之前一直用系統定義的監聽接口,這次通過簡單的一個自定義View,我們也可以給自己的View寫回調接口了。是不是覺得是件很開心的事情呢?
// 設置開關更新監聽 toggleView.setOnSwitchStateUpdateListener(new ToggleView.OnSwitchStateUpdateListener(){ @Override public void onStateUpdate(boolean state) { Toast.makeText(getApplicationContext(), "state: " + state, Toast.LENGTH_SHORT).show(); } });
以上所述是小編給大家介紹的Android自定義View實現開關按鈕,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對本站網站的支持!
因重定向無法正常goBack()解決方案首先說下問題,初始頁面為A,點擊某個鏈接跳轉到B(http://xxx.com.cn/),B頁面重定向到C頁面(http://xx
提煉接口重構是從一個已存在的類中提煉接口,它可以從某個類中選擇方法,把選中的方法提取到一個單獨的接口中.操作步驟:? 菜單欄: Refactor —>
本篇是Activity啟動模式篇的基礎篇,介紹Activity四種啟動模式的基本概念、Intent Flag設置啟動模式以及應用場景。在介紹四種啟動模式之前,先介紹一下
夜深也是無聊,翻看以前的老代碼,發現那個我們經常用的菊花圈,原來是幀動畫做的,有點意思。突然感覺幀動畫做的東西效果不錯啊,至少看起來聽耐看的。開工上代碼: 先是布局文件: