ViewPager和其他View联动动画

  • 实现效果

    anim.gif

    图中的灰色区域都是可滑动区域,其中我用三种文本颜色表示区域中三个不同的View,黑色部分是TextView,蓝色部分是LinearLayout,红色部分是ViewPager内部的内容。

  • 技术难点

    首先要禁用ViewPager的随手指拖动,手指滑动如果抬起时没有一个fling的势头则不会触发动画效果;

    其次点击灰色区域的手势监听实现滚动的同时,不能影响ViewPager内容的点击事件,也就是点击红色区域还会触发点击事件;

    最后就是这三部分的动画是联动的。

  • 布局和代码

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:paddingTop="15dp"
        android:paddingBottom="15dp"
        android:background="@android:color/darker_gray"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >
    
        <TextView
            android:id="@+id/airport"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_marginStart="23dp"
            android:layout_height="wrap_content"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="无题"
            android:textSize="20sp"
            android:textColor="@android:color/black"
            />
    
        <LinearLayout
            android:id="@+id/ll_title"
            android:layout_marginTop="15dp"
            android:layout_marginStart="23dp"
            android:layout_marginEnd="20dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/airport"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            >
    
            <TextView
                android:layout_width="0dp"
                android:layout_weight="1"
                android:layout_height="wrap_content"
                android:text="姓名"
                android:textSize="12sp"
                android:textColor="@android:color/holo_blue_dark"
                />
    
            <TextView
                android:layout_width="0dp"
                android:layout_weight="1"
                android:layout_height="wrap_content"
                android:text="性别"
                android:textSize="12sp"
                android:textColor="@android:color/holo_blue_dark"
                />
    
            <TextView
                android:layout_width="0dp"
                android:layout_weight="1"
                android:layout_height="wrap_content"
                android:text="年龄"
                android:textSize="12sp"
                android:textColor="@android:color/holo_blue_dark"
                />
    
            <TextView
                android:layout_width="0dp"
                android:layout_weight="1"
                android:layout_height="wrap_content"
                android:text="身高"
                android:textSize="12sp"
                android:textColor="@android:color/holo_blue_dark"
                />
    
            <TextView
                android:layout_width="0dp"
                android:layout_weight="1"
                android:layout_height="wrap_content"
                android:text="臂展"
                android:textSize="12sp"
                android:textColor="@android:color/holo_blue_dark"
                />
    
        </LinearLayout>
    
        <com.mph.jetpackproj.cc_demo.view_pager.NoScrollViewPager
            android:id="@+id/flightDataPanel"
            android:layout_marginStart="23dp"
            android:layout_marginEnd="20dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/ll_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            />
    
        <LinearLayout
            android:id="@+id/indicator"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/flightDataPanel"
            app:layout_constraintEnd_toEndOf="parent"
            android:orientation="horizontal"
            android:padding="5dp"
            >
    
        </LinearLayout>
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    airport对应黑色部分View,ll_title对应蓝色部分View,flightDataPanel对应红色部分ViewPager。

    NoScrollViewPager继承自ViewPager:

    /**
     *
     * @author mph
     * @date 2020/9/24
     */
    class NoScrollViewPager(context: Context, attrs: AttributeSet?) : ViewPager(context, attrs) {
    
        private var beforeX = 0f
        private var lastX = 0f
    
        companion object {
            private const val FLING_MIN_VELOCITY = 100
        }
    
        private lateinit var mVelocityTracker: VelocityTracker
    
        /**
         * wrap_content高度
         */
        override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
            var newHeightMeasureSpec = heightMeasureSpec
            var height = 0
            for (i in 0 until childCount) {
                val child: View = getChildAt(i)
                child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED))
                val h: Int = child.measuredHeight
                if (h > height) height = h
            }
            newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
            super.onMeasure(widthMeasureSpec, newHeightMeasureSpec)
        }
    
        /**
         * 禁止滑动
         */
        override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
            when (ev.action) {
                MotionEvent.ACTION_DOWN -> {
                    mVelocityTracker = VelocityTracker.obtain()
                    mVelocityTracker.addMovement(ev)
                    beforeX = ev.x
                }
                MotionEvent.ACTION_MOVE -> {
                    mVelocityTracker.addMovement(ev)
                }
                MotionEvent.ACTION_UP -> {
                    lastX = ev.x
                    mVelocityTracker.addMovement(ev)
                    //最近500毫秒内的速度
                    mVelocityTracker.computeCurrentVelocity(500)
                    val velocityX = abs(mVelocityTracker.xVelocity)
                    mVelocityTracker.clear()
                    mVelocityTracker.recycle()
                    return when {
                        beforeX == lastX -> { // 点击事件
                            super.onInterceptTouchEvent(ev)
                        }
                        velocityX > FLING_MIN_VELOCITY -> {
                            if (beforeX > lastX) { // 向左滑动
                                listener?.onLeftFling()
                            } else if (beforeX < lastX) { // 向右滑动
                                listener?.onRightFling()
                            }
                            beforeX = 0f
                            lastX = 0f
                            true
                        }
                        else -> {
                            true
                        }
                    }
                }
            }
            return false
        }
    
        interface FlingListener {
            fun onLeftFling()
            fun onRightFling()
        }
    
        private var listener: FlingListener? = null
    
        fun setFlingListener(listener: FlingListener?) {
            this.listener = listener
        }
    
    }
    

    VelocityTracker是用来检测手势滑动速度的,在ACTION_DOWN的时候obtain,并且在每个事件中都要addMovement,最后在ACTION_UP时通过computeCurrentVelocity(500)计算最近500毫秒内的速度,也就是松开手指时的速度,然后调用mVelocityTracker.xVelocity获取x轴上的速度,这里我判断大于100则属于onFling动作beforeX 和lastX大小来判断是左滑还是右滑,最后记得clear和recycle。同时注意这里并没有消费掉点击事件。

    ViewPager部分解决了再来看整个的灰色区域的View封装:

    /**
     *
     * @author mph
     * @date 2020/9/24
     */
    class AirportBigScreen @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
    ) : LinearLayout(context, attrs, defStyleAttr), GestureDetector.OnGestureListener,
        View.OnTouchListener {
    
        companion object {
            //左滑/右滑
            private const val LEFT_SCROLL = 0
            private const val RIGHT_SCROLL = 1
    
            //动画
            private const val DELAY_TIME_DURATION = 180L
            private const val ANIM_DURATION = 200L
    
        }
    
        private var mDetector: GestureDetector = GestureDetector(context, this)
    
        private val mRootView =
            LayoutInflater.from(context).inflate(R.layout.view_airport_big_screen, this)
    
        private var mData: ArrayList<BigScreenResponse.Data.AirportDataItem> = arrayListOf()
    
        private var mViewPager: NoScrollViewPager
    
        private var mAdapter: MyPagerAdapter
    
        private var mIndicator: LinearLayout
    
        private var mAirport: TextView
    
        private var mTitlePanel: LinearLayout
    
        private var mAnimSet: AnimatorSet? = null
    
        init {
            setOnTouchListener(this)
            mViewPager = mRootView.findViewById(R.id.flightDataPanel)
            mViewPager.setFlingListener(object : NoScrollViewPager.FlingListener {
                override fun onLeftFling() {
    //                Toast.makeText(context, "向左滑", Toast.LENGTH_SHORT).show()
                    switchPager(mViewPager.currentItem + 1, LEFT_SCROLL)
                }
    
                override fun onRightFling() {
    //                Toast.makeText(context, "向右滑", Toast.LENGTH_SHORT).show()
                    switchPager(mViewPager.currentItem - 1, RIGHT_SCROLL)
                }
            })
            mAdapter = MyPagerAdapter(context)
            mViewPager.adapter = mAdapter
            mIndicator = mRootView.findViewById(R.id.indicator)
            mAirport = mRootView.findViewById(R.id.airport)
            mTitlePanel = mRootView.findViewById(R.id.ll_title)
        }
    
        fun setData(data: List<BigScreenResponse.Data.AirportDataItem>) {
            mData.clear()
            mData.addAll(data)
            mAdapter.notifyDataSetChanged()
    
            //指示标记
            initIndicator()
    
        }
    
        private fun initIndicator() {
            mIndicator.removeAllViews()
            //只有一个机场的时候不显示下面的切换提示View
            if (mData.size > 1) {
                for (i in mData?.indices ?: IntRange(0, 0)) {
                    val item = View(context)
                    val lp: LinearLayout.LayoutParams = LinearLayout.LayoutParams(
                        dip2px(context, 8f),
                        dip2px(context, 2f)
                    )
                    lp.leftMargin = dip2px(context, 2f)
                    lp.rightMargin = dip2px(context, 2f)
                    item.layoutParams = lp
                    item.setBackgroundResource(R.drawable.module_main_selector_indicator_item_back)
                    item.isSelected = false
                    mIndicator.addView(item)
                }
                if (null != mIndicator.getChildAt(mViewPager.currentItem)) {
                    mIndicator.getChildAt(mViewPager.currentItem).isSelected = true
                }
            }
    
            mAirport.text = mData[0].airportName
        }
    
        /**
         * @param index viewpager新标签下标
         * @param flag  触发事件是左滑还是右滑
         */
        private fun switchPager(index: Int, flag: Int) {
            //如果合法范围内
            if (index >= 0 && index < mData.size) {
                //触发动画
                when (flag) {
                    LEFT_SCROLL -> startAnimOnLeftScroll(index)
                    RIGHT_SCROLL -> startAnimOnRightScroll(index)
                }
            }
        }
    
        /**
         * 左滑
         */
        private fun startAnimOnLeftScroll(index: Int) {
            onLeftOut(index)
        }
    
        /**
         * 右滑
         */
        private fun startAnimOnRightScroll(index: Int) {
            onRightOut(index)
        }
    
        /**
         * 左边滑出
         */
        private fun onLeftOut(index: Int) {
            mAnimSet = AnimatorSet()
            val animator0 = ObjectAnimator.ofFloat(
                mAirport,
                "translationX",
                -(mAirport.width + mAirport.marginStart).toFloat()
            )
            val animator1 = ObjectAnimator.ofFloat(
                mTitlePanel,
                "translationX",
                -(mTitlePanel.width + mTitlePanel.marginStart).toFloat()
            )
            val animator2 = ObjectAnimator.ofFloat(
                mViewPager,
                "translationX",
                -(mViewPager.width + mViewPager.marginStart).toFloat()
            )
    
            animator1.startDelay = DELAY_TIME_DURATION
            animator2.startDelay = DELAY_TIME_DURATION
    
            mAnimSet?.duration = ANIM_DURATION
            mAnimSet?.interpolator = LinearInterpolator()
            mAnimSet?.playTogether(
                animator0,
                animator1,
                animator2
            )
            mAnimSet?.addListener(object : Animator.AnimatorListener {
                override fun onAnimationRepeat(animation: Animator?) {
                }
    
                override fun onAnimationEnd(animation: Animator?) {
                    //动画结束切换viewpager
                    mViewPager.setCurrentItem(index, false)
                    /**更新机场名字*/
                    val info: BigScreenResponse.Data.AirportDataItem = mData[mViewPager.currentItem]
                    mAirport.text = info.airportName
    
                    mAirport.translationX = getScreenWidth(context).toFloat()
                    mTitlePanel.translationX = getScreenWidth(context).toFloat()
                    mViewPager.translationX = getScreenWidth(context).toFloat()
    
                    onRightIn()
                }
    
                override fun onAnimationCancel(animation: Animator?) {
                }
    
                override fun onAnimationStart(animation: Animator?) {
                }
    
            })
            mAnimSet?.start()
        }
    
        /**
         * 右边滑入
         */
        private fun onRightIn() {
            mAnimSet = AnimatorSet()
            val animator0 = ObjectAnimator.ofFloat(
                mAirport,
                "translationX",
                mRootView.left.toFloat()
            )
            val animator1 = ObjectAnimator.ofFloat(
                mTitlePanel,
                "translationX",
                mRootView.left.toFloat()
            )
            val animator2 = ObjectAnimator.ofFloat(
                mViewPager,
                "translationX",
                mRootView.left.toFloat()
            )
    
            animator1.startDelay = 150
            animator2.startDelay = 150
    
            mAnimSet?.duration = 300
            mAnimSet?.interpolator = LinearInterpolator()
            mAnimSet?.playTogether(
                animator0,
                animator1,
                animator2
            )
            mAnimSet?.addListener(object : Animator.AnimatorListener {
                override fun onAnimationRepeat(animation: Animator?) {
                }
    
                override fun onAnimationEnd(animation: Animator?) {
                    //更新指示条
                    updateIndicators()
                }
    
                override fun onAnimationCancel(animation: Animator?) {
                }
    
                override fun onAnimationStart(animation: Animator?) {
                }
    
            })
            mAnimSet?.start()
        }
    
        /**
         * 右边滑出
         */
        private fun onRightOut(index: Int) {
            mAnimSet = AnimatorSet()
            val animator0 = ObjectAnimator.ofFloat(
                mAirport,
                "translationX",
                getScreenWidth(context).toFloat()
            )
            val animator1 = ObjectAnimator.ofFloat(
                mTitlePanel,
                "translationX",
                getScreenWidth(context).toFloat()
            )
            val animator2 = ObjectAnimator.ofFloat(
                mViewPager,
                "translationX",
                getScreenWidth(context).toFloat()
            )
    
            animator0.startDelay = DELAY_TIME_DURATION
    
            mAnimSet?.duration = ANIM_DURATION
            mAnimSet?.interpolator = LinearInterpolator()
            mAnimSet?.playTogether(
                animator0,
                animator1,
                animator2
            )
            mAnimSet?.addListener(object : Animator.AnimatorListener {
                override fun onAnimationRepeat(animation: Animator?) {
                }
    
                override fun onAnimationEnd(animation: Animator?) {
                    //动画结束切换viewpager
                    mViewPager.setCurrentItem(index, false)
                    /**更新机场名字*/
                    val info: BigScreenResponse.Data.AirportDataItem = mData[mViewPager.currentItem]
                    mAirport.text = info.airportName
    
                    mAirport.translationX = -(mAirport.width + mAirport.marginStart).toFloat()
                    mTitlePanel.translationX = -(mTitlePanel.width + mTitlePanel.marginStart).toFloat()
                    mViewPager.translationX = -(mViewPager.width + mViewPager.marginStart).toFloat()
    
                    onLeftIn()
                }
    
                override fun onAnimationCancel(animation: Animator?) {
                }
    
                override fun onAnimationStart(animation: Animator?) {
                }
    
            })
            mAnimSet?.start()
        }
    
        /**
         * 左边滑入
         */
        private fun onLeftIn() {
            mAnimSet = AnimatorSet()
            val animator0 = ObjectAnimator.ofFloat(
                mAirport,
                "translationX",
                (mRootView.left).toFloat()
            )
            val animator1 = ObjectAnimator.ofFloat(
                mTitlePanel,
                "translationX",
                (mRootView.left).toFloat()
            )
            val animator2 = ObjectAnimator.ofFloat(
                mViewPager,
                "translationX",
                (mRootView.left).toFloat()
            )
    
            animator0.startDelay = DELAY_TIME_DURATION
    
            mAnimSet?.duration = ANIM_DURATION
            mAnimSet?.interpolator = LinearInterpolator()
            mAnimSet?.playTogether(
                animator0,
                animator1,
                animator2
            )
            mAnimSet?.addListener(object : Animator.AnimatorListener {
                override fun onAnimationRepeat(animation: Animator?) {
                }
    
                override fun onAnimationEnd(animation: Animator?) {
                    //更新指示条
                    updateIndicators()
                }
    
                override fun onAnimationCancel(animation: Animator?) {
                }
    
                override fun onAnimationStart(animation: Animator?) {
                }
    
            })
            mAnimSet?.start()
        }
    
        /**
         * @desc 更新indicator
         */
        private fun updateIndicators() {
    
            //只有一个机场的时候不显示下面的切换提示View
            if (mData.size > 1) {
                for (i in mData.indices) {
                    mIndicator.getChildAt(i).isSelected = false
                }
                mIndicator.getChildAt(mViewPager.currentItem).isSelected = true
            }
        }
    
        inner class MyPagerAdapter(context: Context) : PagerAdapter() {
    
            private var mContext = context
    
            override fun isViewFromObject(view: View, `object`: Any): Boolean {
                return view == `object`
            }
    
            override fun getCount(): Int {
                return mData.size
            }
    
            @SuppressLint("InflateParams")
            override fun instantiateItem(container: ViewGroup, position: Int): Any {
                return (LayoutInflater.from(mContext)
                    .inflate(R.layout.vp_item, null) as RecyclerView).apply {
                    layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
                    addItemDecoration(DividerItemDecoration(context, RecyclerView.VERTICAL))
                    val mAdapter = AirportFlightAdapter(
                        mData[position].flightInfo,
                        R.layout.view_large_screen_adapter_item
                    )
                    adapter = mAdapter
                    container.addView(this)
                }
            }
    
            override fun getItemPosition(`object`: Any): Int {
                return POSITION_NONE
            }
    
            override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
                container.removeView(`object` as View)
            }
        }
    
        override fun onTouch(v: View?, event: MotionEvent?): Boolean {
            return mDetector.onTouchEvent(event)
        }
    
        override fun onShowPress(e: MotionEvent?) {
    
        }
    
        override fun onSingleTapUp(e: MotionEvent?): Boolean {
            return false
        }
    
        override fun onDown(e: MotionEvent?): Boolean {
            return true
        }
    
        override fun onFling(
            e1: MotionEvent?,
            e2: MotionEvent?,
            velocityX: Float,
            velocityY: Float
        ): Boolean {
            val e1RawX: Float = e1?.rawX ?: 0f
            val e2RawX: Float = e2?.rawX ?: 0f
            if (abs(e2RawX - e1RawX) > 20) {
                //往右滑
                if (e2RawX - e1RawX > 0) {
    //                Toast.makeText(context, "往右滑--x轴加速度: $velocityX", Toast.LENGTH_SHORT).show()
                    switchPager(mViewPager.currentItem - 1, RIGHT_SCROLL)
                } else if (e2RawX - e1RawX < 0) {
    //                Toast.makeText(context, "往左滑--x轴加速度: $velocityX", Toast.LENGTH_SHORT).show()
                    switchPager(mViewPager.currentItem + 1, LEFT_SCROLL)
                }
                return true
            }
            return true
        }
    
        override fun onScroll(
            e1: MotionEvent?,
            e2: MotionEvent?,
            distanceX: Float,
            distanceY: Float
        ): Boolean {
            return false
        }
    
        override fun onLongPress(e: MotionEvent?) {
        }
    
        private fun dip2px(context: Context?, dipValue: Float?): Int {
            if (context == null || dipValue == null) {
                return 0
            }
            return (context.resources.displayMetrics.density * dipValue).roundToInt()
        }
    
        fun getScreenWidth(context: Context?): Int {
            if (context == null) {
                return 0
            }
            return context.resources.displayMetrics.widthPixels
        }
    
    }
    

    mViewPager.setFlingListener来设置ViewPager在onFling时的操作,这个操作就是动画、标题切换和ViewPager显示页切换,同样在封装View本身的onFling时也要触发这一系列操作,封装View本身的手势监听我们用GestureDetector.OnGestureListener来实现,注意我们还实现了View.OnTouchListener的onTouch方法来把触摸事件交给mDetector.onTouchEvent(event)处理,这样就可以监听手势变化。

    滑动触发的操作起点就是switchPager方法,一个参数是ViewPager要切换的新tab的index,另一个是左/右滑动标志,阅读代码可知,它是有一个特定的执行顺序的,比如左滑的顺序就是,mAirport、mTitlePanel、mViewPager向左滑出屏幕,然后调用mViewPager.setCurrentItem()切换tab,同时更改mAirport的值,因为在执行右边划入的时候看到的应该是即将要显示的新的值,所以在此时滑出屏幕后不可见时要默默地切换成新值。然后在右边滑入动画前执行setTranslationX方法让这三个View都移动到屏幕右侧不可见,因为右边滑入动画是从右滑到左,所以在它之前要把起始位置修改到左边,不然只会看到一个左边滑出的reverse效果,可以看成setTranslationX操作是瞬间完成的,看不到从左到右的效果。最后执行右边滑入操作。

    最后要讲一下动画的实现:

    ObjectAnimator.ofFloat方法第一个参数是要执行动画的对象,这里显然是TextView、LinearLayout和ViewPager,第二个参数是要改变的属性,这里是translationX,最后是可变参数,可有多个值,意思是View所在当前坐标轴y轴要移动到的距离,View的位置自然会跟着y轴变化。

    左边滑出时设置的是-(mAirport.width + mAirport.marginStart).toFloat(),因为mAirport初始位置离屏幕左边缘有一个margin,所以向左移动到不可见的话除了要移动一个View的宽度还要移动一个margin的宽度,接下来setTranslationX和属性动画的设置效果一样,mAirport.translationX = getScreenWidth(context).toFloat()就是把mAirport移动到屏幕右边缘外面,最后再调用右边滑入的属性动画把View恢复到原来位置,至此整个效果就完成了。

    三个动画使用AnimatorSet来联动执行,注意mAirport的动画和另外两个View的动画是有时间差值的,左滑的时候mAirport动画先执行,右滑的时候mAirport动画延迟执行。

  • 总结和demo地址

    实现的关键就是translationX属性的属性动画,还有view.setTranslationX(float x)进行平移,他们两个都是相当于移动坐标系y轴,比如说初始位置在距屏幕左侧30像素的位置,然后translationX设置成30(即y轴移动到30的位置上),那此时的y轴就是在30像素的位置上,自然View会向右移动到距离屏幕左侧60像素的位置上,margin也会影响偏移,所以偏移的时候要考虑margin。

    demo地址:目录是JetpackLearning /app /src /main /java /com /mph /jetpackproj /cc_demo /view_pager

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