1鹃祖、View的幾種不同狀態(tài)屬性
2、如何根據(jù)不同狀態(tài)去切換我們的背景圖片普舆。
開篇介紹:Android背景選擇器selector用法匯總
對Android開發(fā)有經(jīng)驗(yàn)的同學(xué)恬口,對 **<selector>**節(jié)點(diǎn)的使用一定很熟悉,該節(jié)點(diǎn)的作用就是定義一組狀態(tài)資源圖片沼侣,使其能夠
在不同的狀態(tài)下更換某個View的背景圖片祖能。例如,如下的hello_selection.xml文件定義:
"1.0" encoding="utf-8" ?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:state_window_focused="true" android:drawable= "@drawable/pic1" />
<item android:state_pressed="true" android:state_focused="false" android:drawable="@drawable/pic2" />
<item android:state_selected="true" android:drawable="@drawable/pic3" />
<item android:state_focused="true" android:drawable="@drawable/pic4" />
<item android:drawable="@drawable/pic5" />
</selector>
更多關(guān)于 **<selector>**節(jié)點(diǎn)的使用請參考該博客**<****[android背景選擇器selector用法匯總](http://blog.sina.com.cn/s/blog_4b93170a0100qhwa.html)****>**
其實(shí)蛾洛,前面說的xml文件养铸,最終會被Android框架解析成**StateListDrawable類對象。**
知識點(diǎn)一:StateListDrawable類介紹
類功能說明:該類定義了不同狀態(tài)值下與之對應(yīng)的圖片資源轧膘,即我們可以利用該類保存多種狀態(tài)值钞螟,多種圖片資源。
常用方法為:
public void addState (int[] stateSet, Drawable drawable)
功能: 給特定的狀態(tài)集合設(shè)置drawable圖片資源
使用方式:參考前面的hello_selection.xml文件扶供,我們利用代碼去構(gòu)建一個相同的StateListDrawable類對象筛圆,如下:
//初始化一個空對象
StateListDrawable stalistDrawable = new StateListDrawable();
//獲取對應(yīng)的屬性值 Android框架自帶的屬性 attr
int pressed = android.R.attr.state_pressed;
int window_focused = android.R.attr.state_window_focused;
int focused = android.R.attr.state_focused;
int selected = android.R.attr.state_selected;
stalistDrawable.addState(new int []{pressed , window_focused}, getResources().getDrawable(R.drawable.pic1));
stalistDrawable.addState(new int []{pressed , -focused}, getResources().getDrawable(R.drawable.pic2);
stalistDrawable.addState(new int []{selected }, getResources().getDrawable(R.drawable.pic3);
stalistDrawable.addState(new int []{focused }, getResources().getDrawable(R.drawable.pic4);
//沒有任何狀態(tài)時顯示的圖片,我們給它設(shè)置我空集合
stalistDrawable.addState(new int []{}, getResources().getDrawable(R.drawable.pic5);
上面的“-”負(fù)號表示對應(yīng)的屬性值為false
當(dāng)我們?yōu)槟硞€View使用其作為背景色時椿浓,會根據(jù)狀態(tài)進(jìn)行背景圖的轉(zhuǎn)換太援。
public boolean isStateful ()
功能: 表明該狀態(tài)改變了,對應(yīng)的drawable圖片是否會改變扳碍。
注:在StateListDrawable類中提岔,該方法返回為true,顯然狀態(tài)改變后笋敞,我們的圖片會跟著改變碱蒙。
知識點(diǎn)二:View的五種狀態(tài)值
一般來說,Android框架為View定義了四種不同的狀態(tài)夯巷,這些狀態(tài)值的改變會引發(fā)View相關(guān)操作赛惩,例如:更換背景圖片、是否
觸發(fā)點(diǎn)擊事件等趁餐;視
視圖幾種不同狀態(tài)含義見下圖:
其中selected和focused的區(qū)別有如下幾點(diǎn):
1喷兼,我們通過查看setSelected()方法,來獲取相關(guān)信息后雷。
SDK中對setSelected()方法----對于與selected狀態(tài)有如下說明:
public void **setSelected (boolean selected)
Since: [APILevel 1](file:///F:/android-sdk-windows/docs/guide/appendix/api-levels.html#level1)
Changes the selection state of this view. Aview can be selected or not. Note that selection is not the same as
focus. Views are typically selected in the context of an AdapterView like ListView or GridView ;the selected view is
the view that is highlighted.
** Parameters selected true if the view must be selected, false otherwise
由以上可知:selected不同于focus狀態(tài)季惯,通常在AdapterView類群下例如ListView或者GridView會使某個View處于
selected狀態(tài)吠各,并且獲得該狀態(tài)的View處于高亮狀態(tài)。
2勉抓、一個窗口只能有一個視圖獲得焦點(diǎn)(focus)贾漏,而一個窗口可以有多個視圖處于”selected”狀態(tài)中。
總結(jié):focused狀態(tài)一般是由按鍵操作引起的藕筋;
pressed狀態(tài)是由觸摸消息引起的纵散;
selected則完全是由應(yīng)用程序主動調(diào)用setSelected()進(jìn)行控制。
例如:當(dāng)我們觸摸某個控件時念逞,會導(dǎo)致pressed狀態(tài)改變困食;獲得焦點(diǎn)時,會導(dǎo)致focus狀態(tài)變化翎承。于是硕盹,我們可以通過這種
更新后狀態(tài)值去更新我們對應(yīng)的Drawable對象了。
問題:如何根據(jù)狀態(tài)值的改變?nèi)ダL制/顯示對應(yīng)的背景圖叨咖?
當(dāng)View任何狀態(tài)值發(fā)生改變時瘩例,都會調(diào)用refreshDrawableList()方法去更新對應(yīng)的背景Drawable對象。
其整體調(diào)用流程如下: View.[Java](http://lib.csdn.net/base/javase)類中
//路徑:\frameworks\base\core\java\android\view\View.java
/* Call this to force a view to update its drawable state. This will cause
* drawableStateChanged to be called on this view. Views that are interested
* in the new state should call getDrawableState.
/
//主要功能是根據(jù)當(dāng)前的狀態(tài)值去更換對應(yīng)的背景Drawable對象
public void refreshDrawableState() {
mPrivateFlags |= DRAWABLE_STATE_DIRTY;
//所有功能在這個函數(shù)里去完成
drawableStateChanged();
...
}
/ This function is called whenever the state of the view changes in such
* a way that it impacts the state of drawables being shown.
/
// 獲得當(dāng)前的狀態(tài)屬性--- 整型集合 甸各; 調(diào)用Drawable類的setState方法去獲取資源垛贤。
protected void drawableStateChanged() {
//該視圖對應(yīng)的Drawable對象,通常對應(yīng)于StateListDrawable類對象
Drawable d = mBGDrawable;
if (d != null && d.isStateful()) { //通常都是成立的
//getDrawableState()方法主要功能:會根據(jù)當(dāng)前View的狀態(tài)屬性值趣倾,將其轉(zhuǎn)換為一個整型集合
//setState()方法主要功能:根據(jù)當(dāng)前的獲取到的狀態(tài)聘惦,更新對應(yīng)狀態(tài)下的Drawable對象。
d.setState(getDrawableState());
}
}
/Return an array of resource IDs of the drawable states representing the
* current state of the view.
*/
public final int[] getDrawableState() {
if ((mDrawableState != null) && ((mPrivateFlags & DRAWABLE_STATE_DIRTY) == 0)) {
return mDrawableState;
} else {
//根據(jù)當(dāng)前View的狀態(tài)屬性值儒恋,將其轉(zhuǎn)換為一個整型集合善绎,并返回
mDrawableState = onCreateDrawableState(0);
mPrivateFlags &= ~DRAWABLE_STATE_DIRTY;
return mDrawableState;
}
}
通過這段代碼我們可以明白View內(nèi)部是如何獲取更細(xì)后的狀態(tài)值以及動態(tài)獲取對應(yīng)的背景Drawable對象----setState()方法
去完成的。這兒我簡單的分析下Drawable類里的setState()方法的功能诫尽,把流程給走一下:
Step 1 禀酱、 setState()函數(shù)原型 ,
函數(shù)位于:frameworks\base\graphics\java\android\graphics\drawable\StateListDrawable.java 類中
[java] view plaincopyprint?
//如果狀態(tài)態(tài)值發(fā)生了改變牧嫉,就回調(diào)onStateChange()方法剂跟。
public boolean setState(final int[] stateSet) {
if (!Arrays.equals(mStateSet, stateSet)) {
mStateSet = stateSet;
return onStateChange(stateSet);
}
return false;
}
該函數(shù)的主要功能: 判斷狀態(tài)值是否發(fā)生了變化,如果發(fā)生了變化酣藻,就調(diào)用onStateChange()方法進(jìn)一步處理曹洽。
Step 2 、onStateChange()函數(shù)原型:
該函數(shù)位于 frameworks\base\graphics\java\android\graphics\drawable\StateListDrawable.java 類中
//狀態(tài)值發(fā)生了改變辽剧,我們需要找出第一個吻合的當(dāng)前狀態(tài)的Drawable對象
protected boolean onStateChange(int[] stateSet) {
//要找出第一個吻合的當(dāng)前狀態(tài)的Drawable對象所在的索引位置衣洁, 具體匹配算法請自己深入源碼看看
int idx = mStateListState.indexOfStateSet(stateSet);
...
//獲取對應(yīng)索引位置的Drawable對象
if (selectDrawable(idx)) {
return true;
}
...
}
該函數(shù)的主要功能: 根據(jù)新的狀態(tài)值,從StateListDrawable實(shí)例對象中抖仅,找到第一個完全吻合該新狀態(tài)值的索引下標(biāo)處 坊夫;
繼而,調(diào)用selectDrawable()方法去獲取索引下標(biāo)的當(dāng)前Drawable對象撤卢。
具體查找算法在 mStateListState.indexOfStateSet(stateSet) 里實(shí)現(xiàn)了环凿。基本思路是:查找第一個能完全吻合該新狀態(tài)值
的索引下標(biāo)放吩,如果找到了智听,則立即返回。 具體實(shí)現(xiàn)過程渡紫,只好看看源碼咯到推。
Step 3 、selectDrawable()函數(shù)原型:
該函數(shù)位于 frameworks\base\graphics\java\android\graphics\drawable\StateListDrawable.java 類中
public boolean selectDrawable(int idx)
{
if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {
//獲取對應(yīng)索引位置的Drawable對象
Drawable d = mDrawableContainerState.mDrawables[idx];
...
mCurrDrawable = d; //mCurrDrawable即使當(dāng)前Drawable對象
mCurIndex = idx;
...
} else {
...
}
//請求該View刷新自己,這個方法我們稍后講解惕澎。
invalidateSelf();
return true;
}
該函數(shù)的主要功能是選擇當(dāng)前索引下標(biāo)處的Drawable對象莉测,并保存在mCurrDrawable中。
知識點(diǎn)三: 關(guān)于Drawable.Callback接口
該接口定義了如下三個函數(shù):
[java] view plaincopyprint?
//該函數(shù)位于 frameworks\base\graphics\java\android\graphics\drawable\Drawable.java 類中
public static interface Callback {
//如果Drawable對象的狀態(tài)發(fā)生了變化唧喉,會請求View重新繪制捣卤,
//因此我們對應(yīng)于該View的背景Drawable對象能夠”繪制出來”.
public void invalidateDrawable(Drawable who);
//該函數(shù)目前還不懂
public void scheduleDrawable(Drawable who, Runnable what, long when);
//該函數(shù)目前還不懂
public void unscheduleDrawable(Drawable who, Runnable what);
}
其中比較重要的函數(shù)為:
public void**invalidateDrawable**(Drawable who)
函數(shù)功能:如果Drawable對象的狀態(tài)發(fā)生了變化,會請求View重新繪制八孝,因此我們對應(yīng)于該View的背景Drawable對象
能夠重新”繪制“出來董朝。
Android框架View類繼承了該接口,同時實(shí)現(xiàn)了這三個函數(shù)的默認(rèn)處理方式干跛,其中**invalidateDrawable**()方法如下:
[java] view plaincopyprint?
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource
{
...
//Invalidates the specified Drawable.
//默認(rèn)實(shí)現(xiàn)子姜,重新繪制該視圖本身
public void invalidateDrawable(Drawable drawable) {
if (verifyDrawable(drawable)) { //是否是同一個Drawable對象,通常為真
final Rect dirty = drawable.getBounds();
final int scrollX = mScrollX;
final int scrollY = mScrollY;
//重新請求繪制該View楼入,即重新調(diào)用該View的draw()方法 ...
invalidate(dirty.left + scrollX, dirty.top + scrollY,
dirty.right + scrollX, dirty.bottom + scrollY);
}
}
...
}
因此哥捕,我們的Drawable類對象必須將View設(shè)置為回調(diào)對象,否則浅辙,即使改變了狀態(tài)扭弧,也不會顯示對應(yīng)的背景圖。 如下:
Drawable d ; // 圖片資源
d.setCallback(View v) ; // 視圖v的背景資源為 d 對象
知識點(diǎn)四:View繪制背景圖片過程
在前面的博客中《[**Android中View繪制流程以及invalidate()等相關(guān)方法分析**](http://blog.csdn.net/qinjuning/article/details/7110211)》记舆,我們知道了一個視圖的背景繪制過程時在
View類里的draw()方法里完成的鸽捻,我們這兒在回顧下draw()的流程,同時重點(diǎn)講解下繪制背景的操作泽腮。
[java] view plaincopyprint?
//方法所在路徑:frameworks\base\core\java\android\view\View.java
//draw()繪制過程
private void draw(Canvas canvas){
//該方法會做如下事情
//1 御蒲、繪制該View的背景
//其中背景圖片繪制過程如下:
//是否透明, 視圖通常是透明的 诊赊, 為true
if (!dirtyOpaque) {
//開始繪制視圖的背景
final Drawable background = mBGDrawable;
if (background != null) {
final int scrollX = mScrollX; //獲取偏移值
final int scrollY = mScrollY;
//視圖的布局坐標(biāo)是否發(fā)生了改變厚满, 即是否重新layout了。
if (mBackgroundSizeChanged) {
//如果是碧磅,我們的Drawable對象需要重新設(shè)置大小了碘箍,即填充該View遵馆。
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
}
//View沒有發(fā)生偏移
if ((scrollX | scrollY) == 0) {
background.draw(canvas); //OK, 該方法會繪制當(dāng)前StateListDrawable的當(dāng)前背景Drawable
} else {
//View發(fā)生偏移丰榴,由于背景圖片值顯示在布局坐標(biāo)中货邓,即背景圖片不會發(fā)生偏移,只有視圖內(nèi)容onDraw()會發(fā)生偏移
//我們調(diào)整canvas對象的繪制區(qū)域四濒,繪制完成后對canvas對象屬性調(diào)整回來
canvas.translate(scrollX, scrollY);
background.draw(canvas); //OK换况, 該方法會繪制當(dāng)前StateListDrawable的當(dāng)前背景Drawable
canvas.translate(-scrollX, -scrollY);
}
}
}
...
//2、為繪制漸變框做一些準(zhǔn)備操作
//3盗蟆、調(diào)用onDraw()方法繪制視圖本身
//4戈二、調(diào)用dispatchDraw()方法繪制每個子視圖,dispatchDraw()已經(jīng)在Android框架中實(shí)現(xiàn)了喳资,在ViewGroup方法中觉吭。
//5、繪制漸變框
}
That's all ! 我們用到的知識點(diǎn)也就這么多吧骨饿。 如果大家有絲絲不明白的話亏栈,可以去看下源代碼,具體去分析下這些流程到底
是怎么走下來的宏赘。
我們從宏觀的角度分析了View繪制不同狀態(tài)背景的原理绒北,View框架就是這么做的。為了易于理解性察署,
下面我們通過一個小Demo來演示前面種種流程闷游。
** Demo 說明:**
我們參照View框架中繪制不同背景圖的實(shí)現(xiàn)原理,自定義一個View類贴汪,通過給它設(shè)定StateListDrawable對象脐往,使其能夠在
不同狀態(tài)時能動態(tài)"繪制"背景圖片。 基本流程方法和View.java類實(shí)現(xiàn)過程一模一樣扳埂。
截圖如下:
![](http://upload-images.jianshu.io/upload_images/4112363-d7585d2fa4add2ed.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![](http://upload-images.jianshu.io/upload_images/4112363-f9c0c04f84810089.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
** 初始背景圖 觸摸后顯示的背景圖(pressed)**
一业簿、主文件MainActivity.java如下:
[java] view plaincopyprint?
/**
-
@author http://http://blog.csdn.net/qinjuning
*/
public class MainActivity extends Activity
{@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);LinearLayout ll = new LinearLayout(MainActivity.this); CustomView customView = new CustomView(MainActivity.this); //簡單設(shè)置為 width 200px - height 100px吧 ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(200 , 100); customView.setLayoutParams(lp); //需要將該View設(shè)置為可點(diǎn)擊/觸摸狀態(tài),否則觸摸該View沒有效果阳懂。 customView.setClickable(true); ll.addView(customView); setContentView(ll);
}
}功能很簡單梅尤,為Activity設(shè)置了視圖 。
二岩调、 自定義View如下 巷燥, CustomView.java :
[java] view plaincopyprint?
/**
-
@author http://http://blog.csdn.net/qinjuning
/
//自定義View
public class CustomView extends View /extends Button*/
{
private static String TAG = "TackTextView";private Context mContext = null;
private Drawable mBackground = null;
private boolean mBGSizeChanged = true;; //視圖View布局(layout)大小是否發(fā)生變化public CustomView(Context context)
{
super(context);
mContext = context;
initStateListDrawable(); // 初始化圖片資源
}// 初始化圖片資源
private void initStateListDrawable()
{
//有兩種方式獲取我們的StateListDrawable對象:
// 獲取方式一、手動構(gòu)建一個StateListDrawable對象
StateListDrawable statelistDrawable = new StateListDrawable();int pressed = android.R.attr.state_pressed; int windowfocused = android.R.attr.state_window_focused; int enabled = android.R.attr.state_enabled; int stateFoucesd = android.R.attr.state_focused; //匹配狀態(tài)時号枕,是一種優(yōu)先包含的關(guān)系缰揪。 // "-"號表示該狀態(tài)值為false .即不匹配 statelistDrawable.addState(new int[] { pressed, windowfocused }, mContext.getResources().getDrawable(R.drawable.btn_power_on_pressed)); statelistDrawable.addState(new int[]{ -pressed, windowfocused }, mContext.getResources().getDrawable(R.drawable.btn_power_on_nor)); mBackground = statelistDrawable; //必須設(shè)置回調(diào),當(dāng)改變狀態(tài)時葱淳,會回掉該View進(jìn)行invalidate()刷新操作. mBackground.setCallback(this); //取消默認(rèn)的背景圖片钝腺,因?yàn)槲覀冊O(shè)置了自己的背景圖片了抛姑,否則可能造成背景圖片重疊。 this.setBackgroundDrawable(null); // 獲取方式二拍屑、途戒、使用XML獲取StateListDrawable對象 // mBackground = mContext.getResources().getDrawable(R.drawable.tv_background);
}
protected void drawableStateChanged()
{
Log.i(TAG, "drawableStateChanged");
Drawable d = mBackground;
if (d != null && d.isStateful())
{
d.setState(getDrawableState());
Log.i(TAG, "drawableStateChanged and is 111");
}Log.i(TAG, "drawableStateChanged and is 222"); super.drawableStateChanged();
}
//驗(yàn)證圖片是否相等 , 在invalidateDrawable()會調(diào)用此方法,我們需要重寫該方法僵驰。
protected boolean verifyDrawable(Drawable who)
{
return who == mBackground || super.verifyDrawable(who);
}
//draw()過程,繪制背景圖片...
public void draw(Canvas canvas)
{
Log.i(TAG, " draw -----");
if (mBackground != null)
{
if(mBGSizeChanged)
{
//設(shè)置邊界范圍
mBackground.setBounds(0, 0, getRight() - getLeft(), getBottom() - getTop());
mBGSizeChanged = false ;
}
if ((getScrollX() | getScrollY()) == 0) //是否偏移
{
mBackground.draw(canvas); //繪制當(dāng)前狀態(tài)對應(yīng)的圖片
}
else
{
canvas.translate(getScrollX(), getScrollY());
mBackground.draw(canvas); //繪制當(dāng)前狀態(tài)對應(yīng)的圖片
canvas.translate(-getScrollX(), -getScrollY());
}
}
super.draw(canvas);
}
public void onDraw(Canvas canvas) {
...
}
}將該View設(shè)置的背景圖片轉(zhuǎn)換為節(jié)點(diǎn)xml唁毒,形式如下:
[java] view plaincopyprint?
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:state_window_focused="true"
android:drawable="@drawable/btn_power_on_pressed"></item>
<item android:state_pressed="false"
android:state_window_focused="true"
android:drawable="@drawable/btn_power_on_nor"></item>
</selector>
基本上所有功能都在這兒顯示出來了蒜茴, 和我們前面說的一模一樣吧。
當(dāng)然了浆西,如果你想偷懶粉私,大可用系統(tǒng)定義好的一套工具 , 即直接使用setBackgroundXXX()或者在設(shè)置對應(yīng)的屬性近零,但是诺核,
萬變不離其宗,掌握了繪制原理久信,可以瀟灑走江湖了窖杀。
** 示例Demo下載地址: http://download.csdn.net/detail/qinjuning/4237298**