android 新手引導(dǎo)浮層的實(shí)現(xiàn)

源碼:xufangzhen/NewbieGuide
轉(zhuǎn)載請標(biāo)明出處:http://www.reibang.com/p/5aa96683d0dc

前言

這個(gè)模塊寫了很早了孤钦,在實(shí)際項(xiàng)目中更新了很多次血当,但是太懶了塌碌,博客里沒有跟著更新,看到閱讀人數(shù)這么多梆造,在回頭看看自己寫的代碼這么爛涡驮,都不好意思了沾凄,于是決定更新一下。PS:看了評價(jià)里有人說放在fragment里顯示會(huì)亂鳄抒,我表示不知道為什么他們會(huì)這么想闯捎,在fragment顯示正常的。
更新(2016-11-22):

  • 使用建筑者模式構(gòu)造引導(dǎo)浮層
  • 支持在onCreate()等方法(View還未加載時(shí))中設(shè)置并顯示引導(dǎo)浮層
  • 支持高亮洞洞的額外擴(kuò)大和縮小

本來在我的項(xiàng)目中使用的新手引導(dǎo)浮層是這個(gè)TourGuide開源項(xiàng)目许溅,這個(gè)新手引導(dǎo)項(xiàng)目功能比較多瓤鼻,一部分功能用不到,用到的地方只能大體上滿足視覺的樣式贤重,不能一模一樣茬祷。因此決定重構(gòu)一遍,滿足自己項(xiàng)目中的新手引導(dǎo)并蝗,本人項(xiàng)目中新手引導(dǎo)頁的樣式如下圖所示:

新手引導(dǎo).png

實(shí)現(xiàn)的功能

  1. 選中的view高亮可以有任意多個(gè)祭犯,形狀有矩形,圓形滚停,橢圓形
  2. 指示箭頭或者其他圖片可以在任意位置沃粗,可以有任意多個(gè)
  3. 文字和我知道了按鈕可以在任意位置(默認(rèn)我知道了在文字下方,兩者水平居中键畴,上下可調(diào))
  4. 點(diǎn)擊我知道了引導(dǎo)浮層消失
  5. 可設(shè)置點(diǎn)擊任何位置引導(dǎo)浮層消失(默認(rèn)點(diǎn)擊消失)
  6. 浮層出現(xiàn)和消失可以有回調(diào)接口最盅,可以延遲出現(xiàn)

實(shí)現(xiàn)的原理

1. 浮層的位置,放在activity的DecorView里起惕,DecorView為FrameLayout的子類涡贱。

DecorView為整個(gè)Window界面的最頂層View。
DecorView只有一個(gè)子元素為LinearLayout惹想,代表整個(gè)Window界面盼产。
LinearLayout里有兩個(gè)FrameLayout子元素,分別是標(biāo)題欄和內(nèi)容勺馆。

可通過以下代碼獲取戏售,不清楚可參考這篇文章Android DecorView淺析

mParentView = (FrameLayout) mActivity.getWindow().getDecorView();

2. 引導(dǎo)浮層布局及上面的元素

  1. 浮層為相對布局,除了高亮的地方和半透明的背景其余都是通過addView的方式添加進(jìn)去草穆,通過設(shè)置margin來調(diào)整添加子view的位置灌灾。
    比如箭頭元素的添加,offsetX(offsetY)負(fù)數(shù)則從右邊(下邊)開始偏移,CENTER為居中悲柱,方便設(shè)置具體位置
    public NewbieGuide addIndicateImg(int id, int offsetX, int offsetY) {
        ImageView arrowImg = new ImageView(mActivity);
        arrowImg.setImageResource(id);
        mGuideView.addView(arrowImg, getLp(offsetX, offsetY));
        return this;
    }```
```java
    private RelativeLayout.LayoutParams getLp(int offsetX, int offsetY) {
        RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(ViewGroup
                .LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        //水平方向
        if (offsetX == CENTER) {
            lp.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
        } else if (offsetX < 0) {
            lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
            lp.rightMargin = -offsetX;
        } else {
            lp.leftMargin = offsetX;
        }
        //垂直方向
        if (offsetY == CENTER) {
            lp.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
        } else if (offsetY < 0) {
            lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE);
            lp.bottomMargin = -offsetY;
        } else {
            lp.topMargin = offsetY;
        }
        return lp;
    }
  1. 高亮洞洞的繪制
    思路一锋喜,本人想了一個(gè)比較巧妙的方法,拿圓形的高亮洞洞來說盼铁,畫一個(gè)鏤空的圓形枝恋,你會(huì)發(fā)現(xiàn)當(dāng)畫筆足夠粗到把屏幕都遮起來的時(shí)候,剛好中心的小圓沒有畫到是高亮的可款,這個(gè)方法只需要調(diào)整好畫筆的粗度和圓形的直徑就可以了炉奴,矩形也是可垟逼庞,調(diào)整好邊長和畫筆的粗度就可以實(shí)現(xiàn)了,不過最后發(fā)現(xiàn)橢圓是不行的瞻赶, 所以總結(jié)下這個(gè)方法只能一個(gè)高亮洞洞赛糟,而且只能圓形或矩形。
    畫筆粗到可以遮擋屏幕時(shí)砸逊,中間的地方就高亮的洞洞

    所以最后還是使用和TourGuide同樣的方法璧南,通過畫筆的setXfermode來實(shí)現(xiàn),即當(dāng)兩個(gè)畫布上都繪制了圖片是师逸,可以控制最終顯示的樣式司倚,有取重疊部分,有去除重疊部分的等等篓像,這個(gè)有16中規(guī)則对湃,具體下圖:
    setXfermode屬性

    具體用法可以參考兩篇文章 Android中Xfermode簡單用法詳解Paint的setXfermode。簡單地說遗淳,TourGuide做法就是在一個(gè)畫布上畫了一個(gè)屏幕大小背景拍柒,在另一個(gè)畫布上畫了一個(gè)圓形,因?yàn)橹丿B了屈暗,所以去除了重疊的部分拆讯,高亮洞洞就顯示出來了。
    本人針對這個(gè)做法做了點(diǎn)優(yōu)化养叛,即畫了一個(gè)和洞洞所需要一樣大小的圖片而不是屏幕一樣大小种呐,如果有兩個(gè)洞洞,則畫了一個(gè)能同時(shí)容下兩個(gè)洞洞的矩形大小的圖片弃甥,這樣顯示的結(jié)果最終會(huì)變成下面這樣:
    只畫了滿足高亮洞洞大小的圖片

    高亮洞洞都顯示出來了爽室,但是只畫了一部分的背景,首先這樣做的目的是為了用較少的內(nèi)存去完成(一個(gè)1080p屏幕大小的半透明的背景圖bitmap大概需要3M以上)那剩余的空白部分該怎么填充呢淆攻,一個(gè)方法是用四個(gè)view去填充上阔墩,這個(gè)做法是可行的,但是麻煩瓶珊,其實(shí)一個(gè)比較簡單的做法我已經(jīng)在上面提到了啸箫,就是思路一中說的畫一個(gè)矩形,調(diào)整好畫筆的粗度和長寬即可填充完伞芹,關(guān)鍵代碼如下忘苛。
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(mHoleList != null && mHoleList.size() > 0) {
            mPaint.setXfermode(pdf);
            mPaint.setMaskFilter(bmf);
            mPaint.setStyle(Paint.Style.FILL);
            for (HoleBean hole : mHoleList) {
                switch (hole.getType()) {
                    case HoleBean.TYPE_CIRCLE:
                        mCanvas.drawCircle(hole.getCenterX() - mBitmapRect.left, hole
                                .getCenterY() - mBitmapRect.top, hole.getRadius(),
                                mPaint);
                        break;
                    case HoleBean.TYPE_RECTANGLE:
                        mCanvas.drawRect(modifyRect(hole.getRectF()), mPaint);
                        break;
                    case HoleBean.TYPE_OVAL:
                        mCanvas.drawOval(modifyRect(hole.getRectF()), mPaint);
                        break;
                }
            }
            canvas.drawBitmap(mBitmap, mBitmapRect.left, mBitmapRect.top, null);
            //繪制剩余空間的矩形
            mPaint.setXfermode(null);
            mPaint.setMaskFilter(null);
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setStrokeWidth(mStrokeWidth + 0.1f);
            canvas.drawRect(fillRect(mBitmapRect), mPaint);
        }
    }

3. 注意點(diǎn)

  • 在準(zhǔn)備調(diào)用對某個(gè)View高亮的引導(dǎo)層方法時(shí)蝉娜,需要確定這個(gè)view是已經(jīng)加載完成了,即可以獲取長和寬扎唾,在activity的onCreate和onResume等方法都不是正在加載完view的地方召川,真正加載完view的是onWindowFocusChanged,所以要注意調(diào)用時(shí)機(jī)胸遇,適當(dāng)延遲荧呐。
  • 最后本人寫了一個(gè)manager來管理不同地方的新手引導(dǎo)浮層,通過SharedPreferences來保存狀態(tài)狐榔。
    每次使用的時(shí)候都需要判斷下是夠顯示過了坛增,如下:
    if(NewbieGuideManager.isNeverShowed(this, NewbieGuideManager.TYPE_COLLECT)) {
        new NewbieGuideManager(this, NewbieGuideManager.TYPE_COLLECT).addView
                (mCollect, HoleBean.TYPE_CIRCLE).addView(mTitleTv, HoleBean
                .TYPE_RECTANGLE).show();
    }  

有時(shí)候在list滾動(dòng)到某個(gè)位置時(shí)获雕,會(huì)顯示出某個(gè)引導(dǎo)浮層薄腻,這個(gè)要注意listview快速滾動(dòng)的情況,這里提供一種寫法届案,在onScroll當(dāng)滾動(dòng)到position為6的item時(shí)庵楷,顯示:

    @Override
    public void onScroll(final AbsListView view, int firstVisibleItem, int
            visibleItemCount, int totalItemCount) {
        if(firstVisibleItem >= 6 && NewbieGuideManager.isNeverShowed(this,
                NewbieGuideManager.TYPE_LIST) && !isShow) {
            isShow = true;
            mListView.smoothScrollToPosition(6);
            mListView.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mListView.setSelection(6);
                    mListView.post(new Runnable() {
                        @Override
                        public void run() {
                            new NewbieGuideManager(MainActivity.this,
                                    NewbieGuideManager.TYPE_LIST).addView(view
                                    .getChildAt(0).findViewById(R.id.logo), HoleBean
                                    .TYPE_RECTANGLE).show();
                        }
                    });
                }
            }, 200);
        }
    }
滾動(dòng)到item6后顯示引導(dǎo)浮層
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市楣颠,隨后出現(xiàn)的幾起案子尽纽,更是在濱河造成了極大的恐慌,老刑警劉巖童漩,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弄贿,死亡現(xiàn)場離奇詭異,居然都是意外死亡矫膨,警方通過查閱死者的電腦和手機(jī)差凹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來侧馅,“玉大人危尿,你說我怎么就攤上這事∧俪眨” “怎么了谊娇?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長罗晕。 經(jīng)常有香客問我济欢,道長,這世上最難降的妖魔是什么小渊? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任船逮,我火速辦了婚禮,結(jié)果婚禮上粤铭,老公的妹妹穿的比我還像新娘挖胃。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布酱鸭。 她就那樣靜靜地躺著吗垮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪凹髓。 梳的紋絲不亂的頭發(fā)上烁登,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機(jī)與錄音蔚舀,去河邊找鬼饵沧。 笑死,一個(gè)胖子當(dāng)著我的面吹牛赌躺,可吹牛的內(nèi)容都是我干的狼牺。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼礼患,長吁一口氣:“原來是場噩夢啊……” “哼是钥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起缅叠,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤悄泥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后肤粱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弹囚,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年领曼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鸥鹉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,724評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡悯森,死狀恐怖宋舷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瓢姻,我是刑警寧澤祝蝠,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站幻碱,受9級特大地震影響绎狭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜褥傍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一儡嘶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧恍风,春花似錦蹦狂、人聲如沸誓篱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽窜骄。三九已至,卻和暖如春摆屯,著一層夾襖步出監(jiān)牢的瞬間邻遏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工虐骑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留准验,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓廷没,卻偏偏與公主長得像糊饱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子腕柜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評論 2 350

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