說到自定義控制項不得不提的就是介面回調,在Android開發中介面回調用的還是蠻多的。在這篇博客開始的時候呢,我想聊一下iOS的自定義控制項。在iOS中自定義控制項的思路是繼承自UIView, 在UIView的子類中組合一些控制項,對外暴漏一些屬性和回調介面,並留有必要的實現方法。在iOS自定義控制項中常用的 ...
說到自定義控制項不得不提的就是介面回調,在Android開發中介面回調用的還是蠻多的。在這篇博客開始的時候呢,我想聊一下iOS的自定義控制項。在iOS中自定義控制項的思路是繼承自UIView, 在UIView的子類中組合一些控制項,對外暴漏一些屬性和回調介面,並留有必要的實現方法。在iOS自定義控制項中常用的回調有兩種,一是委托代理回調(Delegate),另一種是Block回調。如果你想對這兩者有所瞭解,請參考我之前的博客《Objective-C中的委托(代理)模式》、《Objective-C中的Block回調模式》、《設計模式(十三):從“FQ”中來認識代理模式(Proxy Pattern)》。
在Android自定義控制項時用到的介面回調和iOS開發中使用到的Delegate回調以及Block回調即為相似,就連實現方式都大同小異。今天的內容就自定義一個Android控制項,並且以此控制項為基礎,聊一下Android中的介面回調(確切的說應該是Java語言中的介面回調)。廢話少說,進入今天的主題。
一.自定義控制項的UI實現
上面有提到,iOS開發中,自定義控制項一般式繼承自UIView的,然後再UIView的子類中做一些事情。而Android開發中的自定義控制項也是繼承自View, 但是今天我們的自定義控制項是繼承自FrameLayout, 在此基礎上我們自定義一些東西。因為FrameLayout, LinearLayout等佈局方式都是繼承自ViewGroup的,而ViewGroup則繼承自View, 所以在自定義控制項時,繼承自FrameLayout等佈局方式肯定是可以的。
1. 實現效果分析
接下來我們要自定義一個導航欄,而這個導航欄是模仿iOS系統中的NavigationBar。因為Android開發中沒有這個控制項,所以我們需要自定義這個控制項供開發者使用。下方是我們要實現的效果。上方的導航欄是我們自定義的NavigationBar,和iOS系統的導航欄類似。點擊左邊的返回按鈕,會退出當前Activity。點擊右邊的藉口回調測試,會通過介面回調的形式來在當前Activity中顯示Toast提示。在調用該組件時,可以知道中間的Title.
2. UI佈局分析以及Xml佈局文件實現
接下來我們將會對UI進行拆分,詳細的看一下上面的NavigationBar是由哪些基礎控制項組成的。分析完畢後,在通過一些佈局方式將這些基礎控制項進行組合,拼裝,最終成為我們想要使用的自定義控制項。下方是手動畫的上述自定義控制項UI原理圖,如下所示。
最下邊的佈局我們採用的時FrameLayout方式,並設置其背景顏色。返回圖標(ImageView)和 返迴文字(TextView)放在了一個水平佈局的LinearLayout上。這兩者上面放了一個透明的Button, 用來實現返回操作。中間的Title(TextView) 在FrameLayout中設置成居中顯示即可。Call Back是一個Button, 用來測試下麵的介面回調。
上面的佈局格式具體落實到xml中,如下所示,具體思想就是上方敘述的東西。該自定義控制項佈局文件如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="wrap_content" 5 android:background="#cccccc"> 6 <LinearLayout 7 android:orientation="horizontal" 8 android:layout_width="match_parent" 9 android:layout_height="20pt" 10 android:background="@drawable/background"> 11 <ImageView 12 android:id="@+id/back_title" 13 android:layout_width="wrap_content" 14 android:layout_height="18pt" 15 android:layout_gravity="center" 16 android:src="@drawable/back"/> 17 <TextView 18 android:layout_width="wrap_content" 19 android:layout_height="wrap_content" 20 android:layout_gravity="center" 21 android:textSize="8pt" 22 android:text="返回"/> 23 </LinearLayout> 24 25 <Button 26 android:id="@+id/back_button" 27 android:layout_width="50pt" 28 android:layout_height="20pt" 29 android:layout_gravity="center_vertical" 30 android:alpha="0" /> 31 32 <TextView 33 android:id="@+id/navigation_title" 34 android:layout_width="wrap_content" 35 android:layout_height="wrap_content" 36 android:textSize="10pt" 37 android:layout_gravity="center" 38 android:text="標題"/> 39 40 <Button 41 android:id="@+id/call_back" 42 android:layout_width="wrap_content" 43 android:layout_height="wrap_content" 44 android:layout_gravity="right" 45 android:text="介面回調測試"/> 46 </FrameLayout>
二. 為UI綁定事件以及留出回調介面
UI實現好後,就說明我們自定義組件的殼兒已經做好了,但是其內在的東西還需要實現。也就是說需要為上述實現的UI綁定Java類,併在類中處理控制項的一些響應事件,以及在類中留出必要的介面來改變自定義組件的屬性。接下來來實現xml對應的Java類。
因為上述佈局中,最外層我們使用的是FrameLayout佈局,上面已經粗略的提過,我們可以繼承自FrameLayout來做一些東西,因為FrameLayout的父類是View, 所以我們可以在此基礎上做一些東西。同理,如果上述佈局是使用其他佈局來實現的,那麼你就可以繼承自其他佈局的類來做一些東西。在本篇博客中我們就以FrameLayout為父類來實現我們自定義組件的關聯類。
1. 繼承FrameLayout並實現相應的構造函數,下方是我們要實現的構造函數。在構造函數中,我們需要與上述我們實現的xml佈局文件進行關聯,當然,我們使用的是LayoutInflater來實現的,自定義組件的構造函數如下所示。
/* *自定義組件的構造方法 */ public CustomNavigationBar(Context context, AttributeSet attrs) { super(context, attrs); LayoutInflater.from(context).inflate(R.layout.custom_navigation, this); //載入佈局文件 }
2. 實現好相應的構造方法並關聯好相應的佈局文件後,我們需要對佈局文件中的控制項進行事件的處理。下方的代碼就是點擊返回按鈕要做的事情,因為點擊返回按鈕要做的事情就是結束當前Activity,所以不需要給調用者留有回調介面,在自定義組件的內部處理即可。下方代碼就是獲取UI中返回按鈕,並處理返回事件的方法。下方的方法需要在構造函數中調用才會起作用,函數不調用怎麼執行呢,對吧~。 下方代碼較為簡單,就是結束當前顯示的Activity,處理返回按鈕的事件如下:
1 /* 2 * 點擊返回按鈕方法 3 */ 4 private void onClickBackButton() { 5 Button button = (Button) findViewById(R.id.back_button); 6 button.setOnClickListener(new OnClickListener() { 7 @Override 8 public void onClick(View v) { 9 ((Activity) getContext()).finish(); 10 } 11 }); 12 }
3.處理好返回事件後,我們需要做的還有就是為標題欄的標題留出設置的方法。也就是說在調用該自定義組件時,我們要能設置該組件的標題。要滿足這一點,我們就需要在自定義組件中留出Title的setter方法了,並且這個Setter方法的訪問許可權必須是Public的,不然在外界就沒辦法訪問這個方法了。下方就是這個設置title的Public方法。其實下方的代碼還是比較簡單的,就是通過ID來獲取標題的TextView,並設置相應的title即可,代碼如下:
1 public String navigationTitle = "標題欄"; 2 3 /* 4 * 設置標題欄的標題 5 */ 6 public void setNavigationTitle(String navigationTitle) { 7 this.navigationTitle = navigationTitle; 8 TextView textView = (TextView) findViewById(R.id.navigation_title); 9 textView.setText(navigationTitle); 10 }
4. 上面如果還算簡單的話,下方就是自定義控制項中稍稍有點難度的地方了。接下來我們要實現相應按鈕的介面回調,在實現之前我們介紹一下為什麼要實現介面的回調。因為有時候點擊自定義控制項中的按鈕時,所做的事情在自定義控制項的內部無法獨立完成,需要在調用者中進行事件的處理,在這種情況下,我們就可以使用介面回調來處理。
上面實現的返回事件的處理就沒必要使用介面的回調了,因為在自定義組件內部完全可以該功能。舉個使用介面回調的慄子:比如點擊自定義控制項中某個按鈕時,我們需要跳轉到其他Activity,而這個Activity在我們實現自定義控制項時是未知的,這時候就要用到我們的介面回調來實現了。在iOS開發中,同樣遇到上述問題,所以iOS開發中也有各種回調比如Block回調,Delegate回調,Target-Action回調等都是iOS開發中常用的回調。雖然實現形式不同,但是其作用和Java中的介面回調是極為相似的。好,說這麼多,接下來我們要為XML佈局文件中id為call_back的按鈕的點擊事件通過介面回調的形式傳遞到調用者中。
(1)第一步我們要先實現介面回調的介面,這也是必須的,因為介面回調如果沒有介面怎麼能行呢。該介面是Public類型的,不然在調用者中是無法使用的。我們介面的名字為onClickCallBackListener, 在其中有一個方法,該方法是介面回調時要執行的方法。
1 /* 2 *創建回調介面 3 */ 4 public static interface OnClickCallBackListener { 5 public void OnClickButton(View v); 6 }
(2) 聲明一個私有的介面對象,併為這個私有的對象實現setter方法,該私有的介面對象是用來接受自定義組件調用者傳過來的回調方法的。代碼比較簡單,在此就不做過多贅述了。
1 private OnClickCallBackListener callBackListener; //聲明介面對象 2 public void setCallBackListener(OnClickCallBackListener callBackListener) { 3 this.callBackListener = callBackListener; 4 }
(3) 實現好介面以及接收回調對象的變數後,接下來要做的事情就是獲取自定義組件中相應按鈕點擊的事件,併在此按鈕點擊事件中執行傳過來的介面對象相應的回調方法。下方這個方法,要在構造函數中調用。該方法的功能就是獲取自定義組件的相應按鈕的點擊事件並執行介面對象的回調方法。具體實現如下:
/* *點擊按鈕時執行介面回調 */ private void callBackButton() { Button button = (Button) findViewById(R.id.call_back); button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (callBackListener != null) { callBackListener.OnClickButton(v); } } }); }
上方是把代碼拆開來講的,下方是整個自定義組件的實現類。具體代碼如下所示。
1 package com.example.lizelu.customnavigationbar; 2 3 import android.annotation.TargetApi; 4 import android.app.Activity; 5 import android.content.Context; 6 import android.os.Build; 7 import android.util.AttributeSet; 8 import android.view.LayoutInflater; 9 import android.view.View; 10 import android.widget.Button; 11 import android.widget.FrameLayout; 12 import android.widget.TextView; 13 14 /** 15 * Created by lizelu on 15/11/29. 16 */ 17 public class CustomNavigationBar extends FrameLayout { 18 19 /* 20 *創建回調介面 21 */ 22 public static interface OnClickCallBackListener { 23 public void OnClickButton(View v); 24 } 25 26 27 private OnClickCallBackListener callBackListener; //聲明介面對象 28 29 public String navigationTitle = "標題欄"; 30 31 /* 32 * 設置標題欄的標題 33 */ 34 public void setNavigationTitle(String navigationTitle) { 35 this.navigationTitle = navigationTitle; 36 TextView textView = (TextView) findViewById(R.id.navigation_title); 37 textView.setText(navigationTitle); 38 } 39 40 public void setCallBackListener(OnClickCallBackListener callBackListener) { 41 this.callBackListener = callBackListener; 42 } 43 44 /* 45 *自定義組件的構造方法 46 */ 47 public CustomNavigationBar(Context context, AttributeSet attrs) { 48 super(context, attrs); 49 LayoutInflater.from(context).inflate(R.layout.custom_navigation, this); //載入佈局文件 50 onClickBackButton(); 51 callBackButton(); 52 } 53 /* 54 * 點擊返回按鈕方法 55 */ 56 private void onClickBackButton() { 57 Button button = (Button) findViewById(R.id.back_button); 58 button.setOnClickListener(new OnClickListener() { 59 @Override 60 public void onClick(View v) { 61 ((Activity) getContext()).finish(); 62 } 63 }); 64 } 65 66 /* 67 *點擊按鈕時執行介面回調 68 */ 69 private void callBackButton() { 70 Button button = (Button) findViewById(R.id.call_back); 71 button.setOnClickListener(new OnClickListener() { 72 @Override 73 public void onClick(View v) { 74 if (callBackListener != null) { 75 callBackListener.OnClickButton(v); 76 } 77 } 78 }); 79 } 80 }View Code
三.該自定義組件的調用方式
經過上面的過程,我們自定控制項以及實現好了,接下來就是如何使用了。其實自定義組件的使用方式和系統自帶的組件使用起來區別不大,沒有什麼特別之處。下方就讓我們在Activity中使用上述我們自定義的控制項吧。
1.首先在我們要使用該組件的Activity所對應的佈局文件中載入我們的自定義組件的佈局。要註意的一點是自定義組件的標簽我們要使用包的全面才可以,其他的和Android的系統組件使用方法類似,具體代碼如下:
1 <com.example.lizelu.customnavigationbar.CustomNavigationBar 2 android:id="@+id/custom_navigation_bar" 3 android:layout_width="match_parent" 4 android:layout_height="wrap_content"/>
2.在Activity的Java類中,通過id獲取我們自定義組件的對象,並實現其相應的回調即可。具體代碼如下:
1 private void setNavigationTitle(String title) { 2 CustomNavigationBar navigationBar = (CustomNavigationBar) findViewById(R.id.custom_navigation_bar); 3 navigationBar.setNavigationTitle(title); 4 5 //實現組件上的按鈕的介面回調 6 navigationBar.setCallBackListener(new CustomNavigationBar.OnClickCallBackListener() { 7 @Override 8 public void OnClickButton(View v) { 9 Toast.makeText(MainActivity.this, "回調執行的方法", Toast.LENGTH_SHORT).show(); 10 } 11 }); 12 }
到此,自定義組件的實現和調用實現完畢。雖然上述自定義控制項雖然比較簡單,但是麻雀雖小,五臟俱全。再複雜的自定義控制項也是有簡單的東西慢慢的拼裝而成。所以理解自定義控制項的實現原理還是比較重要的。今天的博客就先到這兒,下方是上述Demo在GitHub上的分享地址,需要的小伙伴請自行Clone。
github分享地址:https://github.com/lizelu/AndroidCustomNavigationBar