Android用建造者模式實(shí)現(xiàn)一個(gè)新功能引導(dǎo)頁

最近每次版本更新都會(huì)在UI變動(dòng)或加了新功能的地方加一個(gè)引導(dǎo)蒙層頁面(新功能標(biāo)記、文案和一個(gè)“知道了”的按鈕)贰谣,有時(shí)候一個(gè)版本會(huì)加三四個(gè)頁面硬霍,原來的做法是:

  • 1.在每個(gè)Activity的布局文件外層添加一個(gè)FrameLayout(這方法好蠢,又要嵌套一層布局)瘦真;
  • 2.再將需要顯示的引導(dǎo)頁布局加在后面(或用include的方式);
  • 3.寫兩個(gè)方法往史,獲取和設(shè)置是否顯示了該引導(dǎo)頁的判斷仗颈,存儲(chǔ)到SharedPreferences;
  • 4.最后在對(duì)應(yīng)Activity頁面添加對(duì)應(yīng)控制引導(dǎo)頁顯隱的邏輯

每添加一個(gè)頁面就要重復(fù)上面四個(gè)步驟,而且之后版本迭代需要去掉這些冗余代碼時(shí)也比較麻煩椎例,改動(dòng)的地方比較多挨决,不利于管理。
這方法太笨了订歪,不能純粹只為了完成功能呀脖祈,于是想了一個(gè)簡(jiǎn)單有效的方法。

新功能引導(dǎo)頁面

首先解決避免改動(dòng)原Activity布局的問題刷晋,只要通過findViewById(android.R.id.content)獲取Activity根布局下的FrameLayout, 再將需要添加的引導(dǎo)頁布局addView(view)進(jìn)入就可以了

FrameLayout rootLayout = (FrameLayout) activity.findViewById(android.R.id.content);
View layoutView = View.inflate(activity, layoutId, null);
rootLayout.add(layoutView);

這樣就解決了改動(dòng)Activity布局的問題盖高,同時(shí)也將引導(dǎo)頁的內(nèi)容單獨(dú)放在一個(gè)Layout文件(說得好像是廢話,這不是必須的嗎眼虱,不過我還真看過有人將所有布局代碼都放在Activity的布局文件的 o(╯□╰)o)

然后后面的優(yōu)化就好辦了喻奥。將存儲(chǔ)到SharedPreferences引導(dǎo)頁是否已展示的方法增加一個(gè)pageTag的參數(shù),通過外部使用時(shí)傳入捏悬,這樣就避免了每個(gè)頁面都寫同類型的方法的問題了撞蚕。

最后將上面所說的方法封裝在一個(gè)類GuidePage,使用時(shí)傳入activity, layoutId, knowViewId(知道了按鈕)和pageTag即可邮破,寫好這個(gè)工具類后诈豌,之后再需要增加引導(dǎo)頁時(shí)只需要:
1.寫一個(gè)引導(dǎo)頁面的布局仆救;
2.在對(duì)應(yīng)的Activity頁面調(diào)用這個(gè)工具方法即可抒和;

private void apply(@LayoutRes int layoutId, @IdRes int knowViewId, String pageTag) {
    FrameLayout rootLayout = (FrameLayout) activity.findViewById(android.R.id.content);
    View layoutView = View.inflate(activity, layoutId, null);
    rootLayout.addView(layoutView);
    layoutView.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            return true;
        }
    });
    layoutView.findViewById(knowViewId).setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            rootLayout.removeView(layoutView);
            SpUtil.setHasShowedGuidePage(activity, pageTag, true);
        }
    });
}
//調(diào)用時(shí)矫渔,直接在Activity寫
if(!SpUtil.hasShowedGuidePage()){
    new GuidePage(this).apply(R.layout.view_home_guide_page, R.id.home_guide_page_know, pageTag);
}

減少了大量重復(fù)代碼,不用修改原來的布局文件摧莽,后期刪除也比較方便庙洼,簡(jiǎn)單吧 -_-

理論上,這樣就完了镊辕,不過程序員比較喜歡折騰油够,這樣直接在構(gòu)造器寫四個(gè)參數(shù)好像不太好看吧,好像可以用建造者模式改下征懈,于是就開始了將簡(jiǎn)單的事情搞復(fù)雜的道路-_-

------------------------------分割線------------------------------------

期待這樣調(diào)用就可以了

new GuidePage.Builder(this)
      .setLayoutId(R.layout.view_home_guide_page)
      .setKnowViewId(R.id.home_guide_page_know)
      .setPageTag(pageTag)
      .builder()
      .apply();

建造者模式有兩種寫法石咬,一種是有設(shè)計(jì)者的(有序),一種是無設(shè)計(jì)者的(無序)卖哎,AlertDialog是無設(shè)計(jì)者的方式鬼悠,簡(jiǎn)單一點(diǎn),比較喜歡這種連寫的方式亏娜。不記得建造者模式的可以看篇文章回憶下:[Android] 設(shè)計(jì)模式-建造者模式

建一個(gè)GuidePage的類焕窝,里面有個(gè)Builder的內(nèi)部類,通過內(nèi)部類設(shè)置activity, layoutId, knowViewId等參數(shù)维贺。存儲(chǔ)引導(dǎo)頁是否已展示的方法好像可以直接寫在GuidePage它掂,就不用在外部判斷了,不過這樣要執(zhí)行一些無用的代碼溯泣,不適合虐秋;還有pageTag好像可以直接用activity.getClass().getSimpleName(), 也不用傳這個(gè)值了,不過如果同一個(gè)Activity有兩個(gè)引導(dǎo)頁(如不同F(xiàn)ragment需要不同引導(dǎo)頁)就不適用了垃沦,還是只能通過參數(shù)傳入好點(diǎn)

好吧客给,這么簡(jiǎn)單,就不多說了栏尚,直接上代碼:

public class GuidePage {
    private int layoutId;
    private int knowViewId;
    private String pageTag;
    private boolean mCancel = false;

    private Activity activity;
    private FrameLayout rootLayout;
    private View layoutView;
    //設(shè)置為 protected或private, 不被外部直接調(diào)用
    protected GuidePage(){
    }

    public static class Builder{
        private GuidePage guidePage = new GuidePage();

        public Builder(Activity activity){
            guidePage.activity = activity;
        }

        public Builder setLayoutId(@LayoutRes int layoutId){
            guidePage.layoutId = layoutId;
            return this;
        }

        public Builder setKnowViewId(@IdRes int knowViewId){
            guidePage.knowViewId = knowViewId;
            return this;
        }

        /**
         * 引導(dǎo)唯一的標(biāo)記起愈,用作存儲(chǔ)到SharedPreferences的key值,不同引導(dǎo)頁必須不一樣
         * @param pageTag
         * @return
         */
        public Builder setPageTag(String pageTag){
            guidePage.pageTag = pageTag;
            return this;
        }

        public Builder setCloseOnTouchOutside(boolean cancel){
            guidePage.mCancel = cancel;
            return this;
        }

        public GuidePage builder(){
            if(TextUtils.isEmpty(guidePage.pageTag)){
                throw new RuntimeException("the guide page must set page tag");
            }
            guidePage.setLayoutView();
            guidePage.setKnowEvent();
            guidePage.setCloseOnTouchOutside();
            return guidePage;
        }
    }

    public void setLayoutView(){
        rootLayout = (FrameLayout) activity.findViewById(android.R.id.content);
        layoutView = View.inflate(activity, layoutId, null);
    }

    public void setKnowEvent(){
        if(layoutView!=null) {
            layoutView.findViewById(knowViewId).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    cancel();
                }
            });
        }
    }

    public void setCloseOnTouchOutside(){
        if(layoutView == null)
            return;
        layoutView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if(mCancel){
                    cancel();
                }
                return true;  //消費(fèi)事件译仗,不下發(fā)
            }
        });
    }

    public void apply(){
        rootLayout.addView(layoutView);
    }

    public void cancel(){
        if(rootLayout!=null && layoutView!=null) {
            rootLayout.removeView(layoutView);
            GuidePageManager.setHasShowedGuidePage(activity, pageTag, true);
        }
    }
}

下面是判斷頁面是否顯示引導(dǎo)頁的類抬虽,或者用來定義一些靜態(tài)的pageTag也行

public class GuidePageManager {

    private GuidePageManager(){
    }
    /**
     * @param activity
     * @param pageKey 使用時(shí),傳入的值必須和GuidePage設(shè)置的值一樣
     * @return
     */
    public static boolean hasNotShowed(Activity activity, String pageKey){
        return !hasShowedGuidePage(activity, pageKey);
    }

    public static void setHasShowedGuidePage(Context context, String key, boolean hasShowed){
        SharedPreferences settings= context.getSharedPreferences(context.getPackageName(), 0);
        SharedPreferences.Editor editor=settings.edit();
        editor.putBoolean(key, hasShowed);
        editor.commit();
    }

    private static boolean hasShowedGuidePage(Context context, String key){
        SharedPreferences settings=context.getSharedPreferences(context.getPackageName(), 0);
        boolean value=settings.getBoolean(key, false);
        return value;
    }
}

最后纵菌,使用方法

if(GuidePageManager.hasNotShowed(this, MainActivity.class.getSimpleName())){
    new GuidePage.Builder(this)
            .setLayoutId(R.layout.view_home_guide_page)
            .setKnowViewId(R.id.btn_home_act_enter_know)
            .setPageTag(MainActivity.class.getSimpleName())
            .builder()
            .apply();
}

完了阐污。
Github代碼: GuidePageManage

首發(fā)于: Android用建造者模式實(shí)現(xiàn)一個(gè)新功能引導(dǎo)頁

---EOF---

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市咱圆,隨后出現(xiàn)的幾起案子笛辟,更是在濱河造成了極大的恐慌功氨,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,080評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件手幢,死亡現(xiàn)場(chǎng)離奇詭異捷凄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)围来,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,422評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門跺涤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人监透,你說我怎么就攤上這事桶错。” “怎么了胀蛮?”我有些...
    開封第一講書人閱讀 157,630評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵院刁,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我粪狼,道長(zhǎng)退腥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,554評(píng)論 1 284
  • 正文 為了忘掉前任鸳玩,我火速辦了婚禮阅虫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘不跟。我一直安慰自己颓帝,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,662評(píng)論 6 386
  • 文/花漫 我一把揭開白布窝革。 她就那樣靜靜地躺著购城,像睡著了一般。 火紅的嫁衣襯著肌膚如雪虐译。 梳的紋絲不亂的頭發(fā)上瘪板,一...
    開封第一講書人閱讀 49,856評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音漆诽,去河邊找鬼侮攀。 笑死,一個(gè)胖子當(dāng)著我的面吹牛厢拭,可吹牛的內(nèi)容都是我干的兰英。 我是一名探鬼主播,決...
    沈念sama閱讀 39,014評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼供鸠,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼畦贸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起楞捂,我...
    開封第一講書人閱讀 37,752評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤薄坏,失蹤者是張志新(化名)和其女友劉穎趋厉,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胶坠,經(jīng)...
    沈念sama閱讀 44,212評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡君账,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,541評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了涵但。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片杈绸。...
    茶點(diǎn)故事閱讀 38,687評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帖蔓,死狀恐怖矮瘟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情塑娇,我是刑警寧澤澈侠,帶...
    沈念sama閱讀 34,347評(píng)論 4 331
  • 正文 年R本政府宣布,位于F島的核電站埋酬,受9級(jí)特大地震影響哨啃,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜写妥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,973評(píng)論 3 315
  • 文/蒙蒙 一拳球、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧珍特,春花似錦祝峻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,777評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嗜桌,卻和暖如春奥溺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背骨宠。 一陣腳步聲響...
    開封第一講書人閱讀 32,006評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工浮定, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人层亿。 一個(gè)月前我還...
    沈念sama閱讀 46,406評(píng)論 2 360
  • 正文 我出身青樓桦卒,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親棕所。 傳聞我的和親對(duì)象是個(gè)殘疾皇子闸盔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,576評(píng)論 2 349

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,800評(píng)論 25 707
  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標(biāo)簽?zāi)J(rèn)的外補(bǔ)...
    _Yfling閱讀 13,737評(píng)論 1 92
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,731評(píng)論 22 665
  • 1.什么是Activity?問的不太多躲撰,說點(diǎn)有深度的 四大組件之一,一般的,一個(gè)用戶交互界面對(duì)應(yīng)一個(gè)activit...
    JoonyLee閱讀 5,729評(píng)論 2 51
  • 書籍, 是這個(gè)城市里精神的拾荒者击费, 撿起人們拋下的靈魂拢蛋。
    丁子永不放棄閱讀 344評(píng)論 0 4