自定義百分比布局RelatvieLayout

PercentRelativeLayout.png

自定義布局系列之自定義RelativeLayout

眾所周知 , Android碎片化非常嚴(yán)重 年枕, 全球那么多機(jī)型 炫欺, 數(shù)以千計(jì)的分辨率 , 給我們的開發(fā)帶來的不小的難度 画切, 那么機(jī)型都需要配置 , 谷歌也做了一些努力 囱怕, 退出了與分辨率無關(guān)的dp , sp等等單位 霍弹, 但這些單位在不同的機(jī)型上面還是顯示各異 , 谷歌在15年的時(shí)候推出了百分比布局 娃弓, 可以在一定程度上 典格, 解決我們的布局差異問題 , 但卻只有兩種百分比布局 台丛, PercentRelativeLayout 和 PercentFrameLayout 耍缴。

 下面我們就深究其理 , 寫一個(gè)我們自己的百分比布局 挽霉。

在寫自定義布局之前 防嗡, 我們首先要弄明白的一點(diǎn)是 , 怎樣進(jìn)行百分比布局 侠坎, 只有明白其中原理 蚁趁, 代碼就可以隨之寫出了 。我們知道 实胸, View最重要的三個(gè)方法是onMeasure ,onLayout , onDraw 這三個(gè)方法決定了View的寬高他嫡,在什么位置 , 以及呈現(xiàn)的形態(tài) 庐完, 這和我們繪畫是一致的 钢属, 都需要明確 , 畫什么(確定寬高屬性) 门躯, 在什么地方畫 (確定繪畫的位置)淆党, 怎樣畫(用什么顏色的畫筆,尺子等)等問題 。

明確了上述問題 宁否, 接下來就是分析我們的百分比布局 窒升, 用到了哪些方法 。 我們的百分比布局 慕匠, 主要改變的是子控件的寬高饱须,以及外邊距的屬性 , 也就是畫什么 台谊, 需要多寬多高 蓉媳, 所以我們需要在onMeasure方法里面做工作 , 去改變子控件的屬性值 锅铅。

我們都寫過LayoutInflater.infalte()這個(gè)方法 酪呻, 將我們的xml文件布局inflate成一個(gè)View對(duì)象 , 我們?cè)趯懽远x控件的時(shí)候 盐须, 也都需要實(shí)現(xiàn)一個(gè)帶屬性參數(shù)的構(gòu)造函數(shù)玩荠,PercentRelativeLayout(Context context, AttributeSet attrs) ,如果沒有 贼邓, 則在xml中使用中會(huì)報(bào)錯(cuò) 阶冈。 在xml文件中寫的View控件標(biāo)簽 , 最后都會(huì)inflate成一個(gè)View對(duì)象 塑径,而inflate里面 女坑, 使用的pull解析 , 將xml標(biāo)簽解析成一個(gè)個(gè)View對(duì)象 统舀。


    final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }

        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }

在解析View標(biāo)簽的時(shí)候 匆骗, 必然也會(huì)將標(biāo)簽的屬性一并解析 , 并將標(biāo)簽屬性設(shè)置到LayoutParams對(duì)象中 誉简, 所以我們才可以通過子控件拿到布局屬性 碉就。


    // 拿到根布局的View
    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

    ViewGroup.LayoutParams params = null;

    if (root != null) {
        if (DEBUG) {
            System.out.println("Creating params from root: " +
                    root);
        }
        // 創(chuàng)建一個(gè)布局參數(shù)對(duì)象
        params = root.generateLayoutParams(attrs);
        if (!attachToRoot) {
            // Set the layout params for temp if we are not
            // attaching. (If we are, we use addView, below)
            temp.setLayoutParams(params);
        }
    }

    /**
     * Returns a new set of layout parameters based on the supplied attributes set.
     *
     * @param attrs the attributes to build the layout parameters from
     *
     * @return an instance of {@link android.view.ViewGroup.LayoutParams} or one
     *         of its descendants
     */
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

知道了上面兩點(diǎn) , 我們就可以自定義一個(gè)我們自己的布局參數(shù) 闷串, 通過generateLayoutParams() 返回我們的自定義布局參數(shù)對(duì)象 铝噩。之后我們就可以通過判斷是否是我們自定義的布局參數(shù) , 來進(jìn)行子控件的寬高改變 窿克。

首先我們將自定義的布局屬性得到 , 并實(shí)例化自定義布局屬性對(duì)象:

    
    /**
     * 自定義布局參數(shù)類 骏庸, 因?yàn)橹皇峭卣筊elativeLayout屬性 , 所以繼承自RelativeLayout.LayoutParams類
     * 年叮,保留原有的RelativeLayout屬性 具被。
     */
    static class PercentLayoutParams extends RelativeLayout.LayoutParams {

        public PercentLayoutInfo info = null;

        public PercentLayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);

            /* 初始化布局自定義布局屬性信息對(duì)象 */
            info = info != null ? info : new PercentLayoutInfo();

            /* 得到自定義布局屬性值 */
            TypedArray typedArray = c.obtainStyledAttributes(attrs, R.styleable.PercentLayout);
            info.setPercentHeight(typedArray.getFloat(R.styleable.PercentLayout_layout_percentHeight, 0));
            info.setPercentWidth(typedArray.getFloat(R.styleable.PercentLayout_layout_percentWidth, 0));

            info.setPercentMarginOfWidth(typedArray.getFloat(R.styleable.PercentLayout_layout_percentMarginOfWidth, 0));
            info.setPercentMarginOfHeight(typedArray.getFloat(R.styleable.PercentLayout_layout_percentMarginOfHeight, 0));
            info.setPercentMarginLeft(typedArray.getFloat(R.styleable.PercentLayout_layout_percentMarginLeft, 0));
            info.setPercentMarginTop(typedArray.getFloat(R.styleable.PercentLayout_layout_percentMarginTop, 0));
            info.setPercentMarginRight(typedArray.getFloat(R.styleable.PercentLayout_layout_percentMarginRight, 0));
            info.setPercentMarginBottom(typedArray.getFloat(R.styleable.PercentLayout_layout_percentMarginBottom, 0));

            typedArray.recycle();
        }

    }

自己的LayoutParams類 , 是繼承自RelativeLayout的 只损, 因?yàn)槲覀冎皇亲鰯U(kuò)展 一姿, 所有保留原有的RelativeLayout的屬性七咧。接下來我們將PercentLayoutParams對(duì)象 ,作為返回值返給我們的generateLayoutParams方法 叮叹, 這樣我們就可以在測(cè)量的時(shí)候 艾栋, 進(jìn)行LayoutParams對(duì)象的判斷了。

    
    /*
    * 生成布局參數(shù)對(duì)象
    * */
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {

        /* 將返回的布局參數(shù)對(duì)象設(shè)置成自定義的參數(shù)對(duì)象 */
        return new PercentLayoutParams(getContext(), attrs);
    }

測(cè)量的時(shí)候 蛉顽, 我們對(duì)LayoutParams對(duì)象進(jìn)行判斷 蝗砾, 然后將我們自定義的屬性 , 設(shè)置到子控件相應(yīng)的屬性 :


    /* 測(cè)量View的寬高 */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        /* 測(cè)量RelativeLayout的寬高 */
        int viewGroupWidth = MeasureSpec.getSize(widthMeasureSpec);
        int viewGroupHeight = MeasureSpec.getSize(heightMeasureSpec);

        /* 得到RelativeLayout中的子控件個(gè)數(shù) */
        int childCount = this.getChildCount();

        /* 循環(huán)取得子控件 */
        for (int i = 0; i < childCount; i++) {

            View child = this.getChildAt(i);
            /* 得到子控件的布局參數(shù) */
            ViewGroup.LayoutParams layoutParams = child.getLayoutParams();

            /* 判斷LayoutParams對(duì)象是不是自定義的Params對(duì)象 */
            if (layoutParams instanceof PercentRelativeLayout.PercentLayoutParams) {

                /* 得到布局參數(shù)信息對(duì)象 */
                PercentLayoutInfo info = ((PercentLayoutParams) layoutParams).info;
                if (info != null) {
                    /* 得到自定義的寬高比 */
                    float percentWidth = info.getPercentWidth();
                    float percentHeight = info.getPercentHeight();

                    /* 如果寬高比大于0 携冤,則與父容器寬高進(jìn)行計(jì)算 悼粮, 并將結(jié)果賦值給子控件 */
                    if (percentHeight > 0) {
                        layoutParams.height = (int) (percentHeight * viewGroupHeight);
                    }

                    if (percentWidth > 0) {
                        layoutParams.width = (int) (percentWidth * viewGroupWidth);
                    }

                    /* 設(shè)置外邊距百分比 */
                    setLayoutPercentMargin(layoutParams, info, viewGroupWidth, viewGroupHeight);

                }

            }
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

設(shè)置外邊距百分比:


    /**
     * 設(shè)置子控件外邊距百分比
     * @param layoutParams 子控件的布局參數(shù)
     * @param info  自定義屬性對(duì)象
     * @param parentWidth 父容器的寬度
     * @param parentHeight 父容器的高度
     */
    private void setLayoutPercentMargin(ViewGroup.LayoutParams layoutParams, PercentLayoutInfo info, int parentWidth, int parentHeight) {
        /* 得到自定義屬性的值 */
        float percentMarginOfWidth = info.getPercentMarginOfWidth();
        float percentMarginOfHeight = info.getPercentMarginOfHeight();
        float percentMarginLeft = info.getPercentMarginLeft();
        float percentMarginTop = info.getPercentMarginTop();
        float percentMarginRight = info.getPercentMarginRight();
        float percentMarginBottom = info.getPercentMarginBottom();

        /* 判斷子控件是否設(shè)置了外邊距的參數(shù) */
        if (layoutParams instanceof MarginLayoutParams) {

            if (percentMarginOfWidth > 0) {
                setLayoutMarginParams(layoutParams, percentMarginOfWidth, parentWidth);
            }

            if (percentMarginOfHeight > 0 ) {
                setLayoutMarginParams(layoutParams, percentMarginOfHeight, parentHeight);
            }

            if (percentMarginLeft > 0) {
                ((MarginLayoutParams) layoutParams).leftMargin = (int) (percentMarginLeft * parentWidth);
            }

            if (percentMarginTop > 0) {
                ((MarginLayoutParams) layoutParams).topMargin = (int) (percentMarginTop * parentHeight);
            }

            if (percentMarginRight > 0) {
                ((MarginLayoutParams) layoutParams).rightMargin = (int) (percentMarginRight * parentWidth);
            }

            if (percentMarginBottom > 0) {
                ((MarginLayoutParams) layoutParams).bottomMargin = (int) (percentMarginBottom * parentHeight);
            }
        }

    }

    /**
     * 設(shè)置子控件的外邊距 , 根據(jù)父容器的某個(gè)寬度和高度的百分比
     * @param layoutParams 子控件的布局參數(shù)對(duì)象
     * @param percent 自定義屬性百分比
     * @param parent 父容器的寬度或高度
     */
    private void setLayoutMarginParams(ViewGroup.LayoutParams layoutParams, float percent, int parent) {
        ((MarginLayoutParams) layoutParams).leftMargin = (int) (percent * parent);
        ((MarginLayoutParams) layoutParams).topMargin = (int) (percent * parent);
        ((MarginLayoutParams) layoutParams).rightMargin = (int) (percent * parent);
        ((MarginLayoutParams) layoutParams).bottomMargin = (int) (percent * parent);
    }

控件屬性XML:


    <declare-styleable name="PercentLayout">
        <!-- 寬高比 -->
        <attr name="layout_percentWidth" format="float"/>
        <attr name="layout_percentHeight" format="float"></attr>

        <!-- 外邊距百分比 -->

        <!-- 按照父容器的寬度來設(shè)置子控件的四個(gè)外邊距的百分比 -->
        <attr name="layout_percentMarginOfWidth" format="float"></attr>
        <!-- 按照父容器的高度來設(shè)置子控件的四個(gè)外邊距的百分比 -->
        <attr name="layout_percentMarginOfHeight" format="float"></attr>

        <!-- 四個(gè)外邊距的百分比 -->
        <attr name="layout_percentMarginLeft" format="float"></attr>
        <attr name="layout_percentMarginRight" format="float"></attr>
        <attr name="layout_percentMarginTop" format="float"></attr>
        <attr name="layout_percentMarginBottom" format="float"></attr>

    </declare-styleable>

自定義RelativeLayout就到這里 曾棕, 其他的百分比布局 扣猫, 原理都是類似的 , 這里就不贅述了 翘地, 如果寫多個(gè)百分比布局 申尤, 可以將那些屬性處理 , 屬性設(shè)置 衙耕, 抽取一個(gè)幫助類出來 昧穿。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市臭杰,隨后出現(xiàn)的幾起案子粤咪,更是在濱河造成了極大的恐慌谚中,老刑警劉巖渴杆,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異宪塔,居然都是意外死亡磁奖,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門某筐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來比搭,“玉大人,你說我怎么就攤上這事南誊∩砼担” “怎么了?”我有些...
    開封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵抄囚,是天一觀的道長(zhǎng)霉赡。 經(jīng)常有香客問我,道長(zhǎng)幔托,這世上最難降的妖魔是什么穴亏? 我笑而不...
    開封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任蜂挪,我火速辦了婚禮,結(jié)果婚禮上嗓化,老公的妹妹穿的比我還像新娘棠涮。我一直安慰自己,他們只是感情好刺覆,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開白布严肪。 她就那樣靜靜地躺著,像睡著了一般隅津。 火紅的嫁衣襯著肌膚如雪诬垂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天伦仍,我揣著相機(jī)與錄音结窘,去河邊找鬼。 笑死充蓝,一個(gè)胖子當(dāng)著我的面吹牛隧枫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谓苟,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼官脓,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了涝焙?” 一聲冷哼從身側(cè)響起卑笨,我...
    開封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎仑撞,沒想到半個(gè)月后赤兴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡隧哮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年桶良,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沮翔。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡陨帆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出采蚀,到底是詐尸還是另有隱情疲牵,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布榆鼠,位于F島的核電站纲爸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏璧眠。R本人自食惡果不足惜缩焦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一读虏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧袁滥,春花似錦盖桥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至嵌赠,卻和暖如春塑荒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背姜挺。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工齿税, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人炊豪。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓凌箕,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親词渤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子牵舱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,322評(píng)論 25 707
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程,因...
    小菜c閱讀 6,449評(píng)論 0 17
  • 在上一篇文章里缺虐,我總結(jié)了一下自定義控件需要了解的基礎(chǔ)知識(shí):View的繪制流程——《自定義控件知識(shí)儲(chǔ)備-View的繪...
    蘑菇君的小小世界閱讀 9,394評(píng)論 15 94
  • 近日芜壁,發(fā)現(xiàn)曾經(jīng)上過的培訓(xùn),沒有好好上高氮,反而對(duì)現(xiàn)在的項(xiàng)目有很大的幫助慧妄。 后來好好學(xué)的,反而說不上很有用纫溃。 可是那時(shí)候...
    SandmanLi閱讀 211評(píng)論 0 0
  • 其實(shí)我一直知道的 我相信鐵樹開花 但從不瞥過一眼 我猜不透火山緘默 卻相信就在明天 遠(yuǎn)行路上的茶寮 坐歇 續(xù)程 長(zhǎng)...
    缺耳閱讀 249評(píng)論 0 10