打造萬能的BannerView(ViewPager)無限輪播圖

為什么寫這篇文章流纹,因為在網(wǎng)上看到的絕大多數(shù)BannerView實現(xiàn)了右無限輪播圖,甚至沒有實現(xiàn)無限輪播圖违诗,說成是無限輪播圖漱凝,實現(xiàn)了左右無限輪播圖的,并沒有做性能上的優(yōu)化诸迟。

先看張效果圖

device-2018-05-11-173850.gif

工程目錄圖

project.png

BannerAdapter:banner輪播圖的適配器茸炒,因為服務(wù)器返回的列表圖片的url,顯示的時候需要轉(zhuǎn)成IamgeViw阵苇;  
BannerScroller:設(shè)置切換頁面的持續(xù)時間壁公;  
BannerView:繼承RelativeLayout,包含BannViewPager和底部DotIndicatorView指示器绅项;
BannerViewPager:繼承ViewPager紊册,設(shè)置ViewPager的適配器Adpter和動畫;
DotIndicatorView:底部指示器趁怔;

DotIndicatorView類

public class DotIndicatorView extends View {
//形狀
private int mShape;
// 矩形
public static final int SHAPE_REC = 1;
// 圓形
public static final int SHAPE_CIRCLE = 2;
private Drawable mDrawable;

public DotIndicatorView(Context context) {
    this(context, null);
}

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

public DotIndicatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DotIndicatorView);
    //默認(rèn)是圓形
    mShape = typedArray.getInteger(R.styleable.DotIndicatorView_shape, SHAPE_CIRCLE);
    typedArray.recycle();

}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (mDrawable != null) {
        Bitmap bitmap = drawableToBitmap(mDrawable);
        if (mShape == SHAPE_CIRCLE) {
            Bitmap circleBitmap = getCircleBitmap(bitmap);
            canvas.drawBitmap(circleBitmap, 0, 0, null);
        } else if (mShape == SHAPE_REC) {
            Bitmap recBitmap = getRecBitmap(bitmap);
            canvas.drawBitmap(recBitmap, 0, 0, null);
        }
    }
}

public void setDrawable(Drawable drawable) {
    mDrawable = drawable;
    invalidate();
}

/**
 * drawable轉(zhuǎn)bitmap
 *
 * @param drawable
 * @return
 */
private Bitmap drawableToBitmap(Drawable drawable) {
    if (drawable instanceof BitmapDrawable) {
        return (( BitmapDrawable ) drawable).getBitmap();
    }
    //其他類型 ColorDrawable
    //創(chuàng)建一個什么也沒有的Bitmap湿硝;
    Bitmap outBitmap = Bitmap.createBitmap(getMeasuredWidth(),
            getMeasuredHeight(), Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(outBitmap);
    //把drawable畫到Bitmap上   --》將drawable繪制在canvas內(nèi)部
    drawable.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
    drawable.draw(canvas);
    return outBitmap;
}

public void setShape(int shape) {
    mShape = shape;
}

public int getShape() {
    return mShape;
}

/**
 * 圓形
 *
 * @param bitmap
 * @return
 */
private Bitmap getCircleBitmap(Bitmap bitmap) {
    //創(chuàng)建一個Bitmap
    Bitmap circleBitmap = Bitmap.createBitmap(getMeasuredWidth(),
            getMeasuredHeight(), Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(circleBitmap);
    Paint paint = new Paint();
    paint.setAntiAlias(true);
    paint.setFilterBitmap(true);
    //防止抖動
    paint.setDither(true);
    //在畫布上繪制一個圓
    canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, getMeasuredWidth() / 2, paint);
    //設(shè)置畫筆的圖層薪前,PorterDuff.Mode.SRC_IN 取圖層交集的上層
    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    //在把原來的bitmap繪制到圓上面
    canvas.drawBitmap(bitmap, 0, 0, paint);
    //回收Bitmap
    bitmap.recycle();
    return circleBitmap;
}

/**
 * 帶圓角的矩形
 *
 * @param bitmap
 * @return
 */
private Bitmap getRecBitmap(Bitmap bitmap) {
    Bitmap recBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(),
            Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(recBitmap);
    Paint paint = new Paint();
    paint.setAntiAlias(true);
    paint.setFilterBitmap(true);
    //防止抖動
    paint.setDither(true);
    //在畫布上繪制一個圓角的矩形
    canvas.drawRoundRect(new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()),
            DensityUtil.dip2px(this.getContext(), 2), DensityUtil.dip2px(this.getContext(), 2), paint);
    //設(shè)置畫筆的圖層润努,PorterDuff.Mode.SRC_IN 取圖層交集的上層
    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    //在把原來的bitmap繪制到圓上面
    canvas.drawBitmap(bitmap, 0, 0, paint);
    //回收Bitmap
    bitmap.recycle();
    return recBitmap;
}
 }

一般底部會有兩種類型指示器,一是矩形的示括,二是圓形的铺浇,這個類實現(xiàn)了如何自定義矩形和圓形指示器,其實這個類也可以實現(xiàn)圓形的和帶圓角的矩形的圖片垛膝,用PorterDuffXfermode圖層的概念鳍侣。

BannerAdapter類

public abstract class BannerAdapter {
    /**
     * 根據(jù)位置獲取ViewPager的子View
     *
     * @param position
     * @return
     */
    public abstract View getView(int position, View convertView);

    /**
     * 返回數(shù)量
     *
     * @return
     */
    public abstract int getCount();
}

BannerAdapter這個類是輪播圖的適配器,因為服務(wù)器返回的列表圖片的url吼拥,顯示的時候需要轉(zhuǎn)成IamgeViw倚聚,用適配器設(shè)計模式轉(zhuǎn)一下。

BannerViewPager類

public class BannerViewPager extends ViewPager {
    private static final String TAG = BannerViewPager.class.getSimpleName();
    private static final int SCROLL_MSG = 0x011;
    private BannerAdapter mBannerAdapter;
    private int mCutDownTime = 3000;
    private BannerScroller mBannerScroller;
    //內(nèi)存優(yōu)化界面復(fù)用
    private List<View> mConvertView;
    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case SCROLL_MSG:
                    setCurrentItem(getCurrentItem() + 1);
                    startLoop();
                    break;
        }
    }
};

public BannerViewPager(Context context) {
    this(context, null);
}

public BannerViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
    //改變ViewPager切換的速率
    try {
        //獲取ViewPager的私有的屬性mScroller
        Field field = ViewPager.class.getDeclaredField("mScroller");
        mBannerScroller = new BannerScroller(context);
        //設(shè)置強制改變
        field.setAccessible(true);
        //設(shè)置參數(shù) 第一個參數(shù)object當(dāng)前屬性的那個類 第二參數(shù)需要設(shè)置的值
        field.set(this, mBannerScroller);
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    }
    mConvertView = new ArrayList<>();
}

/**
 * 設(shè)置切換頁面的持續(xù)時間
 *
 * @param scrollerDuration
 */
public void setScrollerDuration(int scrollerDuration) {
    mBannerScroller.setScrollerDuration(scrollerDuration);
}

public void setAdapter(BannerAdapter adapter) {
    this.mBannerAdapter = adapter;
    setAdapter(new BannerPagerAdapter());
    //管理Activity的生命周期
    (( Activity ) (getContext())).getApplication().registerActivityLifecycleCallbacks(mDefaultActivityLifecycleCallbacks);
}

/**
 * 開啟輪播
 */
public void startLoop() {
    mHandler.removeMessages(SCROLL_MSG);
    mHandler.sendEmptyMessageDelayed(SCROLL_MSG, mCutDownTime);
}

/**
 * 銷毀Handler
 */
@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    mHandler.removeMessages(SCROLL_MSG);
    mHandler = null;

}

private class BannerPagerAdapter extends PagerAdapter {
    /**
     * 給一個很大的值凿可,為了實現(xiàn)無限輪播
     * 這個方法是返回ViewPager有多少個View
     */
    @Override
    public int getCount() {
        return Integer.MAX_VALUE;
    }

    @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        //Adapter設(shè)計模式為了完全讓用戶自定義
        //position 0-2的31次方
        Log.i(TAG, "instantiateItem:position=" + position + "mBannerAdapter.getCount()=" + mBannerAdapter.getCount());
        //position % mBannerAdapter.getCount() 求模
        View bannerItemView = mBannerAdapter.getView(position % mBannerAdapter.getCount(), getConvertView());
        container.addView(bannerItemView);
        return bannerItemView;
    }

    @Override
    public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
        return view == object;
    }

    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        container.removeView(( View ) object);
        mConvertView.add(( View ) object);
    }
}

private float mDownX;

@Override
public boolean onTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mDownX = ev.getX();
            mHandler.removeMessages(SCROLL_MSG);
            break;
        case MotionEvent.ACTION_MOVE:
            break;
        case MotionEvent.ACTION_UP:
            //左滑動到第一張惑折,跳轉(zhuǎn)到getCount() - 1
            if (this.getCurrentItem() == 0) {
                if (ev.getX() - mDownX > 0) {
                    this.setCurrentItem(mBannerAdapter.getCount() - 1);
                    Log.i(TAG, "onTouchEvent: " + this.getCurrentItem());
                }
            }
            mHandler.sendEmptyMessageDelayed(SCROLL_MSG, mCutDownTime);

            break;
    }
    return super.onTouchEvent(ev);
}

/**
 * 處理頁面復(fù)用
 *
 * @return
 */
public View getConvertView() {
    for (int i = 0; i < mConvertView.size(); i++) {
        if (mConvertView.get(i).getParent() == null) {
            return mConvertView.get(i);
        }
    }
    return null;
}

/**
 * 管理Activity的生命周期
 */
DefaultActivityLifecycleCallbacks mDefaultActivityLifecycleCallbacks = new DefaultActivityLifecycleCallbacks() {
    @Override
    public void onActivityResumed(Activity activity) {
        super.onActivityResumed(activity);
        if (activity == getContext()) {
            //開啟輪播
            mHandler.sendEmptyMessageDelayed(SCROLL_MSG, mCutDownTime);
        }
    }

    @Override
    public void onActivityPaused(Activity activity) {
        super.onActivityPaused(activity);
        if (activity == getContext()) {
            //停止輪播
            mHandler.removeMessages(SCROLL_MSG);
        }
    }
};
}

繼承PagerAdapter實現(xiàn)getCount()這個方法,這個方法返回的是ViewPager有多少個View枯跑。為了實現(xiàn)無限輪播圖返回了Integer.MAX_VALUE惨驶,用戶不會手殘一直向右滑動吧,造成溢出吧敛助,哈粗卜。DefaultActivityLifecycleCallbacks 去監(jiān)聽Activity的生命周期,為什么要監(jiān)聽呢纳击?因為當(dāng)用戶點擊home鍵的時候续扔,此時應(yīng)用會在后臺攻臀,但是ViewPager里面的ImageView還會循環(huán),所以在Activity執(zhí)行onPaused()的時候测砂,停止輪播茵烈。getConvertView()這個方法是處理界面復(fù)用的,意思是跟RecycleView或者ListView實現(xiàn)列表滑動一樣的砌些,需要界面復(fù)用呜投。最后,小編想實現(xiàn)一個左滑動到position=0存璃,也就是第一張的時候仑荐,想跳轉(zhuǎn)到getCount-1張,具體的做法是想在onTouchEvent()方法監(jiān)聽纵东,手指按下記錄下mDownX粘招,手指抬起的時候ev.getX(),用ev.getX() - mDownX > 0坐下判斷偎球。在設(shè)置下 setCurrentItem(mBannerAdapter.getCount() - 1);
發(fā)現(xiàn)并沒有實現(xiàn)洒扎。也不知道這是為什么,但是我認(rèn)為這種思路沒錯衰絮,哪位大神看到了袍冷,請給出具體解決方案。

BannerView類

public class BannerView extends RelativeLayout {
    private BannerViewPager mBannerViewPager;
    //底部的指示器的View
    private LinearLayout mDotContainerView;
    //適配器
    private BannerAdapter mAdapter;
    private Context mContext;
    //選中的drawable
    private Drawable mIndicatorFocusDrawable;
    //未被選中的drawable
    private Drawable mIndicatorNormalDrawable;
    //當(dāng)前頁面的位置
    private int mCurrentPosition;
    //指示器的位置
    private int mDotGravity = -1;
    //指示器的大小
    private int mDotSize = 6;
    //指示器的間距
    private int mDotDistance = 2;
    //底部顏色默認(rèn)透明
    private int mBottomColor = Color.TRANSPARENT;
    private View mBannerBottomView;

    public BannerView(Context context) {
        this(context, null);
    }

    public BannerView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BannerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        inflate(context, R.layout.banner_layout, this);
        this.mContext = context;
        initAttribute(attrs);
        initView();

    }

    /**
     * 初始化自定義屬性
     *
     * @param attrs
     */
    private void initAttribute(AttributeSet attrs) {
        TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.BannerView);
        mDotGravity = typedArray.getInt(R.styleable.BannerView_dotGravity, -1);
        mIndicatorFocusDrawable = typedArray.getDrawable(R.styleable.BannerView_dotIndicatorFocus);
        if (mIndicatorFocusDrawable == null) {
            mIndicatorFocusDrawable = new ColorDrawable(Color.RED);
        }
        mIndicatorNormalDrawable = typedArray.getDrawable(R.styleable.BannerView_dotIndicatorNormal);
        if (mIndicatorNormalDrawable == null) {
            mIndicatorNormalDrawable = new ColorDrawable(Color.WHITE);
        }
        mDotSize = ( int ) typedArray.getDimension(R.styleable.BannerView_dotSize, DensityUtil.dip2px(mContext, 6));
        mDotDistance = ( int ) typedArray.getDimension(R.styleable.BannerView_dotDistance, DensityUtil.dip2px(mContext, 2));
        mBottomColor = typedArray.getColor(R.styleable.BannerView_bottomColor, mBottomColor);

        typedArray.recycle();
    }

    /**
     * 初始化View
     */
    private void initView() {
        mBannerViewPager = findViewById(R.id.bannerViewPager);
        mDotContainerView = findViewById(R.id.dot_container);
        mBannerBottomView = findViewById(R.id.bannerBottomView);
        mBannerBottomView.setBackgroundColor(mBottomColor);
        mBannerViewPager.setPageTransformer(false, new SlidePageTransformer());
    }

    /**
     * 設(shè)置適配器adapter
     *
     * @param adapter 適配器
     */
    public void setAdapter(BannerAdapter adapter) {
        this.mAdapter = adapter;
        mBannerViewPager.setAdapter(adapter);
        mBannerViewPager.setCurrentItem(mBannerViewPager.getChildCount() / 2);
        initDotIndicator();
        mBannerViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
                //監(jiān)聽下當(dāng)前的位置
                super.onPageSelected(position);
                DotIndicatorView dotIndicatorView = ( DotIndicatorView ) mDotContainerView.
                        getChildAt(mCurrentPosition);
                dotIndicatorView.setDrawable(mIndicatorNormalDrawable);
                mCurrentPosition = position % mAdapter.getCount();
                DotIndicatorView mCurrentIndicatorView = ( DotIndicatorView ) mDotContainerView.
                        getChildAt(mCurrentPosition);
                mCurrentIndicatorView.setDrawable(mIndicatorFocusDrawable);
            }
        });
    }

    public void startLoop() {
        mBannerViewPager.startLoop();
    }

    public void setScrollerDuration(int scrollerDuration) {
        mBannerViewPager.setScrollerDuration(scrollerDuration);
    }

    /**
     * 初始化指示器
     */
    private void initDotIndicator() {
        //獲取廣告位的數(shù)量
        int count = mAdapter.getCount();
        //設(shè)置指示器的位置
        mDotContainerView.setGravity(getDotGravity());
        for (int i = 0; i < count; i++) {
            DotIndicatorView dot = new DotIndicatorView(mContext);
            //設(shè)置指示器的形狀
            dot.setShape(1);
            LinearLayout.LayoutParams param = null;
            //矩形
            if (dot.getShape() == 1) {
                //給指示器指定大小
                param = new LinearLayout.LayoutParams(mDotSize * 3, DensityUtil.dip2px(this.getContext(), 2));
                //圓形
            } else if (dot.getShape() == 2) {
                param = new LinearLayout.LayoutParams(mDotSize, mDotSize);
            }
            //設(shè)置間距
            param.leftMargin = param.rightMargin = mDotDistance;
            dot.setLayoutParams(param);
            if (i == 0) {
                dot.setDrawable(mIndicatorFocusDrawable);
            } else {
                dot.setDrawable(mIndicatorNormalDrawable);
            }
            mDotContainerView.addView(dot);
        }
    }

    public int getDotGravity() {
        switch (mDotGravity) {
            case 0:
                return Gravity.CENTER;
            case 1:
                return Gravity.RIGHT;
            case -1:
                return Gravity.LEFT;
        }
        return Gravity.RIGHT;
    }

SlidePageTransformer類

public class SlidePageTransformer implements ViewPager.PageTransformer {
@Override 
public void transformPage(@NonNull View page, float position) {
    if (position > 0 && position <= 1) {
        page.setPivotX(0);
        page.setScaleX(1 - position);
    } else if (position >= -1 && position < 0) {
        page.setPivotX(page.getWidth());
        page.setScaleX(1 + position);
    } 
} 
} 

BannerView這個類主要是一些自定義屬性猫牡,底部指示器的大小胡诗、顏色、間距等等淌友。主要說下這個 mBannerViewPager.setPageTransformer(false, new SlidePageTransformer());這個給ViewPager設(shè)置了一個平滑的縮放的動畫煌恢,但是看到了一個ViewPager設(shè)置動畫的一個坑,發(fā)現(xiàn)滑到第一張的時候震庭,在向右滑動的時候瑰抵,圖片會滑出一點邊緣。也不知道為什么器联?我認(rèn)為我的代碼沒有問題二汛,也聽說Android的源碼ViewPager去設(shè)置動畫,會有坑的存在主籍。哪位大神看到了,望賜教千元!

項目的完整代碼https://github.com/StevenYan88/BannerView

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末苫昌,一起剝皮案震驚了整個濱河市幸海,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌袜硫,老刑警劉巖氯葬,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異婉陷,居然都是意外死亡帚称,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門秽澳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來闯睹,“玉大人,你說我怎么就攤上這事担神÷コ裕” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵妄讯,是天一觀的道長孩锡。 經(jīng)常有香客問我,道長亥贸,這世上最難降的妖魔是什么躬窜? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮砌函,結(jié)果婚禮上斩披,老公的妹妹穿的比我還像新娘溜族。我一直安慰自己讹俊,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布煌抒。 她就那樣靜靜地躺著仍劈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪寡壮。 梳的紋絲不亂的頭發(fā)上贩疙,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機與錄音况既,去河邊找鬼这溅。 笑死,一個胖子當(dāng)著我的面吹牛棒仍,可吹牛的內(nèi)容都是我干的悲靴。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼莫其,長吁一口氣:“原來是場噩夢啊……” “哼癞尚!你這毒婦竟也來了耸三?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤浇揩,失蹤者是張志新(化名)和其女友劉穎仪壮,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胳徽,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡积锅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了养盗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乏沸。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖爪瓜,靈堂內(nèi)的尸體忽然破棺而出蹬跃,到底是詐尸還是另有隱情,我是刑警寧澤铆铆,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布蝶缀,位于F島的核電站,受9級特大地震影響薄货,放射性物質(zhì)發(fā)生泄漏翁都。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一谅猾、第九天 我趴在偏房一處隱蔽的房頂上張望柄慰。 院中可真熱鬧,春花似錦税娜、人聲如沸坐搔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽概行。三九已至,卻和暖如春弧岳,著一層夾襖步出監(jiān)牢的瞬間凳忙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工禽炬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留涧卵,地道東北人。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓腹尖,卻偏偏與公主長得像柳恐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

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