日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Pressed状态和clickable,duplicateParentState的关系

發布時間:2023/12/6 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Pressed状态和clickable,duplicateParentState的关系 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

??? 做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
  • /**?
  • ?*?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?||?//這里是為什么設置A的clickable為true的原因,否則,press?A的時候,沒有界面元素處理touch?event,最終會由Activity的onTouchEvent函數處理??
  • ????????????(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已經是頂層元素了,沒有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設置pressed?=?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
  • /**?
  • ?*?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函數內部調用了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];??
  • ????????//?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函數會把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
  • /**?
  • ????*?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);??
  • ???????}??
  • ??
  • ???????......??
  • ???}??
  • ??????? 從上面的代碼,可以看到,當設置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
  • /**?
  • ?*?{@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時,ListView才會執行performClick(AbsListView.PerformClick):

    [java] view plain copy
  • 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會調用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
  • /**?
  • ?*?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),會導致ItemClickListener失效。解決方案兩個:

    設置特定的控件focusable = false

    不使用onItemClickListener,而直接在Item上設置onClickListener監聽點擊事件。

    總結

    以上是生活随笔為你收集整理的Pressed状态和clickable,duplicateParentState的关系的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。