『Android自定義View實(shí)戰(zhàn)』自定義炫酷側(cè)滑解鎖效果

前言

在App中常見(jiàn)的解鎖動(dòng)畫(huà)有很多種,側(cè)滑解鎖也是較為常見(jiàn)的一種解鎖交互行為,例如我們常見(jiàn)的側(cè)滑驗(yàn)證登陸猩系,一個(gè)比較長(zhǎng)的橫條,里面嵌套著一個(gè)滑塊中燥,手指從左至右拖動(dòng)完成驗(yàn)證寇甸。于是決定自己造一個(gè),先來(lái)看下最終效果:


側(cè)滑解鎖最終效果.gif

?

實(shí)現(xiàn)

思路

從動(dòng)畫(huà)的組成來(lái)看疗涉,可以分為幾部分拿霉,分別是背景色條、滑塊樣式咱扣、文本樣式绽淘、滑塊滑出之后左側(cè)的背景樣式、背景和滑塊背景自然不用說(shuō)闹伪,直接通過(guò)繪制矩形即可收恢,然后右邊的文字掃光效果可以通過(guò)屬性動(dòng)畫(huà)結(jié)合漸變器去實(shí)現(xiàn),滑塊上的箭頭透明度可以通過(guò)屬性動(dòng)畫(huà)實(shí)現(xiàn)祭往,然后通過(guò)判斷觸摸事件的區(qū)域來(lái)控制滑塊滑動(dòng)時(shí)的邏輯伦意。

1.繪制背景矩形
2.繪制滑塊及滑塊上的箭頭
3.繪制提示文案
4.在手指觸摸事件中判斷是否點(diǎn)擊了滑塊,并處理滑動(dòng)邏輯
5.箭頭動(dòng)畫(huà)及文字掃光

封面圖

?

1.繪制背景矩形

背景條可以直接取整個(gè)View的背景寬高作為自己的寬高硼补,并且設(shè)置圓角參數(shù)驮肉,通過(guò)drawRoundRect繪制圓角矩形條:

class ProfileSlideView : View {

    private lateinit var mBgPaint: Paint
    private lateinit var mBgRectF: RectF
    /**
     * 側(cè)滑條圓角度數(shù)
     */
    private var mCorner: Float = 0f

    //...此處省略構(gòu)造方法及初始化代碼

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        val viewWidth = right - left
        val viewHeight = bottom - top

        mBgRectF.left = 0f
        mBgRectF.top = 0f
        mBgRectF.right = viewWidth * 1.0f
        mBgRectF.bottom = viewHeight * 1.0f
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        //繪制背景
        drawBackground(canvas)
    }

    private fun drawBackground(canvas: Canvas) {
        canvas.drawRoundRect(mBgRectF, mCorner, mCorner, mBgPaint)
    }
}

效果如下:


背景色條.png

?

2.繪制滑塊及滑塊上的箭頭

滑塊的其實(shí)本質(zhì)也是一個(gè)圓角矩形,且度數(shù)與背景條是一致的已骇,只是做了個(gè)顏色上的區(qū)分:

private fun drawSlider(canvas: Canvas) {
      mSliderPaint.style = Paint.Style.FILL
      mSliderPaint.color = mSliderColor
      canvas.drawRoundRect(mSliderRectF, mCorner, mCorner, mSliderPaint)
}

這里有個(gè)細(xì)節(jié)离钝,需要給滑塊與背景條之間,有一個(gè)Padding值褪储,使得滑塊有一種內(nèi)嵌在里面的感覺(jué)卵渴,可以直接在設(shè)置滑塊的RectF的時(shí)候計(jì)算好這個(gè)間距:

 override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
    super.onLayout(changed, left, top, right, bottom)
    val viewWidth = right - left
    val viewHeight = bottom - top

    mBgRectF.left = 0f
    mBgRectF.top = 0f
    mBgRectF.right = viewWidth * 1.0f
    mBgRectF.bottom = viewHeight * 1.0f
    //mPadding為滑塊與背景條之間的邊距
    mSliderRectF.left = mPadding
    mSliderRectF.top = mPadding
    mSliderRectF.right = mSliderWidth
    mSliderRectF.bottom = viewHeight * 1.0f - mPadding
}

這里用來(lái)繪制滑塊的RectF也是等會(huì)兒做滑動(dòng)邏輯的關(guān)鍵之處。現(xiàn)在效果如下:

滑塊.png

接下來(lái)繪制滑塊上的三個(gè)箭頭鲤竹,一個(gè)箭頭由3點(diǎn)2線組成浪读,且是有一定規(guī)律的,比如中間的這個(gè)箭頭辛藻,箭頭中心點(diǎn)的y坐標(biāo)肯定是滑塊高度的一半碘橘,箭頭上頂點(diǎn)定為滑塊高度的1/3,下頂點(diǎn)定為滑塊高度的2/3吱肌,也就是最終整個(gè)箭頭的高度占據(jù)滑塊高度的1/3痘拆,這是目前調(diào)整的一個(gè)比較合適的比例。然后它們的橫坐標(biāo)也是根據(jù)滑塊的寬度以及箭頭的寬度進(jìn)行計(jì)算氮墨,如下圖:

滑塊箭頭坐標(biāo)示意圖.png

由于三個(gè)箭頭是水平并排的纺蛆,所以其他兩個(gè)箭頭縱坐標(biāo)也跟這個(gè)箭頭是一致的吐葵,只是橫坐標(biāo)有一定的偏移量,最終代碼如下:

mSliderPaint.style = Paint.Style.STROKE
mSliderPaint.strokeWidth = 2f
//箭頭寬度為滑塊的1/16
val arrowWidth = mSliderWidth / 16f
//遍歷下標(biāo)1到3桥氏,依次繪制3個(gè)箭頭
for (index in 1..3) {
    mSlideArrowPath.reset()
    //這里使得3個(gè)箭頭的橫坐標(biāo)依次在滑塊的1/3,1/2,2/3處
    val arrowPos = (index + 1) / 6f
    mSlideArrowPath.moveTo(mSliderRectF.left + mSliderWidth * arrowPos - arrowWidth / 2, mSliderRectF.bottom / 3f)
    mSlideArrowPath.lineTo(mSliderRectF.left + mSliderWidth * arrowPos + arrowWidth, mSliderRectF.bottom / 2f)
    mSlideArrowPath.lineTo(mSliderRectF.left + mSliderWidth * arrowPos - arrowWidth / 2, mSliderRectF.bottom * 2 / 3f)
    canvas.drawPath(mSlideArrowPath, mSliderPaint)
}

?
最終繪制出來(lái)的效果如下:


滑塊箭頭.png

?

3.繪制提示文案

剛才已經(jīng)繪制好了背景和滑塊折联,接下來(lái)繪制文字,由于滑塊已經(jīng)占據(jù)了一部分位置识颊,如果讓文案居中的話(huà)诚镰,會(huì)被遮擋住,所以將文案的位置定在除滑塊之外的剩余區(qū)域的中間祥款。繪制文字就直接采用 CanvasdrawText() 就可以了:

private fun drawTipText(canvas: Canvas) {
    val textWidth = mTextPaint.measureText("滑動(dòng)通過(guò)驗(yàn)證")
    val fontMetrics = mTextPaint.fontMetrics
    val baseLine: Float = mBgRectF.height() * 0.5f - (fontMetrics.ascent + fontMetrics.descent) / 2
    canvas.drawText(mTipText, ((mBgRectF.width() - mSliderWidth - textWidth) / 2f) + mSliderWidth, baseLine, mTextPaint)
}

注意由于要讓文本真正意義上的居中清笨,所以需要根據(jù)文本的長(zhǎng)度和文字的基準(zhǔn)線進(jìn)行計(jì)算,(fontMetrics.ascent + fontMetrics.descent) / 2可以得到文字在豎直方向的中點(diǎn)刃跛。
效果如下:

滑動(dòng)提示文案.png

?

4.在手指觸摸事件中判斷是否點(diǎn)擊了滑塊抠艾,并處理滑動(dòng)邏輯

上面已經(jīng)完成了基本的繪制部分,完成了架子桨昙,還需要注入靈魂——滑動(dòng)解鎖检号,涉及到滑動(dòng)相關(guān),肯定是需要用到View的觸摸事件蛙酪,我們可以重寫(xiě)onTouchEvent方法齐苛,在觸發(fā)MotionEvent.ACTION_DOWN事件的時(shí)候,判斷觸摸的點(diǎn)的坐標(biāo)是否落在滑塊里面(這個(gè)時(shí)候就用到了前文提到的滑塊的RectF了):

override fun onTouchEvent(event: MotionEvent): Boolean {
    val x = event.x
    val y = event.y
    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            if (!checkTouchSlide(x, y)) {
                return false
            }
        }
        MotionEvent.ACTION_MOVE -> {
                
        }
        MotionEvent.ACTION_UP -> {
              
        }
    }
    return true
}

private fun checkTouchSlide(x: Float, y: Float): Boolean {
    return mSliderRectF.contains(x, y)
}

如果不落在滑塊的RectF里面的話(huà)桂塞,ACTION_DOWN事件就返回false凹蜂,不消費(fèi)此次觸摸事件。
接著是按住滑塊時(shí)的處理阁危,假如ACTION_DOWN事件滿(mǎn)足條件了玛痊,則需要開(kāi)始處理ACTION_MOVE事件,滑塊的移動(dòng)應(yīng)該是與手指左右拖動(dòng)的距離同步狂打,那我們就用一個(gè)變量來(lái)記錄上一次事件觸摸點(diǎn)的橫坐標(biāo)擂煞,然后與當(dāng)前觸摸的橫坐標(biāo)做差值,即可得到偏移量趴乡,然后再用這個(gè)偏移量去改變滑塊的RectF的left和right对省,就能實(shí)現(xiàn)拖動(dòng)的效果了

MotionEvent.ACTION_MOVE -> {
        if (mLastTouchX > 0f) {
            when {
                //判斷是否滑到了整個(gè)View的最右邊
                mSliderRectF.left + x - mLastTouchX + mSliderWidth > mBgRectF.width() - mPadding -> {
                    mSliderRectF.left = mBgRectF.width() - mPadding - mSliderWidth
                    mSliderRectF.right = mSliderRectF.left + mSliderWidth
                }
                ////判斷是否滑到了整個(gè)View的最左邊
                mSliderRectF.left + x - mLastTouchX < mPadding -> {
                    mSliderRectF.left = mPadding
                    mSliderRectF.right = mSliderRectF.left + mSliderWidth
                }
                else -> {
                     mSliderRectF.left += x - mLastTouchX
                     mSliderRectF.right = mSliderRectF.left + mSliderWidth
                }
            }
            invalidate()
        }
        mLastTouchX = x
}

這里有個(gè)要注意的點(diǎn),滑動(dòng)的時(shí)候需要處理臨界值浙宜,如果滑塊已經(jīng)滑到了最左邊或最右邊官辽,要限制不能讓它超過(guò)邊界,另外記得每個(gè)ACTION_MOVE事件處理完之后都需要調(diào)用invalidate()通知View刷新繪制粟瞬。

此時(shí)的效果如下:


滑塊拖動(dòng).gif

雖然已經(jīng)可以拖動(dòng)了,但發(fā)現(xiàn)有什么不對(duì)勁的地方萤捆,滑到一半裙品,松開(kāi)手俗批,滑塊就停在了那個(gè)位置,這不符合我們想要的交互市怎,正常的交互應(yīng)該是松開(kāi)手指的時(shí)候岁忘,如果滑塊已經(jīng)滑過(guò)橫條的一半距離,就自動(dòng)滑到右端区匠,反之自動(dòng)滑到左端干像,自動(dòng)滑動(dòng)的話(huà)就需要結(jié)合屬性動(dòng)畫(huà)來(lái)實(shí)現(xiàn)了:

val mAutoSlideAnimator = ValueAnimator()
mAutoSlideAnimator.duration = 500L
mAutoSlideAnimator.addUpdateListener {
    mSliderRectF.left = it.animatedValue as Float
    mSliderRectF.right = mSliderRectF.left + mSliderWidth
    mProgressRectF.right = mSliderRectF.right
    invalidate()
}

根據(jù)動(dòng)畫(huà)的進(jìn)度值,實(shí)時(shí)更新滑塊RectF的left和right驰弄,然后接著便是在MotionEvent.ACTION_UP事件中進(jìn)行判斷并啟動(dòng)動(dòng)畫(huà):

MotionEvent.ACTION_UP -> {
    if (mSliderRectF.centerX() > mBgRectF.centerX()) {
         mAutoSlideAnimator.setFloatValues(mSliderRectF.left, mBgRectF.right - mPadding - mSliderWidth)
        mAutoSlideAnimator.start()
    } else {
        mAutoSlideAnimator.setFloatValues(mSliderRectF.left, mPadding)
        mAutoSlideAnimator.start()
    }
}

mSliderRectFenterXmBgRectFcenterX進(jìn)行比較麻汰,并以滑塊當(dāng)前的位置作為動(dòng)畫(huà)的起始點(diǎn),View的左右邊緣作為動(dòng)畫(huà)的終點(diǎn)戚篙,啟動(dòng)動(dòng)畫(huà):

滑塊拖動(dòng)自動(dòng)歸位.gif

?

5.箭頭動(dòng)畫(huà)及文字掃光

上面已經(jīng)實(shí)現(xiàn)了大體的功能五鲫,但還不足矣,我們可以再給它增添一些效果岔擂,滑塊的三個(gè)箭頭可以做一個(gè)透明度的逐個(gè)變化位喂,文字可以加一個(gè)掃光的效果。

箭頭動(dòng)畫(huà)

箭頭的透明度可以用一個(gè)動(dòng)畫(huà)器配合十六進(jìn)制色值的前兩位透明度進(jìn)行調(diào)整:

val mArrowAlphaAnimator = ValueAnimator()
mArrowAlphaAnimator.setFloatValues(1F, 3F)
mArrowAlphaAnimator.repeatCount = -1
mArrowAlphaAnimator.duration = 800
mArrowAlphaAnimator.addUpdateListener {
    mAlphaProgress = it.animatedValue as Float
    invalidate()
}

//遍歷下標(biāo)1到3乱灵,依次繪制3個(gè)箭頭
for (index in 1..3) {
    val alphaValue = if (index + mAlphaProgress >= 4) {
        index + mAlphaProgress - 3
    } else {
        (index + mAlphaProgress).coerceAtMost(3F)
    }
    mSlideArrowPath.reset()
    val colorBuilder = StringBuilder()
    colorBuilder.clear()
    colorBuilder.append("#")
    colorBuilder.append(Integer.toHexString((85 * alphaValue).toInt()))
    colorBuilder.append("FFFFFF")
    mSliderPaint.color = Color.parseColor(colorBuilder.toString())
    //這里使得3個(gè)箭頭的橫坐標(biāo)依次在滑塊的1/3,1/2,2/3處
    val arrowPos = (index + 1) / 6f
    mSlideArrowPath.moveTo(mSliderRectF.left + mSliderWidth * arrowPos - arrowWidth / 2, mSliderRectF.bottom / 3f)
    mSlideArrowPath.lineTo(mSliderRectF.left + mSliderWidth * arrowPos + arrowWidth, mSliderRectF.bottom / 2f)
    mSlideArrowPath.lineTo(mSliderRectF.left + mSliderWidth * arrowPos - arrowWidth / 2, mSliderRectF.bottom * 2 / 3f)
    canvas.drawPath(mSlideArrowPath, mSliderPaint)
}

mAlphaProgress從1到3一直循環(huán)變化塑崖,然后由于顏色透明度的范圍是0~255,所以分為三等分即是85一份痛倚,通過(guò)Integer.toHexString即可快速轉(zhuǎn)換為十六進(jìn)制弃舒,拼接上#號(hào)和后面的色值,即可得到最終的一個(gè)顏色状原,將其賦給繪制箭頭的畫(huà)筆即可聋呢。當(dāng)然由于是onDraw,所以最好用StringBuilder去拼接颠区。

文字掃光效果

我們可以先為文字的畫(huà)筆設(shè)置一個(gè)線性漸變的Shader削锰,顏色設(shè)置為灰-白-灰的過(guò)渡:

val mLinearGradient = LinearGradient(0F, 0F, viewWidth * 1.0F, 0F, intArrayOf(Color.GRAY, Color.WHITE, Color.GRAY), null, Shader.TileMode.CLAMP)
mTextPaint.shader = mLinearGradient

加好了“光”,還需要讓“光”左右動(dòng)起來(lái)毕莱,可以用一個(gè)Matrix矩陣器贩,通過(guò)對(duì)矩陣進(jìn)行平移,然后設(shè)置給mLinearGradient

val mTextAnimator = ValueAnimator()
mTextAnimator.setFloatValues(0F, 1F)
mTextAnimator.repeatCount = -1
mTextAnimator.duration = 2000
mTextAnimator.addUpdateListener {
    mGradientProgress = it.animatedValue as Float
}
//...
//繪制文字時(shí)
mGradientMatrix.setTranslate(mGradientProgress * mBgRectF.width(), 0F)
mLinearGradient.setLocalMatrix(mGradientMatrix)

最終效果:


箭頭動(dòng)畫(huà)文字掃光.gif

?

結(jié)語(yǔ)

總體效果大概如此朋截,當(dāng)然由于場(chǎng)景的不同蛹稍,會(huì)有一些調(diào)整的地方,比如滑塊自動(dòng)歸位的一個(gè)判斷點(diǎn)部服,不一定是在View的中間唆姐,另外滑動(dòng)成功,甚至還可以在滑塊上加一個(gè)類(lèi)似打勾的效果廓八,讓整個(gè)動(dòng)畫(huà)交互體驗(yàn)更棒奉芦,后面會(huì)再進(jìn)一步優(yōu)化赵抢,由于篇幅有限,一些非關(guān)鍵細(xì)節(jié)就不再詳細(xì)闡述声功,完整代碼已上傳到 一個(gè)集合酷炫效果的自定義組件庫(kù)烦却,歡迎Issue。
?

歡迎關(guān)注 Android小Y 的簡(jiǎn)書(shū)先巴,更多Android精選自定義View

『Android自定義View實(shí)戰(zhàn)』實(shí)現(xiàn)一個(gè)小清新的彈出式圓環(huán)菜單
『Android自定義View實(shí)戰(zhàn)』玩轉(zhuǎn)PathMeasure之自定義支付結(jié)果動(dòng)畫(huà)
『Android自定義View實(shí)戰(zhàn)』自定義弧形旋轉(zhuǎn)菜單欄——衛(wèi)星菜單
『Android自定義View實(shí)戰(zhàn)』自定義帶入場(chǎng)動(dòng)畫(huà)的弧形百分比進(jìn)度條

GitHubGitHub-ZJYWidget
CSDN博客IT_ZJYANG
簡(jiǎn) 書(shū)Android小Y
GitHub 上建了一個(gè)集合炫酷自定義View的項(xiàng)目其爵,里面有很多實(shí)用的自定義View源碼及demo,會(huì)長(zhǎng)期維護(hù)伸蚯,歡迎Star~ 如有不足之處或建議還望指正摩渺,相互學(xué)習(xí),相互進(jìn)步朝卒,如果覺(jué)得不錯(cuò)動(dòng)動(dòng)小手點(diǎn)個(gè)喜歡证逻, 謝謝~

最后編輯于
?著作權(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)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)徒像,“玉大人黍特,你說(shuō)我怎么就攤上這事【庵” “怎么了灭衷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)旁涤。 經(jīng)常有香客問(wèn)我翔曲,道長(zhǎng),這世上最難降的妖魔是什么劈愚? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任瞳遍,我火速辦了婚禮,結(jié)果婚禮上菌羽,老公的妹妹穿的比我還像新娘掠械。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布份蝴。 她就那樣靜靜地躺著犁功,像睡著了一般氓轰。 火紅的嫁衣襯著肌膚如雪婚夫。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 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)封第一講書(shū)人閱讀 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)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至遂庄,卻和暖如春寥院,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背涛目。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 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