請留言 在之前的文章中,我們學(xué)會了 FastScroller 控制框架。在這一結(jié)束篇中,我們將添加觸摸和滾動行為。
http://wiki.jikexueyuan.com/project/android-weekly/images/issue-145/31.gif" alt="Recycler" />
首先我們需要的是一種內(nèi)部方法,當(dāng)由于 FastScroller 的觸摸事件或者 用戶滾動了 RecyclerView,滾動的位置變化時(shí),為了設(shè)置 bubble 和 handle 的位置,該方法會被調(diào)用:
FastScroller.java
public class FastScroller extends LinearLayout {
.
.
.
private void setPosition(float y) {
float position = y / height;
int bubbleHeight = bubble.getHeight();
bubble.setY(getValueInRange(0, height - bubbleHeight, (int) ((height - bubbleHeight) * position)));
int handleHeight = handle.getHeight();
handle.setY(getValueInRange(0, height - handleHeight, (int) ((height - handleHeight) * position)));
}
private int getValueInRange(int min, int max, int value) {
int minimum = Math.max(min, value);
return Math.min(minimum, max);
}
.
.
.
}
這里需要一些數(shù)學(xué)知識,因?yàn)?handle 和 bubble 是不同高度的,且我們需要單獨(dú)的處理每一個(gè)。當(dāng)滾動的時(shí)候,我們想讓每一個(gè)都有自己的上邊緣。
列表中的項(xiàng)目時(shí)可見的。
getValueInRange()
是一個(gè)實(shí)用程序方法,確保 bubble 和 handle 在它們的追蹤中。
我們的 FastScroller 控制與 RecyclerView 相關(guān)聯(lián),所以下一個(gè)任務(wù)是提供一種機(jī)制,使用一個(gè)簡單的設(shè)值函數(shù)來實(shí)現(xiàn)關(guān)聯(lián):
FastScroller.java
public class FastScroller extends LinearLayout {
private final ScrollListener scrollListener = new ScrollListener();
public void setRecyclerView(RecyclerView recyclerView) {
this.recyclerView = recyclerView;
recyclerView.setOnScrollListener(scrollListener);
}
private class ScrollListener extends OnScrollListener {
@Override
public void onScrolled(RecyclerView rv, int dx, int dy) {
View firstVisibleView = recyclerView.getChildAt(0);
int firstVisiblePosition = recyclerView.getChildPosition(firstVisibleView);
int visibleRange = recyclerView.getChildCount();
int lastVisiblePosition = firstVisiblePosition + visibleRange;
int itemCount = recyclerView.getAdapter().getItemCount();
int position;
if (firstVisiblePosition == 0) {
position = 0;
} else if (lastVisiblePosition == itemCount - 1) {
position = itemCount - 1;
} else {
position = firstVisiblePosition;
}
float proportion = (float) position / (float) itemCount;
setPosition(height * proportion);
}
}
}
當(dāng)調(diào)用設(shè)值函數(shù)時(shí),設(shè)置一個(gè) OnScrollListener
實(shí)例,當(dāng)用戶直接 scroll RecyclerView 時(shí),該實(shí)例會被調(diào)用,由此我們可以調(diào)整 handle 和 buddle 的位置。為了在列表頂部和底部提供正確的位置,需要一些邏輯知識。
接下來我們需要看 FastScroller 控制中處理觸摸事件。我們希望實(shí)現(xiàn)的行為是:當(dāng)用戶在控制中輕觸時(shí),handle 會出現(xiàn)。用戶可以上下拖動來改變當(dāng)前位置。當(dāng)用戶釋放時(shí),在 handle 隱藏之前會有一個(gè)短暫的延遲。這是通過覆蓋 onTouchEvent()
實(shí)現(xiàn)的:
FastScroller.java
public class FastScroller extends LinearLayout {
.
.
.
private static final int HANDLE_HIDE_DELAY = 1000;
private static final int TRACK_SNAP_RANGE = 5;
private final HandleHider handleHider = new HandleHider();
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) {
setPosition(event.getY());
if (currentAnimator != null) {
currentAnimator.cancel();
}
getHandler().removeCallbacks(handleHider);
if (handle.getVisibility() == INVISIBLE) {
showHandle();
}
setRecyclerViewPosition(event.getY());
return true;
} else if (event.getAction() == MotionEvent.ACTION_UP) {
getHandler().postDelayed(handleHider, HANDLE_HIDE_DELAY);
return true;
}
return super.onTouchEvent(event);
}
private class HandleHider implements Runnable {
@Override
public void run() {
hideHandle();
}
}
private void setRecyclerViewPosition(float y) {
if (recyclerView != null) {
int itemCount = recyclerView.getAdapter().getItemCount();
float proportion;
if (bubble.getY() == 0) {
proportion = 0f;
} else if (bubble.getY() + bubble.getHeight() >= height - TRACK_SNAP_RANGE) {
proportion = 1f;
} else {
proportion = y / (float) height;
}
int targetPos = getValueInRange(0, itemCount - 1, (int) (proportion * (float) itemCount));
recyclerView.scrollToPosition(targetPos);
}
}
.
.
.
}
當(dāng)接收一個(gè)向下或移動的動作時(shí),我們設(shè)置當(dāng)前的位置來與當(dāng)前的 Y 位置匹配,取消可能運(yùn)行的動畫,取消延遲的處理程序回調(diào)(關(guān)于這點(diǎn)在第二部分中有更多描述)。如果 handle 不可見,那我們調(diào)用之前創(chuàng)建的方法使它顯現(xiàn)。最后在重調(diào) true 之前,我們設(shè)置 RecyclerView 的當(dāng)前位置來使用觸發(fā)事件。
當(dāng)接收一個(gè)向上的動作時(shí),在一個(gè)短暫的延遲后,我們使用一個(gè) Handler 發(fā)布一個(gè)延遲動作來隱藏 handle。
當(dāng)我們設(shè)置 RecyclerView 的位置時(shí),如果我們在底部一定距離內(nèi)來與底部對齊,或若第一項(xiàng)可見時(shí)與頂部對齊,那么我們要明白其邏輯關(guān)系,否則,如果在中間的某個(gè)位置,那就需要計(jì)算出正確的比例值。
注意在這里我們只使用 scrollToPosition()
,而不用 smoothScrollToPosition()
,所以在之前的系列中不會出現(xiàn)問題。
這是我們控制完成的。剩下的就是連接。首先我們將它添加到包含 RecyclerView 的布局:
res/layout/activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:scrollbars="none"
tools:context=".MainActivity" />
<com.stylingandroid.smoothscrolling.FastScroller
android:id="@+id/fast_scroller"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentEnd="true" />
</RelativeLayout>
最后我們需要在 RecyclerView 和 FastScroller 之間創(chuàng)建聯(lián)系:
MainActivity.java
public class MainActivity extends Activity {
.
.
.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setAdapter(LargeAdapter.newInstance(this));
int duration = getResources().getInteger(R.integer.scroll_duration);
recyclerView.setLayoutManager(new ScrollingLinearLayoutManager(this, LinearLayoutManager.VERTICAL, false, duration));
FastScroller fastScroller = (FastScroller) findViewById(R.id.fastscroller);
fastScroller.setRecyclerView(recyclerView);
}
.
.
.
}
就這樣。現(xiàn)在我們可以看到 fas 滾動行為:
最后一點(diǎn):在聯(lián)系人應(yīng)用程序中,F(xiàn)astScroller 處理包含一個(gè)字母表示的列表中的當(dāng)前位置。這是使用了一個(gè)比我們的示例稍微復(fù)雜的適配器,但是添加這個(gè)不應(yīng)該是繁瑣的工作。也許這是我們在以后的部分中要討論的。
可用的源代碼 here。
Mark Allison.版權(quán)所有。這篇文章最開始出現(xiàn)在 Styling Android 。
這個(gè)頁面的部分內(nèi)容是基于 Google 創(chuàng)建和共享的工作修改的。