RecyclerView改造成ViewPager思路
阿新 • • 發佈:2019-01-03
1.實現每個子Item的全屏顯示
自定義一個全屏的Adapter,當Adapter建立根View的時候,強制設定根View的佈局引數為MATCH_PARENT。並且覆蓋掉
/**
* Adapters to set all of the child view to full screen
*
* @author lby 20/07/2017
*/
public abstract class FullScreenAdapter<M extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<M> {
@Override
public M onCreateViewHolder(ViewGroup parent, int viewType) {
M viewHolder = onCreateRawViewHolder(parent, viewType);
if (viewHolder != null) {
View rootView = viewHolder.itemView;
if (rootView != null) {
// Let root view display in full screen
viewHolder.itemView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
}
}
return viewHolder;
}
/**
* create raw ViewHolder
* @param parent The ViewGroup into which the new View will be added after it is bound to
* an adapter position.
* @param viewType The view type of the new View.
*
* @return A new ViewHolder that holds a View of the given view type.
*/
public abstract M onCreateRawViewHolder(ViewGroup parent, int viewType);
}
2.實現跟隨手指的滑動而運動
滑動的過程中,當用戶鬆手之後,RecyclerView預設會滑動,在RecyclerViewPager中覆蓋掉fling方法,以達到鬆手後停止。
/**
* not fling when action_up event occurs
*
* @param velocityX
* @param velocityY
* @return
*/
@Override
public boolean fling(int velocityX, int velocityY) {
return false;
}
3.鬆手自動計算當前位置,並自動滑動到合適的position的頁面
採用VelocityTracker+Scroller+Interpolator+回撥函式computeScroll,根據使用者滑動速度動態改變鬆手滑動的快慢
1.鬆手的時候計算需要滑動的距離
2.根據鬆手位置和滑動的速度和方向,計算要滑動的目標page
3.利用Scroller和鬆手滑動的演算法,自然滑動到指定的page,滑動的效果和體驗同ViewPager
主要的思路是View的彈性滑動:利用VelocityTracker追蹤手指在滑動過程中的速度和Scroller類計算滑動的距離,不斷的重新整理,並在computeScroll回撥中計算當前時刻,正在滾動的View應該處於的位置,以實現View的彈性滾動
@Override
public boolean onTouchEvent(MotionEvent e) {
...
mVelocityTracker.addMovement(e);
...
switch(e){
...
case MotionEvent.ACTION_UP:
mLastMotionX = e.getX();
mLastMotionY = e.getY();
handleActionUpEvent(e);
break;
}
...
return touchEvent;
}
private void handleActionUpEvent(MotionEvent e) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getXVelocity();
final int widthWithMargin = getWidthWithPageMargin();
final int scrollX = mMyScrollX;
final int currentPage = scrollX / widthWithMargin;
final int deltaX = (int) (mLastMotionX - mInitialMotionX);
int nextPage = currentPage;
if (Math.abs(deltaX) > mFlingDistance && Math.abs(initialVelocity) > mMinimumVelocity) {
nextPage = initialVelocity > 0 ? currentPage : currentPage + 1;
}
setCurrentItemInternal(nextPage, true, true, initialVelocity);
}
// switch to the specific page
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
if (getAdapter() == null || getAdapter().getItemCount() <= 0) {
return;
}
mCurItem = item;
dispatchOnPageSelected(item);
// final int destX = (getWidth() + mPageMargin) * item;
final int destX = (getWidthWithPageMargin()) * item;
if (smoothScroll) {
smoothScrollTo(destX, 0, velocity);
} else {
scrollTo(destX, 0);
pageScrolled(destX);
ViewCompat.postInvalidateOnAnimation(this);
}
}
/**
* Like android.view.View.scrollBy(int,int), but scroll smoothly instead of immediately.
*
* @param x the number of pixels to scroll by on the X axis
* @param y the number of pixels to scroll by on the Y axis
* @param velocity velocity the velocity associated with a fling, if applicable. (0 otherwise)
*/
void smoothScrollTo(int x, int y, int velocity) {
if (getChildCount() == 0) {
// Nothing to do.
return;
}
int sx = mMyScrollX;
int sy = mMyScrollY;
int dx = x - sx;
int dy = y - sy;
LayoutManager layoutManager = getLayoutManager();
if ((layoutManager == null) && (!layoutManager.canScrollHorizontally())) {
dx = 0;
}
if ((layoutManager == null) && (!layoutManager.canScrollVertically())) {
dy = 0;
}
if (dx == 0 && dy == 0) {
completeScroll(false);
setScrollState(SCROLL_STATE_IDLE);
return;
}
setScrollState(SCROLL_STATE_SETTLING);
final int width = getWidth();
final int halfWidth = width / 2;
final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
final float distance = halfWidth + halfWidth
* distanceInfluenceForSnapDuration(distanceRatio);
int duration;
velocity = Math.abs(velocity);
if (velocity > 0) {
duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
} else {
final float pageWidthWithMargin = getWidthWithPageMargin();
final float pageDelta = (float) Math.abs(dx) / (pageWidthWithMargin);
duration = (int) ((pageDelta + 1) * 100);
}
duration = Math.min(duration, MAX_SETTLE_DURATION);
mIsStartScroller = true;
// start scroll
mScroller.startScroll(sx, sy, dx, dy, duration);
ViewCompat.postInvalidateOnAnimation(this);
}
@Override
public void computeScroll() {
// !mScroller.isFinished() &&
if (mScroller.computeScrollOffset()) {
int oldX = mMyScrollX;
int oldY = mMyScrollY;
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
if (oldX != x || oldY != y) {
scrollTo(x, y);
if (!pageScrolled(x)) {
mScroller.abortAnimation();
scrollTo(0, y);
}
}
// Keep on drawing until the animation has finished.
ViewCompat.postInvalidateOnAnimation(this);
return;
}
if (mIsStartScroller) {
// System.out.println("completeScroll is calling..");
// Done with scroll, clean up state.
completeScroll(true);
mIsStartScroller = false;
}
}
4.實現OnPageChangeListener事件
在onTouchEvent和setCurrentItem中回撥使用者設定的OnPageChangeListener,以達到當用戶設定滾動、狀態改變、頁面選中時候推送事件發生。
4.1 ViewPager中mOnPageChangeListener.onPageScrolled的呼叫過程
public void computeScroll() 和private boolean performDrag(float x)中回撥
dispatchOnPageScrolled是mOnPageChangeListener.onPageScrolled的唯一呼叫入口
4.2 ViewPager中mOnPageChangeListener.onPageSelected的呼叫過程
public boolean onTouchEvent(MotionEvent ev) case MotionEvent.ACTION_UP:和public void setCurrentItem(int item)中回撥
dispatchOnPageSelected是mOnPageChangeListener.onPageSelected的唯一呼叫入口
4.3 ViewPager中mOnPageChangeListener.onPageScrollStateChanged的呼叫過程
void smoothScrollTo(int x, int y, int velocity)和public boolean onTouchEvent(MotionEvent ev)中回撥
dispatchOnPageSelected是mOnPageChangeListener.onPageSelected的唯一呼叫入口
5.設定當前選中Item
如果當前是第一次設定的話,則啟動requestLayout,並且設定當前選中的item,在佈局中重新佈局requestLayout(); ===》使用標誌位isFirstLayout來標記是否是第一次佈局
6.setPagerMargin的實現
當用戶設定pageMargin的時候,自定義PageMarginItemDecoration,讓pageMargin的大小和ItemDecoration的mPageMarginWidth大小相等
然後整個RecyclerView控制元件的高度 + 上下分割線的總和,讓控制元件高度超出螢幕的高度,在當前螢幕就看不到分割線了。滑動的時候,又可以看到分割線
/**
* Helper ItemDecoration class for RecyclerViewViewPager to set PageMargin
*
* @author lby 25/07/2017
*/
class PageMarginItemDecoration extends RecyclerView.ItemDecoration {
private final int mPageMarginWidth;
public PageMarginItemDecoration(int pageMarginWidth) {
mPageMarginWidth = pageMarginWidth;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
// set pageMargin
outRect.right = mPageMarginWidth;
}
}
public void setPageMargin(int pageMargin) {
mPageMargin = pageMargin;
addItemDecoration(new PageMarginItemDecoration(mPageMargin));
// reLayout
requestLayout();
}
參考文獻:
演示效果和原始碼
[實現程式碼地址]
[效果演示:體驗和ViewPager一致]