關(guān)于自定義View 自定義ViewGroup

場(chǎng)景一:自定義View宾茂,使用父類的 super.onMeasure

這種場(chǎng)景實(shí)際上是使用了 super.onMeasure 先測(cè)量一遍瓷马,讓系統(tǒng)自己先填充 mMeasuredWidth,mMeasuredHeight 成員變量跨晴,之后就可以通過
getMeasuredWidth(); getMeasuredHeight(); 直接獲取測(cè)量之后的寬高值欧聘。最后再調(diào)用 setMeasuredDimension 重新將計(jì)算出來的新的寬高填充 mMeasuredWidth,mMeasuredHeight 成員變量

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

        int measureWidth = getMeasuredWidth();
        int measureHeight = getMeasuredHeight();
        if (measureWidth > measureHeight) {
            measureWidth = measureHeight;
        } else {
            measureHeight = measureWidth;
        }

        setMeasuredDimension(measureWidth, measureHeight);
    }

場(chǎng)景二:自定義View端盆,【不】使用父類的 super.onMeasure

這種場(chǎng)景需要自行根據(jù)View的測(cè)量類型怀骤,算出真實(shí)的寬高费封,并將結(jié)果填充至 mMeasuredWidth,mMeasuredHeight 成員變量蒋伦。

特別說明:widthMeasureSpec弓摘、heightMeasureSpec 是一個(gè)32位的數(shù)值,前2位指代測(cè)量模式痕届,后30位指代大小

xml 中 layout_xxx 的屬性就是父布局對(duì)子Veiw的屬性聲明

MeasureSpec.UNSPECIFIED:父布局對(duì)子View的大小沒有限制韧献,子View想多大都可以
MeasureSpec. AT_MOST:父布局限制了子View的大小上限,子View最大不得超過父布局的上限
MeasureSpec. EXACTLY:父布局指定了子View的大小爷抓,子View只能使用這個(gè)固定值

實(shí)際返回的測(cè)量結(jié)果势决,需要根據(jù)具體業(yè)務(wù)進(jìn)行計(jì)算。
最后依然要調(diào)用 setMeasuredDimension 把測(cè)量出來的結(jié)果填充至 mMeasuredWidth蓝撇,mMeasuredHeight 成員變量果复。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int userSize = 200;
        int measureWidth = customResolveSize(userSize, widthMeasureSpec);
        int measureHeight = customResolveSize(userSize, heightMeasureSpec);

        setMeasuredDimension(measureWidth, measureHeight);
    }

    private static int customResolveSize(int size, int measureSpec) {
        int measureMode = MeasureSpec.getMode(measureSpec);
        int measureSize = MeasureSpec.getSize(measureSpec);

        int realSize = 0;

        switch (measureMode) {
            case MeasureSpec.UNSPECIFIED: // 父view對(duì)子view的大小沒有限制,直接返回子view的size
                realSize = size;
                break;
                case MeasureSpec.AT_MOST: // 父view限制了子view的大小上限渤昌,子view的大小不得超過父view指定的值
                    if (size >= measureSize) {
                        realSize = measureSize;
                    } else {
                        realSize = size;
                    }
                    break;
                    case MeasureSpec.EXACTLY: // 父view指定了子view的大小虽抄,直接返回父view指定的值
                        realSize = measureSize;
                        break;
            default:
                realSize = size;
                break;
        }

        return realSize;
    }

場(chǎng)景三:自定義ViewGroup

自定義ViewGroup比較復(fù)雜,難點(diǎn)在測(cè)量過程独柑,例子代碼迈窟,具體返回的測(cè)量寬高值,根據(jù)實(shí)際業(yè)務(wù)計(jì)算忌栅。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //觸發(fā)所有子View的onMeasure函數(shù)去測(cè)量寬高
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        //MeasureSpec封裝了父View傳遞給子View的布局要求
        int wMode = MeasureSpec.getMode(widthMeasureSpec);
        int wSize = MeasureSpec.getSize(widthMeasureSpec);
        int hMode = MeasureSpec.getMode(heightMeasureSpec);
        int hSize = MeasureSpec.getSize(heightMeasureSpec);

        switch (wMode) {
            case MeasureSpec.EXACTLY:  // 說明這個(gè)ViewGroup在父布局中的寬度是一個(gè)定值
                mWidth = wSize;
                break;
            case MeasureSpec.AT_MOST:  // 說明這個(gè)ViewGroup會(huì)盡量填滿父布局的寬度车酣,但不能超過父布局的寬度
                mWidth = wSize;
                break;
            case MeasureSpec.UNSPECIFIED:  // 說明這個(gè)ViewGroup的寬度不受父布局的寬度約束,有可能會(huì)超過父布局的寬度
                break;
        }

        switch (hMode) {
            case MeasureSpec.EXACTLY:  // 說明這個(gè)ViewGroup在父布局中的高度是一個(gè)定值
                mHeight = hSize;
                break;
            case MeasureSpec.AT_MOST:  // 說明這個(gè)ViewGroup會(huì)盡量填滿父布局的高度索绪,但不能超過父布局的高度
                mHeight = hSize;
                break;
            case MeasureSpec.UNSPECIFIED:  // 說明這個(gè)ViewGroup的寬度不受父布局的高度約束湖员,有可能會(huì)超過父布局的高度
                break;
        }

        // setMeasuredDimension 的作用是將測(cè)量出來的最新寬高值設(shè)置到成員變量  mMeasuredWidth,mMeasuredHeight  中瑞驱,下一階段
        // onLayout 可以獲取到經(jīng)過測(cè)量之后的準(zhǔn)確寬高值
        setMeasuredDimension(mWidth, mHeight);
    }

【重點(diǎn)來了】measureChildren娘摔,做了什么事情?

    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

遍歷子view唤反,過濾掉Gone的子View凳寺,并再次調(diào)用 measureChild 方法。通過 getChildMeasureSpec 方法算出子View的 MeasureSpec 值彤侍,并調(diào)用子View的measure方法肠缨,進(jìn)行子View的測(cè)量

    protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

蛋疼的來了,getChildMeasureSpec 做了什么拥刻?

/**
     * Does the hard part of measureChildren: figuring out the MeasureSpec to
     * pass to a particular child. This method figures out the right MeasureSpec
     * for one dimension (height or width) of one child view.
     *
     * The goal is to combine information from our MeasureSpec with the
     * LayoutParams of the child to get the best possible results. For example,
     * if the this view knows its size (because its MeasureSpec has a mode of
     * EXACTLY), and the child has indicated in its LayoutParams that it wants
     * to be the same size as the parent, the parent should ask the child to
     * layout given an exact size.
     *
     * @param spec The requirements for this view
     * @param padding The padding of this view for the current dimension and
     *        margins, if applicable
     * @param childDimension How big the child wants to be in the current
     *        dimension
     * @return a MeasureSpec integer for the child
     */
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

實(shí)際就是根據(jù)不同的測(cè)量模式怜瞒,算出真實(shí)的 mode、size,并調(diào)用 MeasureSpec.makeMeasureSpec 生成 MeasureSpec吴汪,并返回惠窄。
過程很繞,看英文原著吧漾橙。實(shí)際使用其實(shí)用 measureChildren 讓系統(tǒng)自己測(cè)量就好了杆融,ViewGroup的實(shí)際寬高值根據(jù)具體情況計(jì)算測(cè)量值即可。

下一個(gè)階段是 onLayout霜运,根據(jù)左脾歇、上、右淘捡、下的原則藕各,計(jì)算子View在ViewGroup的內(nèi)部位置,之后使用 child. layout(int l, int t, int r, int b) 方法焦除,將子View進(jìn)行重新定位激况。

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // 對(duì)子View進(jìn)行位置布局
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View childView = getChildAt(i);
            childView.layout(xx,xx,xx,xx);
        }
    }

場(chǎng)景四:讓ViewGroup支持margin

要讓自定義ViewGroup支持 layout_margin 屬性,需要重寫 generateLayoutParams膘魄,generateDefaultLayoutParams

    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        return new MarginLayoutParams(p);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }

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

        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View childView = getChildAt(i);
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);

            MarginLayoutParams marginLayoutParams = (MarginLayoutParams)childView.getLayoutParams();
            int childWidth =
                    childView.getMeasuredWidth() + marginLayoutParams.leftMargin + marginLayoutParams.rightMargin;
            int childHeight =
                    childView.getMeasuredHeight() + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin;

            // ........
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View childView = getChildAt(i);

            MarginLayoutParams marginLayoutParams = (MarginLayoutParams)childView.getLayoutParams();
            int childWidth =
                    childView.getMeasuredWidth() + marginLayoutParams.leftMargin + marginLayoutParams.rightMargin;
            int childHeight =
                    childView.getMeasuredHeight() + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin;

            // .......

        }
    }

為什么要重寫 generateLayoutParams乌逐,generateDefaultLayoutParams ?

/**
     * 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);
    }

    /**
     * Returns a safe set of layout parameters based on the supplied layout params.
     * When a ViewGroup is passed a View whose layout params do not pass the test of
     * {@link #checkLayoutParams(android.view.ViewGroup.LayoutParams)}, this method
     * is invoked. This method should return a new set of layout params suitable for
     * this ViewGroup, possibly by copying the appropriate attributes from the
     * specified set of layout params.
     *
     * @param p The layout parameters to convert into a suitable set of layout parameters
     *          for this ViewGroup.
     *
     * @return an instance of {@link android.view.ViewGroup.LayoutParams} or one
     *         of its descendants
     */
    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return p;
    }

    /**
     * Returns a set of default layout parameters. These parameters are requested
     * when the View passed to {@link #addView(View)} has no layout parameters
     * already set. If null is returned, an exception is thrown from addView.
     *
     * @return a set of default layout parameters or null
     */
    protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

系統(tǒng)默認(rèn)的ViewGroup只返回了LayoutParams對(duì)象,只能獲取到 layout_width创葡,layout_height 屬性浙踢,獲取不到 margin 的屬性

        public LayoutParams(Context c, AttributeSet attrs) {
            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
            setBaseAttributes(a,
                    R.styleable.ViewGroup_Layout_layout_width,
                    R.styleable.ViewGroup_Layout_layout_height);
            a.recycle();
        }

如果想獲取 margin 的屬性,則需要返回 MarginLayoutParams

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

            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
            setBaseAttributes(a,
                    R.styleable.ViewGroup_MarginLayout_layout_width,
                    R.styleable.ViewGroup_MarginLayout_layout_height);

            int margin = a.getDimensionPixelSize(
                    com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1);
            .......
        }

由于 MarginLayoutParams 是 LayoutParams 的派生類灿渴,所以 (MarginLayoutParams) 強(qiáng)轉(zhuǎn)是合法的洛波,不會(huì)報(bào)錯(cuò)

自定義View、ViewGroup講完骚露。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末奋岁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子荸百,更是在濱河造成了極大的恐慌,老刑警劉巖滨攻,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件够话,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡光绕,警方通過查閱死者的電腦和手機(jī)女嘲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诞帐,“玉大人欣尼,你說我怎么就攤上這事。” “怎么了愕鼓?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵钙态,是天一觀的道長。 經(jīng)常有香客問我菇晃,道長册倒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任磺送,我火速辦了婚禮驻子,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘估灿。我一直安慰自己崇呵,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布馅袁。 她就那樣靜靜地躺著域慷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪司顿。 梳的紋絲不亂的頭發(fā)上芒粹,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音大溜,去河邊找鬼化漆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛钦奋,可吹牛的內(nèi)容都是我干的座云。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼付材,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼朦拖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起厌衔,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤璧帝,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后富寿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體睬隶,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年页徐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了苏潜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡变勇,死狀恐怖恤左,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤飞袋,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布戳气,位于F島的核電站,受9級(jí)特大地震影響授嘀,放射性物質(zhì)發(fā)生泄漏物咳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一蹄皱、第九天 我趴在偏房一處隱蔽的房頂上張望览闰。 院中可真熱鬧,春花似錦巷折、人聲如沸压鉴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽油吭。三九已至,卻和暖如春署拟,著一層夾襖步出監(jiān)牢的瞬間婉宰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工推穷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留心包,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓馒铃,卻偏偏與公主長得像蟹腾,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子区宇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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