ViewPager2的原理和使用

一低千、ViewPager2介紹

1 簡介

 ViewPager2是Google 在 androidx 組件包里增加的一個(gè)組件趣惠,目前已經(jīng)到了1.0.0-beta02版本炭序。

谷歌為什么要出這個(gè)組件呢?官方是這么說的:

ViewPager2 replaces ViewPager, addressing most of its predecessor’s pain-points, 
including right-to-left layout support, vertical orientation, modifiable Fragment collections, etc.

2 具體改動:

New features:

  • 支持豎向滾動

  • 完整支持notifyDataSetChanged

  • 能夠關(guān)閉用戶輸入 (setUserInputEnabled, isUserInputEnabled)

API changes:

  • FragmentStateAdapter 替代 FragmentStatePagerAdapter

  • RecyclerView.Adapter 替代 PagerAdapter

  • registerOnPageChangeCallback 替代 addPageChangeListener

3 附上官方鏈接:

官方文檔
https://developer.android.google.cn/jetpack/androidx/releases/viewpager2#1.0.0-alpha01

官方Demo
https://github.com/googlesamples/android-viewpager2

二、ViewPager2原理的簡單介紹

ViewPager2繼承ViewGroup空繁,內(nèi)部核心是RecycleView加LinearLayoutManager,其實(shí)就是對RecycleView封裝了一層朱庆,所有功能都是圍繞著RecyclerView和LinearLayoutManager展開盛泡,不過我們在這里不展開介紹了,我們主要關(guān)注兩個(gè)點(diǎn)娱颊。

  • ViewPager2是怎么使用RecyclerView實(shí)現(xiàn)ViewPager的效果的傲诵?

  • RecyclerView來怎么配合Fragment的生命周期的

ViewPager2是怎么使用RecyclerView實(shí)現(xiàn)ViewPager的效果的

1、我們先從ViewPager2的初始化入手箱硕,代碼如下:

 private void initialize(Context context, AttributeSet attrs) {
        mAccessibilityProvider = sFeatureEnhancedA11yEnabled
                ? new PageAwareAccessibilityProvider()
                : new BasicAccessibilityProvider();
        
        //RecyclerView基本設(shè)置 begin——————————————————
        mRecyclerView = new RecyclerViewImpl(context);
        mRecyclerView.setId(ViewCompat.generateViewId());

        mLayoutManager = new LinearLayoutManagerImpl(context);
        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.setScrollingTouchSlop(RecyclerView.TOUCH_SLOP_PAGING);
        setOrientation(context, attrs);

        mRecyclerView.setLayoutParams(
                new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        mRecyclerView.addOnChildAttachStateChangeListener(enforceChildFillListener());
        //RecyclerView基本設(shè)置 end——————————————————
        
        //創(chuàng)建ScrollEventAdapter拴竹,此adapter作用是將RecyclerView.OnScrollListener事件轉(zhuǎn)變?yōu)閂iewPager2.OnPageChangeCallback事件
        mScrollEventAdapter = new ScrollEventAdapter(this);
        
        //創(chuàng)建模擬拖動事件
        mFakeDragger = new FakeDrag(this, mScrollEventAdapter, mRecyclerView);
        
        //創(chuàng)建PagerSnapHelper對象,用來攔截fling事件剧罩,從而實(shí)現(xiàn)ViewPager頁面切換的效果
        mPagerSnapHelper = new PagerSnapHelperImpl();
        mPagerSnapHelper.attachToRecyclerView(mRecyclerView);
      
        mRecyclerView.addOnScrollListener(mScrollEventAdapter);

        mPageChangeEventDispatcher = new CompositeOnPageChangeCallback(3);
        mScrollEventAdapter.setOnPageChangeCallback(mPageChangeEventDispatcher);

        //pdates mCurrentItem after swipes
        final OnPageChangeCallback currentItemUpdater = new OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position) {
                if (mCurrentItem != position) {
                    mCurrentItem = position;
                    mAccessibilityProvider.onSetNewCurrentItem();
                }
            }

            @Override
            public void onPageScrollStateChanged(int newState) {
                if (newState == SCROLL_STATE_IDLE) {
                    updateCurrentItem();
                }
            }
        };

        // update internal state firstinternal state first
        mPageChangeEventDispatcher.addOnPageChangeCallback(currentItemUpdater);
        mAccessibilityProvider.onInitialize(mPageChangeEventDispatcher, mRecyclerView);
        mPageChangeEventDispatcher.addOnPageChangeCallback(mExternalPageChangeCallbacks);

        // Add mPageTransformerAdapter after mExternalPageChangeCallbacks, because page transform
        // events must be fired after scroll events
        //PageTransformerAdapter 作用是將頁面的滑動事件轉(zhuǎn)變?yōu)楸嚷首兓ò荩热纾粋€(gè)頁面從左到右滑動惠昔,positionOffset變化是從0~1
        mPageTransformerAdapter = new PageTransformerAdapter(mLayoutManager);
        mPageChangeEventDispatcher.addOnPageChangeCallback(mPageTransformerAdapter);
        attachViewToParent(mRecyclerView, 0, mRecyclerView.getLayoutParams());
    }

在initialize()方法里面菱属,初始化了RecyclerView組件。

主要做了這幾件事:

  • RecycleView的基本初始化
  • 設(shè)置了PagerSnapHelper,目的是實(shí)現(xiàn)切面切換的效果
  • 給RecyclerView設(shè)置了滑動監(jiān)聽事件舰罚,涉及到的組件是ScrollEventAdapter纽门,后面的基本功能都需要這個(gè)組件的支持

ViewPager的效果,就是靠PagerSnapHelper和ScrollEventAdapter實(shí)現(xiàn)的营罢。

2赏陵、PagerSnapHelper是做什么的?

PagerSnapHelper繼承于SnapHelper饲漾。SnapHelper顧名思義是Snap+Helper的組合蝙搔,Snap有移到某位置的含義,Helper為輔助者考传,
綜合場景解釋是 將RecyclerView移動到某位置的輔助類吃型。
SnapHelper的內(nèi)部有兩個(gè)監(jiān)聽接口:OnFlingListener和OnScrollListener,分別用來監(jiān)聽RecyclerView的fling事件和scroll事件僚楞。

SnapHelper有三個(gè)重要的方法:

  • findTargetSnapPosition(計(jì)算Fling事件能滑動到位置)
  • findSnapView(找到需要顯示在RecyclerView的最前面的View)
  • calculateDistanceToFinalSnap(計(jì)算需要滑動的距離)勤晚。

PagerSnapHelper重寫了這三個(gè)方法枉层,用以攔截fling事件,做到每次滑動一個(gè)item

那么這些方法什么時(shí)間調(diào)用的赐写?

  • 手指在快速滑動鸟蜡,在手指離開屏幕前,三個(gè)方法均不調(diào)用挺邀。
  • 手指在快速滑后揉忘,手指離開了屏幕,會發(fā)生Fling事件端铛,findTargetSnapPosition方法會被調(diào)用泣矛。
  • 當(dāng)Fling事件結(jié)束時(shí),RecyclerView會回調(diào)SnapHelper內(nèi)部OnScrollListener接口的onScrollStateChanged方法禾蚕。
    此時(shí)RecyclerView的滑動狀態(tài)為RecyclerView.SCROLL_STATE_IDLE,會調(diào)用findSnapView方法來找到需要
    顯示在RecyclerView的最前面的View您朽。找到目標(biāo)View之后,就會調(diào)用calculateDistanceToFinalSnap方法來計(jì)算需要滑動的距離夕膀。然后調(diào)用smoothScrollBy滑動到對應(yīng)位置。

我們先從PagerSnapHelper調(diào)用的attachToRecyclerView方法說起美侦。

SnapHelper.java

    public void attachToRecyclerView(@Nullable RecyclerView recyclerView)
            throws IllegalStateException {
        //判斷是否是同一個(gè)RecyclerView,如果是直接返回产舞,防止重復(fù)attach
        if (mRecyclerView == recyclerView) {
            return; // nothing to do
        }
        //如果是一個(gè)新的RecyclerView,先銷毀所有的回調(diào)接口,這里指的是
        //RecyclerView的scroll監(jiān)聽和fling監(jiān)聽
        if (mRecyclerView != null) {
            destroyCallbacks();
        }
        //保存這個(gè)RecyclerView
        mRecyclerView = recyclerView;
        if (mRecyclerView != null) {
            //重新設(shè)置監(jiān)聽
            setupCallbacks();
            //初始化一個(gè)Scoller菠剩,用于滾動RecylerView
            mGravityScroller = new Scroller(mRecyclerView.getContext(),
                    new DecelerateInterpolator());
            //移動到指定的已存在的View 后面講解
            snapToTargetExistingView();
        }
    }
    
      //設(shè)置回調(diào)關(guān)系
    private void setupCallbacks() throws IllegalStateException {
        if (mRecyclerView.getOnFlingListener() != null) {
            throw new IllegalStateException("An instance of OnFlingListener already set.");
        }
        mRecyclerView.addOnScrollListener(mScrollListener);
        mRecyclerView.setOnFlingListener(this);
    }

    //注銷回調(diào)關(guān)系
    private void destroyCallbacks() {
        mRecyclerView.removeOnScrollListener(mScrollListener);
        mRecyclerView.setOnFlingListener(null);
    }
SnapHelper在attachToRecyclerView方法中先注冊了滾動狀態(tài)和fling的監(jiān)聽易猫,當(dāng)監(jiān)聽觸發(fā)時(shí),如何處理后續(xù)的流程具壮?
我們先來看下onScroll事件的處理准颓。
SnapHelper.java

 // Handles the snap on scroll case.
    private final RecyclerView.OnScrollListener mScrollListener =
            new RecyclerView.OnScrollListener() {
                boolean mScrolled = false;
 
                @Override
                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                    super.onScrollStateChanged(recyclerView, newState);
                    //當(dāng)newState == 靜止?fàn)顟B(tài)且滾動距離不等于0,觸發(fā)snapToTargetExistingView();
                    if (newState == RecyclerView.SCROLL_STATE_IDLE && mScrolled) {
                        mScrolled = false;
                        snapToTargetExistingView();
                    }
                }
 
                @Override
                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                    if (dx != 0 || dy != 0) {
                        mScrolled = true;
                    }
                }
            };
    //移動到指定的已存在的View
     void snapToTargetExistingView() {
        if (mRecyclerView == null) {
            return;
        }
        RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
        if (layoutManager == null) {
            return;
        }
        //查找SnapView
        View snapView = findSnapView(layoutManager);
        if (snapView == null) {
            return;
        }
        //計(jì)算SnapView的距離
        int[] snapDistance = calculateDistanceToFinalSnap(layoutManager, snapView);
        if (snapDistance[0] != 0 || snapDistance[1] != 0) {
            mRecyclerView.smoothScrollBy(snapDistance[0], snapDistance[1]);
        }
    }

snapToTargetExistingView()這個(gè)方法會在第一次attach的時(shí)候和RecyclerView滑動狀態(tài)改變的時(shí)候調(diào)用棺妓,用于將RecyclerView移動到指定的位置攘已,移動的距離根據(jù)當(dāng)前距離RecyclerView中心點(diǎn)最近的那個(gè)itemView獲取,獲取到這個(gè)距離后怜跑,調(diào)用smoothScrollBy方法移動RecyclerView样勃。在這個(gè)方法中findSnapView和calculateDistanceToFinalSnap是兩個(gè)抽象方法。需要子類重寫性芬,實(shí)現(xiàn)不同的效果峡眶。

整理一下滾動狀態(tài)回調(diào)下,SnapHelper的實(shí)現(xiàn)流程圖如下植锉;

image.png

我們接著看下fling事件辫樱,

在RecyclerView中fling這里就不展開了。行為流程圖如下:
image.png
SnapHelper.java

@Override
    public boolean onFling(int velocityX, int velocityY) {
        LayoutManager layoutManager = mRecyclerView.getLayoutManager();
        if (layoutManager == null) {
            return false;
        }
        RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
        if (adapter == null) {
            return false;
        }
        //獲取出發(fā)RecyclerView fling的最小速度俊庇,這是一個(gè)定義好的常量值
        int minFlingVelocity = mRecyclerView.getMinFlingVelocity();
        //當(dāng)滑動速度滿足條件時(shí)狮暑,執(zhí)行snapFromFling方法
        return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity)
                && snapFromFling(layoutManager, velocityX, velocityY);
    }

然后我們來看看SnapHelper的snapFromFling方法:

SnapHelper.java

//處理snap的fling邏輯
private boolean snapFromFling(@NonNull RecyclerView.LayoutManager layoutManager, int velocityX,
            int velocityY) {
        //判斷l(xiāng)ayoutManager要實(shí)現(xiàn)ScrollVectorProvider
        if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
            return false;
        }
        //創(chuàng)建SmoothScroller
        RecyclerView.SmoothScroller smoothScroller = createScroller(layoutManager);
        if (smoothScroller == null) {
            return false;
        }
        //獲得snap position
        int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY);
        if (targetPosition == RecyclerView.NO_POSITION) {
            return false;
        }
          //設(shè)置position
        smoothScroller.setTargetPosition(targetPosition);
        //啟動SmoothScroll
        layoutManager.startSmoothScroll(smoothScroller);
        //返回true攔截掉后續(xù)的fling操作
        return true;
    }
    
    @Nullable
    @Deprecated
    protected LinearSmoothScroller createSnapScroller(RecyclerView.LayoutManager layoutManager) {
        if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
            return null;
        }
        return new LinearSmoothScroller(mRecyclerView.getContext()) {
            @Override
            protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
                if (mRecyclerView == null) {
                    // The associated RecyclerView has been removed so there is no action to take.
                    return;
                }
                 //計(jì)算Snap到目標(biāo)位置的距離
                int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(),
                        targetView);
                final int dx = snapDistances[0];
                final int dy = snapDistances[1];
                //計(jì)算時(shí)間
                final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)));
                if (time > 0) {
                    action.update(dx, dy, time, mDecelerateInterpolator);
                }
            }
          //計(jì)算速度
            @Override
            protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
                return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
            }
        };
    }

fling的邏輯主要在snapFromFling方法中鸡挠,完成fling邏輯需要幾點(diǎn):

  • 要求layoutManager是ScrollVectorProvider的實(shí)現(xiàn)
  • 創(chuàng)建SmoothScroller,主要邏輯是createSnapScroller方法
  • 該方法有默認(rèn)的實(shí)現(xiàn)心例,主要邏輯是創(chuàng)建一個(gè)LinearSmoothScroller
  • 通過findTargetSnapPosition方法獲取目標(biāo)targetPosition
  • 最后把targetPosition賦值給smoothScroller宵凌,通過layoutManager執(zhí)行該scroller
  • snapFromFling要返回true,返回true的話止后,默認(rèn)的ViewFlinger就不會執(zhí)行瞎惫。

fling流程:


image.png

從snapFromFling方法中我們知道,只要findTargetSnapPosition方法返回不為RecyclerView.NO_POSITION,那么接下來的滑動事件會交給SmoothScroller去處理译株,所以RecyclerView最終滑到的位置為當(dāng)前位置的上一個(gè)或者下一個(gè)瓜喇,不會產(chǎn)生Fling的效果。

為了阻止RecyclerView的Fling事件歉糜,findTargetSnapPosition方法需要保證幾點(diǎn)

  • 返回當(dāng)前ItemView的上一個(gè)ItemView或者下一個(gè)ItemView的位置
  • 保證findTargetSnapPosition方法返回的值不為RecyclerView.NO_POSITION乘寒,

查找指定的SnapPosition

/*
這個(gè)方法表示fling操作最終能滑動到I的temView的position。
這個(gè)position稱為targetSnapPosition匪补,位置上對應(yīng)的View就是targetSnapView伞辛。如果找不到position,就返回RecyclerView.NO_POSITION
*/
@Override
    public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX,
            int velocityY) {
        final int itemCount = layoutManager.getItemCount();
        if (itemCount == 0) {
            return RecyclerView.NO_POSITION;
        }

        final OrientationHelper orientationHelper = getOrientationHelper(layoutManager);
        if (orientationHelper == null) {
            return RecyclerView.NO_POSITION;
        }

        // A child that is exactly in the center is eligible for both before and after
        View closestChildBeforeCenter = null;
        int distanceBefore = Integer.MIN_VALUE;
        View closestChildAfterCenter = null;
        int distanceAfter = Integer.MAX_VALUE;

        // Find the first view before the center, and the first view after the center
        final int childCount = layoutManager.getChildCount();
        
        // 找到與當(dāng)前View相鄰的View夯缺,包括左相鄰和右響鈴蚤氏,并且計(jì)算滑動的距離
        for (int i = 0; i < childCount; i++) {
            final View child = layoutManager.getChildAt(i);
            if (child == null) {
                continue;
            }
            final int distance = distanceToCenter(layoutManager, child, orientationHelper);

            if (distance <= 0 && distance > distanceBefore) {
                // Child is before the center and closer then the previous best
                distanceBefore = distance;
                closestChildBeforeCenter = child;
            }
            if (distance >= 0 && distance < distanceAfter) {
                // Child is after the center and closer then the previous best
                distanceAfter = distance;
                closestChildAfterCenter = child;
            }
        }

        // 根據(jù)滑動的方向來返回的相應(yīng)位置
        final boolean forwardDirection = isForwardFling(layoutManager, velocityX, velocityY);
        if (forwardDirection && closestChildAfterCenter != null) {
            return layoutManager.getPosition(closestChildAfterCenter);
        } else if (!forwardDirection && closestChildBeforeCenter != null) {
            return layoutManager.getPosition(closestChildBeforeCenter);
        }

        // 兜底計(jì)算
        View visibleView = forwardDirection ? closestChildBeforeCenter : closestChildAfterCenter;
        if (visibleView == null) {
            return RecyclerView.NO_POSITION;
        }
        int visiblePosition = layoutManager.getPosition(visibleView);
        int snapToPosition = visiblePosition
                + (isReverseLayout(layoutManager) == forwardDirection ? -1 : +1);

        if (snapToPosition < 0 || snapToPosition >= itemCount) {
            return RecyclerView.NO_POSITION;
        }
        return snapToPosition;
    }

當(dāng)RecyclerView滑動完畢之后,此時(shí)會先調(diào)用findSnapView方法獲取來最終位置的ItemView踊兜。當(dāng)RecyclerView觸發(fā)Fling事件時(shí)竿滨,才會觸發(fā)findTargetSnapPosition方法,從而保證RecyclerView滑動到正確位置捏境;那么當(dāng)RecyclerView沒有觸發(fā)Fling事件于游,怎么保證RecyclerView滑動到正確位置呢?當(dāng)然是findSnapView方法和calculateDistanceToFinalSnap方法垫言,這倆方法還有一個(gè)目的就是贰剥,如果Fling沒有滑動正確位置,這倆方法可以做一個(gè)兜底操作筷频。

那么findSnapView和calculateDistanceToFinalSnap究竟怎么做的鸠澈?

我們先來看PagerSnapHelper重寫的findSnapView方法。

PagerSnapHelper.java

  @Nullable
    @Override
    public View findSnapView(RecyclerView.LayoutManager layoutManager) {
        //判斷RecyclerView的滑動方向截驮,這個(gè)可以由layoutManager獲取
        if (layoutManager.canScrollVertically()) {
            //OrientationHelper是對RecycleView中子View管理的工具類
            return findCenterView(layoutManager, getVerticalHelper(layoutManager));
        } else if (layoutManager.canScrollHorizontally()) {
            return findCenterView(layoutManager, getHorizontalHelper(layoutManager));
        }
        return null;
    }
    /**
     * Return the child view that is currently closest to the center of this parent.
     * 如注釋所說笑陈,這個(gè)方法返回的是一個(gè)距離當(dāng)前parent也就是RecyclerView中心位置最近的一個(gè)item
     * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached
     *                      {@link RecyclerView}.
     * @param helper The relevant {@link OrientationHelper} for the attached {@link RecyclerView}.
     *
     * @return the child view that is currently closest to the center of this parent.
     */
    @Nullable
    private View findCenterView(RecyclerView.LayoutManager layoutManager,
            OrientationHelper helper) {
        int childCount = layoutManager.getChildCount();
        if (childCount == 0) {
            return null;
        }
 
        View closestChild = null;
        final int center;
        //clipToPadding是RecyclerView的一個(gè)屬性,表示在含有padding值的時(shí)候
        //會不會裁剪padding位置的view葵袭,如果為true涵妥,表示裁剪,那么在padding
        //位置滾動的時(shí)候是看不到view的
        if (layoutManager.getClipToPadding()) {
            //getStartAfterPadding  獲取RecycleView左側(cè)內(nèi)邊距(paddingLeft)
            //getTotalSpace  Recycleview水平內(nèi)容區(qū)大衅挛(寬度蓬网,除去左右內(nèi)邊距)
            //這些方法都會在OrientationHelper中具體說明窒所,如果是Horizontal,
            //getStartAfterPadding  得到的是paddingLeft帆锋,如果是vertical吵取,那么得到
            //的是paddingTop,自己想象一下
            //含有padding的時(shí)候锯厢,RecyclerView的中心位置應(yīng)該是起始位置的padding
            //值加上內(nèi)容區(qū)的寬度的一半皮官,得到的剛好是RecyclerView顯示內(nèi)容的中心
            //位置的值
            center = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;
        } else {
            //沒有padding,則中心位置的值直接是寬度的一半
            center = helper.getEnd() / 2;
        }
        int absClosest = Integer.MAX_VALUE;
 
        //循環(huán)的計(jì)算所有childView,得到離上邊計(jì)算到的中心點(diǎn)值最近的一個(gè)
        for (int i = 0; i < childCount; i++) {
            final View child = layoutManager.getChildAt(i);
            //getDecoratedStart返回view左邊界點(diǎn)(包含左內(nèi)邊距和左外邊距)在父View中的位置(以父View的(0实辑,0)點(diǎn)位坐標(biāo)系)
            //通俗地講:子View左邊界點(diǎn)到父View的(0捺氢,0)點(diǎn)的水平間距
            ///getDecoratedMeasurement返回view在水平方向上所占位置的大小(包括view的左右外邊距)
            int childCenter = helper.getDecoratedStart(child)
                    + (helper.getDecoratedMeasurement(child) / 2);
            int absDistance = Math.abs(childCenter - center);
 
            /** if child center is closer than previous closest, set it as closest  **/
            if (absDistance < absClosest) {
                absClosest = absDistance;
                closestChild = child;
            }
        }
        return closestChild;
    }

findCenterView方法還是比較長剪撬,但是表示的意思非常簡單摄乒,就是找到當(dāng)前中心距離屏幕中心最近的ItemView。這個(gè)怎么來理解呢?比如說残黑,我們手指在滑動一個(gè)頁面馍佑,滑動到一定距離時(shí)就松開了,此時(shí)屏幕當(dāng)中有兩個(gè)頁面梨水,那么ViewPager2應(yīng)該滑動到哪一個(gè)頁面呢拭荤?當(dāng)然是距離屏幕中心最近的頁面。findCenterView方法的作用便是如此冰木。

經(jīng)過上邊的計(jì)算穷劈,就能得到距離當(dāng)前RecyclerView中心位置最近的一個(gè)itemView笼恰。
找到需要滑到的ItemView踊沸,此時(shí)就應(yīng)該調(diào)用calculateDistanceToFinalSnap方法來計(jì)算,此時(shí)RecyclerView還需要滑動多少距離才能達(dá)到正確位置社证。
    @Nullable
    @Override
    public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,
            @NonNull View targetView) {
        int[] out = new int[2];
        //判斷方向逼龟,獲取方向上移動的值,保存在數(shù)組中返回
        if (layoutManager.canScrollHorizontally()) {
            out[0] = distanceToCenter(layoutManager, targetView,
                    getHorizontalHelper(layoutManager));
        } else {
            out[0] = 0;
        }
 
        if (layoutManager.canScrollVertically()) {
            out[1] = distanceToCenter(layoutManager, targetView,
                    getVerticalHelper(layoutManager));
        } else {
            out[1] = 0;
        }
        return out;
    }
 
    private int distanceToCenter(@NonNull RecyclerView.LayoutManager layoutManager,
            @NonNull View targetView, OrientationHelper helper) {
        //getDecoratedStart 返回view左邊界點(diǎn)(包含左內(nèi)邊距和左外邊距)在父View中的位置(以父View的(0追葡,0)點(diǎn)位坐標(biāo)系)
            //通俗地講:子View左邊界點(diǎn)到父View的(0腺律,0)點(diǎn)的水平間距
        final int childCenter = helper.getDecoratedStart(targetView)
                + (helper.getDecoratedMeasurement(targetView) / 2);
        final int containerCenter;
        if (layoutManager.getClipToPadding()) {
            //獲取RecycleView左側(cè)內(nèi)邊距(paddingLeft)
            //再次計(jì)算RecyclerView中心位置
            containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;
        } else {
            containerCenter = helper.getEnd() / 2;
        }
        //用itemView的中心位置減去RecyclerView的中心位置,得到的就是
        //將要移動的距離
        return childCenter - containerCenter;
    }

calculateDistanceToFinalSnap表達(dá)的意思非常簡單宜肉,就是計(jì)算RecyclerView需要滑動的距離匀钧,主要通過distanceToCenter方法來計(jì)算。PagerSnapHelper的移動規(guī)則是每次滑動將距離中心位置最近的item移動到中心位置.

3谬返、ScrollEventAdapter轉(zhuǎn)換事件

繼承RecyclerView.OnScrollListener之斯,作用是將RecyclerView.OnScrollListener(滑動事件)轉(zhuǎn)變?yōu)閂iewPager2.OnPageChangeCallback(頁面滑動事件)。

ScrollEventAdapter有幾種狀態(tài)值:

名稱 含義
STATE_IDLE 表示當(dāng)前ViewPager2處于停止?fàn)顟B(tài)
STATE_IN_PROGRESS_MANUAL_DRAG 表示當(dāng)前ViewPager2處于手指拖動狀態(tài)
STATE_IN_PROGRESS_SMOOTH_SCROLL 表示當(dāng)前ViewPager2處于緩慢滑動的狀態(tài)遣铝。這個(gè)狀態(tài)只在調(diào)用了ViewPager2的setCurrentItem方法才有可能出現(xiàn)佑刷。
STATE_IN_PROGRESS_IMMEDIATE_SCROLL 表示當(dāng)前ViewPager2處于迅速滑動的狀態(tài)莉擒。這個(gè)狀態(tài)只在調(diào)用了ViewPager2的setCurrentItem方法才有可能出現(xiàn)。
STATE_IN_PROGRESS_FAKE_DRAG 表示當(dāng)前ViewPager2未使用手指滑動瘫絮,而是通過FakerDrag實(shí)現(xiàn)的涨冀。

ScrollEventAdapter的重心是重寫onScrollStateChanged()和onScrolled()
,當(dāng)RecyclerView的滑動狀態(tài)發(fā)生變化麦萤,onScrollStateChanged()方法就會被調(diào)用鹿鳖。

onScrollStateChanged()方法

主要有三個(gè)步驟:

  • 開始拖動,會調(diào)用startDrag方法表示拖動開始频鉴。
  • 拖拽釋放栓辜,此時(shí)ViewPager2會準(zhǔn)備滑動到正確的位置。
  • 滑動結(jié)束垛孔,此時(shí)ScrollEventAdapter會調(diào)用相關(guān)的方法更新狀態(tài)藕甩。
    @Override
    public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
        // 1、用戶開始拖拽
        if ((mAdapterState != STATE_IN_PROGRESS_MANUAL_DRAG
                || mScrollState != SCROLL_STATE_DRAGGING)
                && newState == RecyclerView.SCROLL_STATE_DRAGGING) {
            startDrag(false);
            return;
        }

        // 2周荐、拖拽釋放 ,此時(shí)ViewPager2會準(zhǔn)備滑動到正確的位置 
        //RecyclerView is snapping to page (dragging -> settling)
        // Note that mAdapterState is not updated, to remember we were dragging when settling
        if (isInAnyDraggingState() && newState == RecyclerView.SCROLL_STATE_SETTLING) {
            // Only go through the settling phase if the drag actually moved the page
            if (mScrollHappened) {
                /**
                將狀態(tài)改變的信息分發(fā)到OnPageChangeCallback監(jiān)聽器狭莱,不過需要注意的是:當(dāng)ViewPager2處于停止?fàn)顟B(tài),同時(shí)調(diào)用了setCurrentItem方法來立即切換到某一個(gè)頁面(注意概作,不是緩慢的切換)腋妙,不會回調(diào)OnPageChangeCallback的方法。
                */
                dispatchStateChanged(SCROLL_STATE_SETTLING);
                // Determine target page and dispatch onPageSelected on next scroll event
                mDispatchSelected = true;
            }
            return;
        }

        // 3讯榕、拖拽結(jié)束 此時(shí)ScrollEventAdapter會調(diào)用相關(guān)的方法更新狀態(tài) Drag is finished (dragging || settling -> idle)
        if (isInAnyDraggingState() && newState == RecyclerView.SCROLL_STATE_IDLE) {
            boolean dispatchIdle = false;
            updateScrollEventValues();
             // 如果在拖動期間為產(chǎn)生移動距離
            if (!mScrollHappened) {
                // Pages didn't move during drag, so either we're at the start or end of the list,
                // or there are no pages at all.
                // In the first case, ViewPager's contract requires at least one scroll event.
                // In the second case, don't send that scroll event
                if (mScrollValues.mPosition != RecyclerView.NO_POSITION) {
                    dispatchScrolled(mScrollValues.mPosition, 0f, 0);
                }
                dispatchIdle = true;
            } else if (mScrollValues.mOffsetPx == 0) {
                // Normally we dispatch the selected page and go to idle in onScrolled when
                // mOffsetPx == 0, but in this case the drag was still ongoing when onScrolled was
                // called, so that didn't happen. And since mOffsetPx == 0, there will be no further
                // scroll events, so fire the onPageSelected event and go to idle now.
                // Note that if we _did_ go to idle in that last onScrolled event, this code will
                // not be executed because mAdapterState has been reset to STATE_IDLE.
                dispatchIdle = true;
                if (mDragStartPosition != mScrollValues.mPosition) {
                    dispatchSelected(mScrollValues.mPosition);
                }
            }
            if (dispatchIdle) {
                // Normally idle is fired in last onScrolled call, but either onScrolled was never
                // called, or we were still dragging when the last onScrolled was called
                dispatchStateChanged(SCROLL_STATE_IDLE);
                resetState();
            }
        }
    }

在這個(gè)方法中骤素,前兩點(diǎn)我們都知道,我們主要看第三個(gè)步驟愚屁。
dispatchStateChanged方法會在什么時(shí)候調(diào)用济竹?

  • 沒有滑動,也就是說霎槐,onScrolled方法沒有被調(diào)用送浊;
  • 滑動過,并且在上一次滑動中最后一次調(diào)用onScrolled方法的時(shí)候會被調(diào)用丘跌。

dispatchSelected方法會在什么時(shí)候調(diào)用袭景?

  • 當(dāng)mOffsetPx為0時(shí)會被調(diào)用,mOffsetPx為0表示當(dāng)前ViewPager2根本未滑動闭树。

onScrolled()方法


 @Override
    public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
        mScrollHappened = true;
          // 更新相關(guān)值
        updateScrollEventValues();

        if (mDispatchSelected) {
           // 拖動手勢釋放耸棒,ViewPager2正在滑動到正確的位置
            // Drag started settling, need to calculate target page and dispatch onPageSelected now
            mDispatchSelected = false;
            boolean scrollingForward = dy > 0 || (dy == 0 && dx < 0 == mViewPager.isRtl());

            // "&& values.mOffsetPx != 0": filters special case where we're scrolling forward and
            // the first scroll event after settling already got us at the target
            mTarget = scrollingForward && mScrollValues.mOffsetPx != 0
                    ? mScrollValues.mPosition + 1 : mScrollValues.mPosition;
            if (mDragStartPosition != mTarget) {
                dispatchSelected(mTarget);
            }
        } else if (mAdapterState == STATE_IDLE) {
          
            // onScrolled while IDLE means RV has just been populated after an adapter has been set.
            // Contract requires us to fire onPageSelected as well.
            int position = mScrollValues.mPosition;
            // Contract forbids us to send position = -1 though
            // 調(diào)用了setAdapter方法
            dispatchSelected(position == NO_POSITION ? 0 : position);
        }

        // If position = -1, there are no items. Contract says to send position = 0 instead.
        dispatchScrolled(mScrollValues.mPosition == NO_POSITION ? 0 : mScrollValues.mPosition,
                mScrollValues.mOffset, mScrollValues.mOffsetPx);

        // Dispatch idle in onScrolled instead of in onScrollStateChanged because RecyclerView
        // doesn't send IDLE event when using setCurrentItem(x, false)
        // 因?yàn)檎{(diào)用了setCurrentItem(x, false)不會觸發(fā)IDLE狀態(tài)的產(chǎn)生,所以需要在這里
        // 調(diào)用dispatchStateChanged方法
        if ((mScrollValues.mPosition == mTarget || mTarget == NO_POSITION)
                && mScrollValues.mOffsetPx == 0 && !(mScrollState == SCROLL_STATE_DRAGGING)) {
            // When the target page is reached and the user is not dragging anymore, we're settled,
            // so go to idle.
            // Special case and a bit of a hack when mTarget == NO_POSITION: RecyclerView is being
            // initialized and fires a single scroll event. This flags mScrollHappened, so we need
            // to reset our state. However, we don't want to dispatch idle. But that won't happen;
            // because we were already idle.
            dispatchStateChanged(SCROLL_STATE_IDLE);
            resetState();
        }
    }

onScrolled方法里面主要做了兩件事:

  • 調(diào)用updateScrollEventValues方法更新ScrollEventValues里面的值(mPosition报辱,mOffset与殃,mOffsetPx)。
  • 調(diào)用相關(guān)方法馒吴,更新狀態(tài)媒吗。

對ScrollEventAdapter總結(jié)下:

  • 當(dāng)調(diào)用ViewPager2的setAdapter方法時(shí),此時(shí)應(yīng)該回調(diào)一次dispatchSelected方法伟阔。
  • 當(dāng)調(diào)用setCurrentItem(x, false)方法斗这,不會調(diào)用onScrollStateChanged方法徒像,因而不會產(chǎn)生idle狀態(tài)载萌,因此溯革,我們需要在onScrolled方法特殊處理(onScrolled方法會被調(diào)用)节榜。
  • 正常的拖動和釋放狼忱,就是onScrollStateChanged方法和onScrolled方法的正撑蚴瑁回調(diào)。

經(jīng)過ScrollEventAdapter的轉(zhuǎn)換钻弄,將RecyclerView.OnScrollListener(滑動事件)轉(zhuǎn)變?yōu)閂iewPager2.OnPageChangeCallback(頁面滑動事件)佃却,那么OnPageChangeCallback怎么處理呢?

ViewPager2中還有一個(gè)adapter——PageTransformerAdapter窘俺,作用將OnPageChangeCallback的事件轉(zhuǎn)換成為一種特殊的事件饲帅,什么特殊的事件呢?

我以一個(gè)例子來解釋一下:

  • 假設(shè)ViewPager2此時(shí)從A頁面滑動到B頁面瘤泪,并且是從右往左滑動灶泵,其中A頁面的變化范圍:[0,-1);B頁面的變化范圍:[1,0)对途。
  • 假設(shè)ViewPager2此時(shí)從B頁面滑動到A頁面赦邻,并且是從左往右滑動,其中A頁面的變化范圍:[-1,0)实檀;B頁面的變化范圍:[0,1)惶洲。
 @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        if (mPageTransformer == null) {
            return;
        }

        float transformOffset = -positionOffset;
        for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
            View view = mLayoutManager.getChildAt(i);
            if (view == null) {
                throw new IllegalStateException(String.format(Locale.US,
                        "LayoutManager returned a null child at pos %d/%d while transforming pages",
                        i, mLayoutManager.getChildCount()));
            }
            int currPos = mLayoutManager.getPosition(view);
            float viewOffset = transformOffset + (currPos - position);
            mPageTransformer.transformPage(view, viewOffset);
        }
    }

FragmentStateAdapter與Fragment生命周期

  • 變量
    //key為itemId,value為Fragment膳犹。表示position與所放Fragment的對應(yīng)關(guān)系(itemId與position有對應(yīng)關(guān)系)
    final LongSparseArray<Fragment> mFragments = new LongSparseArray<>();
    //key為itemId恬吕,value為Fragment的狀態(tài)
    private final LongSparseArray<Fragment.SavedState> mSavedStates = new LongSparseArray<>();
    //key為itemId, value為ItemView的id。
    private final LongSparseArray<Integer> mItemIdToViewHolder = new LongSparseArray<>();
  • 方法
    • onCreateViewHolder()
    • onBindViewHolder()
    • onViewAttachedToWindow()
    • onViewRecycled()
    • onFailedToRecycleView()

(1). onCreateViewHolder方法

onCreateViewHolder方法主要創(chuàng)建ViewHolder镣奋,其實(shí)就是調(diào)用了FragmentViewHolder的一個(gè)靜態(tài)方法.

    @NonNull
    @Override
    public final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return FragmentViewHolder.create(parent);
    }

      //創(chuàng)建一個(gè)寬高都MATCH_PARENT的FrameLayout币呵,注意這里并不像PagerAdapter是Fragment的rootView怀愧;
    @NonNull static FragmentViewHolder create(@NonNull ViewGroup parent) {
        FrameLayout container = new FrameLayout(parent.getContext());
        container.setLayoutParams(
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT));
        container.setId(ViewCompat.generateViewId());
        container.setSaveEnabled(false);
        return new FragmentViewHolder(container);
    }

(2). onBindViewHolder方法

onBindViewHolder方法是將Fragment加載到ItemView上侨颈,但是因?yàn)镽ecyclerView的復(fù)用機(jī)制,所以有很多的條件判斷芯义。

我們先看一下代碼:

    public final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {
        final long itemId = holder.getItemId();
        final int viewHolderId = holder.getContainer().getId();
        final Long boundItemId = itemForViewHolder(viewHolderId); // item currently bound to the VH
        // 如果當(dāng)前ItemView已經(jīng)加載了Fragment哈垢,并且不是同一個(gè)Fragment
        // 那么就移除
        if (boundItemId != null && boundItemId != itemId) {
            removeFragment(boundItemId);
            mItemIdToViewHolder.remove(boundItemId);
        }

        mItemIdToViewHolder.put(itemId, viewHolderId); // this might overwrite an existing entry
        // 保證對應(yīng)位置的Fragment已經(jīng)初始化,并且放在mFragments中
        ensureFragment(position);

        final FrameLayout container = holder.getContainer();
        // 特殊情況扛拨,當(dāng)RecyclerView讓ItemView保持在Window耘分,
        // 但是不在視圖樹中。
        if (ViewCompat.isAttachedToWindow(container)) {
            if (container.getParent() != null) {
                throw new IllegalStateException("Design assumption violated.");
            }
            // 當(dāng)ItemView添加在到RecyclerView中才加載Fragment
            container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
                @Override
                public void onLayoutChange(View v, int left, int top, int right, int bottom,
                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
                    if (container.getParent() != null) {
                        container.removeOnLayoutChangeListener(this);
                        // 加載Fragment
                        placeFragmentInViewHolder(holder);
                    }
                }
            });
        }

        gcFragments();
    }
onBindViewHolder方法主要分為三步:
  • 當(dāng)前ItemView上已經(jīng)加載了Fragment,并且不是同一個(gè)Fragment(ItemView被復(fù)用了)求泰,那么先移除掉ItemView上的Fragment
  • 初始化相關(guān)信息央渣。
  • 如果存在特殊情況,會走特殊情況渴频。正常來說芽丹,都會經(jīng)過onAttachToWindow方法來對Fragment進(jìn)行加載。
  • 每次調(diào)用都會gc一次卜朗,主要的避免用戶修改數(shù)據(jù)源造成垃圾對象

(3). onViewAttachedToWindow方法

正常情況下拔第,ItemView會在這個(gè)方法里面加載Fragment。

    @Override
    public final void onViewAttachedToWindow(@NonNull final FragmentViewHolder holder) {
        placeFragmentInViewHolder(holder);
        gcFragments();
    }

和onBindViewHolder一樣场钉,調(diào)用了placeFragmentInViewHolder方法加載Fragment蚊俺。

(4). onViewRecycled方法

當(dāng)ViewHolder被回收,到回收池中的時(shí)候逛万,onViewRecycled方法會被調(diào)用泳猬。
而在onViewRecycled方法里面,會對Fragment進(jìn)行remove宇植。

    @Override
    public final void onViewRecycled(@NonNull FragmentViewHolder holder) {
        final int viewHolderId = holder.getContainer().getId();
        final Long boundItemId = itemForViewHolder(viewHolderId); // item currently bound to the VH
        if (boundItemId != null) {
            removeFragment(boundItemId);
            mItemIdToViewHolder.remove(boundItemId);
        }
    }
這里為什么會在onViewRecycled方法來對Fragment進(jìn)行remove暂殖,而不在onViewDetachedFromWindow方法進(jìn)行remove呢。

當(dāng)onViewRecycled方法被調(diào)用当纱,表示當(dāng)前ViewHolder已經(jīng)徹底沒有用了呛每,需要被放入回收池,等待被復(fù)用坡氯。
此時(shí)存在的情況可能有:

  • 1.當(dāng)前ItemView手動移除掉了
  • 2.當(dāng)前位置對應(yīng)的視圖已經(jīng)徹底不在屏幕中晨横,被當(dāng)前屏幕中某些位置復(fù)用了。

所以在onViewRecycled方法里面移除Fragment比較合適箫柳。

而onViewDetachedFromWindow方法手形,每次一個(gè)頁面被滑走,都會被調(diào)用悯恍,如果對其Fragment進(jìn)行卸載库糠,此時(shí)又滑回來,又要重新加載一次涮毫,這性能就下降了很多瞬欧。
onFailedToRecycleView方法與onViewRecycled方法差不多,不在介紹罢防。

(5). placeFragmentInViewHolder方法

placeFragmentInViewHolder方法主要是加載Fragment艘虎,是整個(gè)FragmentStateAdapter的核心點(diǎn)。

在加載Fragment之前咒吐,我們需要判斷幾個(gè)狀態(tài):

  • Fragment是否添加到ItemView 中
  • Fragment的View是否已經(jīng)創(chuàng)建
  • Fragment的View 是否添加視圖樹中

placeFragmentInViewHolder方法中一共有8種情況野建。

我們來看看代碼:

 void placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder) {

        Fragment fragment = mFragments.get(holder.getItemId());
        if (fragment == null) {
            throw new IllegalStateException("Design assumption violated.");
        }
        FrameLayout container = holder.getContainer();
        View view = fragment.getView();

        /*
        possible states:
        - fragment: { added, notAdded }
        - view: { created, notCreated }
        - view: { attached, notAttached }

        combinations:
        - { f:added, v:created, v:attached } -> check if attached to the right container
        - { f:added, v:created, v:notAttached} -> attach view to container
        - { f:added, v:notCreated, v:attached } -> impossible
        - { f:added, v:notCreated, v:notAttached} -> schedule callback for when created
        - { f:notAdded, v:created, v:attached } -> illegal state
        - { f:notAdded, v:created, v:notAttached } -> illegal state
        - { f:notAdded, v:notCreated, v:attached } -> impossible
        - { f:notAdded, v:notCreated, v:notAttached } -> add, create, attach
         */

        // { f:notAdded, v:created, v:attached } -> illegal state
        // { f:notAdded, v:created, v:notAttached } -> illegal state
        
        //——————————————————————————————————————————————————————
        
        // 1.Fragment未添加到ItemView中属划,但是View已經(jīng)創(chuàng)建
        // 非法狀態(tài)
        if (!fragment.isAdded() && view != null) {
            throw new IllegalStateException("Design assumption violated.");
        }

        // 2.Fragment添加到ItemView中,但是View未創(chuàng)建
        // 先等待View創(chuàng)建完成候生,然后將View添加到Container同眯。
        if (fragment.isAdded() && view == null) {
            scheduleViewAttach(fragment, container);
            return;
        }

        // 3.Fragment添加到ItemView中,同時(shí)View已經(jīng)創(chuàng)建完成并且添加到Container中
        // 需要保證View添加到正確的Container中唯鸭。
        if (fragment.isAdded() && view.getParent() != null) {
            if (view.getParent() != container) {
                addViewToContainer(view, container);
            }
            return;
        }

        // 4.Fragment添加到ItemView中嗽测,同時(shí)View已經(jīng)創(chuàng)建完成但是未添加到Container中
        // 需要將View添加到Container中。
        if (fragment.isAdded()) {
            addViewToContainer(view, container);
            return;
        }

        // 5.Fragment未創(chuàng)建肿孵,View未創(chuàng)建唠粥、未添加
        if (!shouldDelayFragmentTransactions()) {
            scheduleViewAttach(fragment, container);
            mFragmentManager.beginTransaction().add(fragment, "f" + holder.getItemId()).commitNow();
        } else {
            //第六步
            // 調(diào)用了第5步,但是Fragment還未真正創(chuàng)建
            if (mFragmentManager.isDestroyed()) {
                return; // nothing we can do
            }
            mLifecycle.addObserver(new GenericLifecycleObserver() {
                @Override
                public void onStateChanged(@NonNull LifecycleOwner source,
                        @NonNull Lifecycle.Event event) {
                    if (shouldDelayFragmentTransactions()) {  // 7
                        return;
                    }
                    source.getLifecycle().removeObserver(this);
                    if (ViewCompat.isAttachedToWindow(holder.getContainer())) { // 8
                        placeFragmentInViewHolder(holder);
                    }
                }
            });
        }
    }
  • FragmentStateAdapter在遇到預(yù)加載時(shí)停做,只會創(chuàng)建Fragment對象晤愧,不會把Fragment真正的加入到布局中,所以自帶懶加載效果
  • FragmentStateAdapter不會一直保留Fragment實(shí)例蛉腌,回收的ItemView也會移除Fragment官份,所以得做好Fragment`重建后恢復(fù)數(shù)據(jù)的準(zhǔn)備
  • FragmentStateAdapter在遇到offscreenPageLimit>0時(shí),處理離屏Fragment和可見Fragment沒有什么區(qū)別烙丛,所以無法通過setUserVisibleHint判斷顯示與否

總結(jié)

ViewPager2本身是一個(gè)ViewGroup舅巷,只是封裝一個(gè)RecyclerView。

  • PagerSnapHelper實(shí)現(xiàn)頁面切換效果:

    • calculateDistanceToFinalSnap
    • findSnapView
    • findTargetSnapPosition
  • ScrollEventAdapter將RecyclerView的滑動事件轉(zhuǎn)換成為ViewPager2的頁面滑動事件河咽。

  • PageTransformerAdapter的作用將普通的頁面滑動事件轉(zhuǎn)換為特殊事件钠右。

  • FragmentStateAdapter中使用Adapter加載Fragment,也考慮到了ViewHolder的復(fù)用忘蟹,F(xiàn)ragment加載和remove飒房。

三、ViewPager2的使用方式

http://www.reibang.com/p/25aa5cacbfb9

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末媚值,一起剝皮案震驚了整個(gè)濱河市狠毯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌褥芒,老刑警劉巖嚼松,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異锰扶,居然都是意外死亡献酗,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門少辣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凌摄,“玉大人羡蛾,你說我怎么就攤上這事漓帅。” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵忙干,是天一觀的道長器予。 經(jīng)常有香客問我,道長捐迫,這世上最難降的妖魔是什么乾翔? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮施戴,結(jié)果婚禮上反浓,老公的妹妹穿的比我還像新娘。我一直安慰自己赞哗,他們只是感情好雷则,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著肪笋,像睡著了一般月劈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上藤乙,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天猜揪,我揣著相機(jī)與錄音,去河邊找鬼坛梁。 笑死而姐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的划咐。 我是一名探鬼主播毅人,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼尖殃!你這毒婦竟也來了丈莺?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤送丰,失蹤者是張志新(化名)和其女友劉穎缔俄,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體器躏,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡俐载,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了登失。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遏佣。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖揽浙,靈堂內(nèi)的尸體忽然破棺而出状婶,到底是詐尸還是另有隱情意敛,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布膛虫,位于F島的核電站草姻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏稍刀。R本人自食惡果不足惜撩独,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望账月。 院中可真熱鬧综膀,春花似錦、人聲如沸局齿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽项炼。三九已至担平,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锭部,已是汗流浹背暂论。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拌禾,地道東北人取胎。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像湃窍,于是被迫代替她去往敵國和親闻蛀。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容