Android 21點(diǎn)


0x0000 寫在前面

  • 在寫這個(gè)游戲之前,只是模糊地記得文曲星上的21點(diǎn)游戲規(guī)則
    想當(dāng)然地認(rèn)為就是一個(gè)先后發(fā)牌然后開牌比大小的游戲
    結(jié)果百度一下,發(fā)現(xiàn)玩法比我想的復(fù)雜不少
    這個(gè)demo里刪掉了分牌的功能够委,并且只支持單人游戲
  • 轉(zhuǎn)載請注明
    作者和平北路
    原文點(diǎn)擊鏈接

0x0001 功能簡介

  • 莊家發(fā)牌
  • 玩家發(fā)牌
  • 玩家選擇發(fā)牌/停牌/放棄
  • 比較大小

0x0002 工程結(jié)構(gòu)

UML類圖
  • Card是最基本的卡牌對象,包含花色(Suit)和大小(Rank)兩個(gè)屬性
  • Deck是去掉了大小王的一副牌
  • CardImage是對一張牌的封裝,包含了卡牌的內(nèi)容(Card)和與之綁定的一個(gè)視圖對象(ImageView)测砂,用于在屏幕上進(jìn)行旋轉(zhuǎn)、移動等動作
  • BitmapUtils是一個(gè)工具類百匆,用于從一張包含52張卡牌圖案的圖片上裁剪對應(yīng)Card的圖片
  • ScreenUtils是一個(gè)工具了砌些,用于獲取屏幕寬高(只適用于全屏)
  • Rotate3dAnimation是3D旋轉(zhuǎn)動畫,請自行百度Android官方源碼

0x0003 源碼分析

public class Card {

    enum Suit {HEART, SPADE, DIAMOND, CLUB}

    enum Rank {ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING}

    private Suit suit;
    private Rank rank;

    public Card(Suit suit, Rank rank) {
        this.suit = suit;
        this.rank = rank;
    }

    public Suit getSuit() {
        return suit;
    }

    public Rank getRank() {
        return rank;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || obj.getClass() != getClass()) {
            return false;
        }
        Card card = (Card) obj;
        if (card.suit != suit || card.rank != rank) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int result = suit.ordinal();
        result = 31 * result + rank.ordinal();
        return result;
    }
}
public class Deck {

    Collection<Card.Suit> suits = Arrays.asList(Card.Suit.values());
    Collection<Card.Rank> ranks = Arrays.asList(Card.Rank.values());

    private List<Card> deck = new ArrayList<>();

    public Deck() {
        init();
    }

    private void init() {
        for (Iterator<Card.Suit> i = suits.iterator(); i.hasNext(); /*do nothing*/) {
            Card.Suit suit = i.next();
            for (Iterator<Card.Rank> j = ranks.iterator(); j.hasNext(); /*do nothing*/) {
                deck.add(new Card(suit, j.next()));
            }
        }
    }

    public List<Card> getDeck() {
        return deck;
    }

}

public class CardImage {

    private static final long DURATION = 500L;

    private static final float DEPTH_Z = 0f;

    private int translationX;
    private int translationY;

    private Card card;
    private ImageView image;

    private AnimatorSet animatorSet;
    private IAnimationCallback callback;

    public CardImage(Card card, ImageView image) {
        this.card = card;
        this.image = image;
        translationX = 0;
        translationY = 0;
    }

    public void setCallback(IAnimationCallback callback) {
        this.callback = callback;
    }

    public void translate(final int x, final int y) {
        if (image == null) {
            return;
        }

        if (image.getLeft() == 0 || image.getTop() == 0) {
            final ViewTreeObserver observer = image.getViewTreeObserver();
            observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    translate(x, y);
                    observer.removeOnGlobalLayoutListener(this);
                }
            });
            return;
        }

        if (null != animatorSet) {
            animatorSet.end();
        }
        image.clearAnimation();

        ObjectAnimator animX = ObjectAnimator.ofFloat(image, "translationX", x - image.getLeft());
        ObjectAnimator animY = ObjectAnimator.ofFloat(image, "translationY", y - image.getTop());
        translationX = x - image.getLeft();
        translationY = y - image.getTop();

        animatorSet = new AnimatorSet();
        animatorSet.playTogether(animX, animY);
        animatorSet.setDuration(DURATION);
        animatorSet.start();
        animatorSet.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (null != callback)
                    callback.onTranslationEnd();
            }

            @Override
            public void onAnimationCancel(Animator animation) {
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }
        });
    }

    public void front() {
        if (image == null) {
            return;
        }

        if (null != animatorSet) {
            animatorSet.end();
        }
        image.clearAnimation();

        final int x = image.getLayoutParams().width / 2 + translationX;
        final int y = image.getLayoutParams().height / 2 + translationY;
        Animation firstHalf = rotate(0, 90, x, y);
        firstHalf.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                image.setImageBitmap(BitmapUtils.getCardImage(image.getContext(), card));
                Animation secondHalf = rotate(270, 360, x, y);
                secondHalf.setAnimationListener(new Animation.AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) {
                    }

                    @Override
                    public void onAnimationEnd(Animation animation) {
                        if (null != callback)
                            callback.onFrontEnd();
                    }

                    @Override
                    public void onAnimationRepeat(Animation animation) {
                    }
                });
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }
        });
    }

    private Animation rotate(float startDegree, float endDegree, float centerX, float centerY) {
        Rotate3dAnimation anim = new Rotate3dAnimation(
                startDegree, endDegree, centerX, centerY, DEPTH_Z, false);
        anim.setDuration(DURATION);
        anim.setFillAfter(true);
        image.startAnimation(anim);
        return anim;
    }

    public Card getCard() {
        return card;
    }

    public ImageView getImage() {
        return image;
    }

    public interface IAnimationCallback {
        void onFrontEnd();

        void onTranslationEnd();
    }

}
public class BitmapUtils {

    public static Bitmap cards;

    public static Bitmap getCardImage(Context context, Card card) {
        if (null == card)
            return null;
        if (null == cards)
            cards = BitmapFactory.decodeResource(context.getResources(), R.drawable.cards);

        int rows = Card.Suit.values().length;
        int cols = Card.Rank.values().length;
        int width = cards.getWidth() / cols;
        int height = cards.getHeight() / rows;
        int x = width * card.getRank().ordinal();
        int y = height * card.getSuit().ordinal();
        return Bitmap.createBitmap(cards, x, y, width, height);
    }

}
public class ScreenUtils {

    private static int sScreenWidth;
    private static int sScreenHeight;

    public static void init(Context context) {
        Resources resources = context.getResources();
        DisplayMetrics dm = resources.getDisplayMetrics();
        sScreenWidth = dm.widthPixels;
        sScreenHeight = dm.heightPixels;
    }

    public static int getScreenWidth(Context context) {
        if (sScreenWidth <= 0) {
            init(context);
        }
        return sScreenWidth;
    }

    public static int getScreenHeight(Context context) {
        if (sScreenHeight <= 0) {
            init(context);
        }
        return sScreenHeight;
    }

}
public class MainActivity extends Activity {

    private static final int MSG_PLAYER_DEAL = 0x00;
    private static final int MSG_PLAYER_HIT = 0x01;
    private static final int MSG_PLAYER_STAND = 0x02;
    private static final int MSG_PLAYER_FOLD = 0x03;
    private static final int MSG_BANKER_DEAL = 0x04;
    private static final int MSG_BANKER_DEAL_HIDE = 0x05;
    private static final int MSG_BANKER_FRONT_HIDE = 0x06;
    private static final int MSG_RESET = 0x07;

    private static final int TEN = 10;
    private static final int BLACK_JACK = 21;

    private static final int CARD_WIDTH = 225;
    private static final int CARD_HEIGHT = 315;
    private static final int CARD_MARGIN = 10;
    private static final int CARD_MARGIN_TOP = 20;
    private static final int CARD_MARGIN_BOTTOM = 500;

    private int screenWidth;
    private int screenHeight;

    private Random random;

    private ViewGroup parent;
    private View hit;
    private View stand;
    private View fold;

    private List<Card> cards;
    private List<CardImage> playerCards;
    private List<CardImage> bankerCards;

    private Handler msgHandle = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            int what = msg.what;
            switch (what) {
                case MSG_PLAYER_DEAL:
                    dealPlayer();
                    break;
                case MSG_BANKER_DEAL:
                    dealBanker();
                    break;
                case MSG_BANKER_DEAL_HIDE:
                    dealBankerHide();
                    break;
                case MSG_BANKER_FRONT_HIDE:
                    frontBankerHide();
                    break;
                case MSG_PLAYER_HIT:
                    hit();
                    break;
                case MSG_PLAYER_STAND:
                    stand();
                    break;
                case MSG_PLAYER_FOLD:
                    fold();
                    break;
                case MSG_RESET:
                    reset();
                    break;
                default:
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);

        ScreenUtils.init(getApplicationContext());
        setContentView(R.layout.activity_main);

        screenWidth = ScreenUtils.getScreenWidth(this);
        screenHeight = ScreenUtils.getScreenHeight(this);

        random = new Random();
        cards = new Deck().getDeck();
        playerCards = new ArrayList<>();
        bankerCards = new ArrayList<>();

        parent = (ViewGroup) findViewById(R.id.activity_main);

        hit = findViewById(R.id.hit);
        hit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                hit();
            }
        });
        stand = findViewById(R.id.stand);
        stand.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stand();
            }
        });
        fold = findViewById(R.id.fold);
        fold.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                fold();
            }
        });

        deal();
    }

    private void deal() {
        disableButtons();
        msgHandle.sendMessageDelayed(Message.obtain(msgHandle, MSG_PLAYER_DEAL), 500);
        msgHandle.sendMessageDelayed(Message.obtain(msgHandle, MSG_BANKER_DEAL), 2000);
        msgHandle.sendMessageDelayed(Message.obtain(msgHandle, MSG_PLAYER_DEAL), 3500);
        msgHandle.sendMessageDelayed(Message.obtain(msgHandle, MSG_BANKER_DEAL_HIDE), 5000);
    }

    private void dealPlayer() {
        disableButtons();
        CardImage player = new CardImage(
                cards.remove(random.nextInt(cards.size())),
                createCardImage());
        player.setCallback(new CardImage.IAnimationCallback() {
            @Override
            public void onFrontEnd() {
                updatePlayerCardsLocation();
            }

            @Override
            public void onTranslationEnd() {
                if (playerCards.size() >= 2) {
                    enableButtons();
                }
                if (playerCards.size() > 2) {
                    check();
                }
            }
        });
        player.front();
        playerCards.add(player);
    }

    private void dealBanker() {
        disableButtons();
        CardImage banker = new CardImage(
                cards.remove(random.nextInt(cards.size())),
                createCardImage());
        banker.setCallback(new CardImage.IAnimationCallback() {
            @Override
            public void onFrontEnd() {
                updateBankerCardsLocation();
            }

            @Override
            public void onTranslationEnd() {
                if (bankerCards.size() > 2) {
                    disableButtons();
                    if (!check()) {
                        if (count(bankerCards) > count(playerCards)) {
                            lose();
                        } else {
                            msgHandle.sendMessageDelayed(Message.obtain(msgHandle, MSG_BANKER_DEAL), 2000);
                        }
                    }
                }
            }
        });
        banker.front();
        bankerCards.add(banker);
    }

    private void dealBankerHide() {
        disableButtons();
        CardImage banker = new CardImage(
                cards.remove(random.nextInt(cards.size())),
                createCardImage());
        banker.setCallback(new CardImage.IAnimationCallback() {
            @Override
            public void onFrontEnd() {
            }

            @Override
            public void onTranslationEnd() {
                check();
            }
        });
        bankerCards.add(banker);
        updateBankerCardsLocation();
    }

    private void frontBankerHide() {
        disableButtons();
        CardImage banker = bankerCards.get(1);
        banker.front();
    }

    private ImageView createCardImage() {
        ImageView card = new ImageView(this);
        card.setImageResource(R.drawable.card_back);
        card.setScaleType(ImageView.ScaleType.CENTER_CROP);
        RelativeLayout.LayoutParams layoutParams =
                new RelativeLayout.LayoutParams(CARD_WIDTH, CARD_HEIGHT);
        layoutParams.addRule(RelativeLayout.CENTER_VERTICAL);
        layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
        layoutParams.rightMargin = 40;
        parent.addView(card, layoutParams);
        return card;
    }

    private int[] getPlayerCardLocation(int num) {
        int centerX = screenWidth / 2;
        int size = playerCards.size();
        int x = centerX
                - ((size - 1) * CARD_MARGIN + size * CARD_WIDTH) / 2
                + num * (CARD_MARGIN + CARD_WIDTH);
        int y = screenHeight - CARD_MARGIN_BOTTOM;
        return new int[]{x, y};
    }

    private void updatePlayerCardsLocation() {
        int size = playerCards.size();
        for (int i = 0; i < size; i++) {
            int[] location = getPlayerCardLocation(i);
            CardImage cardImage = playerCards.get(i);
            cardImage.translate(location[0], location[1]);
        }
    }

    private int[] getBankerCardLocation(int num) {
        int centerX = screenWidth / 2;
        int size = bankerCards.size();
        int x = centerX
                - ((size - 1) * CARD_MARGIN + size * CARD_WIDTH) / 2
                + num * (CARD_MARGIN + CARD_WIDTH);
        int y = CARD_MARGIN_TOP;
        return new int[]{x, y};
    }

    private void updateBankerCardsLocation() {
        int size = bankerCards.size();
        for (int i = 0; i < size; i++) {
            int[] location = getBankerCardLocation(i);
            CardImage cardImage = bankerCards.get(i);
            cardImage.translate(location[0], location[1]);
        }
    }

    private boolean check() {
        boolean isBankerBlackJack = isBlackJack(bankerCards);
        boolean isPlayerBlackJack = isBlackJack(playerCards);
        if (isBusted(playerCards) || (isBankerBlackJack && !isPlayerBlackJack)) {
            lose();
            return true;
        } else if (isBusted(bankerCards) || (!isBankerBlackJack && isPlayerBlackJack)) {
            win();
            return true;
        } else if (isBankerBlackJack && isPlayerBlackJack) {
            draw();
            return true;
        }
        return false;
    }

    private boolean isBlackJack(List<CardImage> handCards) {
        return count(handCards) == BLACK_JACK;
    }

    private boolean isBusted(List<CardImage> handCards) {
        return count(handCards) > BLACK_JACK;
    }

    private int count(List<CardImage> handCards) {
        int total = 0;
        if (null == handCards) {
            return total;
        }
        for (CardImage cardImage : handCards) {
            if (cardImage == null || cardImage.getCard() == null) {
                continue;
            }
            Card card = cardImage.getCard();
            if (card.getRank() == Card.Rank.ACE) {
                if (total + 11 > BLACK_JACK) {
                    total += 1;
                } else {
                    total += 11;
                }
            } else if (card.getRank().ordinal() >= 9) {
                total += TEN;
            } else {
                total += (card.getRank().ordinal() + 1);
            }
        }
        return total;
    }

    private void reset() {
        cards = new Deck().getDeck();
        for (CardImage cardImage : playerCards) {
            if (cardImage != null && cardImage.getImage() != null) {
                parent.removeView(cardImage.getImage());
            }
        }
        for (CardImage cardImage : bankerCards) {
            if (cardImage != null && cardImage.getImage() != null) {
                parent.removeView(cardImage.getImage());
            }
        }
        playerCards.clear();
        bankerCards.clear();
        for (int i = 0; i < 8; i++) {
            msgHandle.removeMessages(i);
        }
        deal();
    }

    private void enableButtons() {
        hit.setClickable(true);
        stand.setClickable(true);
        fold.setClickable(true);
    }

    private void disableButtons() {
        hit.setClickable(false);
        stand.setClickable(false);
        fold.setClickable(false);
    }

    private void hit() {
        disableButtons();
        msgHandle.sendMessage(Message.obtain(msgHandle, MSG_PLAYER_DEAL));
    }

    private void stand() {
        disableButtons();
        if (count(bankerCards) > count(playerCards)) {
            lose();
            return;
        }
        msgHandle.sendMessage(Message.obtain(msgHandle, MSG_BANKER_DEAL));
    }

    private void fold() {
        disableButtons();
        frontBankerHide();
        lose();
    }

    private void win() {
        disableButtons();
        Toast.makeText(MainActivity.this, "you win!", Toast.LENGTH_SHORT).show();
        msgHandle.sendMessage(Message.obtain(msgHandle, MSG_BANKER_FRONT_HIDE));
        msgHandle.sendMessageDelayed(Message.obtain(msgHandle, MSG_RESET), 4000);
    }

    private void lose() {
        disableButtons();
        Toast.makeText(MainActivity.this, "you lose!", Toast.LENGTH_SHORT).show();
        msgHandle.sendMessage(Message.obtain(msgHandle, MSG_BANKER_FRONT_HIDE));
        msgHandle.sendMessageDelayed(Message.obtain(msgHandle, MSG_RESET), 4000);
    }

    private void draw() {
        disableButtons();
        Toast.makeText(MainActivity.this, "game draws!", Toast.LENGTH_SHORT).show();
        msgHandle.sendMessage(Message.obtain(msgHandle, MSG_BANKER_FRONT_HIDE));
        msgHandle.sendMessageDelayed(Message.obtain(msgHandle, MSG_RESET), 4000);
    }

}

其實(shí)源碼沒有什么技術(shù)難點(diǎn):

  • 所有的動作都以Message方式傳遞給Handler處理加匈,Handler分發(fā)事件調(diào)用各個(gè)方法
  • 每發(fā)一張牌存璃,都是在屏幕上new了一個(gè)ImageView,并對這個(gè)ImageView進(jìn)行動畫操作
  • 自認(rèn)為寫的比較挫的是對連續(xù)動畫實(shí)現(xiàn)的不好雕拼,現(xiàn)在的硬編碼low爆了纵东。一個(gè)AnimationListener嵌套另一個(gè)AnimationListener,而且還需要添加Callback監(jiān)聽旋轉(zhuǎn)和移動動畫完成后的下一個(gè)操作啥寇,更好的實(shí)現(xiàn)方式是單起Thread篮迎,利用sleep或者wait/notify來實(shí)現(xiàn)動畫的銜接
  • Animator的使用沒有想的簡單,說是會改變View屬性示姿,但連續(xù)使用“translation”操作,View對象的邊界其實(shí)沒有改變逊笆,后續(xù)的傳值需要考慮之前的translation賦值栈戳,或者在每次動畫之后調(diào)用View.layout方法更新一遍邊界

0x0004 寫碼感想

  • 像Handler、Animator這些東西自以為源碼看了幾遍應(yīng)該手到擒來的难裆,在使用的時(shí)候還是會發(fā)現(xiàn)各種效果實(shí)現(xiàn)和自己想的不一樣
  • 行勝于言
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末子檀,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子乃戈,更是在濱河造成了極大的恐慌褂痰,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件症虑,死亡現(xiàn)場離奇詭異缩歪,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)谍憔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進(jìn)店門匪蝙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人习贫,你說我怎么就攤上這事逛球。” “怎么了苫昌?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵颤绕,是天一觀的道長。 經(jīng)常有香客問我,道長奥务,這世上最難降的妖魔是什么物独? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮汗洒,結(jié)果婚禮上议纯,老公的妹妹穿的比我還像新娘。我一直安慰自己溢谤,他們只是感情好瞻凤,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著世杀,像睡著了一般阀参。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瞻坝,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天蛛壳,我揣著相機(jī)與錄音,去河邊找鬼所刀。 笑死衙荐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的浮创。 我是一名探鬼主播忧吟,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼斩披!你這毒婦竟也來了溜族?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤垦沉,失蹤者是張志新(化名)和其女友劉穎煌抒,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體厕倍,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡寡壮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了绑青。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诬像。...
    茶點(diǎn)故事閱讀 37,989評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖闸婴,靈堂內(nèi)的尸體忽然破棺而出坏挠,到底是詐尸還是另有隱情,我是刑警寧澤邪乍,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布降狠,位于F島的核電站对竣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏榜配。R本人自食惡果不足惜否纬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蛋褥。 院中可真熱鬧临燃,春花似錦、人聲如沸烙心。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽淫茵。三九已至爪瓜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間匙瘪,已是汗流浹背铆铆。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丹喻,地道東北人薄货。 一個(gè)月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像碍论,于是被迫代替她去往敵國和親菲驴。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評論 2 345

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,498評論 25 707
  • 1骑冗、前言 在我們體驗(yàn)一款A(yù)PP時(shí),炫酷的動畫往往能讓用戶體驗(yàn)大幅度提升先煎。想當(dāng)年我剛學(xué)Android的時(shí)候贼涩,無意中看...
    帶心情去旅行閱讀 37,472評論 7 51
  • 陽光格外得刺眼遥倦,照亮了那片荒地。 “小寶占锯,小寶袒哥,快叫媽媽呀!”床邊一個(gè)面露苦色的女人溫柔地?fù)崦采弦阉剖焖暮?..
    三分甜的甜閱讀 417評論 0 3
  • 5月17日 星期三 晴 今天下午放學(xué)消略,我堡称、媽媽和姐姐的同學(xué)去九龍公園打羽毛球。 我們先吃了一...
    曾博睿閱讀 195評論 2 2
  • 還是無艺演。
    kcaron閱讀 170評論 0 0