可能是第十好的Android 開源 日歷 Calendar 仿小米

SuperCalendar

簡介

  • 博主現在工作在一家教育公司,最近公司的產品狗扔過來一個需求,說要做一個可以周月切換的課表,可以展示用戶在某一天的上課安排。接到這個任務之后我研究了很多的日歷控件,并且抽出了一個calenderlibS。先看一下最后的項目中的效果:
月模式.png

周模式.png
  • 看到本篇文章的同學估計也是實驗課或者項目需求中需要一個日歷表,當我接到這個需求的時候,當時腦子壓根連想都沒想,這么通用的控件,GitHub上一搜一大堆不是嘛??墒堑鹊秸嬲銎饋淼臅r候,扎心了老鐵,GitHub上的大神居然異常的不給力,都是實現了基本功能,能夠滑動切換月份,找實現了周月切換功能的開源庫很難。終于我費盡千辛萬苦找到一個能夠完美切換的項目時,你周月切換之后的數據亂的一塌糊涂?。。?!
  • 算了,自己擼一個!??!

項目鏈接 SuperCalendar

  • 如果你感覺到對你有幫助,歡迎star
  • 如果你感覺對代碼有疑惑,或者需要修改的地方,歡迎issue

主要特性

  • 日歷樣式完全自定義,拓展性強
  • 左右滑動切換上下周月,上下滑動切換周月模式
  • 抽屜式周月切換效果
  • 標記指定日期(marker)
  • 跳轉到指定日期

思路

[圖片上傳失敗...(image-bf19df-1513673145313)]

  • Calendar的繪制由CalendarRenderer完成,IDayRenderer實現自定義的日期效果,CalendarAttr中存儲日歷的屬性。
  • 首先看一下Calendar的代碼,Calendar主要是初始化Renderer和Attr,然后接受View的生命周期
  • 在OnDraw的時候調用Renderer的onDraw方法,在點擊事件onTouchEvent觸發時,調用Renderer的點擊處理邏輯
    private void initAttrAndRenderer() {
        calendarAttr = new CalendarAttr();
        calendarAttr.setWeekArrayType(CalendarAttr.WeekArrayType.Monday);
        calendarAttr.setCalendarType(CalendarAttr.CalendayType.MONTH);
        renderer = new CalendarRenderer(this , calendarAttr , context);
        renderer.setOnSelectDateListener(onSelectDateListener);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        renderer.draw(canvas);
    }
    private float posX = 0;
    private float posY = 0;
    /*
     * 觸摸事件為了確定點擊的位置日期
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                posX = event.getX();
                posY = event.getY();
                break;
            case MotionEvent.ACTION_UP:
                float disX = event.getX() - posX;
                float disY = event.getY() - posY;
                if (Math.abs(disX) < touchSlop && Math.abs(disY) < touchSlop) {
                    int col = (int) (posX / cellWidth);
                    int row = (int) (posY / cellHeight);
                    onAdapterSelectListener.cancelSelectState();
                    renderer.onClickDate(col, row);
                    onAdapterSelectListener.updateSelectState();
                    invalidate();
                }
                break;
        }
        return true;
    }
  • 然后看一下CalendarRenderer的代碼,Renderer承擔了Calendar的繪制任務,首先renderer根據種子日期seedDate填充出Calendar包含的Date數據,calendar中持有一個6*7二維數組來存放日期數據。然后在onDraw的時候通過IDayRenderer來完成對日歷的繪制。當點擊日期改變了日期的狀態時,首先改變對應日期的狀態State,然后重繪Calendar。
    private void instantiateMonth() {
        int lastMonthDays = Utils.getMonthDays(seedDate.year, seedDate.month - 1);  // 上個月的天數
        int currentMonthDays = Utils.getMonthDays(seedDate.year, seedDate.month);   // 當前月的天數
        int firstDayPosition = Utils.getFirstDayWeekPosition(seedDate.year, seedDate.month , CalendarViewAdapter.weekArrayType);

        int day = 0;
        for (int row = 0; row < Const.TOTAL_ROW; row++) {
            day = fillWeek(lastMonthDays, currentMonthDays, firstDayPosition, day,              row);
        }
    }

    private int fillWeek(int lastMonthDays, int currentMonthDays, int firstDayWeek,                 int day, int row) {
        for (int col = 0; col < Const.TOTAL_COL; col++) {
            int position = col + row * Const.TOTAL_COL; // 單元格位置
            if (position >= firstDayWeek && position < firstDayWeek +               currentMonthDays) { // 本月的
                day ++;
                fillCurrentMonthDate(day, row, col);
            } else if (position < firstDayWeek) { //last month
                instantiateLastMonth(lastMonthDays, firstDayWeek, row, col,                 position);
            } else if (position >= firstDayWeek + currentMonthDays) {//next month
                instantiateNextMonth(currentMonthDays, firstDayWeek, row, col,              position);
            }
        }
        return day;
    }
    
    public void draw(Canvas canvas) {
        for (int row = 0; row < Const.TOTAL_ROW; row++) {
            if (weeks[row] != null) {
                for (int col = 0; col < Const.TOTAL_COL; col ++) {
                    if (weeks[row].days[col] != null) {
                        dayRenderer.drawDay(canvas , weeks[row].days[col]);
                    }
                }
            }
        }
    }
    
    public void onClickDate(int col, int row) {
        if (col >= Const.TOTAL_COL || row >= Const.TOTAL_ROW)
            return;
        if (weeks[row] != null) {
            if(attr.getCalendarType() == CalendarAttr.CalendayType.MONTH) {
                if(weeks[row].days[col].getState() == State.CURRENT_MONTH){
                    weeks[row].days[col].setState(State.SELECT);
                    selectedDate = weeks[row].days[col].getDate();
                    CalendarViewAdapter.saveDate(selectedDate);
                    onSelectDateListener.onSelectDate(selectedDate);
                    seedDate = selectedDate;
                } else if (weeks[row].days[col].getState() == State.PAST_MONTH){
                    selectedDate = weeks[row].days[col].getDate();
                    CalendarViewAdapter.saveDate(selectedDate);
                    onSelectDateListener.onSelectOtherMonth(-1);
                    onSelectDateListener.onSelectDate(selectedDate);
                } else if (weeks[row].days[col].getState() == State.NEXT_MONTH){
                    selectedDate = weeks[row].days[col].getDate();
                    CalendarViewAdapter.saveDate(selectedDate);
                    onSelectDateListener.onSelectOtherMonth(1);
                    onSelectDateListener.onSelectDate(selectedDate);
                }
            } else {
                weeks[row].days[col].setState(State.SELECT);
                selectedDate = weeks[row].days[col].getDate();
                CalendarViewAdapter.saveDate(selectedDate);
                onSelectDateListener.onSelectDate(selectedDate);
                seedDate = selectedDate;
            }
        }
    }
  • 調用Renderer的draw方法時使用dayRenderer.drawDay(canvas , weeks[row].days[col]),dayRenderer是一個接口,在lib中有一個DayView 的抽象類實現該接口。 其中的drawDay方法完成了對該天到calendar的canvas上的繪制
    @Override
    public void drawDay(Canvas canvas , Day day) {
        this.day = day;
        refreshContent();
        int saveId = canvas.save();
        canvas.translate(day.getPosCol() * getMeasuredWidth(),
                day.getPosRow() * getMeasuredHeight());
        draw(canvas);
        canvas.restoreToCount(saveId);
    }
  • 使用繼承自ViewPager的MonthPager來存放calendar的view
    viewPageChangeListener = new ViewPager.OnPageChangeListener() {}
    //新建viewPagerChangeListener
    @Override
    protected void onSizeChanged(int w, int h, int oldW, int oldH) {
        cellHeight = h / 6;
        super.onSizeChanged(w, h, oldW, oldH);
    }//重寫onSizeChanged,獲取dayView的高度
    public int getTopMovableDistance() {
        CalendarViewAdapter calendarViewAdapter = (CalendarViewAdapter)             getAdapter();
        rowIndex = calendarViewAdapter.getPagers().get(currentPosition %            3).getSelectedRowIndex();
        return cellHeight * rowIndex;
    }//計算周月切換時在到達選中行之前MonthPager收起的距離
    public int getRowIndex() {
        CalendarViewAdapter calendarViewAdapter = (CalendarViewAdapter)             getAdapter();
        rowIndex = calendarViewAdapter.getPagers().get(currentPosition  %           3).getSelectedRowIndex();
        Log.e("ldf","getRowIndex = " + rowIndex);
        return rowIndex;
    }//計算選中日期所在的行數
  • 使用CalendarViewAdapter為MonthPager填充calendar的實例
    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        super.setPrimaryItem(container, position, object);
        this.currentPosition = position;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {

        if(position < 2){
            return null;
        }
        Calendar calendar = calendars.get(position % calendars.size());
        if(calendarType == CalendarAttr.CalendayType.MONTH) {
            CalendarDate current = seedDate.modifyMonth(position - MonthPager.CURRENT_DAY_INDEX);
            current.setDay(1);//每月的種子日期都是1號
            calendar.showDate(current);
        } else {
            CalendarDate current = seedDate.modifyWeek(position - MonthPager.CURRENT_DAY_INDEX);
            if(weekArrayType == 1) {
                calendar.showDate(Utils.getSaturday(current));
            } else {
                calendar.showDate(Utils.getSunday(current));
            }//每周的種子日期為這一周的最后一天
            calendar.updateWeek(rowCount);
        }
        if (container.getChildCount() == calendars.size()) {
            container.removeView(calendars.get(position % 3));
        }
        if(container.getChildCount() < calendars.size()) {
            container.addView(calendar, 0);
        } else {
            container.addView(calendar, position % 3);
        }
        return calendar;
    }
  • 日歷在切換周月時切換日歷中填充的數據
  • 在月模式切換成周模式時,將當前頁的seedDate拿出來刷新本頁數據,并且更新指定行數的周數據,然后得到seedDate下一周的周日作為下一頁的seedDate,刷新下一頁的數據,并且更新指定行數的周數據。上一頁同理
  • 也是說假設我當前選擇的是6月12號周日,處于日歷的第二行,也是說下一頁的seedDate是6月19號,然后刷新6月19號所在周的數據到選定的第二行。
  • 當切換周月時,把三頁的數據都會重新刷新一遍,以保證數據的正確性。
    public void switchToMonth() {
        if(calendars != null && calendars.size() > 0 && calendarType !=             CalendarAttr.CalendayType.MONTH){
            calendarType = CalendarAttr.CalendayType.MONTH;
            MonthPager.CURRENT_DAY_INDEX = currentPosition;
            Calendar v = calendars.get(currentPosition % 3);//0
            seedDate = v.getSeedDate();

            Calendar v1 =  calendars.get(currentPosition % 3);//0
            v1.switchCalendarType(CalendarAttr.CalendayType.MONTH);
            v1.showDate(seedDate);

            Calendar v2 = calendars.get((currentPosition - 1) % 3);//2
            v2.switchCalendarType(CalendarAttr.CalendayType.MONTH);
            CalendarDate last = seedDate.modifyMonth(-1);
            last.setDay(1);
            v2.showDate(last);

            Calendar v3 = calendars.get((currentPosition + 1) % 3);//1
            v3.switchCalendarType(CalendarAttr.CalendayType.MONTH);
            CalendarDate next = seedDate.modifyMonth(1);
            next.setDay(1);
            v3.showDate(next);
        }
    }

    public void switchToWeek(int rowIndex) {
        rowCount = rowIndex;
        if(calendars != null && calendars.size() > 0 && calendarType !=             CalendarAttr.CalendayType.WEEK){
            calendarType = CalendarAttr.CalendayType.WEEK;
            MonthPager.CURRENT_DAY_INDEX = currentPosition;
            Calendar v = calendars.get(currentPosition % 3);
            seedDate = v.getSeedDate();

            rowCount = v.getSelectedRowIndex();

            Calendar v1 =  calendars.get(currentPosition % 3);
            v1.switchCalendarType(CalendarAttr.CalendayType.WEEK);
            v1.showDate(seedDate);
            v1.updateWeek(rowIndex);

            Calendar v2 = calendars.get((currentPosition - 1) % 3);
            v2.switchCalendarType(CalendarAttr.CalendayType.WEEK);
            CalendarDate last = seedDate.modifyWeek(-1);
            if(weekArrayType == 1) {
                v2.showDate(Utils.getSaturday(last));
            } else {
                v2.showDate(Utils.getSunday(last));
            }//每周的種子日期為這一周的最后一天
            v2.updateWeek(rowIndex);

            Calendar v3 = calendars.get((currentPosition + 1) % 3);
            v3.switchCalendarType(CalendarAttr.CalendayType.WEEK);
            CalendarDate next = seedDate.modifyWeek(1);
            if(weekArrayType == 1) {
                v3.showDate(Utils.getSaturday(next));
            } else {
                v3.showDate(Utils.getSunday(next));
            }//每周的種子日期為這一周的最后一天
            v3.updateWeek(rowIndex);
        }
    }
  • 使用CoordinateLayout的特性來做周月模式切換
  • 1.RecyclerViewBehavior
    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, RecyclerView child,
                                       View directTargetChild, View target, int nestedScrollAxes) {
        LinearLayoutManager linearLayoutManager = (LinearLayoutManager) child.getLayoutManager();
        if(linearLayoutManager.findFirstCompletelyVisibleItemPosition() > 0) {
            return false;
        }

        boolean isVertical = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
        int firstRowVerticalPosition =
                (child == null || child.getChildCount() == 0) ? 0 : child.getChildAt(0).getTop();
        boolean recycleviewTopStatus = firstRowVerticalPosition >= 0;
        return isVertical && (recycleviewTopStatus || !Utils.isScrollToBottom()) && child == directTargetChild;
    }

    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, RecyclerView             child,
                                  View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
        if (child.getTop() <= initOffset && child.getTop() >= minOffset) {
            consumed[1] = Utils.scroll(child, dy, minOffset, initOffset);
            saveTop(child.getTop());
        }
    }

    @Override
    public void onStopNestedScroll(final CoordinatorLayout parent, final        RecyclerView child, View target) {
        Log.e("ldf","onStopNestedScroll");
        super.onStopNestedScroll(parent, child, target);
        if (!Utils.isScrollToBottom()) {
            if (initOffset - Utils.loadTop() > Utils.getTouchSlop(context)){
                scrollTo(parent, child, minOffset, 200);
            } else {
                scrollTo(parent, child, initOffset, 80);
            }
        } else {
            if (Utils.loadTop() - minOffset > Utils.getTouchSlop(context)){
                scrollTo(parent, child, initOffset, 200);
            } else {
                scrollTo(parent, child, minOffset, 80);
            }
        }
    }
  • (2)MonthPagerBehavior 當recyclerView滑動式,MonthPager做相應的變化。
@Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, MonthPager child, View dependency) {
        Log.e("ldf","onDependentViewChanged");
        CalendarViewAdapter calendarViewAdapter = (CalendarViewAdapter) child.getAdapter();
        if (dependentViewTop != -1) {
            int dy = dependency.getTop() - dependentViewTop;    //dependency對其依賴的view(本例依賴的view是RecycleView)

            int top = child.getTop();

            if( dy > touchSlop){
                calendarViewAdapter.switchToMonth();
            } else if(dy < - touchSlop){
                calendarViewAdapter.switchToWeek(child.getRowIndex());
            }

            if (dy > -top){
                dy = -top;
            }

            if (dy < -top - child.getTopMovableDistance()){
                dy = -top - child.getTopMovableDistance();
            }

            child.offsetTopAndBottom(dy);
        } else {
            initRecyclerViewTop = dependency.getTop();
        }

        dependentViewTop = dependency.getTop();
        top = child.getTop();

        if((initRecyclerViewTop - dependentViewTop) >= child.getCellHeight()) {
            Utils.setScrollToBottom(false);
            calendarViewAdapter.switchToWeek(child.getRowIndex());
            initRecyclerViewTop = dependentViewTop;
        }
        if((dependentViewTop - initRecyclerViewTop) >= child.getCellHeight()) {
            Utils.setScrollToBottom(true);
            calendarViewAdapter.switchToMonth();
            initRecyclerViewTop = dependentViewTop;
        }

        return true;
        // TODO: 16/12/8 dy為負時表示向上滑動,dy為正時表示向下滑動,dy為零時表示滑動停止
    }
  • 使用IDayRender來實現自定義的日歷效果

DayView實現IDayRenderer,我們新建一個CustomDayView繼承自DayView,在里面作自定義的顯示

public CustomDayView(Context context, int layoutResource) {
       super(context, layoutResource);
       dateTv = (TextView) findViewById(R.id.date);
       marker = (ImageView) findViewById(R.id.maker);
       selectedBackground = findViewById(R.id.selected_background);
       todayBackground = findViewById(R.id.today_background);
   }

   @Override
   public void refreshContent() {
       renderToday(day.getDate());
       renderSelect(day.getState());
       renderMarker(day.getDate(), day.getState());
       super.refreshContent();
   }

使用方法

XML布局

  • 新建XML布局

RecyclerView的layout_behavior為com.ldf.calendar.behavior.RecyclerViewBehavior

 <android.support.design.widget.CoordinatorLayout
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1">

        <com.ldf.calendar.view.MonthPager
            android:id="@+id/calendar_view"
            android:layout_width="match_parent"
            android:layout_height="300dp"
            android:background="#fff">
        </com.ldf.calendar.view.MonthPager>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/list"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_behavior="com.ldf.calendar.behavior.RecyclerViewBehavior"
            android:background="#c2c2c2"
            android:layout_gravity="bottom"/>

    </android.support.design.widget.CoordinatorLayout>
    

自定義日歷樣式

  • 新建CustomDayView繼承自DayView并重寫refreshContent 和 copy 兩個方法
    @Override
    public void refreshContent() {
        //你的代碼 你可以在這里定義你的顯示規則
        super.refreshContent();
    }

    @Override
    public IDayRenderer copy() {
        return new CustomDayView(context , layoutResource);
    }
  • 新建CustomDayView實例,并作為參數構建CalendarViewAdapter
    CustomDayView customDayView = new CustomDayView(
            context , R.layout.custom_day);
    calendarAdapter = new CalendarViewAdapter(
                context ,
                onSelectDateListener ,
                Calendar.MONTH_TYPE ,
                customDayView);

初始化View

  • 目前來看 相比于Dialog選擇日歷 我的控件更適合于Activity/Fragment在Activity的onCreate 或者Fragment的onCreateView 你需要實現這兩個方法來啟動日歷并裝填進數據
@Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_syllabus);
        initCalendarView();
    }
    
    private void initCalendarView() {
        initListener();
        CustomDayView customDayView = new CustomDayView(
            context , R.layout.custom_day);
        calendarAdapter = new CalendarViewAdapter(
                context ,
                onSelectDateListener ,
                Calendar.MONTH_TYPE ,
                customDayView);
        initMarkData();
        initMonthPager();
    } 

使用此方法回調日歷點擊事件

private void initListener() {
        onSelectDateListener = new OnSelectDateListener() {
            @Override
            public void onSelectDate(CalendarDate date) {
                //your code
            }

            @Override
            public void onSelectOtherMonth(int offset) {
                //偏移量 -1表示上一個月 , 1表示下一個月
                monthPager.selectOtherMonth(offset);
            }
        };
    }

使用此方法初始化日歷標記數據

private void initMarkData() {
       HashMap markData = new HashMap<>();
        //1表示紅點,0表示灰點
       markData.put("2017-8-9" , "1");
       markData.put("2017-7-9" , "0");
       markData.put("2017-6-9" , "1");
       markData.put("2017-6-10" , "0");
       calendarAdapter.setMarkData(markData);
   }

使用此方法給MonthPager添加上相關監聽

monthPager.addOnPageChangeListener(new MonthPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            }

            @Override
            public void onPageSelected(int position) {
                mCurrentPage = position;
                currentCalendars = calendarAdapter.getAllItems();
                if(currentCalendars.get(position % currentCalendars.size()) instanceof Calendar){
                    //you code
                }
            }

            @Override
            public void onPageScrollStateChanged(int state) {
            }
        });

重寫onWindowFocusChanged方法,使用此方法得知calendar和day的尺寸

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if(hasFocus && !initiated) {
            CalendarDate today = new CalendarDate();
            calendarAdapter.notifyDataChanged(today);
            initiated = true;
        }
    }
  • 大功告成,如果還不清晰,請下載DEMO

Download


Gradle:
Step 1. Add it in your root build.gradle at the end of repositories:

allprojects {
    repositories {
    ...
    maven { url 'https://www.jitpack.io' }
    }
}

Step 2. Add the dependency

    dependencies {
            compile 'com.github.MagicMashRoom:SuperCalendar:1.6'
    }

[圖片上傳失敗...(image-526acb-1513673145313)]

Licence

  Copyright 2017 MagicMashRoom, Inc.
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,765評論 25 708
  • 原文地址:https://github.com/MagicMashRoom/SuperCalendar簡介 由于項...
    sirai閱讀 4,135評論 4 3
  • afinalAfinal是一個android的ioc,orm框架 https://github.com/yangf...
    passiontim閱讀 15,486評論 2 45
  • 父母尚在茍且,你卻向往詩和遠方。
    薛什么閱讀 245評論 2 0
  • 在那熙熙攘攘的人群里啊,你是誰?我是誰。看著遠方誒,燈光閃爍。不停地改變著,變換著,前行的路??!無數次,無數次地擦...
    平平__pink閱讀 230評論 0 1