Android 開發(fā)藝術(shù)探索讀書筆記 3 -- View 的事件體系(上)

本篇文章主要介紹以下幾個(gè)知識點(diǎn):

  • View 的基礎(chǔ)知識;
  • View 的滑動(dòng);
  • 彈性滑動(dòng) 。
hello,夏天 (圖片來源于網(wǎng)絡(luò))

3.1 View的基礎(chǔ)知識

3.1.1 什么是View

View 代表一個(gè)控件,是 Android 中所有控件的基類,如 ButtonTextViewRelativeLayoutListview 等的共同基類都是 View。

ViewGroup 繼承 View,內(nèi)部包含了許多個(gè)控件,即一組 View,子 View 同樣還可以是 ViewGroup。例如 Button 是個(gè) View,而 LinearLayout 既是 View 也是一個(gè) ViewGroup。

3.1.2 View的位置參數(shù)

View 的位置由它的四個(gè)頂點(diǎn)決定,分別對應(yīng)于 View 的四個(gè)屬性:top (左上角縱坐標(biāo))、left (左上角橫坐標(biāo))、right (右下角橫坐標(biāo)),bottom (右下角縱坐標(biāo))。

值得注意的是,上面坐標(biāo)都是相對于 View 的父容器來說的,是一種相對坐標(biāo),其關(guān)系如下:

View 的位置坐標(biāo)和父容器的關(guān)系

從圖中的關(guān)系可得寬高的關(guān)系為:

width = right - left 
height = bottom - top

獲取 View 的四個(gè)參數(shù)方式如下:

 Left = getLeft();
 Right = getRight();
 Top = getTop();
 Bottom = getBottom()

從 Android3.0開始,View 增加了額外幾個(gè)參數(shù),xytranslationXtranslationY,其換算關(guān)系如下:

// x,y 是 View 左上角的圖標(biāo)
// translationX、translationY 是左上角相對父容器的偏移量,默認(rèn)值是 0
// 這幾個(gè)參數(shù)也是相對于父容器的坐標(biāo)
x = left + translationX
y = top + translationY

值得注意的是,View 在平移過程中,topleft 是原始左上角的位置信息,不發(fā)生改變,發(fā)生改變的是xytranslationXtranslationY這四個(gè)參數(shù)。

3.1.3 MotionEvent 和 TouchSlop

3.1.3.1 MotionEvent

在手指接觸屏幕后所產(chǎn)生的一系列事件中,典型的事件類型有如下幾種:

  • ACTION_DOWN —— 手指剛接觸屏幕
  • ACTION_MOVE —— 手指在屏幕上移動(dòng)
  • ACTION_UP —— 手機(jī)從屏幕上松開的一瞬間

正常情況下,一次手指觸摸屏幕的行為會(huì)觸發(fā)一系列事件,如下:

  • 點(diǎn)擊屏幕后離開松開,事件序列為 DOWN -> UP
  • 點(diǎn)擊屏幕滑動(dòng)一會(huì)再松開,事件序列為 DOWN -> MOVE ->…..> MOVE_UP

通過 MotionEvent 對象可得到點(diǎn)擊事件發(fā)生的 x 和 y 坐標(biāo)。系統(tǒng)提供了兩組方法:

  • getX/getY 返回相對于當(dāng)前 View左上角的 x 和 y 坐標(biāo)
  • getRawX/getRawY 返回相對于手機(jī)屏幕左上角的 x 和 y 坐標(biāo)

3.1.3.2 TouchSlop

TouchSlop 是一個(gè)常量,指系統(tǒng)所能識別出的滑動(dòng)最小距離,若手指在屏慕上滑動(dòng)的距離小于這個(gè)常量,系統(tǒng)就不認(rèn)為是滑動(dòng)操作。

TouchSlop 的值和設(shè)備有關(guān),不同設(shè)備下可能不同,可通過 ViewConfigurtion.get(getContext()).getScaledTouchSlop獲取。

在處理滑動(dòng)時(shí),可利用這個(gè)常量來做一些過濾,提升用戶體驗(yàn)。

3.1.4 VelocityTracker、GestureDetector 和 Scroller

3.1.4.1 VelocityTracker

速度追蹤,用于追蹤手指在屏幕上滑動(dòng)的速度,包括水平和豎直方向上的速度。使用過程如下:

首先,在 View 的 onTouchEvent方法中追蹤當(dāng)前單擊事件的速度:

VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);

接著,采用如下方式獲得當(dāng)前的速度:

// 獲取速度的之前必須先計(jì)算速度, 在 getXVelocity 和 getYVelocity 前調(diào)用此方法
velocityTracker.computeCurrentVelocity(1000);
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();

值得注意的是,這里的速度是指一段時(shí)間內(nèi)手指滑動(dòng)的屏幕像素,如將時(shí)間設(shè)為 1000ms 時(shí),在 1s 內(nèi),手指在水平方向滑動(dòng) 100 像素,那么水平速度就是 100(注:速度可以為負(fù)數(shù),當(dāng)手指從右向左滑時(shí)為負(fù))。其計(jì)算公式如下:

速度 = (終點(diǎn)位置 -  起點(diǎn)位置)/ 時(shí)間段

根據(jù)上面的公式和 Android 系統(tǒng)的坐標(biāo)系可知,手指逆著坐標(biāo)系的正方向滑動(dòng), 產(chǎn)生的速度為負(fù)值。

最后,調(diào)用 clear 方法來重置并回收內(nèi)存:

velocityTracker.clear();
velocityTracker.recycle();

3.1.4.2 GestureDetector

手勢檢測,用于輔助檢測用戶的單擊、滑動(dòng)、長按、雙擊等行為。使用過程如下:

首先,創(chuàng)建一個(gè) GestureDetector 對象:

GestureDetector mGestureDetector = new GestureDetector(this);
// 解決長按屏幕后無法拖動(dòng)的現(xiàn)象
mGestureDetector.setIsLongpressEnabled(false);

接著,接管目標(biāo) View 的 onTouchEvent 方法,在待監(jiān)聽 View 的 onTouchEvent 方法中添加如下實(shí)現(xiàn):

boolean consume = mGestureDetector.onTouchEvent(event);
return consume;

之后就可以有選擇地實(shí)現(xiàn) OnGestureListenerOnDoubleTapListener 中的方法了,其方法介紹如下:

OnGestureListener 和 OnDoubleTapListener 中的方法介紹(1)
OnGestureListener 和 OnDoubleTapListener 中的方法介紹(2)

上表中較常用的有 onSingleTapUp(單擊),onFling(快速滑動(dòng)),onScroll(推動(dòng)),onLongPress(長按)和 onDoubleTap(雙擊)。
??
實(shí)際開發(fā)中,可不用 GestureDetector,完全可以在 view 中的 onTouchEvent 中實(shí)現(xiàn)所需的監(jiān)聽。

建議:若只是監(jiān)聽滑動(dòng)相關(guān)的,在 onTouchEvent 實(shí)現(xiàn)即可,若要監(jiān)聽雙擊行為的,就使用 GestureDetector

3.1.4.3 Scroller

彈性滑動(dòng)對象,用于實(shí)現(xiàn) View 的彈性滑動(dòng)。

當(dāng)使用 View 的 scrollTo/scrollBy 方法進(jìn)行滑動(dòng)時(shí),可用 Scroller 來實(shí)現(xiàn)滑動(dòng)的過度效果,提升用戶體驗(yàn)。

Scroller 本身是無法讓 View 彈性滑動(dòng),需配合 View 的 computScrioll 方法才能實(shí)現(xiàn),固定代碼如下(后面再介紹它為什么能實(shí)現(xiàn)彈性滑動(dòng)):

    Scroller scroller = new Scroller(getContext());

    // 慢慢滾動(dòng)到指定位置
    private void smoothScrollTo(int destX, int destY){
        int scrollX = getScrollX();
        int delta = destX - scrollX;
        // 1000ms內(nèi)滑向destX,效果就是慢慢的滑動(dòng)
        scroller.startScroll(scrollX,0,delta,0,1000);
        invalidate();
    }

    @Override
    public void computeScroll() {
        if(scroller.computeScrollOffset()){
            scrollTo(scroller.getCurrX(),scroller.getCurrY());
            postInvalidate();
        }
    }

3.2 View的滑動(dòng)

實(shí)現(xiàn) View 的滑動(dòng)有三種方式:

  1. 通過 View 本身提供的 scrollTo/scrollBy 方法來實(shí)現(xiàn)滑動(dòng);

  2. 通過動(dòng)畫給 View 施加平移效果來實(shí)現(xiàn)滑動(dòng);

  3. 通過改變 Viev 的 LayoutParams 使得 View 重新布局從而實(shí)現(xiàn)滑動(dòng)。

3.2.1 使用 scrollTo/scrollBy

View 提供了專門的方法來實(shí)現(xiàn) View 的滑動(dòng),即 scrollTo/scrollBy,其實(shí)現(xiàn)如下:

   /**
     * Set the scrolled position of your view. This will cause a call to
     * {@link #onScrollChanged(int, int, int, int)} and the view will be
     * invalidated.
     * @param x the x position to scroll to
     * @param y the y position to scroll to
     */
    public void scrollTo(int x, int y) {
        // 實(shí)現(xiàn)了基于所傳遞參數(shù)的絕對滑動(dòng)
        if (mScrollX != x || mScrollY != y) {
            // 可通過 getScrollX 和 getScrollY 方法分別得到 View 內(nèi)部的兩個(gè)屬性 mScrollX 和 mScrollY
            int oldX = mScrollX;
            int oldY = mScrollY;
            // 在滑動(dòng)過程中,    
            // mScrollX 的值總是等于 View 左邊緣和 View 內(nèi)容左邊緣在水平方向的距離
            //          從左向右滑動(dòng),mScrollX 為負(fù)值,反之為正值
            // mScrollY 的值總是等于 View 上邊緣和 View 內(nèi)容上邊緣在豎直方向的距離
            //          從上往下滑動(dòng),mScrollY 為負(fù)值,反之為正值
            // 注:View 邊緣是指 View 的位置,由四個(gè)頂點(diǎn)組成,而 View 內(nèi)容邊緣是指 View 中的內(nèi)容的邊緣
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    }

    /**
     * Move the scrolled position of your view. This will cause a call to
     * {@link #onScrollChanged(int, int, int, int)} and the view will be
     * invalidated.
     * @param x the amount of pixels to scroll by horizontally
     * @param y the amount of pixels to scroll by vertically
     */
    public void scrollBy(int x, int y) {
        // 調(diào)用 scrolrTo 方法,實(shí)現(xiàn)了基于當(dāng)前位置的相對滑動(dòng)
        scrollTo(mScrollX + x, mScrollY + y);
    }

值得注意的是,scrolTo/scrollBy 只能改變 View 內(nèi)容的位置而不能變 View 在布局中的位置。

如圖,假設(shè)水平和豎直方向的滑動(dòng)都為100像素,使用 scrollTo/scrollBy 來實(shí)現(xiàn)滑動(dòng),只能將 view 的內(nèi)容進(jìn)行移動(dòng),不能將 view 本身進(jìn)行移動(dòng):

mScrollX 和 mScrollY 的變換規(guī)律示意圖

3.2.2 使用動(dòng)畫

使用動(dòng)畫來移動(dòng) View,主要是操作 View 的 translationXtranslationY 屬性,既可采用傳統(tǒng)的 View 動(dòng)畫,也可采用屬性動(dòng)畫。

如在100ms內(nèi)將一個(gè) View 從原始位置向右下角移動(dòng)100個(gè)像素的 View 動(dòng)畫代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:fillAfter="true"
     android:zAdjustment="normal">

    <translate
        android:duration="100"
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:interpolator="@android:anim/linear_interpolator"
        android:toXDelta="100"
        android:toYDelta="100" />

</set>

若采用屬性動(dòng)畫的話,100ms 內(nèi)將一個(gè) View 從原始位置向右平移100個(gè)像素可以這樣:

 ObjectAnimator.ofFloat(targetView, "translationX", 0, 100).setDuration(100).start();

值得注意的是,View 動(dòng)畫是對 View 的影像做操作,并不能真正改變 View 的位置參數(shù),若要保存動(dòng)畫后的狀態(tài)須將 fillAfter 屬性設(shè)為true,否則動(dòng)畫完成之后就會(huì)消失(注:屬性動(dòng)畫不會(huì)有這樣的問題)。

3.2.3 改變布局參數(shù)

改變布局參數(shù),即改變 LayoutParams,如把一個(gè)Button向右平移100px,只需將這個(gè) Bution 的LayoutParams 里的 marginLeft 的值增加100px即可。

或者,在 button 左邊放置一個(gè)空 view,默認(rèn)寬度為0,當(dāng)向右移動(dòng) Button 時(shí),重置空 View 的寬度即可,當(dāng)空 view 寬度增大時(shí),button 就自動(dòng)被擠向右邊,即實(shí)現(xiàn)了向右平移的效果。

重置一個(gè)View 的 LayoutParams 的代碼如下:

MarginLayoutParams layoutParams = (MarginLayoutParams) mButton.getLayoutParams();
layoutParams.width +=100;
layoutParams.leftMargin +=100;
mButton.requestLayout();
//或者mButton.setLayoutParams(layoutParams);

3.2.4 各種滑動(dòng)方式的對比

上面介紹了三種滑動(dòng)方式,總結(jié)如下:

  • scrollTo/scrollBy:操作簡單,適合對View內(nèi)容的滑動(dòng);
    優(yōu)點(diǎn):可以比較方便地實(shí)現(xiàn)滑動(dòng)效果并且不影響內(nèi)部元素的單擊事件。
    缺點(diǎn):只能滑動(dòng)View的內(nèi)容,并不能滑動(dòng)View本身。

  • 動(dòng)畫:操作簡單,適用于沒有交互的 View 和實(shí)現(xiàn)復(fù)雜的動(dòng)畫效果;
    優(yōu)點(diǎn):一些復(fù)雜的效果必須要通過動(dòng)畫才能實(shí)現(xiàn)。
    缺點(diǎn):使用View動(dòng)畫或者在Android3.0以下使用屬性動(dòng)畫,均不能改變View本身的屬性。

  • 改變布局參數(shù):操作稍微復(fù)雜,適用于有交互的 View。

下面來實(shí)現(xiàn)一個(gè)手滑動(dòng)的效果,自定義一個(gè) View,拖動(dòng)它可以在整個(gè)屏幕上隨意滑動(dòng),核心代碼如下:

    /* 
     * 重寫 onTouchEvent 方法并處理它的 ACTION_MOVE 事件,
     * 根據(jù)兩次滑動(dòng)之間的距離就可以實(shí)現(xiàn)它的滑動(dòng),
     * 為了實(shí)現(xiàn)全屏滑動(dòng),采用改變布局的方式來實(shí)現(xiàn)
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 1. 獲取手指當(dāng)前坐標(biāo)(注:不能使用getX和getY方法,
        // 因?yàn)檫@個(gè)是要全屏滑動(dòng)的,所以需要獲取當(dāng)前點(diǎn)擊事件在屏幕中的坐標(biāo)而不是相對于View本身的坐標(biāo))
        int x = (int) event.getRawX();
        int y = (int) event.getRawY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                break;

            case MotionEvent.ACTION_MOVE:
                // 2. 獲取兩次滑動(dòng)之間的位移
                int deltaX = x - mLastX;
                int deltaY = y - mLastY;
                // 3. 移動(dòng)(這里采用的是動(dòng)畫兼容庫 nineoldandroids 中的 ViewHelper類)
                int trabslationX = ViewHelper.getTranslationX(this) + deltaX;
                int trabslationY = ViewHelper.getTranslationY(this) + deltaY;
                ViewHelper.setTranslationX(this,trabslationX);
                ViewHelper.setTranslationY(this,trabslationY);
                break;

            case MotionEvent.ACTION_UP:
                break;
        }
        mLastX = x;
        mLastY = y;
        return true;
    }

3.3 彈性滑動(dòng)

實(shí)現(xiàn)彈性滑動(dòng)方法很多,其共同的思想是:將一次大的滑動(dòng)分成若干個(gè)小的滑動(dòng),并且在一個(gè)時(shí)間段完成。

下面介紹一些彈性滑動(dòng)的具體實(shí)現(xiàn)方式。

3.3.1 使用 Scroller

Scroller 工作原理概括:Scroller 本身并不能實(shí)現(xiàn) View 的滑動(dòng),需要配合 View 的 computeScroll 方法才能完成彈性滑動(dòng)的效果,它不斷的讓View重繪,而每一次重繪距滑動(dòng)起始時(shí)間會(huì)有一個(gè)時(shí)間間隔,通過這個(gè)時(shí)間間隔 Scroller 就能得出 View 當(dāng)前的滑動(dòng)位置,知道了滑動(dòng)位置就可以用ScrollTo方法來完成View的滑動(dòng)。

就這樣,View 的每一次重繪都會(huì)導(dǎo)致 View 進(jìn)行小幅度的滑動(dòng),而多次的小幅度滑動(dòng)組成了彈性滑動(dòng),這就是Scroller 的工作機(jī)制。

3.3.2 通過動(dòng)畫

動(dòng)畫本身就是一種漸進(jìn)的過程,因此通過它來實(shí)現(xiàn)滑動(dòng)天然就具有彈性效果。

如下代碼可讓一個(gè)view在100ms內(nèi)左移100像素:

ObjectAnimator.ofFloat(targetView, "translationX", 0, 100).setDuration(100).start();

可利用動(dòng)畫的特性來實(shí)現(xiàn)一些動(dòng)畫不能實(shí)現(xiàn)的效果,拿 scorllTo 來說,模仿 scroller 來實(shí)現(xiàn) View 的彈性滑動(dòng),那么利用動(dòng)畫的特性可用這樣做:

        final int startX = 0;
        final int deltaX = 100;
        // 本質(zhì)上沒有作用于任何對象上,只是在1000ms內(nèi)完成了整個(gè)動(dòng)畫過程
        final ValueAnimator animator = ValueAnimator.ofInt(0,1).setDuration(1000);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                // 在動(dòng)畫的每一幀到來時(shí)獲取動(dòng)畫完成的比例,
                // 根據(jù)這個(gè)比例計(jì)算出當(dāng)前View所要滑動(dòng)的距離
                // 其思想和Scroller類似,通過改變一個(gè)百分比配合scrolITo方法來完成View的滑動(dòng)
                float fraction = animator.getAnimatedFraction();
                mButton.scrollTo(startX + (int)(deltaX * fraction),0);
            }
        });
        animator.start();

3.3.3 使用延時(shí)策略

延時(shí)策略,其核心思想是通過發(fā)送一系列延時(shí)消息從而達(dá)到一種漸近式的效果,具體來說可以使用 Handler 或 View 的 postDelayed 方法,也可用線程的sleep方法。

對于 postDelayed 方法來說,可通過它來延時(shí)發(fā)送一個(gè)消息,然后在消息中來進(jìn)行View的滑動(dòng),如果接連不斷地發(fā)送這種延時(shí)消息,那么就可以實(shí)現(xiàn)彈性滑動(dòng)的效果。

對于 sleep 方法來說,通過在while循環(huán)中不斷的滑動(dòng)View和sleep,就可以實(shí)現(xiàn)彈性滑動(dòng)的效果。

下面采用Handler來做個(gè)示例(其他方法思想類似),在大約1000ms內(nèi)將View的內(nèi)容向左移動(dòng)了100像素,代碼如下:

    private static final  int MESSAGE_SCROLL_TO = 1;
    private static final  int FRAME_COUNT = 30;
    private static final  int DELAYED_TIME = 33;

    private int count = 1;

    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case MESSAGE_SCROLL_TO:
                    count++;
                    if(count <= FRAME_COUNT){
                        float fraction = count / (float)FRAME_COUNT;
                        int scrollX = (int)(fraction * 100);
                        mButton.scrollTo(scrollX,0);
                        handler.sendEmptyMessageDelayed(MESSAGE_SCROLL_TO,DELAYED_TIME);
                    }
                    break;
            }
        }
    };

本篇文章就介紹到這。

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

推薦閱讀更多精彩內(nèi)容