Android觸摸事件分發(fā)機(jī)制(1)之View

記得大家剛開(kāi)始接觸安卓的時(shí)候固翰,一個(gè)setOnClickListener就能實(shí)現(xiàn)一個(gè)View的點(diǎn)擊,當(dāng)時(shí)是如此的激動(dòng)~彤委。這大概就是大家對(duì)Android觸摸事件最初的接觸吧剂习。今天我們來(lái)聊下Android重要的觸摸事件分發(fā)機(jī)制揪垄。

例子

我們來(lái)舉一個(gè)栗子吧~

MyButton.java
public class MyButton extends Button  
{   
  
    @Override  
    public boolean onTouchEvent(MotionEvent event)  
    {  
        int action = event.getAction();  
        switch (action)  
        {  
        case MotionEvent.ACTION_DOWN:  
            Log.e("w", "onTouchEvent ACTION_DOWN");  
            break;  
        case MotionEvent.ACTION_MOVE:  
            Log.e("w", "onTouchEvent ACTION_MOVE");  
            break;  
        case MotionEvent.ACTION_UP:  
            Log.e("w", "onTouchEvent ACTION_UP");  
            break;  
        default:  
            break;  
        }  
        return super.onTouchEvent(event);  
    }  
  
    @Override  
    public boolean dispatchTouchEvent(MotionEvent event)  
    {  
        int action = event.getAction();  
  
        switch (action)  
        {  
        case MotionEvent.ACTION_DOWN:  
            Log.e("w", "dispatchTouchEvent ACTION_DOWN");  
            break;  
        case MotionEvent.ACTION_MOVE:  
            Log.e("w", "dispatchTouchEvent ACTION_MOVE");  
            break;  
        case MotionEvent.ACTION_UP:  
            Log.e("w", "dispatchTouchEvent ACTION_UP");  
            break;  
        default:  
            break;  
        }  
        return super.dispatchTouchEvent(event);  
    }  
} 

在onOutchEvent和disatchTouchEvent中打印日志

main_activity.xml
<LinearLayout 
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    >  
  
    <io.weimu.caoyang.MyButton  
        android:id="@+id/id_btn"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="click me" />  
  
</LinearLayout>  
MainActivity.java
public class MainActivity extends Activity  
{  
    private Button mButton ;  
    
    @Override  
    protected void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
      
    mButton = (Button) findViewById(R.id.id_btn); 
     
    mButton.setOnTouchListener(new OnTouchListener()  
    {  
        @Override  
        public boolean onTouch(View v, MotionEvent event)  
        {  
            int action = event.getAction();  
            switch (action)  
            {  
            case MotionEvent.ACTION_DOWN:  
                Log.e(“w”, "onTouch ACTION_DOWN");  
                break;  
            case MotionEvent.ACTION_MOVE:  
                Log.e(“w”, "onTouch ACTION_MOVE");  
                break;  
            case MotionEvent.ACTION_UP:  
                Log.e(“w”, "onTouch ACTION_UP");  
                break;  
            default:  
                break;  
            }  
              
            return false;  
        }  
    });  
    }  
}  

我們?cè)贛yButton設(shè)置了OnTouchListener監(jiān)聽(tīng)杀糯,

我們看一下Log的打印

標(biāo)志 消息
E/MyButton(879): dispatchTouchEvent ACTION_DOWN
E/MyButton(879): onTouch ACTION_DOWN
E/MyButton(879): onTouchEvent ACTION_DOWN
E/MyButton(879): dispatchTouchEvent ACTION_MOVE
E/MyButton(879): onTouch ACTION_MOVE
E/MyButton(879): onTouchEvent ACTION_MOVE
E/MyButton(879): dispatchTouchEvent ACTION_UP
E/MyButton(879): onTouch ACTION_UP
E/MyButton(879): onTouchEvent ACTION_UP`

按照上面的簡(jiǎn)單實(shí)例我們可以簡(jiǎn)單得出一個(gè)結(jié)論:View的事件分發(fā)無(wú)論DOWN济竹、MOVE、UP都會(huì)經(jīng)過(guò)dispatchTouchEventonTouch(如果設(shè)置的話)盈简、onTouchEvent


源碼解讀:

Step1 View

public boolean dispatchTouchEvent(MotionEvent event) {
    ...
    boolean result = false;
    ...
    
    //重點(diǎn)判斷onTouch
    if (li != null && li.mOnTouchListener != null
            && (mViewFlags & ENABLED_MASK) == ENABLED
            && li.mOnTouchListener.onTouch(this, event)) {
        result = true;
    }
    
    //重點(diǎn)判斷onTouchEvent
    if (!result && onTouchEvent(event)) {
        result = true;
    }
    ...
    return result;
}

先判斷mOnTouchListener是否為空艰额?改View是否為Enable陶因?onTouch是否返回true堰汉?若三個(gè)同時(shí)成立,返回true,且onTouchEvent不會(huì)執(zhí)行费什。

mOnTouchListener從哪里來(lái)呢钾恢?

 public void setOnTouchListener(OnTouchListener l) {  
     mOnTouchListener = l;  
 }  

可以看到這就是栗子中Activity.java里mButton.setOnTouchListener設(shè)置的。

如果我們?cè)O(shè)置了onTouchListener鸳址,且設(shè)置返回為true瘩蚪,那么View的onTouchEvent就不會(huì)執(zhí)行!

Step2 View

public boolean onTouchEvent(MotionEvent event) {  
    final int viewFlags = mViewFlags;  
    
    //情況1:如果view為disenable且可點(diǎn)擊,返回true
    //此情況還是會(huì)消費(fèi)此觸摸事件稿黍,只是不做反應(yīng)罷了
    if ((viewFlags & ENABLED_MASK) == DISABLED) { 
        return (((viewFlags & CLICKABLE) == CLICKABLE ||  
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
    }  
    
    ...
    
    //情況2:如果View為enable且可點(diǎn)擊疹瘦,返回ture
    //大部分的觸摸操縱都在這里面
    if (((viewFlags & CLICKABLE) == CLICKABLE ||  
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { 
       
        switch (event.getAction()) {  
            case MotionEvent.ACTION_UP:  
                //這是Part02 
                break;  
            case MotionEvent.ACTION_DOWN:  
                //這是Part01 
                break;  
            case MotionEvent.ACTION_CANCEL:  
                //這是Part04
                break;  
            case MotionEvent.ACTION_MOVE:  
                //這是Part03                       
                break;  
        }  
        return true;  
    }  
    
    //情況3:如果View為enable但不能點(diǎn)擊,直接返回false
    return false;  
}

在onTouchEvent工有3個(gè)主要情況:

  • 情況1:如果view為disEnable且clickable,返回true巡球。此情況還是會(huì)消費(fèi)此觸摸事件言沐,只是不做反應(yīng)
  • 情況2:如果View為enable且clickable,返回ture酣栈。大部分的觸摸操縱都在這里面
  • 情況3:如果View為enable但unClickable险胰,直接返回false。其實(shí)就是View為unClickable矿筝,基本就是返回false了起便。

view的enable和clickable都可以在java和xml設(shè)置。

以上代碼可以看出窖维,onToucheEvent里的重點(diǎn)操作都在switch里了榆综,這里我們分幾個(gè)步驟進(jìn)行分析

Part01 ACTION_DOWN
case MotionEvent.ACTION_DOWN:  
    if (mPendingCheckForTap == null) {  
        mPendingCheckForTap = new CheckForTap();  
    }  
    mPrivateFlags |= PREPRESSED;  
    mHasPerformedLongPress = false;  
    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
break;  
  1. 初始化CheckForTap,此類為Runnable
  2. 給mPrivateFlags設(shè)置一個(gè)PREPRESSED的標(biāo)識(shí)
  3. 設(shè)置mHasPerformedLongPress=false;表示長(zhǎng)按事件還未觸發(fā)铸史;
  4. 發(fā)送一個(gè)延遲為ViewConfiguration.getTapTimeout()=115的延遲消息鼻疮,到達(dá)延時(shí)時(shí)間后會(huì)執(zhí)行CheckForTap()里面的run方法:
CheckForTap
private final class CheckForTap implements Runnable {  
  public void run() {  
      mPrivateFlags &= ~PREPRESSED;  
      mPrivateFlags |= PRESSED;  
      refreshDrawableState();  
      //檢測(cè)長(zhǎng)按
      if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {  
          postCheckForLongClick(ViewConfiguration.getTapTimeout());  
      }  
  }  
}
  1. 取消mPrivateFlags的PREPRESSED
  2. 設(shè)置PRESSED標(biāo)識(shí)
  3. 刷新背景
  4. 如果View支持長(zhǎng)按事件,則再發(fā)一個(gè)延時(shí)消息琳轿,檢測(cè)長(zhǎng)按陋守;

具體如何檢測(cè)長(zhǎng)按呢?

private void postCheckForLongClick(int delayOffset) {  
       mHasPerformedLongPress = false;  
  
       if (mPendingCheckForLongPress == null) {  
           mPendingCheckForLongPress = new CheckForLongPress();  
       }  
       mPendingCheckForLongPress.rememberWindowAttachCount();  
       postDelayed(mPendingCheckForLongPress,  
               ViewConfiguration.getLongPressTimeout() - delayOffset);  
   } 
  1. 初始化CheckForLongPress利赋,此類為Runnable
  2. 發(fā)送一個(gè)延遲為ViewConfiguration.getLongPressTimeout() - delayOffset=(500-115=385)的延遲消息,到達(dá)延時(shí)時(shí)間后會(huì)執(zhí)行CheckForLongPress()里面的run方法:
CheckForLongPress
class CheckForLongPress implements Runnable {  
  
    private int mOriginalWindowAttachCount;  
  
    public void run() {  
        if (isPressed() && (mParent != null)  
                && mOriginalWindowAttachCount == mWindowAttachCount) {  
            if (performLongClick()) {  
                mHasPerformedLongPress = true;  
            }  
        }  
    }

經(jīng)過(guò)一系列判斷猩系,最終調(diào)用performLongClick()即長(zhǎng)按的接口調(diào)用媚送。

這里我們可以得出一個(gè)小結(jié)論:

當(dāng)用戶點(diǎn)擊視圖時(shí),超過(guò)500ms后且設(shè)置了長(zhǎng)按監(jiān)聽(tīng)的話寇甸,會(huì)觸發(fā)長(zhǎng)按監(jiān)聽(tīng)接口塘偎!

Wonder疑問(wèn)

  1. 那當(dāng)用戶在500ms內(nèi)將手抬起會(huì)是什么情況呢疗涉?
  2. LongClick已經(jīng)有了,那我們平時(shí)使用的的Click呢吟秩?
Part02 ACTION_UP
case MotionEvent.ACTION_UP: 
 
    //判斷mPrivateFlags是否包含PREPRESSED
    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
    
   //如果包含PRESSED或者PREPRESSED則進(jìn)入執(zhí)行體咱扣,在115ms的前后抬起都會(huì)進(jìn)入執(zhí)行體。
    if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
    
        //如果該視圖還未獲取焦點(diǎn)涵防,則獲之
        boolean focusTaken = false;  
        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
            focusTaken = requestFocus();  
        }  
        
        //判斷是否為長(zhǎng)按狀態(tài)闹伪,不是的話,進(jìn)入執(zhí)行體
        if (!mHasPerformedLongPress) {  
        
            //這是一個(gè)輕點(diǎn)擊操作壮池,所以要移除長(zhǎng)按檢測(cè)操作
            removeLongPressCallback();  
  
            //只有在按下?tīng)顟B(tài)時(shí)才執(zhí)行點(diǎn)擊動(dòng)作
            if (!focusTaken) { 
             
                //使用Runnable進(jìn)行發(fā)送消息偏瓤,而不是直接執(zhí)行performClick。
                //這樣視圖可以在點(diǎn)擊操作前更新其可視化狀態(tài) 
                
                if (mPerformClick == null) {  
                    mPerformClick = new PerformClick();//*重點(diǎn)01*
                }
                
                //重點(diǎn) * 調(diào)用平時(shí)用的click
                if (!post(mPerformClick)) {  
                    performClick(); 
                }  
            }  
        }  
  
        if (mUnsetPressedState == null) {  
            mUnsetPressedState = new UnsetPressedState();//*重點(diǎn)02*
        }  
        
        //根據(jù)視圖的mPrivateFlags的狀態(tài)進(jìn)行操作
        if (prepressed) {  
            mPrivateFlags |= PRESSED;  
            refreshDrawableState();  
            postDelayed(mUnsetPressedState,  
                    ViewConfiguration.getPressedStateDuration());  
        } else if (!post(mUnsetPressedState)) {  
            // If the post failed, unpress right now  
            mUnsetPressedState.run();  
        }  
        //移除點(diǎn)擊事件的檢測(cè)操作
        removeTapCallback();  
    }  
    break;  

mPrivateFlags的狀態(tài):125ms前為prepressed(點(diǎn)擊前),125ms后位pressed(點(diǎn)擊后)椰憋。以上代碼已經(jīng)做了注釋厅克。這些操作就是500ms內(nèi)的點(diǎn)擊操作處理。

以上有兩個(gè)比較重要的點(diǎn)橙依,這里分析一下:

PerformClick
private final class PerformClick implements Runnable {
    @Override
    public void run() {
        performClick();
    }
}

public boolean performClick() {
    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        li.mOnClickListener.onClick(this);//<----------這里
        result = true;
    } else {
        result = false;
    }

    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    return result;
}
    
//設(shè)置點(diǎn)擊事件回調(diào)
public void setOnClickListener(OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);
    }
    getListenerInfo().mOnClickListener = l;
}

以上代碼可以很清楚的看到onClick的調(diào)用证舟!

UnsetPressedState
private final class UnsetPressedState implements Runnable {
    public void run() {
        setPressed(false);
    }
}
public void setPressed(boolean pressed) {
    if (pressed) {
        mPrivateFlags |= PRESSED;
    } else {
        mPrivateFlags &= ~PRESSED;
    }
    refreshDrawableState();
    dispatchSetPressed(pressed);
}

我們可以看到無(wú)論如何這個(gè)Runnable都會(huì)執(zhí)行,只是對(duì)不同的狀態(tài)(prePressed,pressed)進(jìn)行處理窗骑。修改mPrivateFlags的狀態(tài)女责,刷新背景,分發(fā)SetPress等慧域。

這里我們可以得出一個(gè)小結(jié)論:

當(dāng)用戶點(diǎn)擊視圖時(shí)鲤竹,低于500ms設(shè)置onClick的接口,就會(huì)觸發(fā)onClick的接口昔榴。且這個(gè)過(guò)程是在OnTouchEvent的ACTION_UP完成辛藻。

Part03 ACTION_MOVE
case MotionEvent.ACTION_MOVE:  
    final int x = (int) event.getX();  
    final int y = (int) event.getY();  
  
    //判斷該觸摸事件是否已經(jīng)移出控件外
    int slop = mTouchSlop;  
    if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
            (y < 0 - slop) || (y >= getHeight() + slop)) {  
             
        //當(dāng)觸摸移出當(dāng)前視圖
        //移除點(diǎn)擊回調(diào)
        removeTapCallback();  
        if ((mPrivateFlags & PRESSED) != 0) {  
            //移除長(zhǎng)按檢測(cè) 
            removeLongPressCallback();  
            
            //mPrivateFlags去除PRESSED標(biāo)志
            mPrivateFlags &= ~PRESSED;  
            
            //刷新背景
            refreshDrawableState();  
        }  
    }  
    break; 

ACTION_MOVE的工作相對(duì)簡(jiǎn)單一點(diǎn):不斷的記錄x,y。判斷當(dāng)前觸摸事件是否已經(jīng)移除當(dāng)前控件之外互订?如果移除了吱肌,移除相對(duì)應(yīng)的檢測(cè)回調(diào),以及刷新相對(duì)應(yīng)的變量和背景仰禽。

Part04 ACTION_CANCLE
case MotionEvent.ACTION_CANCEL:  
    mPrivateFlags &= ~PRESSED;  
    refreshDrawableState();  
    removeTapCallback();  
    break;

ACTION_CANCEL的工作主要是:刷新相對(duì)應(yīng)的變量和背景氮墨,移除響度應(yīng)的檢測(cè)回調(diào)。一般遇到的比較少吐葵。有一種情況是:當(dāng)用戶保持按下操作规揪,并從你的控件轉(zhuǎn)移到外層控件時(shí),會(huì)觸發(fā)ACTION_CANCEL温峭。

總結(jié) Summary

  1. View的事件轉(zhuǎn)發(fā)流程為:View.dispatchEvent->View.setOnTouchListener->View.onTouchEvent
  2. 在dispatchTouchEvent中會(huì)進(jìn)行OnTouchListener的判斷猛铅,如果OnTouchListener不為null且返回true,則表示事件被消費(fèi)凤藏,onTouchEvent不會(huì)被執(zhí)行奸忽;否則執(zhí)行onTouchEvent堕伪。
  3. 長(zhǎng)按點(diǎn)擊的回調(diào)是在ACTION_DOWN調(diào)用的。
  4. 輕按點(diǎn)擊的回調(diào)是在ACTION_UP調(diào)用的栗菜。
  5. 判斷觸摸事件是否移除了當(dāng)前控件是在ACTION_MOVE監(jiān)聽(tīng)的欠雌。

額外 Extra

我們?cè)趤?lái)舉一個(gè)栗子:

public class MainActivity extends Activity  
{  
    private Button mButton ;  
    @Override  
    protected void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
          
        mButton = (Button) findViewById(R.id.id_btn);  
        mButton.setOnClickListener(new OnClickListener()  
        {  
            @Override  
            public void onClick(View v)  
            {  
                Log.e("e", "輕觸點(diǎn)擊");  
            }  
        });  
          
        mButton.setOnLongClickListener(new OnLongClickListener()  
        {  
            @Override  
            public boolean onLongClick(View v)  
            {  
                Log.e("e", "長(zhǎng)按點(diǎn)擊");   
                return false;  
            }  
        });  
    }    
} 

如果onLongClick返回的是ture(表示消費(fèi)了),則onClick無(wú)法觸發(fā)。如果返回false,就可以疙筹。大家可以結(jié)合下上面的代碼解析看看為什么會(huì)這樣~

Android觸摸事件分發(fā)機(jī)制(2)之ViewGroup


PS:本文整理自以下文章富俄,若有發(fā)現(xiàn)問(wèn)題請(qǐng)致郵 caoyanglee92@gmail.com
工匠若水 Android觸摸屏事件派發(fā)機(jī)制詳解與源碼分析一(View篇)
Hohohong Android View 事件分發(fā)機(jī)制 源碼解析 (上)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市腌歉,隨后出現(xiàn)的幾起案子蛙酪,更是在濱河造成了極大的恐慌,老刑警劉巖翘盖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桂塞,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡馍驯,警方通過(guò)查閱死者的電腦和手機(jī)阁危,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)汰瘫,“玉大人狂打,你說(shuō)我怎么就攤上這事』烀郑” “怎么了趴乡?”我有些...
    開(kāi)封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蝗拿。 經(jīng)常有香客問(wèn)我晾捏,道長(zhǎng),這世上最難降的妖魔是什么哀托? 我笑而不...
    開(kāi)封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任惦辛,我火速辦了婚禮,結(jié)果婚禮上仓手,老公的妹妹穿的比我還像新娘胖齐。我一直安慰自己,他們只是感情好嗽冒,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布呀伙。 她就那樣靜靜地躺著,像睡著了一般添坊。 火紅的嫁衣襯著肌膚如雪区匠。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音驰弄,去河邊找鬼。 笑死速客,一個(gè)胖子當(dāng)著我的面吹牛戚篙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播溺职,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼岔擂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了浪耘?” 一聲冷哼從身側(cè)響起乱灵,我...
    開(kāi)封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎七冲,沒(méi)想到半個(gè)月后痛倚,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡澜躺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年蝉稳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掘鄙。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡耘戚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出操漠,到底是詐尸還是另有隱情收津,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布浊伙,位于F島的核電站撞秋,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏吧黄。R本人自食惡果不足惜部服,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拗慨。 院中可真熱鬧廓八,春花似錦、人聲如沸赵抢。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)烦却。三九已至宠叼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背冒冬。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工伸蚯, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人简烤。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓剂邮,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親横侦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挥萌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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