自定义ViewGroup


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


初始化

  1. 重写构造函数——三个
  2. 通过this调用
  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. 正常的最终需要调用 setMeasuredDimension 方法,需要的参数 宽高、宽高的spec以及state
  3. match_parent 的最终需要调用 measure方法,需要参数widthMeasureSpec, heightMeasureSpec
  4. onMeasure前面所做的事情都是来获取这些参数
setMeasuredDimension(resolveSizeAndState(maxWidth,widthMeasureSpec,childState)
                ,resolveSizeAndState(maxHeight,heightMeasureSpec,childState << MEASURED_HEIGHT_STATE_SHIFT));
                
                
view.measure(childWidthMeasureSpec,childHeightMeasureSpec);

步骤

  1. 获取子View的数量

代码

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //1. 获取childView的个数
        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());

//        设置具体宽高
        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. 调用layout方法,需要四个参数
view.layout(rLeft,rTop,rRight,rBottom)

padding——内边距
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. 设置放置的位置

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

    }

几个常用函数

getX() 是表示Widget相对于自身左上角的x坐标

getRawX() 是表示相对于屏幕左上角的x坐标值

理解 getScrollX()

image

1是手机屏幕,在此区域内的人眼可以看见
2是幕布
3是内容

getScrollX 其实获取的值,就是这块幕布在窗口左边界时候的值了,而幕布上面哪个点是原点(0,0)呢?就是初始化时内容显示的位置

  • 将幕布往右推动的时候,幕布在窗口左边界的值就会在0的左边(-100)
  • 向左推动,则其值会是在0的右边(100)

scrollTo()和scrollBy(x,y)

scrollTo(int x, int y) 是将View中内容滑动到相应的位置,参考的坐标系原点为parent View的左上角。

  • 参数为正的,右移
  • 参数为负值,左移

scrollTo()指的是移动到指定的(x,y)位置
而scrollBy(x,y)指的是,在当前位置在移动(x,y)个位置

阻止父层的View截获touch事件

调用getParent().requestDisallowInterceptTouchEvent(true);方法。一旦底层View收到touch的action后调用这个方法那么父层View就不会再调用onInterceptTouchEvent了,也无法截获以后的action

ViewGroup generateLayoutParams() 方法的作用

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

如果一个View想要被添加到这个容器中,这个view可以调用此方法生成和容器类匹配的布局LayoutParams,

这个方法主要是用于父容器添加子View时调用

用于生成和此容器类型相匹配的布局参数类

startScroll方法

第一个参数是起始移动的x坐标值,第二个是起始移动的y坐标值,第三个第四个参数都是移到某点的坐标值,而duration 当然就是执行移动的时间

computeScroll方法

当startScroll执行过程中即在duration时间内,computeScrollOffset 方法会一直返回false,但当动画执行完成后会返回返加true.

@Override
    public void computeScroll() {
        //判断Scroller是否执行完毕:
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            //通知View重绘-invalidate()->onDraw()->computeScroll()
            invalidate();
        }
    }

onDetachedFromWindow()

销毁View的时候调用这个方法,我们可以在里面做一些清理工作(做一些收尾工作)如:取消广播注册等等

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,069评论 4 358
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,212评论 1 287
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 105,912评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,424评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,741评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,194评论 1 206
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,553评论 2 307
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,289评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,923评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,251评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,775评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,144评论 2 249
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,698评论 3 228
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,936评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,658评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,214评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,159评论 2 258

推荐阅读更多精彩内容