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

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

【移動應用開發(fā)技術】Android中怎么實現(xiàn)嵌套滾動

Android中怎么實現(xiàn)嵌套滾動,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面在下將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。業(yè)務需求是:VT容器可以滾動;書籍封面可以滾動,并且有視差;當VT容器滾動到頂部時,滾動列表,并且滾動可以銜接。當列表滾動到頂部時,可以滾動書籍封面以及VT容器,并且滾動可以銜接邏輯清楚了,接下來就看如何實現(xiàn)了。在android5以前,對于這種滾動,我們只能選擇自己去攔截事件并處理,但在后面的某個版本,android推出了NestingScroll機制,開發(fā)者的日子就好過多了,并且android提供了一個非常好的容器類:CoordinatorLayout,極大的簡化了開發(fā)者的工作。當然我們也需要投入精力去學習并運用這些新的Api了。當然,我們也要知道如果沒有這些API,我們應當如何去實現(xiàn)這些效果。因此本文會用三種方式去實現(xiàn)這個效果:純事件攔截與派發(fā)方案基于NestingScroll機制的實現(xiàn)方案基于CoordinatorLayout與Behavior方案的實現(xiàn)示例代碼放在Github上,可以clone下來結合文章觀看純事件攔截與派發(fā)方案這是最為原始的方案,當然也靈活性***的了。其它的方案原理上都是系統(tǒng)基于它提供的封裝。使用這種方案時,我們需要解決以下幾個問題:view的滾動(Scroller);view的速度追蹤(VelocityTracker);當VT容器滾動到頂部時,我們如何將事件傳遞給ListView?當ListView滾動到頂部時,VT容器如何攔截到事件?1、2兩點屬于滾動的基礎知識,這里不會做細致的講解。而第3點為何會出現(xiàn)呢?因為android系統(tǒng)在事件派發(fā)時,如果事件被攔截,那么之后的事件都將不會傳遞給子view了。其解決方案也很簡單:在滾動到頂部時主動派發(fā)一次Down事件:if

(mTargetCurrentOffset

+

dy

<=

mTargetEndOffset)

{

moveTargetView(dy);

//

重新dispatch一次down事件,使得列表可以繼續(xù)滾動

int

oldAction

=

ev.getAction();

ev.setAction(MotionEvent.ACTION_DOWN);

dispatchTouchEvent(ev);

ev.setAction(oldAction);

}

else

{

moveTargetView(dy);

}那么第4點是什么問題呢?這里就需要清楚一個坑點了:不是所用的事件都會走入onInterceptTouchEvent。有一種情況是子View主動調用parent.requestDisallowInterceptTouchEvent(true)來告訴系統(tǒng)說:這個事件我要了,父View不要攔截了。這就是所謂的內部攔截法。在ListView的某些時刻它會去調用這個方法。因此一旦事件傳遞給了ListView,外部容器就拿不到這個事件了。因此我們要打破它的內部攔截:@Override

public

void

requestDisallowInterceptTouchEvent(boolean

b)

{

//

去掉默認行為,使得每個事件都會經(jīng)過這個Layout

}方法如上,把requestDisallowInterceptTouchEvent的實現(xiàn)干掉就可以了。主要的技術點已近提出來了。那么下面就看具體實現(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實現(xiàn)了自定義接口ITargetView:public

interface

ITargetView

{

boolean

canChildScrollUp();

void

fling(float

vy);

}這是因為與具體業(yè)務抽離,我并不清楚內層盒子是怎樣的(有可能就是ListView了,也有可能是ViewPager包裹ListView)主要的實現(xiàn)在EventDispatchPlanLayout,使用時在xml中指定header_init_offset、target_init_offset等變量就可以了,基本上與業(yè)務邏輯獨立。其重點實現(xiàn)邏輯在onInterceptTouchEvent與onTouchEvent中了。個人不是很建議去動dispatchTouchEvent,雖然所有事件都會經(jīng)過這里,但是這也明顯會增加代碼處理復雜度:public

boolean

onInterceptTouchEvent(MotionEvent

ev)

{

ensureHeaderViewAndScrollView();

final

int

action

=

MotionEventCompat.getActionMasked(ev);

int

pointerIndex;

//

不阻斷事件的快路徑:如果目標view可以往上滾動或者`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的時候記錄初始的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;

}代碼邏輯很清晰,應該不用多說。接下來看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ù)滾動

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;

//

獲取瞬時速度

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;

}或許有人會說:為何與onInterceptTouchEvent與有很多重復代碼?這是因為如果事件不打斷,并且子類不處理,就會走進onTouchEvent邏輯,所以這些重復處理是有意義的(其實是抄SwipeRefreshLayout的)。里面主要的邏輯就是兩個:滾動容器TouchUp時滾動到特定位置以及fling傳遞滾動容器的邏輯:private

void

moveTargetViewTo(int

target)

{

target

=

Math.max(target,

mTargetEndOffset);

//

用offsetTopAndBottom來偏移view

ViewCompat.offsetTopAndBottom(mTargetView,

target

-

mTargetCurrentOffset);

mTargetCurrentOffset

=

target;

//

滾動書籍封面view,根據(jù)TargetView進行定位

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的滾動邏輯:private

void

finishDrag(int

vy)

{

Log.i(TAG,

"TouchUp:

vy

=

"

+

vy);

if

(vy

>

0)

{

//

向下觸發(fā)fling,需要滾動到Init位置

mNeedScrollToInitPos

=

true;

mScroller.fling(0,

mTargetCurrentOffset,

0,

vy,

0,

0,

mTargetEndOffset,

Integer.MAX_VALUE);

invalidate();

}

else

if

(vy

<

0)

{

//

向上觸發(fā)fling,需要滾動到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();

}

}當然這里會打上一些標志位,具體實現(xiàn)是在computeScroll中,這屬于Scroller的功能,這里就不展開了。這樣大體邏輯就講述清楚了,其它細節(jié)就請看官直接看源碼了。基于NestingScroll機制的實現(xiàn)方案NestingScroll機制是在某個版本support包加入的,不過外界極少有文章介紹,所以應該大多數(shù)人并不知道這個機制。NestingScroll主要有兩個接口:NestedScrollingParentNestedScrollingChild當我們需要使用NestingScroll特性時,我們去實現(xiàn)這兩個接口就好了。NestingScroll本質是內部攔截發(fā)然后將相應的接口開給外界。因此實現(xiàn)NestedScrollingChild接口是有難度的,不過像RecyclerView這些控件,官方已經(jīng)幫我們實現(xiàn)好了NestedScrollingChild,要完成我們的需求,我們直接拿來用就好了(ListView就沒辦法使用了,當然你也可以

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論