Fragment(三)ViewPager中使用Fragment

Fragment(三)ViewPager中使用Fragment

博客对应的Demo地址:GitHubGitee

通过这篇博客,我们能知道以下问题:

  • Fragment 加载到 ViewPage上的过程和用户可见性判断
  • Fragment 加载到 ViewPage2上的过程和用户可见性判断

ViewPager 中使用 Fragment

1. Fragment 怎样加载到 ViewPage

使用 ViewPager需要一个 PagerAdapter,我们如果使用 Fragment 填充 ViewPager 的话,需要使用的是 FragmentPagerAdapter 或者 FragmentStatePagerAdapter ,当然这两个 Adapter 都是继承至 PagerAdapter 的,这两个 Adapter 有什么差别,在后面我们会说到。

对于 PagerAdapter 我们主要看三个方法 instantiateItem()destroyItem()setPrimaryItem()

  • instantiateItem():创建 ViewPager 页面时调用
  • destroyItem():移除 ViewPager 页面时调用
  • setPrimaryItem()ViewPager 页面改变显示位置时调用(比如调用 adapter.setCurrentItem() 方法时)

当然还有两个方法 startUpdate()finishUpdate(),分别表示将要开始改变位置和位置改变完成时调用,代码比较简单,直接看一下。就不多说了【FragmentPagerAdapterFragmentStatePagerAdapter 中都一样】

@Override
public void startUpdate(@NonNull ViewGroup container) {
    if (container.getId() == View.NO_ID) {
        throw new IllegalStateException("ViewPager with adapter " + this
                + " requires a view id");
    }
}

@Override
public void finishUpdate(@NonNull ViewGroup container) {
    if (mCurTransaction != null) {
        if (!mExecutingFinishUpdate) {
            try {
                mExecutingFinishUpdate = true;
                mCurTransaction.commitNowAllowingStateLoss();
            } finally {
                mExecutingFinishUpdate = false;
            }
        }
        mCurTransaction = null;
    }
}

ViewPager 改变位置时的方法调用顺序

当改变 ViewPager 的位置时,方法的调用顺序为: startUpdate() -> instantiateItem()【ViewPager 中的 addNewItem() 方法内部调用】 -> destroyItem() -> setPrimaryItem() -> finishUpdate()

注意:因为 ViewPager 的缓存和预加载机制,instantiateItem()destroyItem()方法会调用多次,并且顺序并非一个调用多次之后另一个在调用多次,而是交叉的调用。

instantiateItem() 方法解析

Fragment 加载到 ViewPage 上,主要在 instantiateItem() 方法中:

  • FragmentPagerAdapter

      // FragmentPagerAdapter 类
      public Object instantiateItem(@NonNull ViewGroup container, int position) {
          if (mCurTransaction == null) {
              mCurTransaction = mFragmentManager.beginTransaction();
          }
    
          final long itemId = getItemId(position);
    
          String name = makeFragmentName(container.getId(), itemId);
          // 当前需要显示的Fragment,是否已经存在
          Fragment fragment = mFragmentManager.findFragmentByTag(name);
          if (fragment != null) {
              // 存在,调用 attach() 方法
              mCurTransaction.attach(fragment);
          } else {
              // 不存在,调用 add() 方法
              fragment = getItem(position);
              mCurTransaction.add(container.getId(), fragment,
                      makeFragmentName(container.getId(), itemId));
          }
    
          return fragment;
      }
    

    先通过 FragmentManager 判断将要显示的 Fragment 是否已经在容器中存在,如果存在调用 FragmentTransaction#attach() 方法,否则通过 getItem(position) 方法获取将要显示的 Fragment,然后调用 FragmentTransaction#add() 方法将 Fragment 添加到容器中。

  • FragmentStatePagerAdapter

      // FragmentStatePagerAdapter 类
      public Object instantiateItem(@NonNull ViewGroup container, int position) {
          if (mFragments.size() > position) {
              // 从集合中获取将要显示的 Fragment
              Fragment f = mFragments.get(position);
              if (f != null) {
                  return f;
              }
          }
    
          if (mCurTransaction == null) {
              mCurTransaction = mFragmentManager.beginTransaction();
          }
    
          // 集合中没有获取到,就调用 getItem() 方法获取Fragment
          Fragment fragment = getItem(position);
          // 状态处理
          if (mSavedState.size() > position) {
              Fragment.SavedState fss = mSavedState.get(position);
              if (fss != null) {
                  fragment.setInitialSavedState(fss);
              }
          }
          // 将当前位置之前的所有位置,添加 null 元素进行占位,保证集合中的位置和 ViewPager 中的位置一一对应
          while (mFragments.size() <= position) {
              mFragments.add(null);
          }
          // 当前位置,使用 fragment 对象替换 null 元素
          mFragments.set(position, fragment);
          // 调用 add() 方法
          mCurTransaction.add(container.getId(), fragment);
    
          return fragment;
      }
    

    首先从缓存集合中获取将要显示的 Fragmnet ,如果获取到了,就直接返回;没有获取到就通过 getItem(position) 方法获取将要显示的 Fragment,然后将其保存到缓存列表中,最后调用 FragmentTransaction#add() 方法将 Fragment 添加到容器中。

通过 instantiateItem() 这个方法,发现不管 FragmentPagerAdapter 还是 FragmentStatePagerAdapter,最终都是调用 FragmentTransaction#add() 方法将 Fragment 添加到容器中。

2. ViewPagerFragment 用户可见性判断

因为 ViewPager 的预加载机制,在 onResume() 监听是不准确的。

这时候,我们可以通过 setUserVisibleHint() 方法来监听,当方法传入值为true的时候,说明Fragment可见,为false的时候说明Fragment被切走了。但是需要注意的是,这个方法不属于生命周期方法,所以它可能在生命周期方法执行之前就执行了,也就是说,有可能执行这个方法的时候,Fragment 还没有被添加到容器中,所以需要进行判断一下。

AndroidX优化方案

在 AndroidX 当中,FragmentPagerAdapterFragmentStatePagerAdapter 的构造方法,添加一个 behavior 参数实现的。

// FragmentPagerAdapter
@Deprecated
public FragmentPagerAdapter(@NonNull FragmentManager fm) {
    this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
}
public FragmentPagerAdapter(@NonNull FragmentManager fm,
        @Behavior int behavior) {
    mFragmentManager = fm;
    mBehavior = behavior;
}

// FragmentStatePagerAdapter
@Deprecated
public FragmentStatePagerAdapter(@NonNull FragmentManager fm) {
    this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
}
public FragmentStatePagerAdapter(@NonNull FragmentManager fm,
        @Behavior int behavior) {
    mFragmentManager = fm;
    mBehavior = behavior;
}

如果我们指定不同的 behavior,会有不同的表现:

  • BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENTViewPager 中切换 Fragment,setUserVisibleHin()t 方法将不再被调用,他会确保 onResume() 的正确调用时机
  • BEHAVIOR_SET_USER_VISIBLE_HINT:跟之前的方式是一致的,我们可以通过 setUserVisibleHint() 结合 Fragment 的生命周期来监听。

具体实现

具体的实现这个功能在 instantiateItem()setPrimaryItem() 方法中,分别来看两个方法

  • instantiateItem() 方法

      // FragmentPagerAdapter#instantiateItem()
      public Object instantiateItem(@NonNull ViewGroup container, int position) {
          if (fragment != mCurrentPrimaryItem) {
              fragment.setMenuVisibility(false);
              if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                  mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
              } else {
                  fragment.setUserVisibleHint(false);
              }
          }
      }
    
      // FragmentStatePagerAdapter#instantiateItem()
      public Object instantiateItem(@NonNull ViewGroup container, int position) {
          fragment.setMenuVisibility(false);
          if (mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) {
              fragment.setUserVisibleHint(false);
          }
    
          if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
              mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
          }
      }
    

    FragmentPagerAdapterFragmentStatePagerAdapter中的 instantiateItem() 关于 behavior 的操作基本一样,如果是 mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT,调用 Fragment#setUserVisibleHint() 方法,否则调用 FragmentTransaction#setMaxLifecycle() 方法,将最大生命周期状态设置为 Lifecycle.State.STARTED ,也就是将 Fragment 的生命周期方法中兴到 onStart(),相关文章: 简书·《Fragment(二)状态改变与管理》CSDN·《Fragment(二)状态改变与管理》

  • setPrimaryItem() 方法

      // FragmentPagerAdapter/FragmentStatePagerAdapter#setPrimaryItem() 方法是一样的
      public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
          Fragment fragment = (Fragment)object;
          if (fragment != mCurrentPrimaryItem) {
              if (mCurrentPrimaryItem != null) {
                   // 当前显示Fragment
                  mCurrentPrimaryItem.setMenuVisibility(false);
                  if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                      // mBehavior 状态为 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
                      if (mCurTransaction == null) {
                          mCurTransaction = mFragmentManager.beginTransaction();
                      }
                      // 最大生命周期设置为STARTED,生命周期回退到onPause()
                      mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
                  } else {
                      // 可见性设置为false
                      mCurrentPrimaryItem.setUserVisibleHint(false);
                  }
              }
              // 将要显示的Fragment
              fragment.setMenuVisibility(true);
              if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                  // mBehavior 状态为 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
                  if (mCurTransaction == null) {
                      mCurTransaction = mFragmentManager.beginTransaction();
                  }
                  // 最大生命周期设置为RESUMED,调用 onResume() 方法
                  mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
              } else {
                  // 可见性设置为true
                  fragment.setUserVisibleHint(true);
              }
    
              mCurrentPrimaryItem = fragment;
          }
      }
    

    FragmentPagerAdapterFragmentStatePagerAdaptersetPrimaryItem() 方法是一样的,具体的步骤在代码中已有注释,就不在重复。相关文章: 简书·《Fragment(二)状态改变与管理》CSDN·《Fragment(二)状态改变与管理》

结论

  • mBehavior 设置为 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 会通过 FragmentTransaction#setMaxLifecycle() 来修改当前Fragment和将要显示的Fragment的状态,使得只有正在显示的 Fragment 执行到 onResume() 方法,其他 Fragment 只会执行到 onStart() 方法,并且当 Fragment 切换到显示时执行 onResume() 方法,切换到不显示状态时触发 onPause() 方法。

  • mBehavior 设置为 BEHAVIOR_SET_USER_VISIBLE_HINT 时,当 Frament 可见性发生变化时调用 setUserVisibleHint(),但需注意Fragment是否已经加载到容器中。

3. FragmentPagerAdapterFragmentStatePagerAdapter 的差别

FragmentPagerAdapterFragmentStatePagerAdapter 的差别,可以从这两个类的 destroyItem() 方法中查看:

// FragmentPagerAdapter
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    Fragment fragment = (Fragment) object;
    // 关键代码
    mCurTransaction.detach(fragment);
    if (fragment.equals(mCurrentPrimaryItem)) {
        mCurrentPrimaryItem = null;
    }
}

// FragmentStatePagerAdapter
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    Fragment fragment = (Fragment) object;
    // 关键代码
    mCurTransaction.remove(fragment);
    if (fragment.equals(mCurrentPrimaryItem)) {
        mCurrentPrimaryItem = null;
    }
}

从上面的代码我们可以直接的看到区别:

  • FragmentPagerAdapter 调用 FragmentTransaction#detach() 方法,只是销毁了 Fragment 的视图,但是没有移除它。具体生命周期方法过程 => onCreateView() -> onDestroyView()
  • FragmentStatePagerAdapter 调用 FragmentTransaction#remove() 方法,移除了不用的 Fragment,具体生命周期方法过程 => onAttach() -> onDetach()

ViewPager2 中使用 Fragment

ViewPager2 是基于 RecyclerView 实现的

public ViewPager2(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initialize(context, attrs);
}

 private void initialize(Context context, AttributeSet attrs) {
    mRecyclerView = new RecyclerViewImpl(context);
    attachViewToParent(mRecyclerView, 0, mRecyclerView.getLayoutParams());
}

使用 ViewPager2 加载 Fragment 也有自己特殊的 Adapter -- FragmentStateAdapter extends RecyclerView.Adapter<FragmentViewHolder>,具体的使用也很简单:

    class Vp2FragmentAdapter(
        activity: FragmentActivity,
        private val fragments: List<Vp2Fragment>
    ) :
        FragmentStateAdapter(activity) {
        override fun getItemCount(): Int {
            return fragments.size
        }

        override fun createFragment(position: Int): Fragment {
            return fragments[position]
        }
    }

重写对应方法,返回总的大小和每个 Fragment

Fragment 怎样加载到 ViewPage2

因为 FragmentStateAdapter 继承 RecyclerView.Adapter,所以我们主要看其中的 onCreateViewHolder()onBindViewHolder() 方法:

FragmentStateAdapteronCreateViewHolder() 方法

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

返回一个 FragmentViewHolder 对象,创建过程如下:

public final class FragmentViewHolder extends ViewHolder {
    private FragmentViewHolder(@NonNull FrameLayout container) {
        super(container);
    }

    @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);
    }

    @NonNull FrameLayout getContainer() {
        return (FrameLayout) itemView;
    }
}

继承 RecyclerView.ViewHolder ,内部创建一个 FrameLayout 作为每一个 item 的根布局,也就是我们最终的包含关系如下 RecyclerView -> FrameLayout -> Fragment。这个方法比较简单,我们接着看下一个方法(onBindViewHolder()

FragmentStateAdapteronBindViewHolder() 方法

public final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {
    // 1. 内部调用抽象方法 createFragment() 创建Fragment,需要子类实现
    ensureFragment(position);

    // 2. 获取Item的根布局 FrameLayout ,增加布局完成监听
    final FrameLayout container = holder.getContainer();
    if (ViewCompat.isAttachedToWindow(container)) {
        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);
                    // 调用方法
                    placeFragmentInViewHolder(holder);
                }
            }
        });
    }

    // 3. 需要从RecyclerView上移除的Fragment
    gcFragments();
}
  • 1.调用 FragmentStateAdapter#ensureFragment() 方法

      // FragmentStateAdapter#ensureFragment()
      private void ensureFragment(int position) {
          long itemId = getItemId(position);
          if (!mFragments.containsKey(itemId)) {
              Fragment newFragment = createFragment(position);
              newFragment.setInitialSavedState(mSavedStates.get(itemId));
              mFragments.put(itemId, newFragment);
          }
      }
    
      public abstract @NonNull Fragment createFragment(int position);
    
  • 2.获取Item的根布局 FrameLayout ,增加布局完成监听,调用 placeFragmentInViewHolder() 方法

      void placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder) {
          Fragment fragment = mFragments.get(holder.getItemId());
          FrameLayout container = holder.getContainer();
          View view = fragment.getView();
    
          // Fragment已经add状态,直接将Fragment的View增加到Item上的FrameLayout上显示
          if (fragment.isAdded() && view.getParent() != null) {
              if (view.getParent() != container) {
                  addViewToContainer(view, container);
              }
              return;
          }
          if (fragment.isAdded()) {
              addViewToContainer(view, container);
              return;
          }
    
          // 如果Fragment不是 add 状态,那么就通过 FragmentManager 添加 Fragment,设置最大生命周期值为 STARTED
          if (!shouldDelayFragmentTransactions()) {
              scheduleViewAttach(fragment, container);
              mFragmentManager.beginTransaction()
                      .add(fragment, "f" + holder.getItemId())
                      .setMaxLifecycle(fragment, STARTED)
                      .commitNow();
              mFragmentMaxLifecycleEnforcer.updateFragmentMaxLifecycle(false);
          } else {
              // 延迟递归调用
              if (mFragmentManager.isDestroyed()) {
                  return; // nothing we can do
              }
              mLifecycle.addObserver(new LifecycleEventObserver() {
                  @Override
                  public void onStateChanged(@NonNull LifecycleOwner source,
                          @NonNull Lifecycle.Event event) {
                      if (shouldDelayFragmentTransactions()) {
                          return;
                      }
                      source.getLifecycle().removeObserver(this);
                      if (ViewCompat.isAttachedToWindow(holder.getContainer())) {
                          // 延迟递归调用
                          placeFragmentInViewHolder(holder);
                      }
                  }
              });
          }
      }
    

    通过以上两步,我们的Fragment就已经加载和显示到 ViewPager2 上了。

  • 3.需要从RecyclerView上移除的Fragment -> gcFragments()

      void gcFragments() {
          // 查询需要移除的 `Fragment`
          Set<Long> toRemove = new ArraySet<>();
          for (int ix = 0; ix < mFragments.size(); ix++) {
              long itemId = mFragments.keyAt(ix);
              if (!containsItem(itemId)) {
                  toRemove.add(itemId);
                  mItemIdToViewHolder.remove(itemId); // in case they're still bound
              }
          }
          if (!mIsInGracePeriod) {
              mHasStaleFragments = false; // we've executed all GC checks
    
              for (int ix = 0; ix < mFragments.size(); ix++) {
                  long itemId = mFragments.keyAt(ix);
                  if (!isFragmentViewBound(itemId)) {
                      toRemove.add(itemId);
                  }
              }
          }
    
          // 遍历移除Fragment
          for (Long itemId : toRemove) {
              // 调用方法,根据id移除对应的Fragment
              removeFragment(itemId);
          }
      }
    
      private void removeFragment(long itemId) {
          Fragment fragment = mFragments.get(itemId);
          // 移除Fragment
          mFragmentManager.beginTransaction().remove(fragment).commitNow();
          // 从缓存数据集中移除
          mFragments.remove(itemId);
      }
    

FragmentViewPage2上用户可见性

我们查看源码,发现回调方法 onAttachedToRecyclerView() 中有相关的代码:

@CallSuper
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
    checkArgument(mFragmentMaxLifecycleEnforcer == null);
    mFragmentMaxLifecycleEnforcer = new FragmentMaxLifecycleEnforcer();
    mFragmentMaxLifecycleEnforcer.register(recyclerView);
}

接着看 FragmentMaxLifecycleEnforcer#register() 的实现:

void register(@NonNull RecyclerView recyclerView) {
        mViewPager = inferViewPager(recyclerView);
        // 页面改变监听,回调updateFragmentMaxLifecycle()方法
        mPageChangeCallback = new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageScrollStateChanged(int state) {
                updateFragmentMaxLifecycle(false);
            }

            @Override
            public void onPageSelected(int position) {
                updateFragmentMaxLifecycle(false);
            }
        };
        mViewPager.registerOnPageChangeCallback(mPageChangeCallback);
    }

继续查看 updateFragmentMaxLifecycle() 方法:

void updateFragmentMaxLifecycle(boolean dataSetChanged) {

        mPrimaryItemId = currentItemId;
        FragmentTransaction transaction = mFragmentManager.beginTransaction();

        Fragment toResume = null;
        // 遍历所有缓存的Fragment
        for (int ix = 0; ix < mFragments.size(); ix++) {
            long itemId = mFragments.keyAt(ix);
            Fragment fragment = mFragments.valueAt(ix);
            if (!fragment.isAdded()) {
                continue;
            }

            // 判断是否Fragment是滑出隐藏还是滑入显示
            if (itemId != mPrimaryItemId) {
                // 滑出的设置最大生命周期为 STARTED(由 RESUMED 变为 STARTED),也就是回调 onPause() 方法
                transaction.setMaxLifecycle(fragment, STARTED);
            } else {
                // 需要显示的 Fragment
                toResume = fragment; // itemId map key, so only one can match the predicate
            }

            fragment.setMenuVisibility(itemId == mPrimaryItemId);
        }
        // 如果有需要显示的,将其生命周期状态设置为 RESUMED,调用 onResume() 方法
        if (toResume != null) { // in case the Fragment wasn't added yet
            transaction.setMaxLifecycle(toResume, RESUMED);
        }

        // 提交修改
        if (!transaction.isEmpty()) {
            transaction.commitNow();
        }
    }

通过以上代码,我们知道了,在 ViewPager2 中使用 Fragment ,它的生命周期方法是正常的,只有正在显示的 Fragment 执行到 onResume() 方法,其他 Fragment 只会执行到 onStart() 方法,并且当 Fragment 切换到显示时执行 onResume() 方法,切换到不显示状态时触发 onPause() 方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容