Pressed狀態(tài)和clickable,duplicateParentState的關(guān)系
掃描二維碼
隨時(shí)隨地手機(jī)看文章
??????? 做Android開發(fā)的人都用過Selector,可以方便的實(shí)現(xiàn)View在不同狀態(tài)下的背景。不過,相信大部分開發(fā)者遇到過和我一樣的問題,本文會(huì)從源碼角度,解釋這些問題。
??????? 首先,這里簡單描述一下,我遇到的問題:
界面上有個(gè)全屏的LinearLayout A,A中有一個(gè)TextView B和Button C,其中,A的clickable=true,并設(shè)置了pressed時(shí),背景色為灰色,B設(shè)置了pressed時(shí),背景色為藍(lán)色
當(dāng)手指觸摸C下方的空白區(qū)域時(shí),看到了這樣的效果:
??????? 在這里看到,在沒有觸摸B的情況下,B的pressed = true,而C的pressed = false。 C的狀態(tài)暫且不討論,按照Android消息傳遞的原則,因?yàn)閠ouch的point不在B內(nèi)部,所以,touch消息應(yīng)該不會(huì)交給B處理,那為什么B的pressed = true?
???????? 下面開始一步一步分析(本文分析的Android源碼為4.2.2)。
Pressed狀態(tài)的設(shè)定
??????? 從View.onTouchEvent函數(shù)看起(frameworks/base/core/java/android/view/View.java):
????/** ?????*?Implement?this?method?to?handle?touch?screen?motion?events. ?????* ?????*?@param?event?The?motion?event. ?????*?@return?True?if?the?event?was?handled,?false?otherwise. ?????*/ ????public?boolean?onTouchEvent(MotionEvent?event)?{ ????????...... ????????if?(((viewFlags?&?CLICKABLE)?==?CLICKABLE?||?//這里是為什么設(shè)置A的clickable為true的原因,否則,press?A的時(shí)候,沒有界面元素處理touch?event,最終會(huì)由Activity的onTouchEvent函數(shù)處理 ????????????????(viewFlags?&?LONG_CLICKABLE)?==?LONG_CLICKABLE))?{ ????????????switch?(event.getAction())?{ ????????????????case?MotionEvent.ACTION_UP: ????????????????????...... ????????????????????break; ????????????????case?MotionEvent.ACTION_DOWN: ????????????????????mHasPerformedLongPress?=?false; ????????????????????...... ????????????????????//?Walk?up?the?hierarchy?to?determine?if?we're?inside?a?scrolling?container. ????????????????????boolean?isInScrollingContainer?=?isInScrollingContainer();//A已經(jīng)是頂層元素了,沒有ScrollView之類的控件存在,所以,isInScrollingContainer?=?false ????????????????????//?For?views?inside?a?scrolling?container,?delay?the?pressed?feedback?for ????????????????????//?a?short?period?in?case?this?is?a?scroll. ????????????????????if?(isInScrollingContainer)?{ ????????????????????????mPrivateFlags?|=?PFLAG_PREPRESSED; ????????????????????????if?(mPendingCheckForTap?==?null)?{ ????????????????????????????mPendingCheckForTap?=?new?CheckForTap(); ????????????????????????} ????????????????????????postDelayed(mPendingCheckForTap,?ViewConfiguration.getTapTimeout()); ????????????????????}?else?{ ????????????????????????//?Not?inside?a?scrolling?container,?so?show?the?feedback?right?away ????????????????????????setPressed(true);//A設(shè)置pressed?=?true ????????????????????????checkForLongClick(0); ????????????????????} ????????????????????break; ????????????????case?MotionEvent.ACTION_CANCEL: ????????????????????...... ????????????????????break; ????????????????case?MotionEvent.ACTION_MOVE: ????????????????????...... ????????????????????break; ????????????} ????????????return?true; ????????} ????????return?false; ????}
???????
??????? 從上面的代碼我們知道,當(dāng)手指觸摸A的時(shí)候,A的pressed被設(shè)置為true。
Pressed狀態(tài)的傳遞
??????? 接著,我們看看setPressed函數(shù)的實(shí)現(xiàn):
????/** ?????*?Sets?the?pressed?state?for?this?view. ?????* ?????*?@param?pressed?Pass?true?to?set?the?View's?internal?state?to?"pressed",?or?false?to?reverts ?????*????????????????the?View's?internal?state?from?a?previously?set?"pressed"?state. ?????*?@see?#isClickable() ?????*?@see?#setClickable(boolean) ?????*/ ????public?void?setPressed(boolean?pressed)?{ ????????final?boolean?needsRefresh?=?pressed?!=?((mPrivateFlags?&?PFLAG_PRESSED)?==?PFLAG_PRESSED); ????????if?(pressed)?{ ????????????mPrivateFlags?|=?PFLAG_PRESSED; ????????}?else?{ ????????????mPrivateFlags?&=?~PFLAG_PRESSED; ????????} ????????if?(needsRefresh)?{ ????????????refreshDrawableState();//切換背景圖片 ????????} ????????dispatchSetPressed(pressed); ????}
??????? setPressed函數(shù)內(nèi)部調(diào)用了dispatchSetPressed函數(shù),這個(gè)讓人很在意(frameworks/base/core/java/android/view/ViewGroup.java):
????@Override ????protected?void?dispatchSetPressed(boolean?pressed)?{ ????????final?View[]?children?=?mChildren; ????????final?int?count?=?mChildrenCount; ????????for?(int?i?=?0;?i?<?count;?i++)?{ ????????????final?View?child?=?children[i]; ????????????//?Children?that?are?clickable?on?their?own?should?not ????????????//?show?a?pressed?state?when?their?parent?view?does. ????????????//?Clearing?a?pressed?state?always?propagates. ????????????if?(!pressed?||?(!child.isClickable()?&&?!child.isLongClickable()))?{ ????????????????child.setPressed(pressed); ????????????} ????????} ????}
??????? 原來,dispatchSetPressed函數(shù)會(huì)把pressed狀態(tài)傳遞給所有clickable=false并且longclickable=false的子元素。
??????? 到這里,前面的現(xiàn)象就可以解釋了,因?yàn)镃是button類,clickable=true,而B的clickable=false。所以,當(dāng)A被觸摸時(shí),B的pressed=true,而C的pressed=false。那么,可以回答下面幾個(gè)小問題了:
如果不希望A的pressed=true時(shí),B的pressed = true,該如何修改?
設(shè)置B的clickable=true如果希望A的pressed = true時(shí),C的pressed = true,那又該如何修改?
設(shè)置C的clickable = false. 但是,這里可能又存在問題了,設(shè)置C的clickable=false,會(huì)導(dǎo)致button按鈕的onclicklistener無法工作,這是個(gè)嚴(yán)重的副作用。那么可以不修改clickable,而設(shè)置android:duplicateParentState為true。
?????? 那么duplicateParentState做了些什么呢?View.setDuplicateParentStateEnabled:
????public?void?setDuplicateParentStateEnabled(boolean?enabled)?{ ????????setFlags(enabled???DUPLICATE_PARENT_STATE?:?0,?DUPLICATE_PARENT_STATE); ????}
????? 再看看View.onCreateDrawableState()
?/** ?????*?Generate?the?new?{@link?android.graphics.drawable.Drawable}?state?for ?????*?this?view.?This?is?called?by?the?view ?????*?system?when?the?cached?Drawable?state?is?determined?to?be?invalid.??To ?????*?retrieve?the?current?state,?you?should?use?{@link?#getDrawableState}. ?????* ?????*?@param?extraSpace?if?non-zero,?this?is?the?number?of?extra?entries?you ?????*???????????????????would?like?in?the?returned?array?in?which?you?can?place?your?own ?????*???????????????????states. ?????*?@return?Returns?an?array?holding?the?current?{@link?Drawable}?state?of ?????*?the?view. ?????*?@see?#mergeDrawableStates(int[],?int[]) ?????*/ ????protected?int[]?onCreateDrawableState(int?extraSpace)?{ ????????if?((mViewFlags?&?DUPLICATE_PARENT_STATE)?==?DUPLICATE_PARENT_STATE?&& ????????????????mParent?instanceof?View)?{ ????????????return?((View)?mParent).onCreateDrawableState(extraSpace); ????????} ????????...... ????}
??????? 從上面的代碼,可以看到,當(dāng)設(shè)置duplicateParentState為true時(shí),View的DrawableState直接使用了其parent的。所以,他的drawable狀態(tài)會(huì)一直保持與其parent同步。
題外,為什么當(dāng)ListView中包含focusable為true的控件時(shí),OnItemClickListener不會(huì)觸發(fā)
??????? 因?yàn)長istView未重載onTouchEvent事件,所以,需要看其父類的AbsListView.onTouchEvent(frameworks/base/core/java/android/widget/AbsListView):
????@Override ????public?boolean?onTouchEvent(MotionEvent?ev)?{ ????????...... ????????switch?(action?&?MotionEvent.ACTION_MASK)?{ ????????case?MotionEvent.ACTION_DOWN:?{ ????????????...... ????????????break; ????????} ????????case?MotionEvent.ACTION_MOVE:?{ ????????????...... ????????????break; ????????} ????????case?MotionEvent.ACTION_UP:?{ ????????????switch?(mTouchMode)?{ ????????????case?TOUCH_MODE_DOWN: ????????????case?TOUCH_MODE_TAP: ????????????case?TOUCH_MODE_DONE_WAITING: ????????????????final?int?motionPosition?=?mMotionPosition; ????????????????final?View?child?=?getChildAt(motionPosition?-?mFirstPosition); ????????????????final?float?x?=?ev.getX(); ????????????????final?boolean?inList?=?x?>?mListPadding.left?&&?x?<?getWidth()?-?mListPadding.right; ????????????????if?(child?!=?null?&&?!child.hasFocusable()?&&?inList)?{ ????????????????????if?(mTouchMode?!=?TOUCH_MODE_DOWN)?{ ????????????????????????child.setPressed(false); ????????????????????} ????????????????????if?(mPerformClick?==?null)?{ ????????????????????????mPerformClick?=?new?PerformClick(); ????????????????????} ????????????????????...... ????????????????????performClick.run(); ????????????????????...... ????????????????} ????????????????...... ????????????case?TOUCH_MODE_SCROLL: ????????????????...... ????????????????break; ????????????case?TOUCH_MODE_OVERSCROLL: ????????????...... ????????????break; ????????} ????????...... ????????case?MotionEvent.ACTION_CANCEL:?{ ????????????...... ????????????break; ????????} ????????case?MotionEvent.ACTION_POINTER_UP:?{ ????????????...... ????????????break; ????????} ????????case?MotionEvent.ACTION_POINTER_DOWN:?{ ????????????...... ????????????break; ????????} ????????} ????????return?true; ????}
???????? 僅在child.hasFocusable()=false時(shí), PerformClick對(duì)象才會(huì)執(zhí)行ViewGroup.hasFocusable:
????/** ?????*?{@inheritDoc} ?????*/ ????@Override ????public?boolean?hasFocusable()?{ ????????if?((mViewFlags?&?VISIBILITY_MASK)?!=?VISIBLE)?{ ????????????return?false; ????????} ????????if?(isFocusable())?{ ????????????return?true; ????????} ????????final?int?descendantFocusability?=?getDescendantFocusability(); ????????if?(descendantFocusability?!=?FOCUS_BLOCK_DESCENDANTS)?{ ????????????final?int?count?=?mChildrenCount; ????????????final?View[]?children?=?mChildren; ????????????for?(int?i?=?0;?i?<?count;?i++)?{ ????????????????final?View?child?=?children[i]; ????????????????if?(child.hasFocusable())?{ ????????????????????return?true; ????????????????} ????????????} ????????} ????????return?false; ????}
??????? 僅在所有的clild的hasFocusable為false時(shí),ListView才會(huì)執(zhí)行performClick(AbsListView.PerformClick):
????private?class?PerformClick?extends?WindowRunnnable?implements?Runnable?{ ????????int?mClickMotionPosition; ????????public?void?run()?{ ????????????//?The?data?has?changed?since?we?posted?this?action?in?the?event?queue, ????????????//?bail?out?before?bad?things?happen ????????????if?(mDataChanged)?return; ????????????final?ListAdapter?adapter?=?mAdapter; ????????????final?int?motionPosition?=?mClickMotionPosition; ????????????if?(adapter?!=?null?&&?mItemCount?>?0?&& ????????????????????motionPosition?!=?INVALID_POSITION?&& ????????????????????motionPosition?<?adapter.getCount()?&&?sameWindow())?{ ????????????????final?View?view?=?getChildAt(motionPosition?-?mFirstPosition); ????????????????//?If?there?is?no?view,?something?bad?happened?(the?view?scrolled?off?the ????????????????//?screen,?etc.)?and?we?should?cancel?the?click ????????????????if?(view?!=?null)?{ ????????????????????performItemClick(view,?motionPosition,?adapter.getItemId(motionPosition)); ????????????????} ????????????} ????????} ????}
??????? 而PerformClick會(huì)調(diào)用performItemClick(AdsListView.performItemClick):
?@Override ????public?boolean?performItemClick(View?view,?int?position,?long?id)?{ ????????boolean?handled?=?false; ????????boolean?dispatchItemClick?=?true; ????????...... ????????if?(dispatchItemClick)?{ ????????????handled?|=?super.performItemClick(view,?position,?id); ????????} ????????return?handled; ????}
?????? AdapterView.preformItemClick:
????/** ?????*?Call?the?OnItemClickListener,?if?it?is?defined. ?????* ?????*?@param?view?The?view?within?the?AdapterView?that?was?clicked. ?????*?@param?position?The?position?of?the?view?in?the?adapter. ?????*?@param?id?The?row?id?of?the?item?that?was?clicked. ?????*?@return?True?if?there?was?an?assigned?OnItemClickListener?that?was ?????*?????????called,?false?otherwise?is?returned. ?????*/ ????public?boolean?performItemClick(View?view,?int?position,?long?id)?{ ????????if?(mOnItemClickListener?!=?null)?{ ????????????playSoundEffect(SoundEffectConstants.CLICK); ????????????if?(view?!=?null)?{ ????????????????view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); ????????????} ????????????mOnItemClickListener.onItemClick(this,?view,?position,?id); ????????????return?true; ????????} ????????return?false; ????}
??????? 所以,如果ListView item中包含focusable為true的控件(例如:button, radiobutton),會(huì)導(dǎo)致ItemClickListener失效。解決方案兩個(gè):
設(shè)置特定的控件focusable = false
不使用onItemClickListener,而直接在Item上設(shè)置onClickListener監(jiān)聽點(diǎn)擊事件。