在上兩篇文章中奉呛,我們已經(jīng)實(shí)現(xiàn)了基本的界面的布局和移動(dòng)效果胯陋,但是mImgShotView
、mContentView
卻不能響應(yīng)事件扒磁,而事件的響應(yīng)就需要我們手動(dòng)進(jìn)行事件分發(fā)庆揪!</br>
android 仿當(dāng)樂游戲詳情頁(yè)面(一)</br>
android 仿當(dāng)樂游戲詳情頁(yè)面(二)
事件分發(fā)分析
在前面第二篇中,我們是通過手勢(shì)來實(shí)現(xiàn)布局的移動(dòng)妨托,為了讓系統(tǒng)能響應(yīng)手勢(shì)缸榛,在onTouchEvent(MotionEvent event)
方法里面,調(diào)用了mDetector.onTouchEvent(event);
將系統(tǒng)的焦點(diǎn)傳遞給了手勢(shì)始鱼,因此仔掸,當(dāng)滑動(dòng)mImgShotView
這個(gè)ViewPager時(shí)脆贵,會(huì)出現(xiàn)焦點(diǎn)丟失医清,截圖不能進(jìn)行切換的問題。</br>
因此卖氨,解決這個(gè)問題最好的方法就是重寫dispatchTouchEvent(MotionEvent ev)
方法会烙,對(duì)事件分發(fā)進(jìn)行處理。</br>
在上一篇文章中筒捺,已經(jīng)介紹了柏腻,在仿當(dāng)樂的游戲詳情頁(yè)面中,mContentView
有三種不同的狀態(tài):
- 頂部狀態(tài)時(shí)系吭,
ToolBar
和mContentView
將獲取到焦點(diǎn)五嫂。 - 中間狀態(tài)時(shí),
ToolBar
和mImgShotView
將獲取到焦點(diǎn)肯尺。 - 底部狀態(tài)時(shí)沃缘,上一篇文章中已經(jīng)介紹了,這個(gè)狀態(tài)则吟,
mImgShotView
的參數(shù)將發(fā)生改變槐臀,因此,事件焦點(diǎn)的分發(fā)便需要進(jìn)行改變氓仲。
事件分發(fā)的實(shí)現(xiàn)
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mRawY <= mTopL) {
mCurrentState = STATE_TOP;
} else if (mTopL < mRawY && mRawY <= mCenterL + (mBarH >> 1)) {
mCurrentState = STATE_CENTER;
} else if (mCenterL + (mBarH >> 1) <= mRawY && mRawY < mBottomL + mBarH) {
mCurrentState = STATE_BOTTOM;
} else {
mCurrentState = STATE_OTHER;
}
boolean isTop = mRawY == mTopL;
// 處理橫向滑動(dòng)的事件
if (Math.abs(ev.getX() - mOldX) >= 0 && Math.abs(ev.getY() - mOldY) < 300 && isTop) {
mOldX = ev.getX();
return super.dispatchTouchEvent(ev);
}
float t = Math.abs(ev.getY() - mOldY);
//處于頂部時(shí)的事件過濾區(qū)域
if (isTop && (ev.getY() < mTopL || t < 10)) {
return super.dispatchTouchEvent(ev);
}
//處于中間時(shí)的事件過濾區(qū)域
if (mCurrentState == STATE_CENTER && ev.getY() < (mCenterL + mBarH) && mRawY >= mCenterL) {
return super.dispatchTouchEvent(ev);
}
//處于底部時(shí)的事件過濾區(qū)域
if (mCurrentState == STATE_BOTTOM && ev.getY() < (mBottomL + mBarH) && mRawY >= mBottomL) {
return super.dispatchTouchEvent(ev);
}
boolean isUp = ev.getY() - mOldY < 0;
if (isTop && mCurrentState == STATE_TOP) {
mOldY = (int) ev.getY();
if (isScrollTop && !isUp) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
return super.dispatchTouchEvent(ev);
}
return onTouchEvent(ev);
} else {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
return super.dispatchTouchEvent(ev);
}
return super.dispatchTouchEvent(ev);
}
}
return onTouchEvent(ev);
}
以上便是事件分發(fā)的全部代碼</br>
我們從最簡(jiǎn)單的地方開始分析代碼水慨,在上一篇文章中得糜,定義了幾個(gè)變量mTopL、mCenterL晰洒、mBottomL
用來確定布局移動(dòng)的基準(zhǔn)位置朝抖,mRawY
用來確定布局的當(dāng)前的Y坐標(biāo)。</br>
因此谍珊,便可以使用這幾個(gè)變量來確認(rèn)當(dāng)前布局所處的狀態(tài):
1. mRawY <= mTopL ==> 布局處于頂部狀態(tài)
2. mTopL < mRawY && mRawY <= mCenterL + (mBarH >> 1) ==> 布局處于中間狀態(tài)槽棍。
3. mCenterL + (mBarH >> 1) <= mRawY && mRawY < mBottomL + mBarH ==> 布局處于底部狀態(tài)
在2、3中抬驴,mBarH
表示的是ToolBar的高度常量炼七,+ mBarH,表示往下移動(dòng)的偏移常量布持。</br>
處理普通的事件過濾
繼續(xù)看代碼豌拙,
float t = Math.abs(ev.getY() - mOldY);
//處于頂部時(shí)的事件過濾區(qū)域
if (isTop && (ev.getY() < mTopL || t < 10)) {
return super.dispatchTouchEvent(ev);
}
//處于中間時(shí)的事件過濾區(qū)域
if (mCurrentState == STATE_CENTER && ev.getY() < (mCenterL + mBarH) && mRawY >= mCenterL) {
return super.dispatchTouchEvent(ev);
}
//處于底部時(shí)的事件過濾區(qū)域
if (mCurrentState == STATE_BOTTOM && ev.getY() < (mBottomL + mBarH) && mRawY >= mBottomL) {
return super.dispatchTouchEvent(ev);
}
- 第一個(gè)if語(yǔ)句,是用來處理頂部狀態(tài)的事件過濾的题暖,但是由于布局處于頂部狀態(tài)時(shí)按傅,
mContentView
需要獲取事件,而mContentView
是一個(gè)ViewPager胧卤,如果ViewPager加載的Fragment有滑動(dòng)控件唯绍,將是一個(gè)很復(fù)雜的分發(fā)過程,而Fragment滑動(dòng)控件的處理我們是必須考慮的枝誊。</br>
因此况芒,第一個(gè)if語(yǔ)句里面,只處理mContentView
的處于頂部狀態(tài)時(shí)的點(diǎn)擊事件叶撒,只要t
小于10绝骚,就判斷當(dāng)前的事件為點(diǎn)擊事件。而為了讓焦點(diǎn)從手勢(shì)交還給系統(tǒng)祠够,只需要return super.dispatchTouchEvent(ev);
便能將事件焦點(diǎn)攔截交還給系統(tǒng)压汪。 - 第二個(gè)if語(yǔ)句就比較簡(jiǎn)單了,當(dāng)處于中間狀態(tài)時(shí),只要事件的Y坐標(biāo)小于
mCenterL + mBarH
坐標(biāo)時(shí),統(tǒng)統(tǒng)將事件焦點(diǎn)交還給系統(tǒng),mRawY >= mCenterL
缀磕,有這個(gè)判斷時(shí),系統(tǒng)才能確定是中間狀態(tài)時(shí)的事件過濾穿香,不會(huì)導(dǎo)致晚上移動(dòng)的情況下,莫名奇妙事件就交給了系統(tǒng)叽奥。 - 第三個(gè)if語(yǔ)句和第二個(gè)if語(yǔ)句差不多扔水,
mRawY >= mBottomL
這句代碼同上所示,只有這個(gè)判斷時(shí)朝氓,才能確認(rèn)是底部事件的過濾魔市,如果沒有這句話主届,mContentView
在中間狀態(tài)時(shí),就會(huì)處理待德,底部事件的過濾君丁,將導(dǎo)致,mContentView
處于中間狀態(tài)時(shí)将宪,將失去對(duì)手勢(shì)的控制绘闷。
接下來便是處理mContentView
的滑動(dòng)控件的事件處理了!</br>
在當(dāng)樂的游戲詳情界面中较坛,mContentView
處于頂部時(shí)印蔗,里面的ListView或者ScrollView,只有滑動(dòng)到最頂部時(shí)丑勤,再外下滑動(dòng)华嘹,mContentView
才能將焦點(diǎn)交還給系統(tǒng)。
處理Fragment 中滑動(dòng)事件過濾
boolean isUp = ev.getY() - mOldY < 0;
if (isTop && mCurrentState == STATE_TOP) {
mOldY = (int) ev.getY();
if (isScrollTop && !isUp) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
return super.dispatchTouchEvent(ev);
}
return onTouchEvent(ev);
} else {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
return super.dispatchTouchEvent(ev);
}
return super.dispatchTouchEvent(ev);
}
}
如上的代碼所示法竞,如果耙厚,當(dāng)mContentView
中的Fragment的滑動(dòng)控件滑動(dòng)到頂部,并且mContentView
處于頂部岔霸,并且手勢(shì)向上則將事件焦點(diǎn)交給手勢(shì)薛躬,否則,交還給系統(tǒng)呆细。</br>
這里需要注意以下兩行代碼:
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
return super.dispatchTouchEvent(ev);
}
這兩行代碼是整個(gè)滑動(dòng)事件分發(fā)的靈魂!!!型宝,這兩句代碼是告訴系統(tǒng),當(dāng)焦點(diǎn)進(jìn)行交換時(shí)侦鹏,告訴當(dāng)前掌控焦點(diǎn)的服務(wù)(系統(tǒng)诡曙、手勢(shì))交出焦點(diǎn)臀叙,重新進(jìn)行分配B运!如果沒有這兩句代碼劝萤,焦點(diǎn)的切換將很有可能失斣ɡ浴(就為了寫出兩行代碼,工期延期了整整一個(gè)星期床嫌,說多了都是淚?缡汀!Q岽Α1钐浮!@妗@峦蕖)捷绒。
處理橫向事件過濾
if (Math.abs(ev.getX() - mOldX) >= 0 && Math.abs(ev.getY() - mOldY) < 300 && isTop) {
mOldX = ev.getX();
return super.dispatchTouchEvent(ev);
}
以上代碼是實(shí)現(xiàn)mContentView
fragment的切換的,這代碼很簡(jiǎn)單贯要,只要是橫向手勢(shì)暖侨,并且X的偏差小于300就認(rèn)為其是橫向事件。
最終效果
寫在最后
來來回回一個(gè)多月崇渗,這個(gè)頁(yè)面的blog算是寫完了(整個(gè)功能實(shí)現(xiàn)也就7字逗、8天,寫著blog寫了快兩個(gè)月了宅广,懶癌晚期傷不起)『簦現(xiàn)在的效果已經(jīng)和當(dāng)樂的差不多了,但是還是有點(diǎn)差別跟狱,比如挖息,我這沒有底部欄,比如兽肤,我這中間狀態(tài)不能對(duì)mContentView
進(jìn)行切換套腹,其實(shí)這些都很容易實(shí)現(xiàn)(其實(shí)我是懶癌晚期,不想寫了..)</br>
最后還是說個(gè)思路吧:
- 底部導(dǎo)航欄那個(gè)资铡,在布局里面寫FrameLayout电禀,然后編寫自定義View,在主界面的代碼里面笤休,給mContentView設(shè)置
addOnPageChangeListener
事件監(jiān)聽尖飞,在滑動(dòng)的過程中,F(xiàn)rameLayout動(dòng)態(tài)添加你的自定義的導(dǎo)航欄View店雅。(我在公司的APP里面采用的是這個(gè)思路) -
mContentView
中間狀態(tài)的切換政基,這個(gè)只需要在下面的語(yǔ)句中添加橫向狀態(tài)的添加橫向手勢(shì)移動(dòng)的判斷方法便可以了,需要注意下事件分發(fā)的范圍
if (mCurrentState == STATE_CENTER && ev.getY() < (mCenterL + mBarH) && mRawY >= mCenterL) {
return super.dispatchTouchEvent(ev);
}
源代碼
其實(shí)上面說的全部都是廢話闹啦,真正重要的還是源代碼>诿鳌!</br>
點(diǎn)擊我獲取源代碼窍奋,最后跪求star和issues