[Android]朝花夕拾之仿MIUI时钟效果绘制

本文为原创文章,转载请注明出处,原创不易,且转且珍惜

1. 前言

几年前做过一个类似MIUI时钟的效果,逻辑比较简单,虽然MIUI经过几年的系统迭代,时钟早已不是这个效果,但当时做需求时涉及到一些canvas绘制的技巧,想来还是有些意思,并且这些思路如果最近把代码翻了出来,回顾一下当时的想法和策略。

2.效果

俗话说的好,没有图你说个XX,先把效果图发出来看一下:


miui[00_00_03--00_00_23].gif

3. 用到的知识点

3.1. Canvas#saveLayer和Canvas#restore

Canvas 在一般的情况下可以看作是一张画布,所有的绘图操作如drawBitmap, drawCircle都发生在这张画布上,这张画板还定义了一些属性比如Matrix,颜色等等。但是如果需要实现一些相对复杂的绘图操作,比如多层动画,地图(地图可以有多个地图层叠加而成,比如:政区层,道路层,兴趣点层)。Canvas提供了图层(Layer)支持,缺省情况可以看作是只有一个图层Layer。如果需要按层次来绘图,Android的Canvas可以使用SaveLayerXXX, Restore 来创建一些中间层,对于这些Layer是按照“栈结构“来管理的:

1355906035_7646.png

创建一个新的Layer到“栈”中,可以使用saveLayer, savaLayerAlpha, 从“栈”中推出一个Layer,可以使用restore,restoreToCount。但Layer入栈时,后续的DrawXXX操作都发生在这个Layer上,而Layer退栈时,就会把本层绘制的图像“绘制”到上层或是Canvas上,在复制Layer到Canvas上时,可以指定Layer的透明度(Layer),这是在创建Layer时指定的:public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags)本例Layers 介绍了图层的基本用法:Canvas可以看做是由两个图层(Layer)构成的。

3.2 Canvas转换

Canvas的转换主要有旋转、缩放、扭曲、平移、裁剪等,本文主要用到的是旋转(rotate)

4. 思路

4.1 总体思路

我们默认Canvas和系统坐标系是对应的,没有发生任何旋转缩放,因此,初始状态将每个元素绘制在View的12点钟方向,绘制每个元素时都要新建图层,待绘制完成后将图层旋转至当前时间所指示的方向即可。

4.2 当前时间角度的计算

一个圆的角度是360度,我们默认12点钟为0度,那么当前时针、分针、秒针所旋转的角度分别为:

mHourAngle = (Calendar.getInstance().get(Calendar.HOUR) + ((float)Calendar.getInstance().get(Calendar.MINUTE)) / 60) * (360 / 12);
mMinuteAngle = (Calendar.getInstance().get(Calendar.MINUTE) + ((float)Calendar.getInstance().get(Calendar.SECOND)) / 60) * (360 / 60);
mSecondStartAngle = Math.round((Calendar.getInstance().get(Calendar.SECOND) + Calendar.getInstance().get(Calendar.MILLISECOND) / Constants.SECOND) * (360 / 60));

4.3 秒针(三角)的初始化

这里秒针的实现是使用的Canvas#drawPath,在drawPath之前,我们需要穿件一个Path使其成为一个封闭的三角形,Canvas提供了moveTo、lineTo、colse方法帮我们实现这个效果:

        //初始化三角, 该三角形为底边40, 高27的等腰三角形
        mTriangle = new Path();
        mTriangle.moveTo(mGraduationPoint.x , mGraduationPoint.y + 70);// 此点为多边形的起点
        mTriangle.lineTo(mGraduationPoint.x - 20, mGraduationPoint.y + 97);
        mTriangle.lineTo(mGraduationPoint.x + 20, mGraduationPoint.y + 97);
        mTriangle.close(); // 使这些点构成封闭的多边形

初始化完成后,秒针针头指向12点钟方向

4.4 当前时间的绘制

这里用到的是图层的创建和保存,以及Canvs#save方法,首先绘制秒针和中间圆环:

        int layerCount = canvas.saveLayer(0 , 0 , canvas.getWidth() , canvas.getHeight() , mDefaultPaint , Canvas.ALL_SAVE_FLAG);
        Log.d("zyl", "sanjiaolayerCount = " + layerCount);
        // 将图层旋转至秒针所指的方向
        canvas.rotate(mClockAngle + mSecondStartAngle , mCenterPoint.x , mCenterPoint.y);

        //画三角
        canvas.drawPath(mTriangle, mPaint);

        //画中心的圆圈
        canvas.drawBitmap(mCircleBitmap , null , mDstCircleRect , mDefaultPaint);

        canvas.restoreToCount(layerCount); // 恢复图层

注意,调用restoreToCount或restore后,会将图层恢复至save或saveLayer之前的状态

秒针和中间圆环绘制完成后,绘制时针和分针,操作同上:

        //画时针
        layerCount = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), mDefaultPaint, Canvas.ALL_SAVE_FLAG); //新建图层
        Log.d("zyl", "shizhenLayerCount = " + layerCount);
        canvas.rotate(mHourAngle , mCenterPoint.x , mCenterPoint.y);
        canvas.drawBitmap(mHourBitmap , null , mDstHourRect , mDefaultPaint);

        canvas.restoreToCount(layerCount);

        //画分针
        layerCount = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), mDefaultPaint, Canvas.ALL_SAVE_FLAG);
        Log.d("zyl", "fenzhenlayerCount = " + layerCount);
        canvas.rotate(mMinuteAngle , mCenterPoint.x , mCenterPoint.y);
        canvas.drawBitmap(mMinuteBitmap , null , mDstMinuteRect , mDefaultPaint);

        canvas.restoreToCount(layerCount);

4.5 周边刻度的绘制

4.5.1 刻度绘制

圆环周边的刻度共有180个,我们需要新建一个图层,旋转180次,每次旋转2度即可:

        Log.d("zyl", "fenzhenlayerCount = " + layerCount);
        canvas.rotate(mSecondAngle + mSecondStartAngle , mCenterPoint.x, mCenterPoint.y);
        for (int i = 0; i < GRADUATION_COUNT; i++) {
            canvas.drawLine(mGraduationPoint.x, mGraduationPoint.y + 5, mGraduationPoint.x, mGraduationPoint.y + GRADUATION_LENGTH, mGraduationPaint);
            canvas.rotate(-PER_GRADUATION_ANGLE, mCenterPoint.x, mCenterPoint.y);
        }
4.5.2 拖尾效果

刻度有一个从透明度255到透明度120的渐变拖尾效果,因此,我们需要逆时针绘制刻度,并且每次绘制时将透明度 减三,直到透明度到达120:

        //画刻度
        layerCount = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), mDefaultPaint, Canvas.ALL_SAVE_FLAG);
        Log.d("zyl", "fenzhenlayerCount = " + layerCount);
        canvas.rotate(mSecondAngle + mSecondStartAngle , mCenterPoint.x, mCenterPoint.y);
        for (int i = 0; i < GRADUATION_COUNT; i++) {
            int alpha = 255 - i * 3;
            if (alpha > 120) {
                mGraduationPaint.setAlpha(alpha);
            }
            canvas.drawLine(mGraduationPoint.x, mGraduationPoint.y + 5, mGraduationPoint.x, mGraduationPoint.y + GRADUATION_LENGTH, mGraduationPaint);
            canvas.rotate(-PER_GRADUATION_ANGLE, mCenterPoint.x, mCenterPoint.y);
        }
        canvas.restoreToCount(layerCount);

5 动效

注意看效果图,秒针的运动比较圆润丝滑,而刻度的运动时从上一个跳到下一个,有一种秒针指引刻度运动的感觉,因此我们需要定义两个动画,一个秒针动画,使用float值,一个刻度动画,使用int值,这两个动画选择其中一个监听动画变化即可

public void startAnimation() {
        //三角刻度动画
        mClockAnimator = ValueAnimator.ofFloat(0 , GRADUATION_COUNT);
        mClockAnimator.setDuration(Constants.MINUTE);
        mClockAnimator.setInterpolator(new LinearInterpolator());
        mClockAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mClockAngle = (float) valueAnimator.getAnimatedValue() * PER_GRADUATION_ANGLE;

            }
        });
        mClockAnimator.setRepeatCount(ValueAnimator.INFINITE);

        //圆圈刻度动画
        mSecondAnimator = ValueAnimator.ofInt(0 , GRADUATION_COUNT);
        mSecondAnimator.setDuration(Constants.MINUTE);
        mSecondAnimator.setInterpolator(new LinearInterpolator());
        mSecondAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mSecondAngle = (int) valueAnimator.getAnimatedValue() * PER_GRADUATION_ANGLE;
                mHourAngle = (Calendar.getInstance().get(Calendar.HOUR) + ((float)Calendar.getInstance().get(Calendar.MINUTE)) / 60) * (360 / 12);
                mMinuteAngle = (Calendar.getInstance().get(Calendar.MINUTE) + ((float)Calendar.getInstance().get(Calendar.SECOND)) / 60) * (360 / 60);
                Log.d("zyl", "second = " + Calendar.getInstance().get(Calendar.SECOND));
                Log.d("zyl", "mMinuteAngle = " + mMinuteAngle);
                invalidate();
            }
        });
        mSecondAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {
                mHourAngle = (Calendar.getInstance().get(Calendar.HOUR) + ((float)Calendar.getInstance().get(Calendar.MINUTE)) / 60) * (360 / 12);
                mMinuteAngle = (Calendar.getInstance().get(Calendar.MINUTE) + ((float)Calendar.getInstance().get(Calendar.SECOND)) / 60) * (360 / 60);
                mSecondStartAngle = Math.round((Calendar.getInstance().get(Calendar.SECOND) + Calendar.getInstance().get(Calendar.MILLISECOND) / Constants.SECOND) * (360 / 60));
            }

            @Override
            public void onAnimationEnd(Animator animator) {

            }

            @Override
            public void onAnimationCancel(Animator animator) {

            }

            @Override
            public void onAnimationRepeat(Animator animator) {

            }
        });
        mSecondAnimator.setRepeatCount(ValueAnimator.INFINITE);

        mSecondAnimator.start();
        mClockAnimator.start();
    }

完整版代码:

public class MIUIClock extends View {

    private Paint mPaint;
    private Context mContext;
    private Paint mDefaultPaint;
    private Paint mGraduationPaint;
    private Rect mContentRect;
    private Path mTriangle;
    private Point mGraduationPoint;
    private Point mCenterPoint;
    private Rect mDstCircleRect; //时钟中心圆圈所在位置
    private Rect mDstHourRect; //时针所在位置
    private Rect mDstMinuteRect; //分针所在位置
    private ValueAnimator mClockAnimator;
    private ValueAnimator mSecondAnimator;
    private float mSecondStartAngle; //圆环的起始角度
    private float mClockAngle; //三角指针角度
    private int mSecondAngle; //圆环角度
    private float mHourAngle; //时针角度
    private float mMinuteAngle; //分针角度
    private static final int GRADUATION_LENGTH = 50; //圆环刻度长度
    private static final int GRADUATION_COUNT = 180; //一圈圆环刻度的数量
    private static final int ROUND_ANGLE = 360; //圆一周的角度
    private static final int PER_GRADUATION_ANGLE = ROUND_ANGLE / GRADUATION_COUNT; //每个刻度的角度
    private Bitmap mCircleBitmap; //时钟中心的圆圈
    private Bitmap mHourBitmap; //时针
    private Bitmap mMinuteBitmap; //分针

    public MIUIClock(Context context) {
        super(context);
        init(context);
    }

    public MIUIClock(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        mContext = context;
        mDefaultPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.WHITE);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setAlpha(120);

        mGraduationPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mGraduationPaint.setColor(Color.WHITE);
        mGraduationPaint.setStrokeWidth(4);
        mGraduationPaint.setStrokeCap(Paint.Cap.ROUND);

        mCircleBitmap = BitmapFactory.decodeResource(mContext.getResources() , R.mipmap.ic_circle);
        mHourBitmap = BitmapFactory.decodeResource(mContext.getResources() , R.mipmap.ic_hour);
        mMinuteBitmap = BitmapFactory.decodeResource(mContext.getResources() , R.mipmap.ic_minute);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mContentRect = new Rect(0 , 0 , w, h); // 本View内容区域
        mGraduationPoint = new Point(w /2 , 0); // 圆圈刻度绘制的参照位置
        mCenterPoint = new Point(w /2 , h /2); // 本View中心点位置
        //初始化三角, 该三角形为底边40, 高27的等腰三角形
        mTriangle = new Path();
        mTriangle.moveTo(mGraduationPoint.x , mGraduationPoint.y + 70);// 此点为多边形的起点
        mTriangle.lineTo(mGraduationPoint.x - 20, mGraduationPoint.y + 97);
        mTriangle.lineTo(mGraduationPoint.x + 20, mGraduationPoint.y + 97);
        mTriangle.close(); // 使这些点构成封闭的多边形

        //初始化circle所在位置, 将圆圈置于View 中心
        int circleWidth = mCircleBitmap.getWidth();
        int circleHeight = mCircleBitmap.getHeight();
        mDstCircleRect = new Rect(mCenterPoint.x - circleWidth /2 , mCenterPoint.y - circleHeight/2 ,
                mCenterPoint.x + circleWidth /2 , mCenterPoint.y  + circleHeight /2);

        //初始化时针所在位置
        int hourWidth = mHourBitmap.getWidth();
        int hourHeight = mHourBitmap.getHeight();
        mDstHourRect = new Rect(mCenterPoint.x - hourWidth / 2 , mCenterPoint.y - hourHeight - circleHeight / 2 - 5,
                mCenterPoint.x + hourWidth / 2, mCenterPoint.y - circleHeight / 2 - 5);

        //初始化分针所在位置
        int minuteWidth = mMinuteBitmap.getWidth();
        int minuteHeight = mMinuteBitmap.getHeight();
        mDstMinuteRect = new Rect(mCenterPoint.x - minuteWidth / 2 , mCenterPoint.y - minuteHeight - circleHeight / 2 - 5,
                mCenterPoint.x + minuteWidth / 2 , mCenterPoint.y - circleHeight / 2 - 5);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int layerCount = canvas.saveLayer(0 , 0 , canvas.getWidth() , canvas.getHeight() , mDefaultPaint , Canvas.ALL_SAVE_FLAG);
        Log.d("zyl", "sanjiaolayerCount = " + layerCount);
        canvas.rotate(mClockAngle + mSecondStartAngle , mCenterPoint.x , mCenterPoint.y);

        //画三角
        canvas.drawPath(mTriangle, mPaint);

        //画中心的圆圈
        canvas.drawBitmap(mCircleBitmap , null , mDstCircleRect , mDefaultPaint);

        canvas.restoreToCount(layerCount);

        //画时针
        layerCount = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), mDefaultPaint, Canvas.ALL_SAVE_FLAG); //新建图层
        Log.d("zyl", "shizhenLayerCount = " + layerCount);
        canvas.rotate(mHourAngle , mCenterPoint.x , mCenterPoint.y);
        canvas.drawBitmap(mHourBitmap , null , mDstHourRect , mDefaultPaint);

        canvas.restoreToCount(layerCount);

        //画分针
        layerCount = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), mDefaultPaint, Canvas.ALL_SAVE_FLAG);
        Log.d("zyl", "fenzhenlayerCount = " + layerCount);
        canvas.rotate(mMinuteAngle , mCenterPoint.x , mCenterPoint.y);
        canvas.drawBitmap(mMinuteBitmap , null , mDstMinuteRect , mDefaultPaint);

        canvas.restoreToCount(layerCount);

        //画刻度
        layerCount = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), mDefaultPaint, Canvas.ALL_SAVE_FLAG);
        Log.d("zyl", "fenzhenlayerCount = " + layerCount);
        canvas.rotate(mSecondAngle + mSecondStartAngle , mCenterPoint.x, mCenterPoint.y);
        for (int i = 0; i < GRADUATION_COUNT; i++) {
            int alpha = 255 - i * 3;
            if (alpha > 120) {
                mGraduationPaint.setAlpha(alpha);
            }
            canvas.drawLine(mGraduationPoint.x, mGraduationPoint.y + 5, mGraduationPoint.x, mGraduationPoint.y + GRADUATION_LENGTH, mGraduationPaint);
            canvas.rotate(-PER_GRADUATION_ANGLE, mCenterPoint.x, mCenterPoint.y);
        }
        canvas.restoreToCount(layerCount);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(widthMeasureSpec , heightMeasureSpec);
    }

    public void startAnimation() {
        //三角刻度动画
        mClockAnimator = ValueAnimator.ofFloat(0 , GRADUATION_COUNT);
        mClockAnimator.setDuration(Constants.MINUTE);
        mClockAnimator.setInterpolator(new LinearInterpolator());
        mClockAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mClockAngle = (float) valueAnimator.getAnimatedValue() * PER_GRADUATION_ANGLE;

            }
        });
        mClockAnimator.setRepeatCount(ValueAnimator.INFINITE);

        //圆圈刻度动画
        mSecondAnimator = ValueAnimator.ofInt(0 , GRADUATION_COUNT);
        mSecondAnimator.setDuration(Constants.MINUTE);
        mSecondAnimator.setInterpolator(new LinearInterpolator());
        mSecondAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mSecondAngle = (int) valueAnimator.getAnimatedValue() * PER_GRADUATION_ANGLE;
                mHourAngle = (Calendar.getInstance().get(Calendar.HOUR) + ((float)Calendar.getInstance().get(Calendar.MINUTE)) / 60) * (360 / 12);
                mMinuteAngle = (Calendar.getInstance().get(Calendar.MINUTE) + ((float)Calendar.getInstance().get(Calendar.SECOND)) / 60) * (360 / 60);
                Log.d("zyl", "second = " + Calendar.getInstance().get(Calendar.SECOND));
                Log.d("zyl", "mMinuteAngle = " + mMinuteAngle);
                invalidate();
            }
        });
        mSecondAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {
                mHourAngle = (Calendar.getInstance().get(Calendar.HOUR) + ((float)Calendar.getInstance().get(Calendar.MINUTE)) / 60) * (360 / 12);
                mMinuteAngle = (Calendar.getInstance().get(Calendar.MINUTE) + ((float)Calendar.getInstance().get(Calendar.SECOND)) / 60) * (360 / 60);
                mSecondStartAngle = Math.round((Calendar.getInstance().get(Calendar.SECOND) + Calendar.getInstance().get(Calendar.MILLISECOND) / Constants.SECOND) * (360 / 60));
            }

            @Override
            public void onAnimationEnd(Animator animator) {

            }

            @Override
            public void onAnimationCancel(Animator animator) {

            }

            @Override
            public void onAnimationRepeat(Animator animator) {

            }
        });
        mSecondAnimator.setRepeatCount(ValueAnimator.INFINITE);

        mSecondAnimator.start();
        mClockAnimator.start();
    }

    public void cancelAnimation() {
        if (mClockAnimator != null) {
            mClockAnimator.removeAllUpdateListeners();
            mClockAnimator.removeAllListeners();
            mClockAnimator.cancel();
            mClockAnimator = null;
        }

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

推荐阅读更多精彩内容