上一節我們細致地、猥瑣地、小心翼翼地、猶如絲滑般撫摸、啊不,是講解了如何去測量一個布局控件,再次強調,如我之前多次強調那樣,控件的測量必須要邏輯縝密嚴謹,盡量少地避免出現較大的邏輯錯誤。在整個系列撰寫的過程中,有N^N個朋友曾多次不間斷地小窗我問View是否也有生命周期,我也多次細心地、耐心地打開小窗然后默默地關掉它,不是我不愿回答而是問的人太多我們干脆就在blog中詳細闡述下,即便你是第一天學習**Android**,你也一定會用到Activity,用到Activity你一定會接觸到onCreate方法,然后你會從各種途徑了解到類似這樣的方法還有7個,我們稱之為Activity生命周期:
~~~
/**
* 主界面
*
* @author Aige {@link http://blog.csdn.net/aigestudio}
* @since 2014/11/17
*/
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onStop() {
super.onStop();
}
@Override
protected void onRestart() {
super.onRestart();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
~~~
Android framework在Activity的不同時期調用不同的生命周期方法并將其提供給我們以便我們能在Activity加載的不同時期根據自己的需要做不同的事,For example:我們可以在onCreate中設置我們的布局文件或顯示的View,在onStop中處理Activity位于后臺時的操作,在onDestroy中銷毀一些不必要的強引用避免在Activity銷毀后造成泄漏等等等等,這些方法給我們控制Activity帶來了極大的便利,而在View中我們也學習了onMeasure、onLayout和onDraw這三個類似的方法,它們也是依次在View創建的不同時期被調用,那么是否還應存在其他類似的方法呢?The answer is yes,View也提供了一下幾個類似“生命周期”的方法:

如上所示的這些方法,除了提到的“生命周期”方法外還有一些事件的回調,多說無益,我們還是來看看這些方法會在View的什么時候被調用,老樣子我們新建一個繼承于View的子類并重寫這些方法:
~~~
/**
*
* @author AigeStudio {@link http://blog.csdn.net/aigestudio}
* @since 2015/1/27
*
*/
public class LifeCycleView extends View {
private static final String TAG = "AigeStudio:LifeCycleView";
public LifeCycleView(Context context) {
super(context);
Log.d(TAG, "Construction with single parameter");
}
public LifeCycleView(Context context, AttributeSet attrs) {
super(context, attrs);
Log.d(TAG, "Construction with two parameters");
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
Log.d(TAG, "onFinishInflate");
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.d(TAG, "onMeasure");
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
Log.d(TAG, "onLayout");
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
Log.d(TAG, "onSizeChanged");
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d(TAG, "onDraw");
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
Log.d(TAG, "onAttachedToWindow");
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
Log.d(TAG, "onDetachedFromWindow");
}
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
Log.d(TAG, "onWindowVisibilityChanged");
}
}
~~~
上面的方法中我過濾掉了事件和焦點觸發的方法,僅看View運行時被調用的方法,運行看看我們LogCat中的輸出:

首先是調用了構造方法,這是不用猜都該知道的,然后呢調用了onFinishInflate方法,這個方法當xml布局中我們的View被解析完成后則會調用,具體的實現在LayoutInflater的rInflate方法中:
~~~
public abstract class LayoutInflater {
void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
// 省去無數代碼…………
if (finishInflate) parent.onFinishInflate();
}
}
~~~
也就是說如果我們不從xml布局文件中解析的話,該方法就不會被調用,我們在Activity直接加載View的實例:
~~~
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new LifeCycleView(this));
}
}
~~~
這時再次運行我們的APP,就會發現不會再去調用onFinishInflate方法:

緊接著調用的是onAttachedToWindow方法,此時表示我們的View已被創建并添加到了窗口Window中,該方法后緊接著一般會調用onWindowVisibilityChanged方法,只要我們當前的Window窗口中View的可見狀態發生改變都會被觸發,這時View是被顯示了,隨后就會開始調用onMeasure方法對View進行測量,如果測量結果被確定則會先調用onSizeChanged方法通知View尺寸大小發生了改變,緊跟著便會調用onLayout方法對子元素進行定位布局,然后再次調用onMeasure方法對View進行二次測量,如果測量值與上一次相同則不再調用onSizeChanged方法,接著再次調用onLayout方法,如果測量過程結束,則會調用onDraw方法繪制View。我們看到,onMeasure和onLayout方法被調用了兩次,很多童鞋會很糾結為何onMeasure方法回被多次調用,其實沒必要過于糾結這個問題,onMeasure的調用取決于控件的父容器以及View Tree的結構,不同的父容器有不同的測量邏輯,比如上一節**自定義控件其實很簡單2/3**中,我們在SquareLayout測量子元素時就采取了二次測量,在API 19的時候Android對測量邏輯做了進一步的優化,比如在19之前只會對最后一次的測量結果進行Cache而在19開始則會對每一次測量結果都進行Cache,如果相同的代碼相同布局相同的邏輯在19和19之前你有可能會看到不一樣的測量次數結果,所以沒必要去糾結這個問題,一般情況下只要你邏輯正確onMeasure都會得到正確的調用。
上面這些方法都很好理解,我們主要關心的是其調用流程,雖然上面我們通過LogCat的輸出大致了解了一下其執行順序,但是如果你好奇心足夠重,一定會想真是這樣的么?在**自定義控件其實很簡單7/12**中我曾留下一個疑問:
~~~
/**
*
* @author AigeStudio {@link http://blog.csdn.net/aigestudio}
* @since 2015/1/12
*
*/
public class ImgView extends View {
private Bitmap mBitmap;// 位圖對象
public ImgView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
// 繪制位圖
canvas.drawBitmap(mBitmap, 0, 0, null);
}
/**
* 設置位圖
*
* @param bitmap
* 位圖對象
*/
public void setBitmap(Bitmap bitmap) {
this.mBitmap = bitmap;
}
}
~~~
就是如上代碼片段是否有什么問題?細心的盆友其實已經發現了,我們在onDraw中用到的mBitmap竟不為null,按照我們上面分析的結果,如果順次調用View的各個方法,那么此時如果我們在Activity中調用setBitmap方法為我們的ImgView設置Bitmap:
~~~
public class MainActivity extends Activity {
private ImgView mImgView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImgView = (ImgView) findViewById(R.id.main_pv);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.lovestory);
mImgView.setBitmap(bitmap);
}
}
~~~
的話onDraw方法獲取到的Bitmap應該為null才對,或者直白地說setBitmap方法應該在onDraw之后才被調用才對。對么?如果你這樣想,別說Android,甚至連Java基礎都不過關~~~為什么這么說呢?記住上面這些除了構造方法外的onXXX方法,都是一系列的回調方法,當且僅當一定條件成立才會被觸發,比如上面說過的onFinishInflate方法,只有在該View以及其子View從xml解析完畢才會被調用,打個比方,如果我們編譯Android源代碼,嘗試在rInflate方法解析完xml生前View調用onFinishInflate之前調用我們的setBitmap方法,這時候你就會看到執行順序的改變。具體的原理設計太多的framework源碼,在該系列我就不在多貼一些系統的源碼了,如果你想更深地了解,我會在后續的《深入剖析Android GUI框架》系列中詳細闡述,這里我僅作簡單的介紹,注意到上面我在說View的“生命周期”時使用了一個引號,雖然說到上面的一些方法會在View運行過程中依次被調用,但事實上真正稱得上View的生命周期的階段只有三個:
* 處理控件動畫的階段
* 處理測量的階段
* 處理繪制的階段
Android的Animation動畫體系龐大不在本系列的講解范疇內,暫時Skip,測量和繪制的主要過程由我們之前所講的三個方法onMeasure、onLayout和onDraw所控制,這三個方法呢在framework中又主要由measure、layout、draw以及其派生方法所控制,在View中形成這樣一個體系:

再次注意:View的測量過程是由多個方法調用共同構成,measure和onMeasure僅僅代表該過程中的兩個方法而已。
如果控件繼承于ViewGroup實現的是一個布局容器,那么會多出一個dispatchDraw方法:

dispatchDraw方法本質上實現的是父容器對子元素的繪制分發,雖然邏輯不盡相同但是作用類似于draw,在高仿網易評論列表效果之界面生成中我們曾利用該方法在繪制子元素前繪制蓋樓背景,具體不再多說了。在我們調用setContentView方法后,如果你傳入的是一個資源文件ID,此時framework會使用LayoutInflater去解析布局文件,當解析到我們自定義控件LifeCycleView的標簽時,通過反射獲取一個對應的LifeCycleView類實例,此時構造方法被調用,爾后開始解析LifeCycleView標簽下的各類屬性并存值,LifeCycleView標簽下的所有屬性(如果是個容器的話也會層層解析)解析完成后調用onFinishInflate方法表示當前LifeCycleView所有的(注意不是整個布局哦僅僅是該View對應標簽)xml解析完畢,之后嘗試將View添加至當前Activity所在的Window,然后將處理UI事件的Msg壓入Message Queue開始至上而下地對整個View Tree進行測量,假設我們有如下的View Tree結構:

那么我們的測量總是從根部RelativeLayout開始逐層往下進行調用,在Android翻頁效果原理實現之引入折線中我們曾在講滑動時對Message Queue作過一個簡單的淺析,當Msg壓入Queue并最終得到處理的這段過程并不是立即的,也就是說其中會有一定的延時,這相對于我們在setContentView后立即setBitmap來說時間要長很多很多,這也是為什么我們在onMeasure中獲取Bitmap不為null的原因,具體的源碼邏輯實現會在《深入剖析Android GUI框架》深度講解,本系列除了后面要涉及到的事件分發外不會再涉及過多的源碼畢竟與基礎篇的定位不符,好了,這里我再留一個問題,setBitmap和onMeasure、onLayout等這些回調方法之間是異步呢還是同步呢?其實答案很明顯了……OK,不說了,既然我們知道這樣直接setBitmap是不對的(即便可行)那么我們該如何改進呢?答案很簡單,Andorid提供給我們極其簡便的方法,我們只需在設置Bitmap后調用requestLayout方法和invalidate即可:
~~~
public void setBitmap(Bitmap bitmap) {
this.mBitmap = bitmap;
requestLayout();
invalidate();
}
~~~
requestLayout方法的意義在于如果你的操作有可能會讓控件的尺寸或位置發生改變那么就可以調用該方法請求布局,調用該方法后framework會嘗試調用measure對控件重新測量:

而invalidate方法呢我們則用的多了不再多說:
但是要注意的一點是,requestLayout方法和invalidate方法并非都必需調用的,比如我們有一個更改字體顏色的方法:
~~~
public void setTextColor(int color) {
mPaint.setColor(color);
invalidate();
}
~~~
這時我們僅需調用invalidate方法標識重繪視圖即可,但是,如上我們所說,如果一旦尺寸大小或位置發生了變化,那么我們最好重新布局并迫使視圖重繪,比如我們有個改變字體大小的方法:
~~~
public void setTextSize(int size) {
mPaint.setTextSize(size);
requestLayout();
invalidate();
}
~~~
這時候我們就需要調用requestLayout請求布局,因為字體大小的改變有可能會影響到控件的尺寸大小和位置的改變,同樣,如果位置大小都變了,那我們是否該重新繪制呢?The answer is yes~好了,別嫌我啰嗦,最近有盆友反應說前面章節太難理解……其實之所以覺得前面的章節難是因為涉及繪制的API大多跟一些圖像處理有關,而coder正恰恰缺乏這方面的一些知識所以不好理解,你看我前面的章節壓根就沒涉及什么源碼,只有從測量開始才涉及了一次,此后也不再打算再過多地涉及,畢竟與該系列基礎篇的定位不符。閑話不多說了,如上以及前面兩節的內容所述,其實在應用開發使用的過程中設計測量邏輯的API并不多,也沒太多可講的,最主要的還是自己的邏輯,So,這一部分暫且為止,在自定義控件其實很簡單7/12中我們曾定義過一個類似圖標的控件:

當時我們是直接extends View去做的,繪制了文本、繪制了Bitmap還有在此之前對其進行測量、定位等等,即便我們考慮周詳,但是也極難將一個裝載文本和圖片的控件做成一個TextView和ImageView的復合體,更難以像TextView和ImageView那樣提供盡可能多的接口方法,誒!等等!既然我們的這個圖標控件看上去就是個TextView和ImageView雜交的后代,那么我們是否可以簡單地將這兩種控件組合起來變成一個新的控件呢?答案是肯定的撒!而且比起我們直接extends view來說簡單很多很多很多,首先我們先定義一個布局,這個布局里面呢只包含一個ImageView和一個TextView,大體來說樣式跟上面我們自定義的類似:

ADT中直接展示的效果如下:

是不是跟我們自定義的一樣呢?如我所說,僅僅是一個ImageView和TextView的組合而已,接下來我們要做的則是將這個xml布局文件“集成”到我們的自定義控件中去,方法也很簡單,在自定義控件的構造方法里引入該布局文件并將其作為控件的布局則可:
~~~
/**
*
* @author AigeStudio {@link http://blog.csdn.net/aigestudio}
* @since 2015/2/6
*
*/
public class ComplexView extends FrameLayout {
private ImageView ivIcon;// 復合控件中的ImageView
private TextView tvTitle;// 復合控件中的TextView
public ComplexView(Context context, AttributeSet attrs) {
super(context, attrs);
// 加載布局文件
((LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(
R.layout.view_complex, this);
// 獲取控件
ivIcon = (ImageView) findViewById(R.id.view_complex_image_iv);
tvTitle = (TextView) findViewById(R.id.view_complex_title_tv);
}
}
~~~
上面的代碼中我選擇繼承了FrameLayout,實際上你可以選擇繼承任何一種布局容器類,關鍵在于我們加載xml布局文件時以該布局容器作為根布局:
[java] view plain copy print?
xxxxxxxxxxxxxxxxxxxxx.inflate(R.layout.view_complex, this);
“this”就表示了將整個xml布局文件作為子元素加載至ComplexView(extends FrameLayout)下,構成如下圖所示的一個層級關系:

而后我們只需直接使用這個ComplexView符合控件即可:
~~~
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#ffffff"
android:layout_height="match_parent" >
<com.aigestudio.customviewdemo.views.ComplexView
android:id="@+id/main_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
~~~
這時候就可以直接在ADT中查看效果:

非常完美,不需要我們去處理測繪邏輯,所有的這些都由Android自帶的控件自行去計算,我們只是簡單地將它們組合在一起了而已,所以說,每當Android提供的控件不能滿足你的需求時,首先你應該想想是否可以在現有控件的基礎上修改一下來達到你的目的,而不是盲目地直接重寫View或ViewGroup類,你可以提供不同的接口方法來修改你復合控件中的各類元素,比如下面我們提供一個setImageIcon方法來為復合控件中的ImageView設置圖片:
~~~
public void setImageIcon(int resId) {
ivIcon.setImageResource(resId);
}
~~~
你甚至可以直接提供一個getter方法去獲取復合元素的引用:
~~~
public TextView getTitle() {
return tvTitle;
}
~~~
這樣你就可以對復合控件中的TextView為所欲為了……如前幾節我們所說,framework對xml文件的解析是相當耗時的,如果可以,我們應當盡量避免對xml文檔的讀取,特別是元素結構復雜的xml文件,這里我們用到的xml布局文件還不算復雜,如果我不想從xml文檔讀取而是直接實例化類呢?我們來重新修改下我們的代碼:
~~~
/**
*
* @author AigeStudio {@link http://blog.csdn.net/aigestudio}
* @since 2015/2/6
*
*/
public class ComplexView extends LinearLayout {
private ImageView ivIcon;// 復合控件中的ImageView
private TextView tvTitle;// 復合控件中的TextView
public ComplexView(Context context, AttributeSet attrs) {
super(context, attrs);
// 設置線性布局排列方式
setOrientation(LinearLayout.VERTICAL);
// 設置線性布局子元素對齊方式
setGravity(Gravity.CENTER);
// 實例化子元素
ivIcon = new ImageView(context);
ivIcon.setImageResource(R.drawable.logo);
tvTitle = new TextView(context);
tvTitle.setText("AigeStudio");
tvTitle.setTextSize(MeasureUtil.dp2px(context, 30));
tvTitle.setTypeface(Typeface.DEFAULT_BOLD);
// 將子元素添加到復合控件
addView(ivIcon, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
addView(tvTitle, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
}
}
~~~
直接讓復合控件類繼承LinearLayout,在構造方法中實例化子元素并設置其屬性值然后添加至LinearLayout中搞定,復合控件在實際應用中也使用得相當廣泛,因為很多時候使用符合控件不需要處理復雜的測繪邏輯,簡單方便高效。在Android自帶的控件中有個checkBox復選框控件:

效果單一乏味不好看,而我想要的效果很簡單也與之類似,通過不斷點擊控件往復切換控件的兩種狀態即可:

達到類似效果有多種方法,最簡單的是更改checkBox,最復雜的是繼承View自己寫一個,而上面我們了解過復合控件,那么我們能不能馬上學以致用使用一個復合控件來達到該效果呢?答案是肯定的!細心觀察可以看得出上面的效果無非就是兩張不同的圖片來回顯示/隱藏地切換而已,更直白地說就是兩個ImageView不斷地顯示/隱藏切換對吧,Such easy,下面直接看全部代碼:
~~~
/**
* 自定義CheckBox
*
* @author AigeStudio {@link http://blog.csdn.net/aigestudio}
* @since 2015/2/6
*
*/
public class CustomCheckBox extends FrameLayout {
private ImageView ivCheckOn, ivCheckOff;// 兩種狀態的ImageView
private CustomCheckBoxChangeListener customCheckBoxChangeListener;// 切換的監聽器
private boolean isCheck;// 是否被選中的標志值
public CustomCheckBox(Context context) {
this(context, null);
}
public CustomCheckBox(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomCheckBox(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 設置布局文件
LayoutInflater.from(context).inflate(R.layout.view_custom_check_box, this);
// 獲取控件元素
ivCheckOn = (ImageView) findViewById(R.id.view_custom_check_box_on);
ivCheckOff = (ImageView) findViewById(R.id.view_custom_check_box_off);
// 設置兩個ImageView的點擊事件
ivCheckOn.setOnClickListener(new ClickListener());
ivCheckOff.setOnClickListener(new ClickListener());
// 讀取xml中設置的資源屬性ID
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomCheckBox);
int imageOnResId = array.getResourceId(R.styleable.CustomCheckBox_imageOn, -1);
int imageOffResId = array.getResourceId(R.styleable.CustomCheckBox_imageOff, -1);
// 設置顯示資源
setOnImage(imageOnResId);
setOffImage(imageOffResId);
// 對象回收
array.recycle();
// 默認顯示的是沒被選中的狀態
setCheckOff();
}
/**
* 為CustomCheckBox設置監聽器
*
* @param customCheckBoxChangeListener
* 監聽器接口對象
*/
public void setCustomCheckBoxChangeListener(
CustomCheckBoxChangeListener customCheckBoxChangeListener) {
this.customCheckBoxChangeListener = customCheckBoxChangeListener;
}
/**
* 設置開啟狀態時CustomCheckBox的圖片
*
* @param resId
* 圖片資源ID
*/
public void setOnImage(int resId) {
ivCheckOn.setImageResource(resId);
}
/**
* 設置關閉狀態時CustomCheckBox的圖片
*
* @param resId
* 圖片資源ID
*/
public void setOffImage(int resId) {
ivCheckOff.setImageResource(resId);
}
/**
* 設置CustomCheckBox為關閉狀態
*/
public void setCheckOff() {
isCheck = false;
ivCheckOn.setVisibility(GONE);
ivCheckOff.setVisibility(VISIBLE);
}
/**
* 設置CustomCheckBox為開啟狀態
*/
public void setCheckOn() {
isCheck = true;
ivCheckOn.setVisibility(VISIBLE);
ivCheckOff.setVisibility(GONE);
}
/**
* 獲取CustomCheckBox的選擇狀態
*
* @return true CustomCheckBox已被選擇
* @return false CustomCheckBox未被選擇
*/
public boolean isCheck() {
return isCheck;
}
/**
* 狀態改變監聽接口
*/
public interface CustomCheckBoxChangeListener {
void customCheckBoxOn();
void customCheckBoxOff();
}
/**
* 自定義CustomCheckBox中控件的事件監聽器
*/
private class ClickListener implements OnClickListener {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.view_custom_check_box_on:
setCheckOff();
customCheckBoxChangeListener.customCheckBoxOff();
break;
case R.id.view_custom_check_box_off:
setCheckOn();
customCheckBoxChangeListener.customCheckBoxOn();
break;
}
}
}
}
~~~
整個復合控件非常簡單,我們只是簡單地將兩個ImageView重疊組合了在一起并設置其點擊事件監聽,對外我們公布了一個CustomCheckBoxChangeListener監聽接口以監聽狀態的改變并處理一些邏輯,只需在Activity中獲取CustomCheckBox控件并設置監聽對象即可:
~~~
/**
* 主界面
*
* @author Aige {@link http://blog.csdn.net/aigestudio}
* @since 2014/11/17
*/
public class MainActivity extends Activity {
private CustomCheckBox ccbTest;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ccbTest = (CustomCheckBox) findViewById(R.id.main_ccb);
ccbTest.setCustomCheckBoxChangeListener(new CustomCheckBoxChangeListener() {
@Override
public void customCheckBoxOn() {
Toast.makeText(MainActivity.this, "Check on", Toast.LENGTH_SHORT).show();
}
@Override
public void customCheckBoxOff() {
Toast.makeText(MainActivity.this, "Check off", Toast.LENGTH_SHORT).show();
}
});
}
}
~~~
當CustomCheckBox的當前狀態為未被選中時會觸發customCheckBoxOff方法否則觸發customCheckBoxOn方法:

CustomCheckBox中加載用到的xml布局文件如下:
~~~
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="@+id/view_custom_check_box_on"
android:layout_width="match_parent"
android:scaleType="fitCenter"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/view_custom_check_box_off"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitCenter" />
</FrameLayout>
~~~