項目需求討論- 自定義圓形排版的ViewGroup來構(gòu)成動態(tài)彈框菜單

大家好唾琼,又到了新的一次需求分析兄春,這次我們的需求是:在不同的條件的前提下,點擊一個菜單按鈕父叙,出來不同的菜神郊。

比如:下面是一系列的公司列表(當(dāng)然也可以是不同的地區(qū),不同的城市趾唱,等等)涌乳,然后當(dāng)你選擇好某個之后,我們點擊菜單按鈕甜癞,這時候出來不同的菜單


然后我們出來的菜單是:

公司1

公司2

公司3

公司4

5

6

7

然后大家說了夕晓。這不是很簡單的事么。做4個布局悠咱,分別作為這四個公司的菜單蒸辆,然后選擇哪個公司征炼,就彈出哪個公司的菜單。
少年,Too Young Too Simple,比如我們一共有10項中的業(yè)務(wù)躬贡,某個A公司有我們的三個功能谆奥,然后你前段界面寫死,B公司有五項功能拂玻,然后你這時候?qū)懥硕€界面酸些,這時候,突然A公司說我升級了檐蚜。我也要跟B公司一樣有五項功能魄懂,然后你又去改界面? 一共有A闯第,B市栗,C...W 公司,難道你就去寫A-W個布局咳短?填帽?(同理,如果是城市劃分诲泌,比如在不同的城市可能支持的功能業(yè)務(wù)不同盲赊,出現(xiàn)不同的菜單。大城市覆蓋的功能更全敷扫,小城市功能更少)

所以這里我們公司的數(shù)量,公司相對于的功能诚卸,功能名字葵第,功能圖片名字,都是后臺傳到前端合溺,我們只需要準(zhǔn)備一個界面卒密,然后在不同情況下,去顯示不同的菜單功能即可棠赛。

比如后臺傳給我們:

{
    "companys": [
        {
            "公司1": [
                {
                    "name": "吃飯",
                    "iconName": "icon_xxx",
                    "typeId": "1"
                }
            ]
        }
    ]
}

這樣我們就很大程序前段就自由了哮奇。那我們的難點就變成了:
既然我們是動態(tài)的顯示這個菜單,拿到這些數(shù)據(jù)后怎么來呈現(xiàn)呢
很多人應(yīng)該做這么個界面會覺得簡單睛约,但是如果是一個根據(jù)數(shù)量自動排好的菜單界面就有點不知所措了鼎俘。所以這里我們的難點就變成了。如果給了我們N個數(shù)據(jù)辩涝,我們要在這個彈框中顯示出N個贸伐,那我們的問題也就變成了:能否提供一個自定義的ViewGroup,然后我傳入幾個View對象怔揩,可以按照一定的規(guī)則幫我自動排布捉邢,這樣我們拿到N個數(shù)據(jù)后脯丝,只需要新建相應(yīng)的View對象,然后添加到這個ViewGroup就行了伏伐。

答案當(dāng)然是能提供宠进。(這波B裝的太累了。喘口氣藐翎。)


既然我們要做的是一個自動按照上面圖片顯示排布規(guī)則的ViewGroup材蹬,系統(tǒng)肯定是沒有自帶的。所以我們就需要自定義一個ViewGroup阱高。

  1. 自定義ViewGroup的第一步:繼承ViewGroup:
public class CircleLayout extends ViewGroup {

    private float mAngleOffset;
    private float mAngleRange;
    private int mInnerRadius;

    public CircleLayout(Context context) {
        super(context);
    }

    public CircleLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CircleLayout, 0, 0);

        try {
            mAngleOffset = a.getFloat(R.styleable.CircleLayout_angleOffset, -90f);
            mAngleRange = a.getFloat(R.styleable.CircleLayout_angleRange, 360f);
            mInnerRadius = a.getDimensionPixelSize(R.styleable.CircleLayout_innerRadius, 80);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            a.recycle();
        }
    }
}

說明下三個參數(shù):

private float mAngleOffset;//擺放子View的角度偏移值
private float mAngleRange;//子VIew可以擺放的角度范圍赚导,比如最多是360度
private int mInnerRadius;//子View距離這個ViewGroup中心點的距離

2.實現(xiàn)onMeasure方法:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    final int count = getChildCount();

    int maxHeight = 0;
    int maxWidth = 0;


    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
            maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
        }
    }

    maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
    maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

    int width = resolveSize(maxWidth, widthMeasureSpec);
    int height = resolveSize(maxHeight, heightMeasureSpec);

    if(MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST){
        setMeasuredDimension(1000, 1000);
    }else if(MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST){
        setMeasuredDimension(1000, height);
    }else if(MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST){
        setMeasuredDimension(width, 1000);
    }else{
        setMeasuredDimension(width, height);
    }
}

3.實現(xiàn)onLayout方法:
這個很重要,我重點也是講解這個onLayout方法赤惊,因為如果你繼承ViewGroup吼旧,會提示你一定要實現(xiàn)這個方法。而且我們也知道了我們最主要的點就在于怎么根據(jù)傳進來的子View的個數(shù)來進行相應(yīng)的擺放未舟。所以我們直接來看onLayout的具體實現(xiàn)圈暗。


onLayout方法:

我們假設(shè)我們的自定義ViewGroup是占滿整個屏幕的,都是match_parent裕膀。然后就如下圖所示:

這時候如果我們想擺四個子View(四個的分析起來簡單點)员串,這時候的界面應(yīng)該是:

這時候大家肯定想,這有什么規(guī)則嗎昼扛,完全沒想法啊寸齐。。哈哈不要急抄谐,看我一步操作渺鹦,你馬上懂:


我們移動畫布,等價我們讓坐標(biāo)系移動了中間蛹含,這時候你是不是恍然大悟毅厚,我們只需要按照角度來不就可以了嗎。

我們繼續(xù)往下看:

好的浦箱。我們可以看到每個子View分到的角度應(yīng)該是(360 / 4 = 90),而這個子View的中心點又是子View分到的角度的一半:(90/2)吸耿。而且這些子View 的中心離原點的距離,都是這個我畫的圓形的半徑酷窥。好了所以現(xiàn)在我們就知道了咽安。

我們假設(shè)是寬比高小,我們的圓形的半徑就是寬(也就是說圓形的半徑取得是(寬和高中的偏小的值))子View的擺放位置的中心點就是這個圓形的半徑R(在此處也就是viewGroup.Width/2)竖幔,而這個子View的top值就是(半徑R*sin(相應(yīng)的角度) - 子View高度/2),子View的left值就是(半徑R*cos(相應(yīng)的角度) - 子View寬度/2)板乙,子View的bottom值就是(半徑R*sin(相應(yīng)角度) + 子View高度/2),子View的right值就是(半徑R*cos(相應(yīng)角度) + 子View寬度/2)

還記不記得我們前面有自定義三個屬性,就是:

private float mAngleOffset;//擺放子View的角度偏移值
private float mAngleRange;//子VIew可以擺放的角度范圍募逞,比如最多是360度
private int mInnerRadius;//子View距離這個ViewGroup中心點的距離

那我們再外加上著三個屬性:

  1. mAngleOffset:角度偏移值蛋铆,就用在原本的子View的中心角度(90/2)處,變成了(90/2 + mAngleOffset),這樣View 就等于轉(zhuǎn)過來了一定的角度放接。
  2. mAngleRange:總的角度刺啦,我們上面默認(rèn)是360度,所以每個子View 所占的角度范圍是(360/4 = 90 ),如果設(shè)置了這個值纠脾,我們就是(mAngleRange/4)玛瘸。
  3. mInnerRadius:距離中心的距離,我們本來半徑是(viewGroup.Width /2),現(xiàn)在變?yōu)?code>((viewGroup.Width - mInnerRadius) / 2),也就是說離坐標(biāo)系的中間的的距離更近了苟蹈。子View之間也就更近了糊渊。

千萬別忘了。我們前面的討論的前提都是坐標(biāo)系已經(jīng)移動到了這個屏幕的中間慧脱,所以我們最后要子View的X ,Y 都重新加上相應(yīng)的偏移值渺绒,也就是 (x+ width/2),(y + height /2 ),還有就是如果子View的數(shù)量是1的話菱鸥,直接就放在了中心地方宗兼,也就是 width/2 和height /2處。

最終的代碼:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    final int childs = getChildCount();

    final int width = getWidth();
    final int height = getHeight();

    final float minDimen = width > height ? height : width;
    final float radius = (minDimen - mInnerRadius) / 2f;

    float startAngle = mAngleOffset;

    for (int i = 0; i < childs; i++) {
        final View child = getChildAt(i);

        final LayoutParams lp = child.getLayoutParams();

        final float angle = mAngleRange / childs;

        final float centerAngle = startAngle + angle / 2f;
        int x;
        int y;

        if (childs > 1) {
            x = (int) (radius * Math.cos(Math.toRadians(centerAngle))) + width / 2;
            y = (int) (radius * Math.sin(Math.toRadians(centerAngle))) + height / 2;
        } else {
            x = width / 2;
            y = height / 2;
        }

        final int halfChildWidth = child.getMeasuredWidth() / 2;
        final int halfChildHeight = child.getMeasuredHeight() / 2;

        final int left = lp.width != LayoutParams.MATCH_PARENT ? x - halfChildWidth : 0;
        final int top = lp.height != LayoutParams.MATCH_PARENT ? y - halfChildHeight : 0;
        final int right = lp.width != LayoutParams.MATCH_PARENT ? x + halfChildWidth : width;
        final int bottom = lp.height != LayoutParams.MATCH_PARENT ? y + halfChildHeight : height;

        child.layout(left, top, right, bottom);
        startAngle += angle;

    }
    invalidate();
}

如果哪里錯了氮采∫笊埽或者有好的改進,希望大家多指出鹊漠,謝謝主到。

附上DEMO:CirclerMenuDialog

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市躯概,隨后出現(xiàn)的幾起案子镰烧,更是在濱河造成了極大的恐慌,老刑警劉巖楞陷,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異茉唉,居然都是意外死亡固蛾,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門度陆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來艾凯,“玉大人,你說我怎么就攤上這事懂傀≈菏” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長恃泪。 經(jīng)常有香客問我郑兴,道長,這世上最難降的妖魔是什么贝乎? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任情连,我火速辦了婚禮,結(jié)果婚禮上览效,老公的妹妹穿的比我還像新娘却舀。我一直安慰自己,他們只是感情好锤灿,可當(dāng)我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布挽拔。 她就那樣靜靜地躺著,像睡著了一般但校。 火紅的嫁衣襯著肌膚如雪螃诅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天始腾,我揣著相機與錄音州刽,去河邊找鬼。 笑死浪箭,一個胖子當(dāng)著我的面吹牛穗椅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播奶栖,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼匹表,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了宣鄙?” 一聲冷哼從身側(cè)響起袍镀,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎冻晤,沒想到半個月后苇羡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡鼻弧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年设江,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片攘轩。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡叉存,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出度帮,到底是詐尸還是另有隱情歼捏,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站瞳秽,受9級特大地震影響瓣履,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜寂诱,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一拂苹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧痰洒,春花似錦瓢棒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至泉粉,卻和暖如春连霉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嗡靡。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工跺撼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人讨彼。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓歉井,卻偏偏與公主長得像,于是被迫代替她去往敵國和親哈误。 傳聞我的和親對象是個殘疾皇子哩至,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,440評論 2 348

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