自定義ViewGroup


title: 自定義ViewGroup
tags: ViewGroup,Android,自定義View


初始化

  1. 重寫構(gòu)造函數(shù)——三個
  2. 通過this調(diào)用
  3. init來獲取自定義的屬性

    public SwipeLayout(Context context) {
    this(context,null);
    }
    
    public SwipeLayout(Context context, AttributeSet attrs) {
    this(context, attrs,0);
    }
    
    public SwipeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr);
    }
    

獲取自定義屬性

布局文件 attr.xml

    <declare-styleable name="SwipeLayout">
        <attr name="leftView" format="reference"/>
        <attr name="rightView" format="reference"/>
        <attr name="contentView1" format="reference"/>
        <attr name="canRightSwipe1" format="boolean" />
        <attr name="canLeftSwipe1" format="boolean" />
        <attr name="fraction1" format="float" />
    </declare-styleable>

獲取屬性

        TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.SwipeLayout);
        leftViewId = typedArray.getResourceId(R.styleable.SwipeLayout_leftView,-1);
        rightViewId = typedArray.getResourceId(R.styleable.SwipeLayout_rightView,-1);
        contentViewId = typedArray.getResourceId(R.styleable.SwipeLayout_contentView1,-1);
        canLeftSwipe = typedArray.getBoolean(R.styleable.SwipeLayout_canLeftSwipe1,true);
        canRightSwipe = typedArray.getBoolean(R.styleable.SwipeLayout_canRightSwipe1,true);
        mFraction = typedArray.getFloat(R.styleable.SwipeLayout_fraction1,0.5f);
       
        typedArray.recycle();

onMeasure

思路

  1. View是match_parent 的特殊處理,其他的正常處理
  2. 正常的最終需要調(diào)用 setMeasuredDimension 方法,需要的參數(shù) 寬高、寬高的spec以及state
  3. match_parent 的最終需要調(diào)用 measure方法,需要參數(shù)widthMeasureSpec, heightMeasureSpec
  4. onMeasure前面所做的事情都是來獲取這些參數(shù)
setMeasuredDimension(resolveSizeAndState(maxWidth,widthMeasureSpec,childState)
                ,resolveSizeAndState(maxHeight,heightMeasureSpec,childState << MEASURED_HEIGHT_STATE_SHIFT));
                
                
view.measure(childWidthMeasureSpec,childHeightMeasureSpec);

步驟

  1. 獲取子View的數(shù)量

代碼

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //1. 獲取childView的個數(shù)
        setClickable(true);
        int count = getChildCount();
        //參考frameLayout測量代碼
        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                        MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        mMatchParentChildren.clear();
        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;
        //遍歷childViews
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                measureChildWithMargins(child,widthMeasureSpec,0,heightMeasureSpec,0);
                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
                maxHeight = Math.max(maxHeight,child.getMeasuredHeight()+lp.leftMargin+lp.rightMargin);
                maxWidth = Math.max(maxWidth,child.getMeasuredWidth()+lp.topMargin+lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());

                if (measureMatchParentChildren){
                    if ( lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }

        //寬度和高度還要考慮背景的大小
        maxHeight = Math.max(maxHeight,getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth,getSuggestedMinimumWidth());

//        設(shè)置具體寬高
        setMeasuredDimension(resolveSizeAndState(maxWidth,widthMeasureSpec,childState)
                ,resolveSizeAndState(maxHeight,heightMeasureSpec,childState << MEASURED_HEIGHT_STATE_SHIFT));

        count = mMatchParentChildren.size();
        if (count > 1) {
            for (int i = 0; i < count; i++) {
                View view = mMatchParentChildren.get(i);
                MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();

                final int childWidthMeasureSpec;
                if ( lp.width == LayoutParams.MATCH_PARENT) {
                    int width = Math.max(0,getMeasuredWidth()
                            - lp.leftMargin - lp.rightMargin);
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,MeasureSpec.EXACTLY);
                }else {
                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,lp.leftMargin+lp.rightMargin
                            ,lp.width);
                }

                final int childHeightMeasureSpec;
                if ( lp.height == LayoutParams.MATCH_PARENT) {
                    int height = Math.max(0,getMeasuredHeight()
                            - lp.topMargin - lp.bottomMargin);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,MeasureSpec.EXACTLY);
                }else{
                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,lp.topMargin+lp.bottomMargin
                            ,lp.height);
                }

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

onLayout

思路

  1. 調(diào)用layout方法,需要四個參數(shù)
view.layout(rLeft,rTop,rRight,rBottom)

padding——內(nèi)邊距
margin——外邊距

代碼

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        // 1. 先找到布局中的View
        int count = getChildCount();
        int left = 0 + getPaddingLeft();
        int right = 0 + getPaddingLeft();
        int top = 0 + getPaddingTop();
        int bottom = 0 + getPaddingTop();

        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            if (mLeftView == null && child.getId() == leftViewId) {
                mLeftView = child;
                mLeftView.setClickable(true);
            }else if (mRightView == null && child.getId() == rightViewId){
                mRightView = child;
                mRightView.setClickable(true);
            }else if (mContentView == null && child.getId() == contentViewId){
                mContentView = child;
                mContentView.setClickable(true);
            }
        }

        //2. 設(shè)置放置的位置

        if (mContentView != null) {
            mContentViewLp = (MarginLayoutParams) mContentView.getLayoutParams();
            int cTop = mContentViewLp.topMargin + top;
            int cLeft = mContentViewLp.leftMargin + left;
            int cRight = cLeft + mContentView.getMeasuredWidth();

            //TODO  此處能不能使用 ??? mContentViewLp.height
            int cBottom = cTop + mContentView.getMeasuredHeight();

            mContentView.layout(cLeft, cTop, cRight, cBottom);
        }

        if (mLeftView != null) {
            MarginLayoutParams leftViewLp = (MarginLayoutParams) mLeftView.getLayoutParams();
            int cTop = leftViewLp.topMargin + top;
            int cRight = 0 - leftViewLp.rightMargin ;
            int cLeft = 0 - mLeftView.getMeasuredWidth() + leftViewLp.rightMargin + leftViewLp.leftMargin;
            int cBottom = top + mLeftView.getMeasuredHeight() + leftViewLp.bottomMargin;

            mLeftView.layout(cLeft,cTop,cRight,cBottom);
        }

        if (mRightView != null) {
            MarginLayoutParams rightViewLp = (MarginLayoutParams) mRightView.getLayoutParams();
            int rTop = rightViewLp.topMargin + top;
            int rLeft = rightViewLp.leftMargin + mContentView.getRight() + mContentViewLp.rightMargin;
            int rRight = rLeft + mRightView.getMeasuredWidth();
            int rBottom = rTop + mRightView.getMeasuredHeight();;

            mRightView.layout(rLeft,rTop,rRight,rBottom);
        }

    }

幾個常用函數(shù)

getX() 是表示W(wǎng)idget相對于自身左上角的x坐標(biāo)

getRawX() 是表示相對于屏幕左上角的x坐標(biāo)值

理解 getScrollX()

image

1是手機(jī)屏幕,在此區(qū)域內(nèi)的人眼可以看見
2是幕布
3是內(nèi)容

getScrollX 其實獲取的值,就是這塊幕布在窗口左邊界時候的值了,而幕布上面哪個點(diǎn)是原點(diǎn)(0,0)呢?就是初始化時內(nèi)容顯示的位置

  • 將幕布往右推動的時候,幕布在窗口左邊界的值就會在0的左邊(-100)
  • 向左推動,則其值會是在0的右邊(100)

scrollTo()和scrollBy(x,y)

scrollTo(int x, int y) 是將View中內(nèi)容滑動到相應(yīng)的位置,參考的坐標(biāo)系原點(diǎn)為parent View的左上角。

  • 參數(shù)為正的,右移
  • 參數(shù)為負(fù)值,左移

scrollTo()指的是移動到指定的(x,y)位置
而scrollBy(x,y)指的是,在當(dāng)前位置在移動(x,y)個位置

阻止父層的View截獲touch事件

調(diào)用getParent().requestDisallowInterceptTouchEvent(true);方法。一旦底層View收到touch的action后調(diào)用這個方法那么父層View就不會再調(diào)用onInterceptTouchEvent了,也無法截獲以后的action

ViewGroup generateLayoutParams() 方法的作用

父容器生成 子view 的布局LayoutParams;

如果一個View想要被添加到這個容器中,這個view可以調(diào)用此方法生成和容器類匹配的布局LayoutParams,

這個方法主要是用于父容器添加子View時調(diào)用

用于生成和此容器類型相匹配的布局參數(shù)類

startScroll方法

第一個參數(shù)是起始移動的x坐標(biāo)值,第二個是起始移動的y坐標(biāo)值,第三個第四個參數(shù)都是移到某點(diǎn)的坐標(biāo)值,而duration 當(dāng)然就是執(zhí)行移動的時間

computeScroll方法

當(dāng)startScroll執(zhí)行過程中即在duration時間內(nèi),computeScrollOffset 方法會一直返回false,但當(dāng)動畫執(zhí)行完成后會返回返加true.

@Override
    public void computeScroll() {
        //判斷Scroller是否執(zhí)行完畢:
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            //通知View重繪-invalidate()->onDraw()->computeScroll()
            invalidate();
        }
    }

onDetachedFromWindow()

銷毀View的時候調(diào)用這個方法,我們可以在里面做一些清理工作(做一些收尾工作)如:取消廣播注冊等等

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,991評論 2 374

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