??? 做Android開發的人都用過Selector,可以方便的實現View在不同狀態下的背景。不過,相信大部分開發者遇到過和我一樣的問題,本文會從源碼角度,解釋這些問題。
??????? 首先,這里簡單描述一下,我遇到的問題:
界面上有個全屏的LinearLayout A,A中有一個TextView B和Button C,其中,A的clickable=true,并設置了pressed時,背景色為灰色,B設置了pressed時,背景色為藍色
當手指觸摸C下方的空白區域時,看到了這樣的效果:
??????? 在這里看到,在沒有觸摸B的情況下,B的pressed = true,而C的pressed = false。 C的狀態暫且不討論,按照Android消息傳遞的原則,因為touch的point不在B內部,所以,touch消息應該不會交給B處理,那為什么B的pressed = true?
???????? 下面開始一步一步分析(本文分析的Android源碼為4.2.2)。
Pressed狀態的設定
??????? 從View.onTouchEvent函數看起(frameworks/base/core/java/android/view/View.java):
[java] view plain
copy public?boolean?onTouchEvent(MotionEvent?event)?{??????......??????if?(((viewFlags?&?CLICKABLE)?==?CLICKABLE?||?????????????(viewFlags?&?LONG_CLICKABLE)?==?LONG_CLICKABLE))?{??????????switch?(event.getAction())?{??????????????case?MotionEvent.ACTION_UP:??????????????????......??????????????????break;????????????????case?MotionEvent.ACTION_DOWN:??????????????????mHasPerformedLongPress?=?false;??????????????????......??????????????????????????????????boolean?isInScrollingContainer?=?isInScrollingContainer();??????????????????????????????????????????????????if?(isInScrollingContainer)?{??????????????????????mPrivateFlags?|=?PFLAG_PREPRESSED;??????????????????????if?(mPendingCheckForTap?==?null)?{??????????????????????????mPendingCheckForTap?=?new?CheckForTap();??????????????????????}??????????????????????postDelayed(mPendingCheckForTap,?ViewConfiguration.getTapTimeout());??????????????????}?else?{??????????????????????????????????????????setPressed(true);????????????????????checkForLongClick(0);??????????????????}??????????????????break;????????????????case?MotionEvent.ACTION_CANCEL:??????????????????......??????????????????break;????????????????case?MotionEvent.ACTION_MOVE:??????????????????......??????????????????break;??????????}??????????return?true;??????}????????return?false;??}?? ???????
??????? 從上面的代碼我們知道,當手指觸摸A的時候,A的pressed被設置為true。
Pressed狀態的傳遞
??????? 接著,我們看看setPressed函數的實現:
[java] view plain
copy 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函數內部調用了dispatchSetPressed函數,這個讓人很在意(frameworks/base/core/java/android/view/ViewGroup.java):
[java] view plain
copy @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];??????????????????????????????????if?(!pressed?||?(!child.isClickable()?&&?!child.isLongClickable()))?{??????????????child.setPressed(pressed);??????????}??????}??}?? ??????? 原來,dispatchSetPressed函數會把pressed狀態傳遞給所有clickable=false并且longclickable=false的子元素。
??????? 到這里,前面的現象就可以解釋了,因為C是button類,clickable=true,而B的clickable=false。所以,當A被觸摸時,B的pressed=true,而C的pressed=false。那么,可以回答下面幾個小問題了:
如果不希望A的pressed=true時,B的pressed = true,該如何修改?設置B的clickable=true如果希望A的pressed = true時,C的pressed = true,那又該如何修改?設置C的clickable = false. 但是,這里可能又存在問題了,設置C的clickable=false,會導致button按鈕的onclicklistener無法工作,這是個嚴重的副作用。那么可以不修改clickable,而設置android:duplicateParentState為true。
?????? 那么duplicateParentState做了些什么呢?View.setDuplicateParentStateEnabled:
[java] view plain
copy public?void?setDuplicateParentStateEnabled(boolean?enabled)?{??????setFlags(enabled???DUPLICATE_PARENT_STATE?:?0,?DUPLICATE_PARENT_STATE);??}?? ????? 再看看View.onCreateDrawableState()
[java] view plain
copy ???protected?int[]?onCreateDrawableState(int?extraSpace)?{?????????if?((mViewFlags?&?DUPLICATE_PARENT_STATE)?==?DUPLICATE_PARENT_STATE?&&?????????????????mParent?instanceof?View)?{?????????????return?((View)?mParent).onCreateDrawableState(extraSpace);?????????}???????????......?????}?? ??????? 從上面的代碼,可以看到,當設置duplicateParentState為true時,View的DrawableState直接使用了其parent的。所以,他的drawable狀態會一直保持與其parent同步。
題外,為什么當ListView中包含focusable為true的控件時,OnItemClickListener不會觸發
??????? 因為ListView未重載onTouchEvent事件,所以,需要看其父類的AbsListView.onTouchEvent(frameworks/base/core/java/android/widget/AbsListView):
[java] view plain
copy @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時, PerformClick對象才會執行ViewGroup.hasFocusable:
[java] view plain
copy @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時,ListView才會執行performClick(AbsListView.PerformClick):
[java] view plain
copy private?class?PerformClick?extends?WindowRunnnable?implements?Runnable?{??????int?mClickMotionPosition;????????public?void?run()?{??????????????????????????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?(view?!=?null)?{??????????????????performItemClick(view,?motionPosition,?adapter.getItemId(motionPosition));??????????????}??????????}??????}??}?? ??????? 而PerformClick會調用performItemClick(AdsListView.performItemClick):
[java] view plain
copy @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:
[java] view plain
copy 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),會導致ItemClickListener失效。解決方案兩個:
設置特定的控件focusable = false
不使用onItemClickListener,而直接在Item上設置onClickListener監聽點擊事件。
總結
以上是生活随笔為你收集整理的Pressed状态和clickable,duplicateParentState的关系的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。