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一致]