工作交接

**(一)首页的整体结构 **

整体采用ViewPager + Fragment的结构,共有“首页”,“比赛大厅”,"资讯",“24小时”,“分类”,“原创”,“会员” 七个fragment。

首页.pic.jpg

MainActivity开发中注意事项总结如下:

  • 设置ViewPager的facusable属性为false,因为如果ViewPager先获取焦点的话,那么Fragment中的view将有可能不能获取焦点了。

  • ViewPager预加载的前后4个Fragment,目的是加载更多的页面,用户如果在一级导航来回快速切换的话,由于预加载的Fragment比较多,那么ViewPager将不会那么频繁地回收Fragment,自然将不会给用户产生卡顿的感觉

this.mViewPager.setFocusable(false);
this.mViewPager.setOffscreenPageLimit(PRELOAD_PAGE);
  • 由于24小时(原轮播台)带有一个播放器,如果快速切换一级导航的话,将会导致播放器不停的播放和停止播放。有时还会导致ANR的情况发生,所以我们在ViewPager的onPageSelected方法中,做了一个延时切换到特定Fragment的操作,这样可以有效避免卡顿和ANR的发生。
 /**
     * 处理头部导航Fragment
     *
     * @param tabIndex
     */
    private void switchHeadChannel(int tabIndex) {
        mJumpType = tabIndex;
        mTabs[tabIndex].requestFocus();
        mNavigation.setViewFocusable(mJumpType == (PAGE_COUNT - 1));
        for (int i = PAGE_COUNT - 1; i >= 0; i--) {
            mTabs[i].setSelected(i == mJumpType);
        }
        if (mHandler.hasMessages(MSG_UPDATE_ITEM)) {
            mHandler.removeMessages(MSG_UPDATE_ITEM);
        }
        mHandler.sendEmptyMessageDelayed(MSG_UPDATE_ITEM, 100);
    }
  • 一级导航来回切换的时候
    (1)Fragment被回收时的生命周期方法执行顺序是
    onPause -> onStop -> onDestroyView
    注意:并没有执行onDestroy方法,所以内存回收,请求取消的一些操作要放在 onDestroyView中进行。

(2)Fragment被回收后,又被恢复时,它的生命周期方法执行顺序是
onCreateView ->onActivityCreated -> onStart ->onResume

  **注意:Fragment恢复时,并没有回调onCreate方法,Fragment恢复时   的一些操作可以放到onCreateView中进行**

下面用CompetitionHallFragment的onDestroyView方法做个示范:

 @Override
    public void onDestroyView() {
        cancelHallRequest();
        destroyMenuParams();
        if (mGridView != null) {
            mGridView.removeCallbacks(mPostRunnable);
        }
        super.onDestroyView();
        mLogger.d(TAG, "==onDestroyView==");
    }
  • MainActivity的启动模式是singleTask,当Activity重复开启时,会回调onNewIntent方法。在做打洞的时候要格外注意这个细节。
 /**
     * activity已经存在的时候调用startActivity()方法时触发
     *
     * @param intent
     */
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        mLogger.d("=== onNewIntent===");
        if (null != intent) {
            int lastJumpType = mJumpType;
            mJumpType = intent.getIntExtra("JumpType", GlobalConstant.INDEX_HOME);
            isFromOutApp = intent.getBooleanExtra("isFromOutApp", false);

            mLogger.i("lastJumpType == " + lastJumpType);
            mLogger.i("mJumpType == " + mJumpType);
            mLogger.i("isFromOutApp value = " + String.valueOf(isFromOutApp));
            /**
             如果Activity存在,而又要打开的时候,如果当前tab不是新闻,
             要显示一级导航,并让新闻切换成小屏幕
             */
            if (mJumpType != GlobalConstant.INDEX_CAROUSEL
                    && lastJumpType == GlobalConstant.INDEX_CAROUSEL && mAdapter != null
                    && mAdapter.getItem(GlobalConstant.INDEX_CAROUSEL) != null
                    && mAdapter.getItem(GlobalConstant.INDEX_CAROUSEL).isAdded()) {
                CarouselFragment carouselFragment = ((CarouselFragment) mAdapter.getItem(GlobalConstant.INDEX_CAROUSEL));
                if (carouselFragment.isFullScreen()) {
                    carouselFragment.switchFullScreen(false);
                }
            }
            if (Math.abs(lastJumpType - mJumpType) > PRELOAD_PAGE) {
                mLogger.i("current tab is destroy");

                if (getIntent() != null) {
                    getIntent().getIntExtra("selectedPosition", intent.getIntExtra("selectedPosition", 1));
                }
                setGotoHallTab(true);
                focusCurrentSelectTab();
            } else {
                mLogger.i("current tab is not destroy");
                focusTabByIndex(mJumpType, SearchParams.STATUS_TYPE_ALL);
            }
        }
    }


**(二)比赛大厅(大陆) **###

  • **菜单列表的开发注意事项 **

    (1)请求菜单列表前,我会默认添加“全部”、“更多”这两个按钮

  • 接口请求到菜单数据后,我会把接口请求的菜单项添加进来。

  • 如果接口请求失败,或者没有数据为空,那么页面只会显示“全部”和更多两个菜单选项。

这样可以做的优点是:请求比赛接口和请求菜单接口单独同时请求,而不用等菜单接口请求完了后,再去请求比赛接口。

 /**
     * 添加搜索"全部"的频道,默认值为0
     */
    public void addDefaultChannel(ArrayList<ChannelModel> allChannelList) {
        if (allChannelList != null && allChannelList.size() > 0) {
            /**(1)过滤出推荐的数据 **/
            mRecommendChannelList = new ArrayList<ChannelModel>();
            for (ChannelModel model : allChannelList) {
                if (model.isRecommend()) {
                    mRecommendChannelList.add(model);
                }
            }

            /**(2)添加"全部项目"菜单**/
            ChannelModel allGameChannel = new ChannelModel();
            allGameChannel.setResourceId(SearchParams.ALL_DAY_TYPE);//默认值为0
            allGameChannel.setName(getResources().getString(R.string.all_game));
            mRecommendChannelList.add(0, allGameChannel);
            /**(3) 添加"推荐"菜单**/
          /*  ChannelModel recommendChanel = new ChannelModel();
            recommendChanel.setResourceId(RECOMMEND_ID);//默认值为1
            recommendChanel.setName(getResources().getString(R.string.hall_game_recommend));
            mRecommendChannelList.add(0, recommendChanel);*/


            /**(4) 添加"更多项目"按钮,跳转到菜单页**/
            ChannelModel lastChannel = new ChannelModel();
            lastChannel.setResourceId(MORE_CHANNEL_ID);
            lastChannel.setName(getResources().getString(R.string.more_game));
            mRecommendChannelList.add(lastChannel);
        } else {
            mRecommendChannelList = new ArrayList<ChannelModel>();
            /**(2)添加"全部项目"菜单**/
            ChannelModel allGameChannel = new ChannelModel();
            allGameChannel.setResourceId(SearchParams.ALL_DAY_TYPE);//默认值为0
            allGameChannel.setName(getResources().getString(R.string.all_game));
            mRecommendChannelList.add(0, allGameChannel);
            /**(3) 添加"推荐"菜单**/
          /*  ChannelModel recommendChanel = new ChannelModel();
            recommendChanel.setResourceId(RECOMMEND_ID);//默认值为1
            recommendChanel.setName(getResources().getString(R.string.hall_game_recommend));
            mRecommendChannelList.add(0, recommendChanel);*/
        }
    }

(2)菜单的点击事件处理,我分给给“全部”,“一般推荐的菜单”,“更多” 这三种类型菜单,设置了TAG,当点击菜单按钮的时候,通过这个TAG类型去响应事件。

  @Override
    public void onClick(View view) {
        //后期添加了点击事件要注意
        int clickIndex = mMenuButtonList.indexOf(view);
        mLogger.d(TAG, "clickIndex == " + clickIndex);

        /**  点击按钮后,样式改变   **/
        mMenuButtonList.get(mSelectedButtonPosition).setSelected(false);
        ((MenuTabView) view).setSelected(true);
        view.requestFocus();
        mSelectedButtonPosition = clickIndex;
        /**  点击按钮后,要刷新焦点,点击更多不需要刷新  **/
        mSelectedButton = (MenuTabView) view;

        if ((Integer) view.getTag() != MORE_MENU_TAG) {
            changeMenuButtonFocusEvent();
        }
        /**请求给干掉 **/
        this.cancelHallRequest();
        /** 逻辑处理 **/
        switch ((Integer) view.getTag()) {

            case MORE_MENU_TAG:
                //(2)跳转到菜单页
                ((MainActivity) getActivity()).showMenuDialog();
                break;
            case NORMAL_MENU_TAG:
                //(3)刷新数据

                mSelectedButton.setNextFocusDownId(R.id.hall_data_gallery);

                if (CollectionUtils.size(mRecommendChannelList) > 0) {
                    mGameId = mRecommendChannelList.get(mSelectedButtonPosition).getResourceId();
                }
                itemSelectedCallback();
                break;
        }
    }

(3) 当用户进入菜单选项的dialog,选中了某个菜单选项,然后返回到比赛大厅时,比赛大厅的菜单列表的样式变化的同时,也要按照这个菜单选项进行比赛的过滤,这个逻辑会比较复杂,下面我来梳理一下:

  • 从菜单选项的dialog会返回一个菜单的reSourceId值,先用这个reSourceId去比赛大厅菜单列表中循环匹配

  • 如果循环完成后,并没有匹配上,这个时候要在“更多”这个菜单项前面添加该菜单选项。

  • ** 如果匹配上了,那么分两种情况:第一,如果选中的是“全部”或者”更多“,从dialog带过来的菜单如果存在,那么该选项要进行删除。同时请求比赛接口 。**
  • 第二,如果选中的是非”全部“和”更多“按钮,说明之前用户已经从dialog中选择过一次比赛,只需要修改该菜单选项的name,然后请求比赛接口。
 /**
     * 选中用户选择的菜单项
     */
    public void showUserSelectedMenuInfo() {
       /* if (StringUtils.isBlank(mChannelName) || getResources().getString(R.string.all_game).equals(mChannelName)) {
            mLogger.e(TAG, "mChannelName为空");
        }*/
        if (CollectionUtils.size(mMenuButtonList) == 0
                || CollectionUtils.size(mRecommendChannelList) == 0) {
            return;
        }
        mLogger.d(TAG, "mChannelName为" + mChannelName);
        mLogger.d(TAG, "mGameId为:" + mGameId);
        boolean menuSelected = false;
        for (int i = 0; i < mRecommendChannelList.size(); i++) {
            MenuTabView child = mMenuButtonList.get(i);
            ChannelModel model = mRecommendChannelList.get(i);
            mLogger.i(TAG, "循环时ResourceId == " + model.getResourceId());
            if (mGameId == model.getResourceId()) {
                //选中
                mLogger.i(TAG, "选中了position == " + i);
                mSelectedButton = child;
                //之前选中的不能选中
                mMenuButtonList.get(mSelectedButtonPosition).setSelected(false);
                child.requestFocus();
                child.setSelected(true);
                mSelectedButtonPosition = i;
                menuSelected = true;//已经选中了某个菜单页
                mLogger.i(TAG, "mSelectedButtonPosition == " + mSelectedButtonPosition);
                changeSelectedFocus(mSelectedButton);
                /**  (1)说明选中的是"全部"、"更多",此时删除非推荐菜单选项(从dialog带过来的菜单选项) */
                if ((model.isRecommend()
                        || model.getResourceId() == SearchParams.GAME_TYPE_ALL
                        || model.getResourceId() == MORE_CHANNEL_ID)
                        && normalTabView != null
                        && model.getResourceId() != RECOMMEND_ID) {
                    removeNormalButton();
                    mLogger.i(TAG, "删除普通菜单");
                }
                /** (2)从dialog带过来的菜单选项 已经添加的话,修改该菜单的name即可**/
                if (!model.isRecommend() && model.getResourceId() != SearchParams.GAME_TYPE_ALL
                        && model.getResourceId() != RECOMMEND_ID
                        && model.getResourceId() != MORE_CHANNEL_ID
                        && normalTabView != null) {
                    child.setVisibility(View.VISIBLE);
                    child.setText(mChannelName);
                    mLogger.i(TAG, "显示普通的菜单");
                }
                break;
            } else {
                /** (1)更新焦点 **/
                changeSelectedFocus(child);
                /** (2)之前选中的菜单,不让其颜色变成选中状态(蓝色)**/
                if (child.isSelected()) {
                    child.setSelected(false);
                }
            }
        }
        /**
         用户从HallDialog中选择了某个普通的菜单,有则刷新,无则添加
         **/
        if (!menuSelected) {
            addUserSelectedMenu();
        }
  • 比赛大厅卡片优化
    比赛卡片分为对阵和非对阵,之前是把对阵和非对阵的样式放在一个布局里,现在我使用了非对阵和对阵两种viewType。

三 比赛大厅(香港)###

(1)由于日期列表有星期的限制,而且开头是以星期天开始的,所以计算出两个礼拜前第一个星期天,作为startDate,添加35(5 * 7天)天作为这个时间列表。

9.pic_hd.jpg
  private void initDateTabs() {

        /**
         * (1) 计算出今天是星期几
         * (2) 计算出两个礼拜后第一个礼拜天作为startDate
         * (3) 开始接口请求日期列表
         * (4) fill Date
         *
         */
        /**(1)计算今天是星期几 **/
        today = new Date();
        int todayWeek = TimeFormatUtil.getWeekOfDate(today);

        mDate = TimeFormatUtil.getTimeForHHMMSS(today.getTime(), "yyyyMMdd");
        todayValue = mDate;


        /**(2)计算出两个礼拜后第一个礼拜日作为startDate作为请求参数 **/

        Date startDateOfSunday = DaysUtil.getDateBefore(today, 14 + todayWeek);
        mTabStartDate = TimeFormatUtil.getTimeForHHMMSS(startDateOfSunday.getTime(), "yyyyMMdd");
        print("startDateOfSunday == " + mTabStartDate);
        if (mDateList != null) {
            mDateList.clear();
        } else {
            mDateList = new ArrayList<Date>();
        }

        for (int i = 0; i < SearchParams.TOTAL_HALL_DATE_COUNT; i++) {
            mDateList.add(DaysUtil.getDateAfter(startDateOfSunday, i));
        }
        mSelectGalleyPosition = mDateList.indexOf(today);
        print("mSelectGalleyPosition = " + mSelectGalleyPosition);

        /** (3) 开始接口请求日期列表 **/
        requestDateTabList();

    }

(2)中间的赛事卡片使用了Leakback 中重写的RecyclerView,用TVRecyclerView也是可以实现的。

(3)整个时间轴的布局分别由TimeAxis(显示时间轴刻度的ViewGroup)TimeMarkerView(显示比赛大厅所有比赛时间的小圆点View + **提示当前选中比赛时间的View) ** 组成。

  • TimeAxis由0~24小时 总共25个小时单位的刻度组成,其内部通过Adapter来完成。

  • TimeMarkerView会根据比赛大厅所有比赛,计算其在时间轴上的marginLeft值,生成一个个小圆点,然后依次添加进TimeMarkerView中;同时也会根据当前选中比赛的时间,添加一个上面为下边中间带箭头的蓝色长方形和下面为一个蓝色小圆点组合而成的布局(CurrentTimeTipView)。

  • 选中比赛和时间轴联动效果是一个属性动画,CurrentTimeTipView由初始的marginLeft值滑到目标比赛marginLeft值,期间这个过程是先加速后减速的。

time axis.png

四 机卡绑定###

机卡绑定流程如下:
(1)首先Application onCreate方法中会请求机卡绑定的接口

(2)机卡绑定分为TV机卡绑定(买电视送会员)和手机机卡绑定(买手机送的会员,要在TV端使用)两种类型。

(3)优先进行TV机卡绑定,直接跳转到用户中心APP即可,绑定成功之后,会触发SportsVipLoginObserver的回调,表示用户已经成为了VIP会员,成功后跳转到MainActivity。

  • 跳转到用户中心进行机卡绑定的代码如下:
LoginUtils.bind();
  • 绑定成功的回调代码如下:
   @Override
    public void callBack(boolean isLogin) {
        if(isLogin && LoginUtils.isLeSportsVip()){
            mLogger.d("binding success and update userVipInfo");
            MainActivity.gotoMainActivity(this);
            finish();
        }
    }
bindDevice.png

注意:这个页面如果按返回键的话,要跳转到首页

(4)其次进行手机机卡绑定,该逻辑是由我们体育这边来实现的。

(5)进入手机机卡绑定页面会出现一个展示绑定时长的列表,当我们点击“领取”,那么会调用一个绑定接口,绑定成功的话进入一个绑定成功页面,绑定失败则进入绑定失败页面。

(6)绑定成功后,要更新账户的信息,代码如下:

 LoginUtils.updateAccountInfo();

绑定成功页:


图片.png

绑定失败页:


图片.png

注意:手机机卡绑定,绑定成功和失败 三个页面都需要对返回键进行处理,让其跳转到首页。

五 搜索###

(1)搜索整体的页面结构是一个ScrollerView里面套了三个ViewGroup:
SearchInputPanel(最左边的键盘布局),SearchSuggestionPanel(中间的关键字列表布局),SearchResultPanel(布局)。

(2)三个Panel 的滑动是通过SearchFragment来控制的,同时也每个panel滑入和滑出有一些相应的逻辑处理,这里我让SearchFragment实现了SlideController接口里面,里面封装了一些控制panel滑入,滑出的方法,最后让每个panel持有SearchFragment的引用。

(3)同时为了方便控制,我们让三个panel,都实现了SlideablePanel接口,控制左右滑动的时候,只要传SlideablePanel对象即可,项目中由SearchFragment继承了SlideablePanel接口,并控制搜索的滑动及各种回调。

public interface SlideablePanel {

    // 滑入时的回调
     void onSlideIn();

    // 滑出时的回调
     void onSlideOut();

    // 是否可以滑入
     boolean canSlideIn();

    // 搜索界面退出回调
     void onSearchBoardExit();

    // 上报PV
     void onPvReport();

    // panel滑动控制器,由SearchBoardActivity来实现。
      interface SlideController {

        // 请求向左滑动
         boolean requestToSlideLeft();

        // 请求向右滑动
         boolean requestToSlideRight();

        // 是否当前的焦点panel
         boolean isCurrentFocusPanel(SlideablePanel requester);

        // 请求隐藏焦点
         void requestToHideFocus();

        // 请求显示焦点
         void requestToShowFocus();

        // 请求成为焦点panel
         void requestToBeFocusPanel(SlideablePanel requester);

        // 是否正在滑动
         boolean isSliding();

        void requestToInputPanel();

        void exitSearch();
    }
}
 /**
     * Controller回调,控制*
     */
    @Override
    public boolean requestToSlideLeft() {
        if (mSliding) {
            return false;
        }
        if (mCurrentPanel == mSuggestionPanel
                && mInputPanel.canSlideIn()) {
            onSlide(mInputPanel);
            return true;
        } else if (mCurrentPanel == mResultPanel) {
            if (mSuggestionPanel.canSlideIn()) {
                onSlide(mSuggestionPanel);
                return true;
            } else {
                onSlide(mInputPanel);
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean requestToSlideRight() {
        if (mSliding) {
            Logger.e(TAG, "mSliding --->" + mSliding + "");
            return false;
        }
        if (mCurrentPanel == mInputPanel) {
            Logger.e(TAG, "mCurrentPanel --->" + mInputPanel + "");
            if (mSuggestionPanel.canSlideIn()) {
                onSlide(mSuggestionPanel);
                return true;
            } else if (mResultPanel.canSlideIn()) {
                onSlide(mResultPanel);
                return true;
            }
        } else if (mCurrentPanel == mSuggestionPanel
                && mResultPanel.canSlideIn()) {
            Logger.e(TAG, "mCurrentPanel --->" + mSuggestionPanel + "");
            Logger.e(TAG, "可以滑动到mResultPanel");
            onSlide(mResultPanel);
            return true;
        }
        return false;
    }

(3)ScrollerView内部滑动是通过Scroller来控制的

 public void slideTo(int toScrollX, int duration) {
        if (this.getScrollX() == toScrollX) {
            return;
        }

        if (this.mFocusView == null) {
            this.mFocusView = FocusViewUtil.findFocusViewOfContainer(this);
        }

        int dx = toScrollX - this.getScrollX();

        this.mScroller.forceFinished(true);
        this.mScroller.startScroll(this.getScrollX(), 0, dx, 0, duration);
        this.handler.removeMessages(MSG_SCROLL);
        this.handler.sendEmptyMessage(MSG_SCROLL);
    }

(4) SearchInputPanel(左边键盘)是一个自定义的ViewGroup,需要注意的是焦点事件这块,键盘里所有按键的焦点控制逻辑都在这里。

提示:如果一个view的焦点控制比较难的时候,重写该View的dispatchKeyEvent方法,是一定能够解决的。

 @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (this.mCurrentFocusView == null) {
            this.mCurrentFocusView = this.getFocusedChild();
        }
        if (event.getAction() == KeyEvent.ACTION_DOWN) {
            switch (event.getKeyCode()) {
                case KeyEvent.KEYCODE_BACK:
                    mSlideController.exitSearch();//退出搜索
                    return true;
                case KeyEvent.KEYCODE_DPAD_LEFT:
                    this.onLeftKeyPressed();
                    return true;
                case KeyEvent.KEYCODE_DPAD_DOWN:
                    this.onDownKeyPressed();
                    return true;
                case KeyEvent.KEYCODE_DPAD_CENTER:
                case KeyEvent.KEYCODE_ENTER:
                case KeyEvent.KEYCODE_BUTTON_A:
                    this.onFocusViewClicked();
                    return true;
                case KeyEvent.KEYCODE_DPAD_RIGHT:
                    this.onRightKeyPressed(event);
                    return true;
                case KeyEvent.KEYCODE_DPAD_UP:
                    this.onUpKeyPressed();
                    return true;
                case KeyEvent.KEYCODE_FORWARD_DEL:
                    this.onClearBtnClicked();
                    return true;
                case KeyEvent.KEYCODE_DEL:
                    this.onDeleteBtnClicked();
                    return true;
                // 键盘输入空格
                case KeyEvent.KEYCODE_SPACE:
                    this.onNewInputChar(" ");
                    return true;
            }

            // 键盘输入字母数字
            if (event.getKeyCode() >= KeyEvent.KEYCODE_0
                    && event.getKeyCode() <= KeyEvent.KEYCODE_9) {
                this.onNewInputChar(this.mDigits[event.getKeyCode()
                        - KeyEvent.KEYCODE_0]);
            } else if (event.getKeyCode() >= KeyEvent.KEYCODE_A
                    && event.getKeyCode() <= KeyEvent.KEYCODE_Z) {
                this.onNewInputChar(this.mLetters[event.getKeyCode()
                        - KeyEvent.KEYCODE_A]);
            }

            return true;
        }
        return true;
    }

(5)左边键盘(SearchInputPanel)和中间的关键词列表(SearchSuggestionPanel)以及SearchResultPanel之间的回调,数据传递等一些逻辑处理,都是通过SearchBoardDataEngine这个类中来实现的。例如:

  • suggestion的点击,光标滑动事件的回调

  • 缓存的清理

  • 重复suggestion的过滤

(6)搜索结果页SearchResultPanel的实现,主要难点在RecyclerView的使用上,详情请关注: http://www.jianshu.com/p/ac7e393689f9

(7)Fragment被创建的时候,要进行suggestion缓存的加载

 @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        //加载缓存数据
        GlobalThreadPool.getUnLimitedThreadPool().execute(new Runnable() {
            @Override
            public void run() {
                SearchBoardDataEngine.getInstance().getHistoryRecordAndRecommendData();
            }
        });

        super.onActivityCreated(savedInstanceState);
    }

(8)退出应用的时候记得清理释放资源

 @Override
    public void onDestroy() {

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,451评论 25 707
  • 一:checkout支付 1,后端接口的调用(获取数据接口和发送数据接口)。 获取数据 其中函数makeSign(...
    9ef822571b70阅读 1,428评论 0 3
  • 下半年以来,部门大批量人员离职。前后人员工作交接的问题,逐步成为产品经理的困扰。 交者有意 无论如何,员工是要走了...
    产品经理读书会阅读 3,903评论 0 4
  • 時隔結業2年的時間,高五期再度相聚,甚似開心。 早上雖然出門各種堵,但是當到達目的地時,大家很興奮。中午的柴火雞,...
    好彩妹阅读 302评论 0 0
  • 《我们如此深爱儿女,他们为何不“爱”我们?》一文中,作者龙建刚记录了老友在国庆假期探望独生女儿想给其意外的惊喜,结...
    舒天阅读 152评论 0 2