Android动画之ObjectAnimator中ofXX函数全解析-自定义Property,TypeConverter,TypeEvaluator

1 Android属性动画中ofXX函数概述

前面一篇属性动画文章中讲解了如何利用ObjectAnimator实现几种补间动画效果和如何自定义ObjectAnimator属性,其中用到了ObjectAnimator对象的ofInt,ofFLoat,ofObject,但这些函数都有众多的重载函数,这一篇接着讲解ObjectAnimator众多的ofXXX函数的用法。
观看本篇文章请务必先看Android动画之ValueAnimator用法和自定义估值器
ObjectAnimator of函数列表:

ofArgb(Object target, String propertyName, int... values)   api21可用
ofArgb(T target, Property<T, Integer> property, int... values)  api21可用

ofFloat(Object target, String propertyName, float... values)  api11 可用
ofFloat(Object target, String xPropertyName, String yPropertyName, Path path)   api21可用
ofFloat(T target, Property<T, Float> property, float... values) api14可用
ofFloat(T target, Property<T, Float> xProperty, Property<T, Float> yProperty, Path path)  api21可用

ofInt(Object target, String propertyName, int... values)  api11可用
ofInt(Object target, String xPropertyName, String yPropertyName,Path path) api21可用
ofInt(T target, Property<T, Integer> property, int... values) api14可用
ofInt(T target, Property<T, Integer> xProperty,Property<T, Integer> yProperty, Path path) api21可用

ofMultiFloat(Object target, String propertyName,float[][] values)  api21可用
ofMultiFloat(Object target, String propertyName, Path path)        api21可用
ofMultiFloat(Object target, String propertyName,
            TypeConverter<T, float[]> converter, TypeEvaluator<T> evaluator, T... values)   api21可用
ofMultiInt(Object target, String propertyName, int[][] values)    api21可用
ofMultiInt(Object target, String propertyName, Path path)     api21可用
ofMultiInt(Object target, String propertyName,
            TypeConverter<T, int[]> converter, TypeEvaluator<T> evaluator, T... values)    api21可用

ofObject(Object target, String propertyName,
            @Nullable TypeConverter<PointF, ?> converter, Path path)  api21可用
ofObject(Object target, String propertyName, TypeEvaluator evaluator, Object... values)  api11
ofObject(T target, Property<T, P> property,
            TypeConverter<V, P> converter, TypeEvaluator<V> evaluator, V... values)  api21
ofObject(T target, @NonNull Property<T, V> property,
            @Nullable TypeConverter<PointF, V> converter, Path path)   api2可用
ofObject(T target, Property<T, V> property, TypeEvaluator<V> evaluator, V... values)  api14可用

ofPropertyValuesHolder(Object target, PropertyValuesHolder... values)  api11可用

2 OfFloat & ofInt

把ofFLoat和ofInt两个函数一起讲解是因为这两个函数除了类型不一样,重载函数很相似。

ofFloat(Object target, String propertyName, float... values)
ofFloat(Object target, String xPropertyName, String yPropertyName, Path path)
ofFloat(T target, Property<T, Float> property, float... values)
ofFloat(T target, Property<T, Float> xProperty, Property<T, Float> yProperty, Path path)

ofInt(Object target, String propertyName, int... values)
ofInt(Object target, String xPropertyName, String yPropertyName,Path path)
ofInt(T target, Property<T, Integer> property, int... values)
ofInt(T target, Property<T, Integer> xProperty,Property<T, Integer> yProperty, Path path)

ObjectAnimator 的ofFloat和ofInt函数用法相似,所以只讲解ofFloat,

ofFloat(Object target, String propertyName, float... values)

这个函数比较简单,第一个参数传入目标对象,第二个参数传入要改变的属性(配合setXX函数,关于如何定义propertyName前面的文章中已经说明),第三个参数是个渐变属性,可以传多个值。
代码示例:
实现TextView的旋转:

ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mTextView, "rotation", 0, 270);
objectAnimator.setDuration(3000);
objectAnimator.setRepeatCount(-1);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.start();

ofFloat(Object target, String xPropertyName, String yPropertyName, Path path)

可以同时操纵两个参数变化,实现动画。
参数说明:
target:动画目标对象,这个目标对象有些特别,沿着一条路径Path能够使用两个属性,路径Path动画在二维空间中移动,由动画坐标(x,y)决定效果,(重要)所以对象必须有两个函数一个是setNameX(),另外一个是setNameY(),类似view的setTranslationX,SetTranslationY,当然也可以自己定义属性,同时对应的xPropertyName和yPropertyName分别为translationX和translationY。
xPropertyName:Path对应的X轴方向的属性值,
yPropertyName:Path对应的Y轴方向的属性值,
path:动画路径。

代码示例:
TextView 在X轴和Y轴方向上移动

Path path = new Path();
path.moveTo(0,0);
path.lineTo(50,50);
path.lineTo(100,20);
path.lineTo(900,400);
path.lineTo(500,1000);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mTextView, "translationX","translationY",path );
objectAnimator.setDuration(3000);
objectAnimator.setRepeatCount(-1);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.start();

上面的示例代码利用了translationX和translationX属性,这两个属性是View的自带的属性,同时也可以是两个互不相干的属性,可以实现类似组合动画的效果。

Path path = new Path();
path.moveTo(0,0);
path.lineTo(50,1);
path.lineTo(100,2);
path.lineTo(900,0.5f);
path.lineTo(500,1);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mTextView, "translationX","scaleX",path );
objectAnimator.setDuration(3000);
objectAnimator.setRepeatCount(-1);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.start();

ofFloat(T target, Property<T, Float> property, float... values)

参数说明:
target:动画目标对象
property:动画作用的属性,有了这个属性对象, 就可以不用写属性对应的字段值,类似不用写“scale”
values:动画取值,如果是一个值则将target开始的状态作为开始值,将values的一个值,作为结束值,如果是两个值则第一个为动画开始值,第二个为动画结束值。

这个函数用到了Property属性,是API14添加的方法,不知道大家注意到没有,每次使用属性动画,我们都需要记得目标对象的setXXX函数后面的相应字符串,虽然不复杂但有时确实会记不清,需要再次确认,而这个带有Property的函数就大大简化了这个过程。

Android为我们提供了简单的常量对象来实现旋转动画:
ObjectAnimator.ofFloat(mTextView, View.ROTATION, 0,30);
这里的View.ROTATION就是个Property对象,可以简单地实现旋转的属性动画。

View.ROTATION源码

public static final Property<View, Float> ROTATION = new FloatProperty<View>("rotation") {
    @Override
    public void setValue(View object, float value) {
        object.setRotation(value);
    }

    @Override
    public Float get(View object) {
        return object.getRotation();
    }
};

View.ROTATION示例

ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mTextView, View.ROTATION, 0,270);
objectAnimator.setDuration(3000);
objectAnimator.setRepeatCount(-1);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.start();

View中具有和View.ROTATION作用的常量还有如下:
ALPHA,TRANSLATION_X,TRANSLATION_Y,TRANSLATION_Z,X,Y,ROTATION, ROTATION_X , ROTATION_Y,SCALE_X,SCALE_Y。

自定义Property

分析上面常量的他们都是实现了FloatProperty,或者IntProperty然后重写setValue和get方法,而FloatProperty,IntProperty又是继承了Property,所以我们可以通过实现FloatPropety,IntProperty或者直接实现Property来自定义Property。

自定义Property:

public static class MyProperty extends Property<TextView,String>{
    public MyProperty(String name) {
        super(String.class, name);
    }

    @Override
    public String get(TextView object) {
        return object.getText().toString();
    }

    @Override
    public void set(TextView object, String value) {
        object.setText(value);
    }
}

自定义估值器:

public static class IntEvaluator implements TypeEvaluator<String>{

    @Override
    public String evaluate(float fraction, String startValue, String endValue) {
        int startInt = Integer.parseInt(startValue) ;
        int endInt = Integer.parseInt(endValue);
        int cur = (int) (startInt + fraction * (endInt - startInt));
        return cur+"";
    }
}

使用自定义Property:

IntEvaluator intEvaluator = new IntEvaluator();
MyProperty property = new MyProperty("text");
ObjectAnimator objectAnimator = ObjectAnimator.ofObject(mTextView,property,intEvaluator,"1", "10");
objectAnimator.setDuration(3000);
objectAnimator.setRepeatCount(-1);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.start();

ofFloat(T target, Property<T, Float> xProperty, Property<T, Float> yProperty, Path path)
函数也用到了Property属性,需要结合property和Path,类似上面函数的用法,不再说明。

3 ofArgb 颜色属性动画

ofArgb(Object target, String propertyName, int... values) 对颜色属性进行动画。

参数说明:
target:动画作用对象
propertyName:动画作用的属性
values:动画使用的可变参数
代码示例:

ObjectAnimator objectAnimator = ObjectAnimator.ofArgb(mTextView,"backgroundColor", Color.RED, Color.GREEN);
objectAnimator.setDuration(3000);
objectAnimator.setRepeatCount(-1);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.start();

ofArgb(T target, Property<T, Integer> property, int... values)

用到了Property属性,但是View中没有类似ROTATION属性的对颜色属性的简写,可以自定义Property,下面的例子只是数值的渐变,如果真的需要颜色渐变,需要设置颜色估值器:

public static class MyProperty extends Property<TextView,Integer>{
    public MyProperty(String name) {
        super(Integer.class, name);
    }

    @Override
    public Integer get(TextView object) {
        Drawable drawable =  object.getBackground();
        if (drawable instanceof ColorDrawable){
            return ((ColorDrawable) drawable).getColor();
        }
        return Color.YELLOW;
    }

    @Override
    public void set(TextView object, Integer value) {
        object.setBackgroundColor(value);
    }
}

使用:

MyProperty property = new MyProperty("background");
ObjectAnimator objectAnimator = ObjectAnimator.ofArgb(mTextView,property, Color.RED, Color.GREEN);
objectAnimator.setDuration(3000);
objectAnimator.setRepeatCount(-1);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.start();

4 ofMultiFloat,ofMultiInt

被称为多参数布局,用的不太多,下面简单介绍:
ofMultiFloat(Object target, String propertyName,float[][] values)
ofMultiFloat(Object target, String propertyName, Path path)
ofMultiFloat(Object target, String propertyName,
TypeConverter<T, float[]> converter, TypeEvaluator<T> evaluator, T... values)
ofMultiInt(Object target, String propertyName, int[][] values)
ofMultiInt(Object target, String propertyName, Path path)
ofMultiInt(Object target, String propertyName,
TypeConverter<T, int[]> converter, TypeEvaluator<T> evaluator, T... values)
ofMultiFloat和ofMultiInt用法相似,由于上面只介绍了ofFloat,下面只介绍ofMultiInt方法:

ofMultiInt(Object target, String propertyName, int[][] values)

参数说明:
propertyName:进行动画的属性名
values[][]:二维数组,至少两组数据,每个values[]中存放一个setter函数中所有的参数,然后从values[0]中取值为动画开始值,从values[最后一组]中取值为动画最后的值,如果之间还有值,就作为过渡,从values[0]-values[1]-........(大家明白的)
需要自定义view添加getter,setter函数。

代码示例:

public class ViewDemo24 extends android.support.v7.widget.AppCompatTextView {

    public ViewDemo24(Context context) {
        this(context,null,0);
    }

    public ViewDemo24(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public ViewDemo24(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
    }

    public void setMulText(int data1,int data2){
        String data = "";
       data = data + data1 + data2;
        setText(data);
    }

    public String getMulText(){
        return getText().toString();
    }
}

使用:

int[][] data = {{1,9},{4,12}} ;
ObjectAnimator objectAnimator = ObjectAnimator.ofMultiInt(viewDemo24,"mulText",data);
objectAnimator.setDuration(3000);
objectAnimator.setRepeatCount(-1);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.start();

ofMultiInt(Object target, String propertyName, Path path)

用法和上面的函数类似,只不过把二维数组换成了Path,并且setter函数只能接收两个int参数,从path中取动画开始值和结束值(从path.moveTo中取动画开始值,后面的值为动画结束值)。
代码示例
**自定义view还用上面的ViewDemo24 **

Path path = new Path();
path.moveTo(0,6);
path.lineTo(5,9);
ObjectAnimator objectAnimator = ObjectAnimator.ofMultiInt(viewDemo24,"mulText",path);
objectAnimator.setDuration(3000);
objectAnimator.setRepeatCount(-1);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.start();

ofMultiInt(Object target, String propertyName,TypeConverter<T, int[]> converter, TypeEvaluator<T> evaluator, T... values)

Converter:把int[]数组转换成需要的T类型,然后利用估值器计算T 得到ObjectAnimator需要的类型。

依然用上面的自定义view ,setter函数需要两个参数:
用到的自定义T类型:

public static class Point {
    int x;
    int y;

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }
}

自定义类型转换,把上面自定义的Point类型转换成int[]数组:

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static class IntConverter extends TypeConverter <Point ,int[]>{
    public IntConverter(Class<Point> fromClass, Class<int[]> toClass) {
        super(fromClass, toClass);
    }

    @Override
    public int[] convert(Point value) {
        int[] intarr = {value.getX(),value.getY()};
        return intarr;
    }
}

自定义估值器:

public static class PointEvaluator implements TypeEvaluator<Point> {

    @Override
    public Point evaluate(float fraction, Point startValue, Point endValue) {
        int startxInt = startValue.getX() ;
        int endxInt = endValue.getX();
        int curx = (int) (startxInt + fraction * (endxInt - startxInt));

        int startyInt = startValue.getY() ;
        int endyInt = endValue.getY();
        int cury = (int) (startyInt + fraction * (endyInt - startyInt));
        Point point = new Point();
        point.setX(curx);
        point.setY(cury);
        return point;
    }
}

代码中使用:

IntConverter intConverter = new IntConverter(Point.class,int[].class);
PointEvaluator pointEvaluator = new PointEvaluator();
Point point1 = new Point();
point1.setX(1);
point1.setY(5);
Point point2 = new Point();
point2.setX(4);
point2.setY(9);
ObjectAnimator objectAnimator = ObjectAnimator.ofMultiInt(viewDemo24,"mulText",intConverter,pointEvaluator,point1,point2);
objectAnimator.setDuration(3000);
objectAnimator.setRepeatCount(-1);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.start();

5 ofObject

对对象进行动画:

ofObject(Object target, String propertyName,
            @Nullable TypeConverter<PointF, ?> converter, Path path)
ofObject(Object target, String propertyName, TypeEvaluator evaluator, Object... values)
ofObject(T target, Property<T, P> property,
            TypeConverter<V, P> converter, TypeEvaluator<V> evaluator, V... values)
ofObject(T target, @NonNull Property<T, V> property,
            @Nullable TypeConverter<PointF, V> converter, Path path)
ofObject(T target, Property<T, V> property, TypeEvaluator<V> evaluator, V... values)

上面讲解了ofInt ,ofFloat,ofMultiInt,ofMultiFloat等函数,仔细观察上面的ofObject函数,可以分析得到ofObject把类型泛型化了,每个函数都提供了TypeEvaluator供ObjectAnimator识别参数,所以ofObject的用法和ofInt,ofFloat相同。

ofObject(T target, Property<T, V> property, TypeEvaluator<V> evaluator, V... values)
参数说明:
target:动画目标对象
property:自定义property,内部调用getter,setter函数,不用再指定propertyName
evaluator:估值器,生成动画所需对象
values:动画传入参数

利用ofObject 实现view的移动
自定义类:

public static class Point {
    int x;
    int y;

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }
}

自定义property

public static class MyProperty2 extends Property<TextView,Point>{
    public MyProperty2(String name) {
        super(Point.class, name);
    }

    @Override
    public Point get(TextView object) {
        Point point = new Point();
        point.setX((int) object.getTranslationX());
        point.setY((int) object.getTranslationY());
        return  point;
    }

    @Override
    public void set(TextView object, Point value) {
        object.setTranslationX(value.getX());
        object.setTranslationY(value.getY());
    }
}

自定义Evaluator估值器

public static class PointEvaluator implements TypeEvaluator<Point> {

    @Override
    public Point evaluate(float fraction, Point startValue, Point endValue) {
        int startxInt = startValue.getX() ;
        int endxInt = endValue.getX();
        int curx = (int) (startxInt + fraction * (endxInt - startxInt));

        int startyInt = startValue.getY() ;
        int endyInt = endValue.getY();
        int cury = (int) (startyInt + fraction * (endyInt - startyInt));
        Point point = new Point();
        point.setX(curx);
        point.setY(cury);
        return point;
    }
}

使用:

MyProperty2 property2 = new MyProperty2("tran");//參數只是为了标识无具体意义
PointEvaluator evaluator = new PointEvaluator();
Point point1 = new Point();
point1.setY(0);
point1.setX(100);

Point point2 = new Point();
point2.setY(700);
point2.setX(1000);
ObjectAnimator objectAnimator = ObjectAnimator.ofObject(mTextView,property2,evaluator,point1,point2);
objectAnimator.setDuration(3000);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.setRepeatCount(-1);
objectAnimator.start();

6 ofPropertyValuesHolder(Object target, PropertyValuesHolder... values)

参数说明:
target:动画目标对象
values:PropertyValuesHolder 动画可变参数。
多动画执行,多个动画一起执行。

PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX", 1,2);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY",0, 1);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("alpha", 0.5f,1.0f);

ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(mTextView, holder1, holder2, holder3);
objectAnimator.setDuration(3000);
objectAnimator.setRepeatCount(-1);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.start();

Keyframe 实现关键帧操作(来自Android Developer)
利用Keyframe可以添加一些关键帧,来控制动画的执行,例如:

Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(.5f, 360f);
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation);
rotationAnim.setDuration(5000);

上面的代码作用为动画执行到一半时旋转360度,动画执行完时再从360旋转到0度。关键帧的作用和插值器的作用一样都是动画执行过程中,返回动画完成程度。
自定义插值器
ofXXX函数全部讲完了

Animation动画概述和执行原理
Android动画之补间动画TweenAnimation
Android动画之逐帧动画FrameAnimation
Android动画之插值器简介和系统默认插值器
Android动画之插值器Interpolator自定义
Android动画之视图动画的缺点和属性动画的引入
Android动画之ValueAnimator用法和自定义估值器
Android动画之ObjectAnimator实现补间动画和ObjectAnimator自定义属性
Android动画之ObjectAnimator中ofXX函数全解析-自定义Property,TypeConverter,TypeEvaluator
Android动画之AnimatorSet联合动画用法
Android动画之LayoutTransition布局动画
Android动画之共享元素动画
Android动画之ViewPropertyAnimator(专用于view的属性动画)
Android动画之Activity切换动画overridePendingTransition实现和Theme Xml方式实现
Android动画之ActivityOptionsCompat概述
Android动画之场景变换Transition动画的使用
Android动画之Transition和TransitionManager使用
Android动画之圆形揭露动画Circular Reveal
Android 动画之 LayoutAnimation 动画
Android动画之视图动画的缺点和属性动画的引入

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

推荐阅读更多精彩内容