下載本文檔
版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
【移動(dòng)應(yīng)用開發(fā)技術(shù)】Android中怎么實(shí)現(xiàn)嵌套滾動(dòng)
Android中怎么實(shí)現(xiàn)嵌套滾動(dòng),很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面在下將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。業(yè)務(wù)需求是:VT容器可以滾動(dòng);書籍封面可以滾動(dòng),并且有視差;當(dāng)VT容器滾動(dòng)到頂部時(shí),滾動(dòng)列表,并且滾動(dòng)可以銜接。當(dāng)列表滾動(dòng)到頂部時(shí),可以滾動(dòng)書籍封面以及VT容器,并且滾動(dòng)可以銜接邏輯清楚了,接下來就看如何實(shí)現(xiàn)了。在android5以前,對(duì)于這種滾動(dòng),我們只能選擇自己去攔截事件并處理,但在后面的某個(gè)版本,android推出了NestingScroll機(jī)制,開發(fā)者的日子就好過多了,并且android提供了一個(gè)非常好的容器類:CoordinatorLayout,極大的簡(jiǎn)化了開發(fā)者的工作。當(dāng)然我們也需要投入精力去學(xué)習(xí)并運(yùn)用這些新的Api了。當(dāng)然,我們也要知道如果沒有這些API,我們應(yīng)當(dāng)如何去實(shí)現(xiàn)這些效果。因此本文會(huì)用三種方式去實(shí)現(xiàn)這個(gè)效果:純事件攔截與派發(fā)方案基于NestingScroll機(jī)制的實(shí)現(xiàn)方案基于CoordinatorLayout與Behavior方案的實(shí)現(xiàn)示例代碼放在Github上,可以clone下來結(jié)合文章觀看純事件攔截與派發(fā)方案這是最為原始的方案,當(dāng)然也靈活性***的了。其它的方案原理上都是系統(tǒng)基于它提供的封裝。使用這種方案時(shí),我們需要解決以下幾個(gè)問題:view的滾動(dòng)(Scroller);view的速度追蹤(VelocityTracker);當(dāng)VT容器滾動(dòng)到頂部時(shí),我們?nèi)绾螌⑹录鬟f給ListView?當(dāng)ListView滾動(dòng)到頂部時(shí),VT容器如何攔截到事件?1、2兩點(diǎn)屬于滾動(dòng)的基礎(chǔ)知識(shí),這里不會(huì)做細(xì)致的講解。而第3點(diǎn)為何會(huì)出現(xiàn)呢?因?yàn)閍ndroid系統(tǒng)在事件派發(fā)時(shí),如果事件被攔截,那么之后的事件都將不會(huì)傳遞給子view了。其解決方案也很簡(jiǎn)單:在滾動(dòng)到頂部時(shí)主動(dòng)派發(fā)一次Down事件:if
(mTargetCurrentOffset
+
dy
<=
mTargetEndOffset)
{
moveTargetView(dy);
//
重新dispatch一次down事件,使得列表可以繼續(xù)滾動(dòng)
int
oldAction
=
ev.getAction();
ev.setAction(MotionEvent.ACTION_DOWN);
dispatchTouchEvent(ev);
ev.setAction(oldAction);
}
else
{
moveTargetView(dy);
}那么第4點(diǎn)是什么問題呢?這里就需要清楚一個(gè)坑點(diǎn)了:不是所用的事件都會(huì)走入onInterceptTouchEvent。有一種情況是子View主動(dòng)調(diào)用parent.requestDisallowInterceptTouchEvent(true)來告訴系統(tǒng)說:這個(gè)事件我要了,父View不要攔截了。這就是所謂的內(nèi)部攔截法。在ListView的某些時(shí)刻它會(huì)去調(diào)用這個(gè)方法。因此一旦事件傳遞給了ListView,外部容器就拿不到這個(gè)事件了。因此我們要打破它的內(nèi)部攔截:@Override
public
void
requestDisallowInterceptTouchEvent(boolean
b)
{
//
去掉默認(rèn)行為,使得每個(gè)事件都會(huì)經(jīng)過這個(gè)Layout
}方法如上,把requestDisallowInterceptTouchEvent的實(shí)現(xiàn)干掉就可以了。主要的技術(shù)點(diǎn)已近提出來了。那么下面就看具體實(shí)現(xiàn),首先看使用xml:<org.cgspine.nestscroll.one.EventDispatchPlanLayout
android:id="@+id/scrollLayout"
android:layout_marginTop="?attr/actionBarSize"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:header_view="@+id/book_header"
app:target_view="@+id/scroll_view"
app:header_init_offset="30dp"
app:target_init_offset="70dp">
<View
android:id="@id/book_header"
android:layout_width="120dp"
android:layout_height="150dp"
android:background="@color/gray"/>
<org.cgspine.nestscroll.one.EventDispatchTargetLayout
android:id="@id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/white">
<android.support.design.widget.TabLayout
android:id="@+id/tab_layout"
android:background="@drawable/list_item_bg_with_border_top_bottom"
android:layout_width="match_parent"
android:layout_height="@dimen/tab_layout_height"
android:fillViewport="true"/>
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</org.cgspine.nestscroll.one.EventDispatchTargetLayout>
</org.cgspine.nestscroll.one.EventDispatchPlanLayout>EventDispatchTargetLayout實(shí)現(xiàn)了自定義接口ITargetView:public
interface
ITargetView
{
boolean
canChildScrollUp();
void
fling(float
vy);
}這是因?yàn)榕c具體業(yè)務(wù)抽離,我并不清楚內(nèi)層盒子是怎樣的(有可能就是ListView了,也有可能是ViewPager包裹ListView)主要的實(shí)現(xiàn)在EventDispatchPlanLayout,使用時(shí)在xml中指定header_init_offset、target_init_offset等變量就可以了,基本上與業(yè)務(wù)邏輯獨(dú)立。其重點(diǎn)實(shí)現(xiàn)邏輯在onInterceptTouchEvent與onTouchEvent中了。個(gè)人不是很建議去動(dòng)dispatchTouchEvent,雖然所有事件都會(huì)經(jīng)過這里,但是這也明顯會(huì)增加代碼處理復(fù)雜度:public
boolean
onInterceptTouchEvent(MotionEvent
ev)
{
ensureHeaderViewAndScrollView();
final
int
action
=
MotionEventCompat.getActionMasked(ev);
int
pointerIndex;
//
不阻斷事件的快路徑:如果目標(biāo)view可以往上滾動(dòng)或者`EventDispatchPlanLayout`不是enabled
if
(!isEnabled()
||
mTarget.canChildScrollUp())
{
Log.d(TAG,
"fast
end
onIntercept:
isEnabled
=
"
+
isEnabled()
+
";
canChildScrollUp
=
"
+
mTarget.canChildScrollUp());
return
false;
}
switch
(action)
{
case
MotionEvent.ACTION_DOWN:
mActivePointerId
=
ev.getPointerId(0);
mIsDragging
=
false;
pointerIndex
=
ev.findPointerIndex(mActivePointerId);
if
(pointerIndex
<
0)
{
return
false;
}
//
在down的時(shí)候記錄初始的y值
mInitialDownY
=
ev.getY(pointerIndex);
break;
case
MotionEvent.ACTION_MOVE:
pointerIndex
=
ev.findPointerIndex(mActivePointerId);
if
(pointerIndex
<
0)
{
Log.e(TAG,
"Got
ACTION_MOVE
event
but
have
an
invalid
active
pointer
id.");
return
false;
}
final
float
y
=
ev.getY(pointerIndex);
//
判斷是否dragging
startDragging(y);
break;
case
MotionEventCompat.ACTION_POINTER_UP:
//
雙指邏輯處理
onSecondaryPointerUp(ev);
break;
case
MotionEvent.ACTION_UP:
case
MotionEvent.ACTION_CANCEL:
mIsDragging
=
false;
mActivePointerId
=
INVALID_POINTER;
break;
}
return
mIsDragging;
}代碼邏輯很清晰,應(yīng)該不用多說。接下來看onTouchEvent的處理邏輯。public
boolean
onTouchEvent(MotionEvent
ev)
{
final
int
action
=
MotionEventCompat.getActionMasked(ev);
int
pointerIndex;
if
(!isEnabled()
||
mTarget.canChildScrollUp())
{
Log.d(TAG,
"fast
end
onTouchEvent:
isEnabled
=
"
+
isEnabled()
+
";
canChildScrollUp
=
"
+
mTarget.canChildScrollUp());
return
false;
}
//
速度追蹤
acquireVelocityTracker(ev);
switch
(action)
{
case
MotionEvent.ACTION_DOWN:
mActivePointerId
=
ev.getPointerId(0);
mIsDragging
=
false;
break;
case
MotionEvent.ACTION_MOVE:
{
pointerIndex
=
ev.findPointerIndex(mActivePointerId);
if
(pointerIndex
<
0)
{
Log.e(TAG,
"Got
ACTION_MOVE
event
but
have
an
invalid
active
pointer
id.");
return
false;
}
final
float
y
=
ev.getY(pointerIndex);
startDragging(y);
if
(mIsDragging)
{
float
dy
=
y
-
mLastMotionY;
if
(dy
>=
0)
{
moveTargetView(dy);
}
else
{
if
(mTargetCurrentOffset
+
dy
<=
mTargetEndOffset)
{
moveTargetView(dy);
//
重新dispatch一次down事件,使得列表可以繼續(xù)滾動(dòng)
int
oldAction
=
ev.getAction();
ev.setAction(MotionEvent.ACTION_DOWN);
dispatchTouchEvent(ev);
ev.setAction(oldAction);
}
else
{
moveTargetView(dy);
}
}
mLastMotionY
=
y;
}
break;
}
case
MotionEventCompat.ACTION_POINTER_DOWN:
{
pointerIndex
=
MotionEventCompat.getActionIndex(ev);
if
(pointerIndex
<
0)
{
Log.e(TAG,
"Got
ACTION_POINTER_DOWN
event
but
have
an
invalid
action
index.");
return
false;
}
mActivePointerId
=
ev.getPointerId(pointerIndex);
break;
}
case
MotionEventCompat.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
case
MotionEvent.ACTION_UP:
{
pointerIndex
=
ev.findPointerIndex(mActivePointerId);
if
(pointerIndex
<
0)
{
Log.e(TAG,
"Got
ACTION_UP
event
but
don't
have
an
active
pointer
id.");
return
false;
}
if
(mIsDragging)
{
mIsDragging
=
false;
//
獲取瞬時(shí)速度
mVelocityTputeCurrentVelocity(1000,
mMaxVelocity);
final
float
vy
=
mVelocityTracker.getYVelocity(mActivePointerId);
finishDrag((int)
vy);
}
mActivePointerId
=
INVALID_POINTER;
//釋放速度追蹤
releaseVelocityTracker();
return
false;
}
case
MotionEvent.ACTION_CANCEL:
releaseVelocityTracker();
return
false;
}
return
mIsDragging;
}或許有人會(huì)說:為何與onInterceptTouchEvent與有很多重復(fù)代碼?這是因?yàn)槿绻录淮驍?,并且子類不處理,就?huì)走進(jìn)onTouchEvent邏輯,所以這些重復(fù)處理是有意義的(其實(shí)是抄SwipeRefreshLayout的)。里面主要的邏輯就是兩個(gè):滾動(dòng)容器TouchUp時(shí)滾動(dòng)到特定位置以及fling傳遞滾動(dòng)容器的邏輯:private
void
moveTargetViewTo(int
target)
{
target
=
Math.max(target,
mTargetEndOffset);
//
用offsetTopAndBottom來偏移view
ViewCompat.offsetTopAndBottom(mTargetView,
target
-
mTargetCurrentOffset);
mTargetCurrentOffset
=
target;
//
滾動(dòng)書籍封面view,根據(jù)TargetView進(jìn)行定位
int
headerTarget;
if
(mTargetCurrentOffset
>=
mTargetInitOffset)
{
headerTarget
=
mHeaderInitOffset;
}
else
if
(mTargetCurrentOffset
<=
mTargetEndOffset)
{
headerTarget
=
mHeaderEndOffset;
}
else
{
float
percent
=
(mTargetCurrentOffset
-
mTargetEndOffset)
*
1.0f
/
mTargetInitOffset
-
mTargetEndOffset;
headerTarget
=
(int)
(mHeaderEndOffset
+
percent
*
(mHeaderInitOffset
-
mHeaderEndOffset));
}
ViewCompat.offsetTopAndBottom(mHeaderView,
headerTarget
-
mHeaderCurrentOffset);
mHeaderCurrentOffset
=
headerTarget;
}TouchUp的滾動(dòng)邏輯:private
void
finishDrag(int
vy)
{
Log.i(TAG,
"TouchUp:
vy
=
"
+
vy);
if
(vy
>
0)
{
//
向下觸發(fā)fling,需要滾動(dòng)到Init位置
mNeedScrollToInitPos
=
true;
mScroller.fling(0,
mTargetCurrentOffset,
0,
vy,
0,
0,
mTargetEndOffset,
Integer.MAX_VALUE);
invalidate();
}
else
if
(vy
<
0)
{
//
向上觸發(fā)fling,需要滾動(dòng)到End位置
mNeedScrollToEndPos
=
true;
mScroller.fling(0,
mTargetCurrentOffset,
0,
vy,
0,
0,
mTargetEndOffset,
Integer.MAX_VALUE);
invalidate();
}
else
{
//
沒有觸發(fā)fling,就近原則
if
(mTargetCurrentOffset
<=
(mTargetEndOffset
+
mTargetInitOffset)
/
2)
{
mNeedScrollToEndPos
=
true;
}
else
{
mNeedScrollToInitPos
=
true;
}
invalidate();
}
}當(dāng)然這里會(huì)打上一些標(biāo)志位,具體實(shí)現(xiàn)是在computeScroll中,這屬于Scroller的功能,這里就不展開了。這樣大體邏輯就講述清楚了,其它細(xì)節(jié)就請(qǐng)看官直接看源碼了?;贜estingScroll機(jī)制的實(shí)現(xiàn)方案NestingScroll機(jī)制是在某個(gè)版本support包加入的,不過外界極少有文章介紹,所以應(yīng)該大多數(shù)人并不知道這個(gè)機(jī)制。NestingScroll主要有兩個(gè)接口:NestedScrollingParentNestedScrollingChild當(dāng)我們需要使用NestingScroll特性時(shí),我們?nèi)?shí)現(xiàn)這兩個(gè)接口就好了。NestingScroll本質(zhì)是內(nèi)部攔截發(fā)然后將相應(yīng)的接口開給外界。因此實(shí)現(xiàn)NestedScrollingChild接口是有難度的,不過像RecyclerView這些控件,官方已經(jīng)幫我們實(shí)現(xiàn)好了NestedScrollingChild,要完成我們的需求,我們直接拿來用就好了(ListView就沒辦法使用了,當(dāng)然你也可以
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 數(shù)學(xué)教師工作計(jì)劃
- 中學(xué)歷史教學(xué)工作計(jì)劃范本
- 2025年度大學(xué)生學(xué)習(xí)個(gè)人工作計(jì)劃范文
- 2025年音樂教研員個(gè)人工作計(jì)劃范例
- 幼兒園后勤工作計(jì)劃024年
- 醫(yī)院弱電系統(tǒng)工程施工勞動(dòng)力進(jìn)場(chǎng)與需求計(jì)劃
- 小學(xué)第一學(xué)期體育教學(xué)工作計(jì)劃
- 2025員工個(gè)人年終工作總結(jié)及計(jì)劃
- 《塑膠模具知識(shí)》課件
- 《塑料模具與設(shè)備》課件
- GB/T 16865-1997變形鋁、鎂及其合金加工制品拉伸試驗(yàn)用試樣
- GB/T 14602-2014電子工業(yè)用氣體氯化氫
- 基坑開挖、土方回填危險(xiǎn)源辨識(shí)及風(fēng)險(xiǎn)分級(jí)評(píng)價(jià)清單
- 裝置氣密性的檢驗(yàn)課件
- 不朽的藝術(shù):走進(jìn)大師與經(jīng)典 期末考試答案
- 豁免知情同意申請(qǐng)表【模板】
- 奧運(yùn)會(huì)的歷史課件
- 醫(yī)學(xué)高級(jí)職稱評(píng)審答辯報(bào)告PPT模板
- 個(gè)體工商戶年度報(bào)表
- 辦公電腦升級(jí)及分配方案(純方案)
評(píng)論
0/150
提交評(píng)論