自定義ViewGroup都要做些啥

一個是斜體

兩個是加粗

三個是斜體加粗

以前都是繼承一個線性布局,相對布局,幀布局做點(diǎn)簡單修改,從來沒有繼承過viewGroup來寫一個,今天順道寫個測試下,結(jié)果發(fā)現(xiàn)各種坑啊,我以為就實(shí)現(xiàn)onLayout這個抽象方法就完事了,結(jié)果。。。

下邊代碼就是簡單實(shí)現(xiàn)了下線性布局類似的順序顯示,其他pading,margin都不考慮。就先看下功能

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        System.err.println(tag+"========onLayout======"+changed+"============="+l+"="+r+"=="+t+"==="+b);
        int top=0;
        for(int i=0;i<getChildCount();i++) {
            View child=getChildAt(i);
            if(child!=null) {
                child.layout(l, top, r,top+child.getMeasuredHeight());
                top+=child.getMeasuredHeight();
            }
        }
    }

代碼如上,結(jié)果測試發(fā)現(xiàn)里邊的child都看不見,打印日志可以發(fā)現(xiàn)child.getMeasuredHeight()這個返回的一直是0,自然看不見了。完事我就把child.getMeasuredHeight()改成一個固定的比如200的值,發(fā)現(xiàn)是可以看到的,那原因就是child沒有進(jìn)行測量。

那就去看下FrameLayout咋弄的。代碼比較多,咱就抽出關(guān)鍵的代碼即可,也就是調(diào)用了measureChildWithMargins這個方法

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        System.err.println(tag+"===========onMeasure===========h="+getMeasuredHeight()+"===w="+getMeasuredWidth());
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        for(int i=0;i<getChildCount();i++) {
            View child=getChildAt(i);
            if(child!=null) {
                 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                //android.view.ViewGroup$LayoutParams cannot be cast to android.view.ViewGroup$MarginLayoutParams
            }
        }
    }

這下以為好了,運(yùn)行下直接掛了。異常
android.view.ViewGroup.LayoutParams cannot be cast to android.view.ViewGroup.MarginLayoutParams

原因就是如下的代碼,可以看到parama進(jìn)行了強(qiáng)轉(zhuǎn)

    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

繼續(xù)看ViewGroup里有如下的幾個方法,里邊的LayoutParams就是ViewGroup里的一個靜態(tài)類public static class LayoutParams

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

那咋辦了,修改下這幾個方法唄,讓默認(rèn)的child的params就是我們需要的MarginLayoutParams即可

    @Override
    protected android.view.ViewGroup.LayoutParams generateDefaultLayoutParams() {
        // TODO Auto-generated method stub
        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }
    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams;
    }

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

    @Override
    public CharSequence getAccessibilityClassName() {
        return TouchEventViewGroup.class.getName();
    }
    
    public static class LayoutParams extends MarginLayoutParams {
    //省略掉構(gòu)造方法了
}

最后說明下如下的關(guān)系,marginLayoutParams就是多了寫margin參數(shù)而已
public static class MarginLayoutParams extends ViewGroup.LayoutParams

繼續(xù)研究,難道一定要用marginLayoutParams,當(dāng)然不一定了,如果你的child不需要設(shè)置margin那就不用了。
如下修改,剔除掉margin相關(guān)的,自然也就不要MarginLayoutParams
其實(shí),核心代碼就是child.measure(childWidthMeasureSpec, childHeightMeasureSpec);來測量child的大小

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        System.err.println(tag+"===========onMeasure===========h="+getMeasuredHeight()+"===w="+getMeasuredWidth());
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        for(int i=0;i<getChildCount();i++) {
            View child=getChildAt(i);
            if(child!=null) {
//               measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                //android.view.ViewGroup$LayoutParams cannot be cast to android.view.ViewGroup$MarginLayoutParams
                ViewGroup.LayoutParams lp=child.getLayoutParams();
                final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                        getPaddingLeft() + getPaddingRight(), lp.width);
                final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                        getPaddingTop()+getPaddingBottom(), lp.height);

                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }

如果沒有特殊的參數(shù),那么直接用MarginLayoutParams即可,如果有自己需要額外添加的參數(shù),那么可以繼承這個,自己寫一個
看下幾個常用的
LinearLayout,多了比重和重心

 public static class LayoutParams extends ViewGroup.MarginLayoutParams

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            TypedArray a =
                    c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);

            weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);
            gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);

            a.recycle();
        }

RelativeLayout
多了一對,相對位置的參數(shù),都放在下邊的數(shù)組里

private int[] mRules = new int[VERB_COUNT];

FrameLayout
多了個child的layout_gravity

public int gravity = UNSPECIFIED_GRAVITY;

總結(jié)下其實(shí)自定義ViewGroup的核心代碼就2行

child.measure(childWidthMeasureSpec, childHeightMeasureSpec); //對childView進(jìn)行大小測量

child.layout(int l, int t, int r, int b) 對child的位置進(jìn)行處理,上下左右4個點(diǎn)已經(jīng)決定了這個child的位置拉。

簡單不,嗯,簡單,再復(fù)雜不會了。

反正知道了基礎(chǔ)就這兩行,以后看別人寫的自定義ViewGroup就好理解了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容