1.仿饭局狼人杀底部动画导航栏

首先看看效果图
ezgif.com-video-to-gif.gif

以前做这样的导航栏控件使用继承View实现的,导航个数写死例如5个,不断的计算变化前后的各个区域的Rect,重写onTouchEvent,根据点击点不同进行重新绘制,以前能实现的效果,只是图片的上移和长图的动态移动,动画效果并不理想。在使用的过程中,如果要增加导航图片数量,计算很麻烦。因此一直想抽空,改一改这个自定义控件。

上面效果图用的方法是:1.继承自ViewGroup,2.利用属性动画。
首先分析实现原理:从效果图可以看出基本的图片包括导航图片(选中/未选中)、整体背景图片、选中的覆盖图片、各图标之间的分割图片,具体如下。


icon_home_game_normal.png
icon_home_game_pressed.png
img_com_tab_bg.png
img_com_tab_pressed_new_x.png
img_home_bottom_item_line.png

我进行这样自定义控件设置的时候,第一步首先把背景、分割图片、选中后的覆盖图片绘制出来。因此先自定义这三个属性 attrs.xml文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <declare-styleable name="BottomWolfKillNavigation">
      <attr name="backgroundimage" format="reference"></attr>
      <attr name="frontimage" format="reference"></attr>
      <attr name="cutimage" format="reference"></attr>
  </declare-styleable>
</resources>

上面的属性值分别代表背景、选中的覆盖图、分割图。
在测试的布局文件中需要设定这三个属性值

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:background="@color/blue"
   >
<LinearLayout
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="vertical">
       <Button
           android:layout_width="match_parent"
           android:layout_height="80dp"
           android:id="@+id/btn2"
           android:text="2个图标"/>
       <Button
           android:layout_width="match_parent"
           android:layout_height="80dp"
           android:id="@+id/btn3"
           android:text="3个图标"/>
       <Button
           android:layout_width="match_parent"
           android:layout_height="80dp"
           android:id="@+id/btn4"
           android:text="4个图标"/>
       <Button
           android:layout_width="match_parent"
           android:layout_height="80dp"
           android:id="@+id/btn5"
           android:text="5个图标"/>
</LinearLayout>



<com.xsl.widget.navigation.BottomWolfKillNavigation
   android:id="@+id/nav"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:layout_alignParentBottom="true"
   app:backgroundimage="@drawable/img_com_tab_bg"
   app:frontimage="@drawable/img_com_tab_pressed_new_x"
   app:cutimage="@drawable/img_home_bottom_item_line"
   ></com.xsl.widget.navigation.BottomWolfKillNavigation>
</RelativeLayout>

在自定义的ViewGroup中绘制

public class BottomWolfKillNavigation extends ViewGroup implements View.OnClickListener{

    //导航栏背景图片
    private int backgroundImageRes;
    //导航栏选择后的前图
    private int frontImageRes;
    //导航栏的分割图
    private int cutImageRes;

    private Paint mPaint;

  public BottomWolfKillNavigation(Context context) {
        this(context,null);
    }

    public BottomWolfKillNavigation(Context context, AttributeSet attrs) {
        //super(context, attrs);
        this(context,attrs,0);

    }

    public BottomWolfKillNavigation(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
        parseAttrs(context, attrs);

        initView();
    }
//获取参数值
    private void parseAttrs(Context context,AttributeSet attrs){

        if (attrs!=null){
            TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.BottomWolfKillNavigation,0,0);
            backgroundImageRes = typedArray.getResourceId(R.styleable.BottomWolfKillNavigation_backgroundimage,0);
            frontImageRes = typedArray.getResourceId(R.styleable.BottomWolfKillNavigation_frontimage,0);
            cutImageRes = typedArray.getResourceId(R.styleable.BottomWolfKillNavigation_cutimage,0);
            typedArray.recycle();
        }
    }

    private void initView(){
        mPaint = new Paint();
        mPaint.setAntiAlias(false);
//ViewGroup默认不会调用onDraw方法设置背景色后会调用
        setBackgroundColor(Color.TRANSPARENT);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setColor(Color.TRANSPARENT);
        Rect transRect = new Rect(0,0,ScreenUtils.getScreenWidth(),SizeUtils.dp2px(20));
        canvas.drawRect(transRect,mPaint);
        mPaint.reset();
        Rect backgroundRect = new Rect(0,SizeUtils.dp2px(20),ScreenUtils.getScreenWidth(),SizeUtils.dp2px(80));
        Bitmap background = BitmapFactory.decodeResource(getResources(),backgroundImageRes);
        canvas.drawBitmap(background,null,backgroundRect,mPaint);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        //设置导航栏的宽度、高度
        setMeasuredDimension(ScreenUtils.getScreenWidth(), SizeUtils.dp2px(80));
        //需要适配虚拟的导航按键
        if (ScreenUtils.isPortrait()){

        }else{

        }
    }

}

通过以上可以画出背景,上面用到的获取屏幕宽度的工具类是在项目中引用第三方工具类implementation "com.blankj:utilcode:1.12.5"。链接地址
http://www.apkbus.com/blog-901770-76998.html
为了能够设置每个导航控件的定义一个Item存选中图片与未选中图标

public class BottomImageItem {
    private int unSelectedImageRes;//没有选择的图片
    private int selectedImageRes;//选择后的图片

    public BottomImageItem(@DrawableRes int unSelectedImageRes, @DrawableRes int selectedImageRes) {
        this.unSelectedImageRes = unSelectedImageRes;
        this.selectedImageRes = selectedImageRes;
    }

    public int getUnSelectedImageRes() {
        return unSelectedImageRes;
    }

    public void setUnSelectedImageRes(int unSelectedImageRes) {
        this.unSelectedImageRes = unSelectedImageRes;
    }

    public int getSelectedImageRes() {
        return selectedImageRes;
    }

    public void setSelectedImageRes(int selectedImageRes) {
        this.selectedImageRes = selectedImageRes;
    }
}

自定义控件的重写方法的顺序 onMeasure onLayout onDraw
onMeasure中已经指定宽高,高度为80dp,其中0-20dp透明,20-80dp是背景图。
为了能够动态设置导航图标的数量,需要有个集合还管理导航图标

//存导航栏每个Item
    private ArrayList<BottomImageItem> bottomImageItemsList = new ArrayList<>();
   //存分割线
    private ArrayList<ImageView> cutImageViewList = new ArrayList<>();
    //存导航图标
    private ArrayList<ImageView> iconImageViewList = new ArrayList<>();

//暴露给外界的接口,用于添加导航图片
    public BottomWolfKillNavigation addBottomImageItem(BottomImageItem item){
        bottomImageItemsList.add(item);
        ImageView cutImage = new ImageView(context);
        this.addView(cutImage);
        if (iconImageViewList.size()>0){
            cutImageViewList.add(cutImage);
            Log.i("xsl","cutlinesize="+cutImageViewList.size());
        }

        ImageView imageView =  new ImageView(context);
//为了能设置监听获取点击的是哪个view
        imageView.setTag(bottomImageItemsList.size()-1);
        iconImageViewList.add(imageView);
        this.addView(imageView);
        return this;
    }

还需要暴露给外界设置该ViewGroup各个孩子的方法

    public void initialise() {

        itemSize = bottomImageItemsList.size();
        //导航栏图片数量+分割线数量+前面选择图片
        //分割线数量等于导航栏图片数量-1
        //前面选择图片数量等于1
        screenWidth = ScreenUtils.getScreenWidth();
        cutWidth = SizeUtils.dp2px(2);

        //除去分割线的宽度
        contentWidth = screenWidth-cutWidth*(itemSize-1);
        //平均每个导航栏的宽度
        everyWidth = (int)(contentWidth/(itemSize+0.5f));
        //被选中的导航栏的宽度
        longWidth = contentWidth-everyWidth*(itemSize-1);
        top = SizeUtils.dp2px(20);
        bottom = SizeUtils.dp2px(80);
        bigImageHalfWidth = SizeUtils.dp2px(30);
        smallImageHalfWidth = SizeUtils.dp2px(25);
        cutMoveLength = longWidth-everyWidth;

        Log.i("xsl","原始计算值"+longWidth+","+everyWidth);
        //drawContent();
        for(int i=0;i<getChildCount();i++){
            if (i!=0){
                if (i%2==0){

                    ((ImageView)getChildAt(i)).setImageResource(cutImageRes);
                    ((ImageView)getChildAt(i)).setScaleType(ImageView.ScaleType.FIT_XY);
                }else{


                    if (i==1){
                        ((ImageView)getChildAt(i)).setImageResource(bottomImageItemsList.get(i/2).getSelectedImageRes());
                    }else{
                        ((ImageView)getChildAt(i)).setImageResource(bottomImageItemsList.get(i/2).getUnSelectedImageRes());
                    }
//设置了图标的监听
                    (getChildAt(i)).setOnClickListener(this);
                    ((ImageView)getChildAt(i)).setScaleType(ImageView.ScaleType.FIT_XY);

                }
            }else{
                frontImageView = (ImageView) getChildAt(i);
                ((ImageView)getChildAt(i)).setImageResource(frontImageRes);
                ((ImageView)getChildAt(i)).setScaleType(ImageView.ScaleType.FIT_XY);
            }
        }
    }

经过上面两个方法其实,我进行设置绘制child的顺序是选中的背景图片、导航图片、分割图片、导航图片、分割图片……一开始我是把选中的背景图片放在最后绘制,结果图标被覆盖,色调变了。
其实onMeasure之后就应该讲onLayout,先贴源码

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        for (int i=0;i<itemSize*2;i++){
            View view = getChildAt(i);
            //单数是图片,双数是分割符,0是选中的前景图片
            if(i==0){
                view.layout(0,top,longWidth,bottom);
            }else{
                if (i%2==0){
                    //分割线
                    view.layout(longWidth + ((i-1)/ 2) * (everyWidth + cutWidth), top, longWidth + ((i -1)/ 2) * (everyWidth + cutWidth) + cutWidth, bottom);
                }else{
                    //导航图片
                    int position =(i-1)/2;
                    int left = 0;
                    int right = 0;
                    if(position==0) {
                        left = 0;
                        right = longWidth;
                    }else{
                        left = longWidth+cutWidth*position+everyWidth*(position-1);
                        right = longWidth+cutWidth*position+everyWidth*(position);
                    }
                    int center = (left+right)/2;
                    if(position==0){
                        view.layout(center-bigImageHalfWidth,0,center+bigImageHalfWidth,bigImageHalfWidth*2);
//                        view.layout(center-smallImageHalfWidth,SizeUtils.dp2px(25),center+smallImageHalfWidth,SizeUtils.dp2px(25)+smallImageHalfWidth*2);
                    }else{
                        view.layout(center-smallImageHalfWidth,SizeUtils.dp2px(25),center+smallImageHalfWidth,SizeUtils.dp2px(25)+smallImageHalfWidth*2);
                    }
                }
            }
        }

    }

目前的onlayout是按第0个位置被选中进行布局的。
下面的任务就是要在点击图标(给孩子设置监听)的时候让所有的组件按想要的方式动起来:
1.选中的背景图片移动
2.分割图片的移动
3.导航图片的缩放及移动

    @Override
    public void onClick(View v) {
        selectPosition = (Integer) v.getTag();
        if (selectPosition!=oldPosition){    
        iconImageViewList.get(oldPosition).setImageResource(bottomImageItemsList.get(oldPosition).getUnSelectedImageRes());
            //前景选中图片的动画
            frontImageAnimation();
        }
    }

前景选中图片的动画函数

    private void frontImageAnimation(){
        float begin = 0f;
        float length = 0f;
        float end = 0f;
        //总共移动距离
        length = (selectPosition-oldPosition)*(everyWidth+cutWidth);
        //左边的开始点
        begin = (oldPosition)*(everyWidth+cutWidth);
        end = begin+length;
        final ObjectAnimator animator = ObjectAnimator.ofFloat(frontImageView,"translationX",begin,end);
        animator.setDuration(500);
        animator.start();
        //设置动画监听移动的同时,分割图片、导航图片要变化
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
//动画开始后要进行分割图片的动画下面有代码
//导航图片的动画

}
            @Override
            public void onAnimationEnd(Animator animation) {
//动画结束
                oldPosition = selectPosition;
                animator.removeAllListeners();

  //设置选中的图标  
 iconImageViewList.get(selectPosition).setImageResource(bottomImageItemsList.get(selectPosition).getSelectedImageRes());

            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
});

}

那么在前景选中图片移动的同时,分割图片也要移动这里要注意到跨距离点击,分割图片是一个一个移动的。具体代码如下:

//用于分割线动画
                 AnimatorSet animatorSet = new AnimatorSet();
                 animatorSet.setDuration(500/(Math.abs(selectPosition-oldPosition)));


                List<Animator> animatorList = new ArrayList<>();

                 if (oldPosition<selectPosition){
                     for (int i=oldPosition;i<selectPosition;i++){
                         View x = cutImageViewList.get(i);
                         ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(x,"translationX",
                                 0,-cutMoveLength);
                         animatorList.add(objectAnimator);


                     }
                     animatorSet.playSequentially(animatorList);
                     animatorSet.start();
                     //点击右边的,图片左移动
                     iconAnimationToRight(oldPosition,selectPosition);


                 }else{
                     for (int i=oldPosition;i>selectPosition;i--){
                         View x = cutImageViewList.get(i-1);
                         ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(x,"translationX",
                                 -cutMoveLength,0);
                         animatorList.add(objectAnimator);
                     }
                     animatorSet.playSequentially(animatorList);
                     animatorSet.start();
                     //这是图标缩放移动的函数
                     iconAnimationToLeft(oldPosition,selectPosition);
                 }

这里要注意设置的时间

animatorSet.setDuration(500/(Math.abs(selectPosition-oldPosition)));

启动动画的方式

animatorSet.playSequentially(animatorList);

还有重要的两个函数就是图标的移动和缩放

 iconAnimationToRight(oldPosition,selectPosition);
 iconAnimationToLeft(oldPosition,selectPosition);

我在分析缩放平移的时候首先是分析两个相邻的之间的缩放
一个图片的动画就包括XY缩放XY平移因此用AnimatorSet.playTogether方法实现,关键跨距离时,中间的经过的各个导航图标都要缩放,因此同样需要设置某个动画监听,结束动画后,继续回调该方法,具体源码

private void iconAnimationToRight(int i, int selectPosition){
                List<Animator> animatorListSmallScale = new ArrayList<>();
                //用于图片平移缩放动画
                AnimatorSet animatorSetSmallScale = new AnimatorSet();
                animatorSetSmallScale.setDuration(500/(Math.abs(selectPosition-oldPosition)));
                ObjectAnimator objectAnimator1 = null;
                if (i==0) {
                    objectAnimator1 = ObjectAnimator.ofFloat(iconImageViewList.get(i),
                            "scaleX",
                            1f, 1f/1.2f);
                    ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(iconImageViewList.get(i),
                            "scaleY",
                            1f, 1f/1.2f);
                    ObjectAnimator objectAnimator3 = ObjectAnimator.ofFloat(iconImageViewList.get(i),
                            "translationX",
                            0, -((longWidth - everyWidth) / 2));
                    ObjectAnimator objectAnimator4 = ObjectAnimator.ofFloat(iconImageViewList.get(i),
                            "translationY",
                            0,SizeUtils.dp2px(18));

                    ObjectAnimator objectAnimator5 = ObjectAnimator.ofFloat(iconImageViewList.get(i + 1),
                            "scaleX",
                            1f, 1.2f);
                    ObjectAnimator objectAnimator6 = ObjectAnimator.ofFloat(iconImageViewList.get(i + 1),
                            "scaleY",
                            1f, 1.2f);
                    ObjectAnimator objectAnimator7 = ObjectAnimator.ofFloat(iconImageViewList.get(i + 1),
                            "translationX",
                            0, -((longWidth - everyWidth) / 2));
                    ObjectAnimator objectAnimator8 = ObjectAnimator.ofFloat(iconImageViewList.get(i + 1),
                            "translationY",
                            0, -SizeUtils.dp2px(15));
                    animatorListSmallScale.add(objectAnimator1);
                    animatorListSmallScale.add(objectAnimator2);
                    animatorListSmallScale.add(objectAnimator3);
                    animatorListSmallScale.add(objectAnimator4);
                    animatorListSmallScale.add(objectAnimator5);
                    animatorListSmallScale.add(objectAnimator6);
                    animatorListSmallScale.add(objectAnimator7);
                    animatorListSmallScale.add(objectAnimator8);
                }else{
                    objectAnimator1 = ObjectAnimator.ofFloat(iconImageViewList.get(i),
                            "scaleX",
                            1.2f, 1f);
                    ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(iconImageViewList.get(i),
                            "scaleY",
                            1.2f, 1f);
                    ObjectAnimator objectAnimator3 = ObjectAnimator.ofFloat(iconImageViewList.get(i),
                            "translationX",
                            -((longWidth - everyWidth) / 2),-(longWidth - everyWidth));
                    ObjectAnimator objectAnimator4 = ObjectAnimator.ofFloat(iconImageViewList.get(i),
                            "translationY",
                            -SizeUtils.dp2px(15),0);

                    ObjectAnimator objectAnimator5 = ObjectAnimator.ofFloat(iconImageViewList.get(i + 1),
                            "scaleX",
                            1f, 1.2f);
                    ObjectAnimator objectAnimator6 = ObjectAnimator.ofFloat(iconImageViewList.get(i + 1),
                            "scaleY",
                            1f, 1.2f);
                    ObjectAnimator objectAnimator7 = ObjectAnimator.ofFloat(iconImageViewList.get(i + 1),
                            "translationX",
                            0, -((longWidth - everyWidth) / 2));
                    ObjectAnimator objectAnimator8 = ObjectAnimator.ofFloat(iconImageViewList.get(i + 1),
                            "translationY",
                            0, -SizeUtils.dp2px(15));
                    animatorListSmallScale.add(objectAnimator1);
                    animatorListSmallScale.add(objectAnimator2);
                    animatorListSmallScale.add(objectAnimator3);
                    animatorListSmallScale.add(objectAnimator4);
                    animatorListSmallScale.add(objectAnimator5);
                    animatorListSmallScale.add(objectAnimator6);
                    animatorListSmallScale.add(objectAnimator7);
                    animatorListSmallScale.add(objectAnimator8);
                }
                animatorSetSmallScale.playTogether(animatorListSmallScale);
                animatorSetSmallScale.start();

                i++;
                final int x=i;
                final int selectflag = selectPosition;

                objectAnimator1.addListener(new Animator.AnimatorListener() {
                    @Override
                    public void onAnimationStart(Animator animation) {

                    }

                    @Override
                    public void onAnimationEnd(Animator animation) {
                        if (x<selectflag){
                            iconAnimationToRight(x,selectflag);
                        }
                        //objectAnimator1.removeAllListeners();
                    }

                    @Override
                    public void onAnimationCancel(Animator animation) {

                    }

                    @Override
                    public void onAnimationRepeat(Animator animation) {

                    }
                });

            }

点击选中的左边同样的逻辑
为了能测试动态改变图标数量还需要暴露接口

    public void clear(){
        cutImageViewList.clear();
        iconImageViewList.clear();
        bottomImageItemsList.clear();
        oldPosition=selectPosition=0;
        removeAllViews();
    }

最后贴出测试代码

public class WolfActivity extends Activity implements View.OnClickListener{

    private BottomWolfKillNavigation navigation;

    private Button bt2;
    private Button bt3;
    private Button bt4;
    private Button bt5;

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn2:
                navigation.clear();
                navigation.addBottomImageItem(new BottomImageItem(R.drawable.icon_home_club_normal,R.drawable.icon_home_club_pressed))
                        .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_game_normal,R.drawable.icon_home_game_pressed))
                        .initialise();
                break;
            case R.id.btn3:
                navigation.clear();
                navigation.addBottomImageItem(new BottomImageItem(R.drawable.icon_home_club_normal,R.drawable.icon_home_club_pressed))
                        .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_game_normal,R.drawable.icon_home_game_pressed))
                        .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_shop_normal,R.drawable.icon_home_shop_pressed))
                        .initialise();
                break;
            case R.id.btn4:
                navigation.clear();
                navigation.addBottomImageItem(new BottomImageItem(R.drawable.icon_home_club_normal,R.drawable.icon_home_club_pressed))
                        .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_game_normal,R.drawable.icon_home_game_pressed))
                        .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_shop_normal,R.drawable.icon_home_shop_pressed))
                        .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_social_normal,R.drawable.icon_home_social_pressed))
                        .initialise();
                break;
            case R.id.btn5:
                navigation.clear();
                navigation.addBottomImageItem(new BottomImageItem(R.drawable.icon_home_club_normal,R.drawable.icon_home_club_pressed))
                        .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_game_normal,R.drawable.icon_home_game_pressed))
                        .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_shop_normal,R.drawable.icon_home_shop_pressed))
                        .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_social_normal,R.drawable.icon_home_social_pressed))
                        .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_watch_normal,R.drawable.icon_home_watch_pressed))
                        .initialise();
                break;
        }
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Utils.init(getApplication());
        setContentView(R.layout.activity_wolf);

        navigation = (BottomWolfKillNavigation) findViewById(R.id.nav);
        navigation.addBottomImageItem(new BottomImageItem(R.drawable.icon_home_club_normal,R.drawable.icon_home_club_pressed))
                .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_game_normal,R.drawable.icon_home_game_pressed))
                .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_shop_normal,R.drawable.icon_home_shop_pressed))
                .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_social_normal,R.drawable.icon_home_social_pressed))
                .addBottomImageItem(new BottomImageItem(R.drawable.icon_home_watch_normal,R.drawable.icon_home_watch_pressed))
                .initialise();

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

推荐阅读更多精彩内容