【移動(dòng)應(yīng)用開發(fā)技術(shù)】Android中怎么實(shí)現(xiàn)嵌套滾動(dòng)_第1頁
【移動(dòng)應(yīng)用開發(fā)技術(shù)】Android中怎么實(shí)現(xiàn)嵌套滾動(dòng)_第2頁
【移動(dòng)應(yīng)用開發(fā)技術(shù)】Android中怎么實(shí)現(xiàn)嵌套滾動(dòng)_第3頁
【移動(dòng)應(yīng)用開發(fā)技術(shù)】Android中怎么實(shí)現(xiàn)嵌套滾動(dòng)_第4頁
【移動(dòng)應(yīng)用開發(fā)技術(shù)】Android中怎么實(shí)現(xiàn)嵌套滾動(dòng)_第5頁
免費(fèi)預(yù)覽已結(jié)束,剩余2頁可下載查看

下載本文檔

版權(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ì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論