【轉(zhuǎn)】封裝并實(shí)現(xiàn)統(tǒng)一的圖片加載架構(gòu)

原文地址:https://juejin.im/post/58b280b92f301e0068078669

GitHub: 統(tǒng)一的圖片加載架構(gòu)

前言

對(duì)于圖片加載框架芬沉,大家用到的可能是Glide躺同,Picasso或者Fresco,這基本上是主流的圖片加載框架丸逸,我們使用它的時(shí)候蹋艺,大都感覺(jué)如臂使指,簡(jiǎn)直愉快的不要不要的黄刚。但是我們還是發(fā)現(xiàn)至少有兩個(gè)問(wèn)題捎谨,以Glide為例,第一隘击,當(dāng)需求變動(dòng),你需要對(duì)圖片加載失敗時(shí)的情景添加一個(gè)單獨(dú)的占位符研铆,這個(gè)時(shí)候你就不得不在每一個(gè)使用到Glide的地方去添加這樣的設(shè)置埋同;第二,當(dāng)你需要對(duì)項(xiàng)目進(jìn)行重構(gòu)時(shí)棵红,或者目前的圖片加載框架無(wú)法實(shí)現(xiàn)某些需求凶赁,而需要替換的時(shí)候,你可能還是需要對(duì)原有項(xiàng)目大動(dòng)干戈逆甜。

大家回顧自己手頭上的代碼虱肄,不知道是否都面臨這樣的隱患?反正當(dāng)我看到我們團(tuán)隊(duì)的項(xiàng)目代碼的時(shí)候交煞,我的頭總是比平時(shí)大兩倍...你問(wèn)我為啥咏窿?一堆歷史遺留問(wèn)題,比如最早就直接在項(xiàng)目中使用Glide,后來(lái)我建議說(shuō)素征,至少稍微做點(diǎn)封裝集嵌,畢竟吃相不能太難看,于是才做了一層封裝御毅,卻依然經(jīng)不起新需求的考驗(yàn)根欧,更別提替換框架的程度了了(這可能就是為什么我們團(tuán)隊(duì)轉(zhuǎn)向了RN,因?yàn)檎l(shuí)都不想看過(guò)去的代碼了)。

如果你以為這是因?yàn)槲沂且粋€(gè)完美主義者端蛆,那么可能沒(méi)有嘗試過(guò)一行一行粘貼復(fù)制凤粗,刪除重構(gòu)的日子。

廢話講完今豆,我們正是開(kāi)始吧

封裝的新使命

我們先聊聊封裝嫌拣,封裝的好處大家都很熟悉柔袁,對(duì)外提供簡(jiǎn)單接口屏蔽內(nèi)部復(fù)雜,保護(hù)數(shù)據(jù)亭罪,保證安全....等瘦馍,大家可能基本上都倒背如流了, 如今我們?cè)陂_(kāi)發(fā)Android項(xiàng)目的時(shí)候封裝的主要目的卻不再是這些了应役,為什么情组,因?yàn)槲覀兯兄T如okhttp,retrofit,Glide,等等框架本身就實(shí)現(xiàn)了完美的封裝箩祥,并達(dá)成了對(duì)外提供簡(jiǎn)單接口屏蔽內(nèi)部復(fù)雜院崇,保護(hù)數(shù)據(jù),保證安全等目的袍祖,如果僅僅是為了這些目的底瓣,我們大可不必在做封裝。

那么我們封裝的新的使命是什么呢蕉陋,是為了達(dá)成對(duì)模塊的控制捐凭,什么意思呢?還是以圖片加載框架為例凳鬓,假如你直接在業(yè)務(wù)代碼中使用了Glide,Picasso或者Fresco的話茁肠,也就意味著,你把圖片加載的控制權(quán)完全交給了他們缩举,后面你想對(duì)圖片加載流程做任何改動(dòng)垦梆,你都需要一個(gè)一個(gè)去修改,那么你就喪失了對(duì)圖片加載模塊的控制權(quán)仅孩。所以托猩,我所說(shuō)的對(duì)于模塊的控制,是你隨時(shí)能夠以很小的代價(jià)修改甚至替換整個(gè)模塊辽慕。

這也是為什么現(xiàn)在各種發(fā)開(kāi)框架已經(jīng)把自己封裝的如此之好的情況下京腥,我們依然需要對(duì)它做封裝的原因。

好了溅蛉,接下來(lái)绞旅,我們就分析具體問(wèn)題。

從封裝Glide開(kāi)始

以Glide為例温艇,Glid通過(guò)鏈?zhǔn)秸{(diào)用因悲,可以隨意的調(diào)用各種圖片加載相關(guān)的設(shè)定,如緩存策略勺爱,動(dòng)畫(huà)晃琳,占位符等等,各類api數(shù)不勝數(shù),而我們現(xiàn)在先要把這些調(diào)用抽象成一個(gè)接口卫旱,進(jìn)而就能輕松實(shí)現(xiàn)對(duì)它的封裝人灼。

一個(gè)簡(jiǎn)單的Glide的調(diào)用可能是這樣的:

        Glide.with(getContext())
                .load(url)
                .skipMemoryCache(true)
                .placeholder(drawable)
                .centerCrop()
                .animate(animator)
                .into(img);

盡管沒(méi)有使用Glide所有的圖片加載相關(guān)的設(shè)置,但是大家應(yīng)該能感受到顾翼,它的圖片加載設(shè)置選項(xiàng)十分豐富投放,也很隨意,那么我們究竟應(yīng)該如何把它封裝到一個(gè)接口里面去呢适贸?可能你首先想到是這種:

public interface ImageLoader{
    static void showImage(ImageView v, Context context,String url, boolean skipMemoryCache,int placeholder,ViewPropertyAnimation.Animator animator)
}

這顯然是很有問(wèn)題的灸芳,對(duì)于一個(gè)有很多可選項(xiàng)的接口做封裝,既要保留豐富的可選項(xiàng)拜姿,還要保證統(tǒng)一而簡(jiǎn)潔的調(diào)用烙样。這么一長(zhǎng)串參數(shù)顯然有傷大雅。

那么應(yīng)該如何設(shè)計(jì)呢蕊肥?我們可以從這個(gè)角度來(lái)分析谒获,對(duì)于圖片加載而言,什么是最基本最重要的必選項(xiàng)壁却,什么是可有可無(wú)的可選項(xiàng):

  • 必選項(xiàng):url(圖片來(lái)源)批狱,ImageView(圖片容器),上下文環(huán)境(Context)
  • 可選項(xiàng):除此必選項(xiàng)之外的所有

那么我們的接口初具雛形了

public interface ImageLoader{
    void showImage(ImageView imageview, String url, Context context,ImageLoaderOptions options);
    void showImage(ImageView imageview,int drawable,Context context,ImageLoaderOptions options);
}

這樣是不是就好了呢展东?也不是赔硫,我們還可以在繼續(xù)探索,
我們發(fā)現(xiàn)ImageView內(nèi)部其實(shí)包含了Context這個(gè)參數(shù)琅锻,完全可以省略卦停,所以我們的基本參數(shù)應(yīng)該是:url,ImageView,options向胡,

public interface ImageLoader{
    void showImage(ImageView imageview,  String url, ImageLoaderOptions options);
    void showImage(ImageView imageview, int drawable,ImageLoaderOptions options);
}

然后我們?cè)賮?lái)看看方法中定義的ImageLoaderOptions恼蓬,這個(gè)其實(shí)比較簡(jiǎn)單,基本上Glide有多少可選項(xiàng)僵芹,你就可以往里面加多少屬性处硬。由于這些屬性都是可選擇的,因此我們需要使用Builder模式來(lái)構(gòu)建它拇派,具體就不贅述了荷辕。

那么,到這里件豌,我們對(duì)于Glide的封裝的設(shè)計(jì)就基本完成了疮方。

統(tǒng)一的圖片加載架構(gòu)

我們說(shuō)了想要打造一個(gè)統(tǒng)一的圖片加載框架,也就是說(shuō)茧彤,不管Glide骡显,還是Fresco,或者Picasso都能在這套架構(gòu)下愉快的玩耍。其實(shí)我們只要在封裝Glide的基礎(chǔ)上進(jìn)一步的做出改進(jìn)即可惫谤,因?yàn)楫?dāng)我們封裝Glide的時(shí)候壁顶,就已經(jīng)是對(duì)圖片加載的抽象了。

我們首先來(lái)看溜歪,之前抽象的接口總體上在其他的圖片加載框架中都是可用的若专,不過(guò)由于Fresco的特殊設(shè)計(jì),自己實(shí)現(xiàn)了圖片容器蝴猪,導(dǎo)致了一點(diǎn)問(wèn)題调衰,但是這也很簡(jiǎn)單,我們?cè)诮涌诶锩嬗肰iew作為圖片容器即可拯腮。

public interface ImageLoader{
    void showImage(View v,  String url, ImageLoaderOptions options);
    void showImage(View v, int drawable,ImageLoaderOptions options);
}

好了窖式,上面這個(gè)接口基本上可以完美兼容Glide,Picasso动壤,F(xiàn)resco這三種加載庫(kù)萝喘,現(xiàn)在的問(wèn)題是如何實(shí)現(xiàn)他們的可替換。這個(gè)時(shí)候我們就需要一種設(shè)計(jì)模式(策略模式迫不及待的跳出來(lái)說(shuō)琼懊,選我選我8篝ぁ)

沒(méi)錯(cuò),就是策略模式哼丈,它的設(shè)計(jì)圖如下:

(圖片畫(huà)的不好启妹,大家多多包含)

至此,我們?cè)谠O(shè)計(jì)上已經(jīng)完成了一個(gè)統(tǒng)一的圖片加載架構(gòu)的設(shè)計(jì)醉旦,但是有一個(gè)問(wèn)題我特意留到了最后饶米,就是ImageLoaderOptions的內(nèi)部的構(gòu)造。

當(dāng)我們只需要封裝一個(gè)Glide的時(shí)候车胡,ImageLoaderOptions可以和Glide中的那些設(shè)置項(xiàng)完全匹配檬输,只要你愿意,你可以把Glide里面的所有圖片加載的相關(guān)的設(shè)置項(xiàng)都放進(jìn)去匈棘。但是丧慈,如果我們要兼容三個(gè)加載框架甚至更多的時(shí)候,還能這樣做么主卫?

理論上是可以的逃默,不過(guò)當(dāng)你這么干了,那么ImageLoaderOptions內(nèi)部可能是可能是這樣的:

public class ImageLoaderOptions {
    //Glide的設(shè)置項(xiàng)
    private int placeHolder=-1; //當(dāng)沒(méi)有成功加載的時(shí)候顯示的圖片
    private ImageReSize size=null; //重新設(shè)定容器寬高
    private int errorDrawable=-1;  //加載錯(cuò)誤的時(shí)候顯示的drawable
    private boolean isCrossFade=false; //是否漸變平滑的顯示圖片
    private  boolean isSkipMemoryCache = false; //是否跳過(guò)內(nèi)存緩存
    private   ViewPropertyAnimation.Animator animator = null; // 圖片加載動(dòng)畫(huà)
    ...
    ...
     //Fresco的設(shè)置項(xiàng)
    private int placeHolder=-1; //當(dāng)沒(méi)有成功加載的時(shí)候顯示的圖片
    private Drawable  pressedStateOverlay =null;  //按下時(shí)顯示的圖層
    private boolean isCrossFade=false; //是否漸變平滑的顯示圖片

    ...
    ...
}

大家很容易發(fā)現(xiàn)簇搅,其實(shí)各個(gè)圖片加載框架之間的設(shè)置項(xiàng)很多功能都是重疊的完域,比如占位符,漸進(jìn)加載瘩将,緩存等等吟税,也有一些設(shè)置項(xiàng)是類似的关噪,因此實(shí)際上我們應(yīng)該把他們合并在一起,也就是說(shuō)乌妙,當(dāng)我們思考對(duì)于ImageLoaderOptions的設(shè)計(jì)的時(shí)候使兔,我們應(yīng)該首先把幾個(gè)框架共同和相似的設(shè)置項(xiàng)合并,因?yàn)檫@代表著圖片加載領(lǐng)域最普遍最重要的需求藤韵。其次我們?cè)侔葱杓尤胱约盒枰母鱾€(gè)框架之間有差異的設(shè)置項(xiàng)虐沥。

下面是我對(duì)于這個(gè)統(tǒng)一圖片加載架構(gòu)的具體實(shí)現(xiàn),大家可以僅作參考泽艘。

接口定義

public interface ImageLoaderStrategy{
    void showImage(View v,  String url, ImageLoaderOptions options);
    void showImage(View v, int drawable,ImageLoaderOptions options);
}

設(shè)置項(xiàng)定義

public class ImageLoaderOptions {
    //你可以把三個(gè)圖片加載框架所有的共同或相似設(shè)置項(xiàng)搬過(guò)來(lái)欲险,現(xiàn)在僅僅用以下幾種作為范例演示。
    private int placeHolder=-1; //當(dāng)沒(méi)有成功加載的時(shí)候顯示的圖片
    private ImageReSize size=null; //重新設(shè)定容器寬高
    private int errorDrawable=-1;  //加載錯(cuò)誤的時(shí)候顯示的drawable
    private boolean isCrossFade=false; //是否漸變平滑的顯示圖片
    private  boolean isSkipMemoryCache = false; //是否跳過(guò)內(nèi)存緩存
    private   ViewPropertyAnimation.Animator animator = null; // 圖片加載動(dòng)畫(huà)


    private ImageLoaderOptions(ImageReSize resize, int placeHolder, int errorDrawable, boolean isCrossFade, boolean isSkipMemoryCache, ViewPropertyAnimation.Animator animator){
        this.placeHolder=placeHolder;
        this.size=resize;
        this.errorDrawable=errorDrawable;
        this.isCrossFade=isCrossFade;
        this.isSkipMemoryCache=isSkipMemoryCache;
        this.animator=animator;
    }
    public class ImageReSize{
        int reWidth=0;
        int reHeight=0;
        public ImageReSize(int reWidth,int reHeight){
            if (reHeight<=0){
                reHeight=0;
            }
            if (reWidth<=0) {
                reWidth=0;
            }
            this.reHeight=reHeight;
            this.reWidth=reWidth;

        }

    }
 public static final  class Builder {
        private int placeHolder=-1; 
        private ImageReSize size=null;
        private int errorDrawable=-1;
        private boolean isCrossFade =false;
        private  boolean isSkipMemoryCache = false;
        private   ViewPropertyAnimation.Animator animator = null;
        public Builder (){

        }
        public Builder placeHolder(int drawable){
            this.placeHolder=drawable;
            return  this;
        }

        public Builder reSize(ImageReSize size){
            this.size=size;
            return  this;
        }

        public Builder anmiator(ViewPropertyAnimation.Animator animator){
            this.animator=animator;
            return  this;
        }
        public Builder errorDrawable(int errorDrawable){
            this.errorDrawable=errorDrawable;
            return  this;
        }
        public Builder isCrossFade(boolean isCrossFade){
            this.isCrossFade=isCrossFade;
            return  this;
        }
        public Builder isSkipMemoryCache(boolean isSkipMemoryCache){
            this.isSkipMemoryCache=isSkipMemoryCache;
            return  this;
        }

        public ImageLoaderOptions build(){

            return new ImageLoaderOptions(this.size,this.placeHolder,this.errorDrawable,this.isCrossFade,this.isSkipMemoryCache,this.animator);
        }
    }

下面以Glide實(shí)現(xiàn)該接口的方式:

public class GlideImageLoaderStrategy implements ImageLoaderStrategy {

    @Override
    public void showImage(View v, String url, ImageLoaderOptions options) {
        if (v instanceof ImageView) {
            //將類型轉(zhuǎn)換為ImageView
            ImageView imageView= (ImageView) v;
            //裝配基本的參數(shù)
            DrawableTypeRequest dtr = Glide.with(imageView.getContext()).load(url);
            //裝配附加參數(shù)
            loadOptions(dtr, options).into(imageView);
        }
    }

    @Override
    public void showImage(View v, int drawable, ImageLoaderOptions options) {
        if (v instanceof ImageView) {
            ImageView imageView= (ImageView) v;
            DrawableTypeRequest dtr = Glide.with(imageView.getContext()).load(drawable);
            loadOptions(dtr, options).into(imageView);
        }
    }
    //這個(gè)方法用來(lái)裝載由外部設(shè)置的參數(shù)
    private DrawableTypeRequest loadOptions(DrawableTypeRequest dtr,ImageLoaderOptions options){
        if (options==null) {
            return dtr;
        }
        if (options.getPlaceHolder()!=-1) {
            dtr.placeholder(options.getPlaceHolder());
        }
        if (options.getErrorDrawable()!=-1){
            dtr.error(options.getErrorDrawable());
        }
        if (options.isCrossFade()) {
            dtr.crossFade();
        }
        if (options.isSkipMemoryCache()){
            dtr.skipMemoryCache(options.isSkipMemoryCache());
        }
        if (options.getAnimator()!=null) {
            dtr.animate(options.getAnimator());
        }
        if (options.getSize()!=null) {
            dtr.override(options.getSize().reWidth,options.getSize().reHeight);
        }
        return dtr;
    }

}

Picsso,Fresco的接口實(shí)現(xiàn)類依照Glide匹涮。

下面就是最后一步天试,實(shí)現(xiàn)整個(gè)圖片加載架構(gòu)的管理類,用于對(duì)外提供圖片加載服務(wù)和圖片加載框架的替換

public class ImageLoaderStrategyManager implements ImageLoaderStrategy {
    private static final ImageLoaderStrategyManager INSTANCE = new ImageLoaderStrategyManager();
    private ImageLoaderStrategy imageLoader;
    private ImageLoaderStrategyManager(){
        //默認(rèn)使用Glide
        imageLoader=new GlideImageLoaderStrategy();
    }
    public static ImageLoaderStrategyManager getInstance(){
        return INSTANCE;
    }
    //可實(shí)時(shí)替換圖片加載框架
  public void setImageLoader(ImageLoaderStrategy loader) {
      if (loader != null) {
          imageLoader=loader;
      }
   }

    @Override
    public void showImage(@NonNull View mView, @NonNull String mUrl, @Nullable ImageLoaderOptions options) {

        imageLoader.showImage(mView,mUrl,options);
    }


    @Override
    public void showImage(@NonNull  View mView, @NonNull int mDraeable, @Nullable ImageLoaderOptions options) {
        imageLoader.showImage(mView,mDraeable,options);
    }

}

至此然低,整個(gè)圖片加載架構(gòu)都已經(jīng)設(shè)計(jì)完畢了喜每,我們也可以基本實(shí)現(xiàn)了對(duì)圖片加載模塊的控制。

這個(gè)小的圖片加載架構(gòu)是不是已經(jīng)很完美了呢雳攘?其實(shí)也不是带兜,由于Fresco的特殊,當(dāng)我們切換到Fresco吨灭,或者從Fresco切換到其他加載框架的時(shí)候刚照,我們可能仍然需要到處去修改xml文件的圖片容器節(jié)點(diǎn)(ImageView/DraweeView),因?yàn)镕resco使用的時(shí)自家的組件喧兄。不過(guò)我也考慮過(guò)一種解決方案无畔,那就是把圖片容器(ImageView/DraweeView)節(jié)點(diǎn)放在一個(gè)單獨(dú)的xml文件中,使用merge的方式添加到布局文件中吠冤,并在代碼層面使統(tǒng)一用View 來(lái)獲取圖片容器(ImageView/DraweeView)的實(shí)例做相應(yīng)操作浑彰。

?著作權(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)店門横辆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(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

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,522評(píng)論 25 707
  • 在Android設(shè)備上,加載網(wǎng)絡(luò)圖片一直是一個(gè)頭疼的問(wèn)題阳掐,因?yàn)锳ndroid設(shè)備種類繁多(當(dāng)然最主要的是配置)始衅,處...
    Code4Android閱讀 17,667評(píng)論 5 96
  • 這么久以來(lái)雖然經(jīng)常用到一些圖庫(kù),但是自己從來(lái)沒(méi)有真正整理過(guò)我們使用過(guò)的這些東西有什么不同點(diǎn),我們?yōu)槭裁匆x擇這個(gè)圖...
    黑石ZB閱讀 3,711評(píng)論 1 16
  • "禪繞"世界不設(shè)期待。 荼靡最初開(kāi)始畫(huà)禪繞畫(huà)缭保,總是會(huì)在腦海里設(shè)想出一個(gè)自己要畫(huà)出的樣子汛闸。可是試過(guò)幾次后...
    所謂荼靡閱讀 297評(píng)論 1 2
  • 大頭兒子和小頭爸爸之第二課糖果牙醫(yī)涮俄,里面說(shuō)明了每天要勤刷牙蛉拙,飯后及時(shí)漱口尸闸,不能吃甜食彻亲,以后才不會(huì)去看牙醫(yī)孕锄,更不可以...
    許致遠(yuǎn)閱讀 175評(píng)論 0 0