ViewPager实现源码分析
2017-08-28 13:48
375 查看
1、松手自动计算当前位置,并自动滑动到合适的position的页面
分析:松手属于View的事件分发机制,事件分发的入口在dispatchTouchEvent和onTouchEvent,大概的分发机制如下:
public boolean dispatchTouchEvent(MotionEvent ev) { boolean consume = false; if(onInterceptTouchEvent(ev)) { consume = onTouchEvent(ev); } else { consume = child.dispatchTouchEvent(ev); } return consume; }
因此,可以在onTouchEvent中处理松手事件 思路1:查看RecyclerView的源码,看系统源码是否提供ACTION_UP的事件的回调接口,重写该接口,实现松手的相应业务逻辑 思路2:覆盖掉onTouchEvent,ACTION_UP事件自己处理,其他事件交由系统默认处理(本项目采用的方法) 类比ViewPager的实现ACTION_UP时候的实现方式 思路3:查看下RecyclerView的事件处理是否交由其他专门的事件处理类去处理(既在onTouchEvent中是否将事件处理转发到其他专门类处理),查找相应类和回调接口并覆盖掉回调接口方法 分析RecyclerView的onTouchEvent函数,并没发现使用其他事件处理类去接收事件,因此该方法排除。
2.松手自动滑动到指定page
方法一:根据松手的位置距离中心点的位置是否超过一半,确定最终位置,并用smoothScrollToPosition实现滑动到指定位置.
缺点:虽然能实现松手滑动到指定位置,但是滑动效果和ViewPager的松手滑动不大一样实现代码:
int widthWithMargin = getWidth() + mPageMargin; // calculate scrollX int scrollX = mMyScrollX; int currentPage = scrollX / widthWithMargin; int offset = scrollX - currentPage*widthWithMargin; final int dstPage = currentPage + ((offset>widthWithMargin/2) ? 1 : 0); getHandler().post(new Runnable() { @Override public void run() { LogUtils.i("run to position"); smoothScrollToPosition(dstPage); } });
方法二:参考ViewPager的松手实现方法:VelocityTracker+Scroller+Interpolator+回调函数computeScroll,根据用户滑动速度动态改变松手滑动的快慢(本工程采用该方法)
参考源码:ViewPager源码问题:滑动的自然性和使用ViewPager的滑动自然性不同?
分析:能够准确滑动,但是自然性不足,可能是因为滑动过程的算法参考的代码是4.0的ViewPager的滑动效果
解决:滑动过程的实现算法采用最新版本的ViewPager代码,滑动自然性体验和现有的ViewPager一致。
3.实现接口的回调(类OnPageChangeListener,如:当前滑动到第几页,当前滑动的状态)
源码中查看下该接口的三个函数,分别在哪个点被回调(使用AS的快捷键查看调用层级树)。然后在自定义的RecyclerView中相应的位置回调这三个方法(还未实现。。)/** * Callback interface for responding to changing state of the selected page. */ public interface OnPageChangeListener { /** * This method will be invoked when the current page is scrolled, either as part * of a programmatically initiated smooth scroll or a user initiated touch scroll. * * @param position Position index of the first page currently being displayed. * Page position+1 will be visible if positionOffset is nonzero. * @param positionOffset Value from [0, 1) indicating the offset from the page at position. * @param positionOffsetPixels Value in pixels indicating the offset from position. */ void onPageScrolled(int position, float positionOffset, int positionOffsetPixels); /** * This method will be invoked when a new page becomes selected. Animation is not * necessarily complete. * * @param position Position index of the new selected page. */ void onPageSelected(int position); /** * Called when the scroll state changes. Useful for discovering when the user * begins dragging, when the pager is automatically settling to the current page, * or when it is fully stopped/idle. * * @param state The new scroll state. * @see ViewPager#SCROLL_STATE_IDLE * @see ViewPager#SCROLL_STATE_DRAGGING * @see ViewPager#SCROLL_STATE_SETTLING */ void onPageScrollStateChanged(int state); }
3.1 ViewPager中mOnPageChangeListener.onPageScrolled的调用过程
public void computeScroll() // implement in ViewPagerRecyclerView private void completeScroll(boolean postEvents) private boolean performDrag(float x) // implement in ViewPagerRecyclerView public void fakeDragBy(float xOffset) private boolean pageScrolled(int xpos) -->protected void onPageScrolled(int position, float offset, int offsetPixels) -->private void dispatchOnPageScrolled(int position, float offset, int offsetPixels) -->mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels); /* dispatchOnPageScrolled是mOnPageChangeListener.onPageScrolled的唯一调用入口 */ private void dispatchOnPageScrolled(int position, float offset, int offsetPixels) { if (mOnPageChangeListener != null) { mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels); // callback onPageScrolled } if (mOnPageChangeListeners != null) { for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) { OnPageChangeListener listener = mOnPageChangeListeners.get(i); if (listener != null) { listener.onPageScrolled(position, offset, offsetPixels); } } } if (mInternalPageChangeListener != null) { mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels); } }
3.2 ViewPager中mOnPageChangeListener.onPageSelected的调用过程
public void onRestoreInstanceState(Parcelable state) public boolean onTouchEvent(MotionEvent ev) case MotionEvent.ACTION_UP:// implement in ViewPagerRecyclerView public void endFakeDrag() public void setAdapter(PagerAdapter adapter) public void setCurrentItem(int item) // implement in ViewPagerRecyclerView void dataSetChanged() -->void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) -->private void dispatchOnPageSelected(int position) /* dispatchOnPageSelected是mOnPageChangeListener.onPageSelected的唯一调用入口 */ private void dispatchOnPageSelected(int position) { if (mOnPageChangeListener != null) { mOnPageChangeListener.onPageSelected(position); } if (mOnPageChangeListeners != null) { for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) { OnPageChangeListener listener = mOnPageChangeListeners.get(i); if (listener != null) { listener.onPageSelected(position); } } } if (mInternalPageChangeListener != null) { mInternalPageChangeListener.onPageSelected(position); // callback onPageSelected } }
3.3 ViewPager中mOnPageChangeListener.onPageScrollStateChanged的调用过程
private final Runnable mEndScrollRunnable void smoothScrollTo(int x, int y, int velocity) // implement in ViewPagerRecyclerView public boolean onInterceptTouchEvent(MotionEvent ev) public boolean onTouchEvent(MotionEvent ev) // implement in ViewPagerRecyclerView public boolean beginFakeDrag() private void setScrollState(int newState) -->private void dispatchOnScrollStateChanged(int state) /* dispatchOnPageSelected是mOnPageChangeListener.onPageSelected的唯一调用入口 */ private void dispatchOnScrollStateChanged(int state) { if (mOnPageChangeListener != null) { mOnPageChangeListener.onPageScrollStateChanged(state); } if (mOnPageChangeListeners != null) { for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) { OnPageChangeListener listener = mOnPageChangeListeners.get(i); if (listener != null) { listener.onPageScrollStateChanged(state); } } } if (mInternalPageChangeListener != null) { mInternalPageChangeListener.onPageScrollStateChanged(state); } }
问题:测试的时候,状态为IDLE的时候会有问题
问题:如何测试onPageScrolled
4.设置当前选中Item
ViewPager:如果当前是第一次设置的话,则启动requestLayout,并且设置当前选中的item,在布局中重新布局requestLayout(); ===》使用标志位isFirstLayout
5.setPagerMargin的实现
5.1.ViewPager的setPagerMargin实现
/* Set the margin between pages. Parameters: marginPixels Distance between adjacent pages in pixels */ 429 public void More ...setPageMargin(int marginPixels) { 430 final int oldMargin = mPageMargin; // record PageMargin for Layout 431 mPageMargin = marginPixels; 432 433 final int width = getWidth(); 434 recomputeScrollPosition(width, width, marginPixels, oldMargin); 435 436 requestLayout(); 437 } 899 @Override 900 protected void More ...onLayout(boolean changed, int l, int t, int r, int b) { 901 mInLayout = true; 902 populate(); 903 mInLayout = false; 904 905 final int count = getChildCount(); 906 final int width = r-l; 907 908 for (int i = 0; i < count; i++) { 909 View child = getChildAt(i); 910 ItemInfo ii; // Use the mPageMargin parameter in the layout 911 if (child.getVisibility() != GONE && (ii=infoForChild(child)) != null) { 912 int loff = (width + mPageMargin) * ii.position; 913 int childLeft = getPaddingLeft() + loff; 914 int childTop = getPaddingTop(); ... 918 child.layout(childLeft, childTop, 919 childLeft + child.getMeasuredWidth(), 920 childTop + child.getMeasuredHeight()); 921 } 922 } 923 mFirstLayout = false; 924 }
5.2 RecyclerView的setPagerMargin的实现
思路:查看RecyclerView中如何布局子View。 问题:查看onLayout后发现布局过程有点复杂,代码中调用dispatchLayoutStep1();,dispatchLayoutStep2();,dispatchLayoutStep3(); 分析:既然从入口函数到目标(View的布局过程)有点复杂(代码量有点多),那么就从后往前推。首先,先查看RecyclerView中哪个地方调用了子View的布局, 既:RecyclerView中应该有调用child.layout或者view.layout,然后顺藤摸瓜,往上找出调用过程栈。经过查找,子view的布局过程如下: onFocusSearchFailed onLayoutChildren(LayoutManager中必须要覆盖的方法) LinearLayoutManager.scrollBy LinearLayoutManager.fill LinearLayoutManager.layoutChunk LayoutManager.layoutDecoratedWithMargins 该方法为public方法,可以覆盖掉 child.layout 想重新布局,但是RecyclerView的布局方式和ViewPager的布局方式不同,滚动的时候布局会乱。 解决: 当用户设置pageMargin的时候,自定义PageMarginItemDecoration,让pageMargin的大小和ItemDecoration的mPageMarginWidth大小相等 然后整个RecyclerView控件的高度 + 上下分割线的总和 让控件高度超出屏幕的高度 在当前屏幕就看不到分割线了 滑动的时候,又可以看到分割线
每个子View的测量和布局过程 void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) { View view = layoutState.next(recycler); if (view == null) { if (DEBUG && layoutState.mScrapList == null) { throw new RuntimeException("received null view when unexpected"); } // if we are laying out views in scrap, this may return null which means there is // no more items to layout. result.mFinished = true; return; } LayoutParams params = (LayoutParams) view.getLayoutParams(); if (layoutState.mScrapList == null) { if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) { addView(view); } else { addView(view, 0); } } else { if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) { addDisappearingView(view); } else { addDisappearingView(view, 0); } } // 1.measure child view measureChildWithMargins(view, 0, 0); result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view); int left, top, right, bottom; if (mOrientation == VERTICAL) { if (isLayoutRTL()) { right = getWidth() - getPaddingRight(); left = right - mOrientationHelper.getDecoratedMeasurementInOther(view); } else { left = getPaddingLeft(); right = left + mOrientationHelper.getDecoratedMeasurementInOther(view); } if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { bottom = layoutState.mOffset; top = layoutState.mOffset - result.mConsumed; } else { top = layoutState.mOffset; bottom = layoutState.mOffset + result.mConsumed; } } else { top = getPaddingTop(); bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view); if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { right = layoutState.mOffset; left = layoutState.mOffset - result.mConsumed; } else { left = layoutState.mOffset; right = layoutState.mOffset + result.mConsumed; } } // 2.layout child view // We calculate everything with View's bounding box (which includes decor and margins) // To calculate correct layout position, we subtract margins. layoutDecoratedWithMargins(view, left, top, right, bottom); // Consume the available space if the view is not removed OR changed if (params.isItemRemoved() || params.isItemChanged()) { result.mIgnoreConsumed = true; } result.mFocusable = view.isFocusable(); } public void measureChildWithMargins(View child, int widthUsed, int heightUsed) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);//use ItemDecoration widthUsed += insets.left + insets.right; heightUsed += insets.top + insets.bottom; final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin + widthUsed, lp.width, canScrollHorizontally()); final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin + heightUsed, lp.height, canScrollVertically()); if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) { child.measure(widthSpec, heightSpec); } }
setMargin/setPadding均失败,查询代码,设置padding
转换思路:让RecyclerView控件的宽度大于屏幕宽度,而不是去考虑更改ItemDecoration的位置(源码不可动)
参考文献:
如何获取Android RecyclerView滑动的距离如何获取 RecyclerView 的滑动距离?
RecyclerView
ViewPager
ViewPager源码
演示视频
相关文章推荐
- ViewPager源码分析之滑动实现
- 【Android API】3.ViewPager的实现原理和源码分析
- 【Android 界面效果45】ViewPager源码分析
- Android利用ViewPager实现滑动广告板实例源码
- Android 使用ViewPager真正实现左右无限滑动(附源码)
- ViewPager部分源码分析三:scroll
- Android无限广告轮播 - ViewPager源码分析
- RecyclerView+SwipeRefreshLayout+ViewPager实现上拉加载更多下拉刷新和添加Banner(附源码)
- Android --------------------ActionBar 与 ViewPager 和 ActionTab 切换 的源码实现
- Android RecyclerView上拉加载更多的实现和源码分析
- viewpager源码分析
- ViewPager异常,对ViewPager源码分析
- 开源项目ViewPagerIndicator源码分析
- android中ViewPager+Picasso 实现轮播本地和网络图片源码
- ViewPager源码分析(1):onMeasure、onLayout
- ViewPager和PagerAdapter之页面加载源码分析
- TabPageIndicator + ViewPager + FragmentPagerAdapter + Fragment分析APP主框架实现
- TabPageIndicator + ViewPager + FragmentPagerAdapter + Fragment分析APP主框架实现
- ViewPager 源码分析(一) —— setAdapter() 与 populate()
- ViewPager源码分析