自定义ViewGroup学习(LinearLayout的布局方式,可以滚动和嵌套)

Posted 我的小侯子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义ViewGroup学习(LinearLayout的布局方式,可以滚动和嵌套)相关的知识,希望对你有一定的参考价值。

自定义ViewGroup学习(LinearLayout的布局方式,可以滚动和嵌套)

首先先看效果图

自定义ViewGroup,必须重写onLayout()方法
当然,还需要onMeasure()
下边就是最基本的

public class MyViewGroup extends ViewGroup 
    public MyViewGroup(Context context, AttributeSet attrs) 
        super(context, attrs);
    

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) 
        //摆放测量后子类位置
    

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //测量
    

首先来看测量onMeasure
要自定义一个类似LinearLayout的容器,先只看水平方向上
容器的width=所有子View的宽的和(先不考虑padding,margin)
容器的height=所有View中最高的高度

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

        childWidth = 0;//容器的宽高
        childHeight = 0;
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) 
            View child = getChildAt(i);
            if (child.getVisibility() != View.GONE) 
                measureChild(child, widthMeasureSpec,
                        heightMeasureSpec);
                    childWidth += child.getMeasuredWidth();
                    childHeight = Math.max(childHeight, child.getMeasuredHeight());
            
        
        setMeasuredDimension(resolveSize(childWidth, widthMeasureSpec), resolveSize(childHeight, heightMeasureSpec));
    

测量完,在onLayout中摆放

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) 
        int childLeft = 0;
        int childTop = 0;

        final int height = b - t;
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) 
            View child = getChildAt(i);
            if (child.getVisibility() != View.GONE) 
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();
                child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
                childLeft += childWidth;
                childTop = 0;
            
        
  

这最基本的ViewGroup就完成了


接下来加入padding和margin以及gravity,orientation属性

首先attrs中添加orientation和gravity两个自定义属性

<declare-styleable name="MyViewGroup">
        <attr name="orientation">
            <enum name="horizontal" value="0" />
            <enum name="vertical" value="1" />
        </attr>
        <attr name="gravity">
            <flag name="top" value="0x30" />
            <flag name="bottom" value="0x50" />
            <flag name="left" value="0x03" />
            <flag name="right" value="0x05" />
            <flag name="center_vertical" value="0x10" />
            <flag name="fill_vertical" value="0x70" />
            <flag name="center_horizontal" value="0x01" />
            <flag name="fill_horizontal" value="0x07" />
            <flag name="center" value="0x11" />
            <flag name="fill" value="0x77" />
            <flag name="clip_vertical" value="0x80" />
            <flag name="clip_horizontal" value="0x08" />
            <flag name="start" value="0x00800003" />
            <flag name="end" value="0x00800005" />
        </attr>
    </declare-styleable>
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mPaddingTop = getPaddingTop();
        mPaddingLeft = getPaddingLeft();
        mPaddingRight = getPaddingRight();
        mPaddingBottom = getPaddingBottom();

        childWidth = 0;//所有child占用的总宽高
        childHeight = 0;
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) 
            View child = getChildAt(i);
            if (child.getVisibility() != View.GONE) 
                //为了添加child的margin属性值(直接强转成MarginLayoutParams会报错,需要重写,或者继承MarginLayoutParams,重写它的generateLayoutParams方法)
                LayoutParams lp = (LayoutParams) child.getLayoutParams();

                //▲变化1:将measureChild改为measureChildWithMargin
                measureChildWithMargins(child, widthMeasureSpec, 0,
                        heightMeasureSpec, 0);
              /*原来:  measureChild(child, widthMeasureSpec,
                        heightMeasureSpec);*/
                if (mOrientation == HORIZONTAL) 
                    childWidth += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
                    childHeight = Math.max(childHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                 else 
                    childWidth = Math.max(childWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                    childHeight += child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
                
            
        
        childWidth += mPaddingLeft + mPaddingRight;
        childHeight += mPaddingTop + mPaddingBottom;
        setMeasuredDimension(resolveSize(childWidth, widthMeasureSpec), resolveSize(childHeight, heightMeasureSpec));
    

child.getLayoutParams()直接强转成MarginLayoutParams会报错,需要重写,或者继承MarginLayoutParams,重写它的generateLayoutParams方法

@Override
    protected android.view.ViewGroup.LayoutParams generateDefaultLayoutParams() 
        return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT);
    

    @Override
    public android.view.ViewGroup.LayoutParams generateLayoutParams(
            AttributeSet attrs) 
        return new LayoutParams(getContext(), attrs);
    

    @Override
    protected android.view.ViewGroup.LayoutParams generateLayoutParams(
            android.view.ViewGroup.LayoutParams p) 
        return new LayoutParams(p);
    

    public static class LayoutParams extends MarginLayoutParams 
        public int gravity = -1;

        public LayoutParams(Context c, AttributeSet attrs) 
            super(c, attrs);

            TypedArray ta = c.obtainStyledAttributes(attrs,
                    R.styleable.MyViewGroup);

            gravity = ta.getInt(R.styleable.MyViewGroup_gravity, -1);

            ta.recycle();
        

        public LayoutParams(int width, int height) 
            this(width, height, -1);
        

        public LayoutParams(int width, int height, int gravity) 
            super(width, height);
            this.gravity = gravity;
        

        public LayoutParams(android.view.ViewGroup.LayoutParams source) 
            super(source);
        

        public LayoutParams(MarginLayoutParams source) 
            super(source);
        
    

onLayout中区分方向,区分gravity重心,并添加padding和margin值来摆放view

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) 
        if (mOrientation == VERTICAL) 
            layoutVertical(l, t, r, b);
         else 
            layoutHorizontal(l, t, r, b);
        
        //摆放好后getParent()才有值
        if (getParent() instanceof MyViewGroup) 
            sameDirection = ((MyViewGroup) getParent()).getOrientation() == getOrientation();
        
    

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    void layoutHorizontal(int l, int t, int r, int b) 
        int childLeft = mPaddingLeft;
        int childTop = mPaddingTop;

        final int height = b - t;
        int childBottom = height - mPaddingBottom;
        int childSpace = height - mPaddingTop - mPaddingBottom;
        final int childCount = getChildCount();
        final int majorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
        final int minorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;

        final int layoutDirection = getLayoutDirection();
        switch (Gravity.getAbsoluteGravity(majorGravity, layoutDirection)) 
            case Gravity.RIGHT:
                childLeft = mPaddingLeft + r - l - childWidth;
                break;
            case Gravity.CENTER_HORIZONTAL:
                childLeft = mPaddingLeft + (r - l - childWidth) / 2;
                break;
            case Gravity.LEFT:
            default:
                childLeft = mPaddingLeft;
                break;
        
        for (int i = 0; i < childCount; i++) 
            View child = getChildAt(i);
            if (child.getVisibility() != View.GONE) 
                LayoutParams params = (LayoutParams) child.getLayoutParams();
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();
                int gravity = params.gravity;
                if (gravity < 0) 
                    gravity = minorGravity;
                

                switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) 
                    case Gravity.TOP:
                        childTop = mPaddingTop + params.topMargin;
                        break;
                    case Gravity.CENTER_VERTICAL:
                        childTop = mPaddingTop + ((childSpace - childHeight) / 2)
                                + params.topMargin - params.bottomMargin;
                        break;
                    case Gravity.BOTTOM:
                        childTop = childBottom - childHeight - params.bottomMargin;
                        break;
                    default:
                        childTop = mPaddingTop;
                        break;
                
                childLeft += params.leftMargin;
                childTop += params.topMargin;
                child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
                childLeft += childWidth + params.rightMargin;
                childTop = mPaddingTop;
            
        
    

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    void layoutVertical(int l, int t, int r, int b) 
        int childLeft = mPaddingLeft;
        int childTop = mPaddingTop;

        final int width = r - l;
        int childRight = width - mPaddingRight;
        int childSpace = width - mPaddingLeft - mPaddingRight;
        final int childCount = getChildCount();
        final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
        final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;

        switch (majorGravity) 
            case Gravity.BOTTOM:
                childTop = mPaddingTop + b - t - childHeight;
                break;
            case Gravity.CENTER_VERTICAL:
                childTop = mPaddingTop + (b - t - childHeight) / 2;
                break;
            case Gravity.TOP:
            default:
                childTop = mPaddingTop;
                break;
        
        for (int i = 0; i < childCount; i++) 
            View child = getChildAt(i);
            if (child == null) 
                childTop += 0;
             else if (child.getVisibility() != GONE) 
                LayoutParams params = (LayoutParams) child.getLayoutParams();
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();
                int gravity = params.gravity;
                if (gravity < 0) 
                    gravity = minorGravity;
                
                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) 
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = mPaddingLeft + ((childSpace - childWidth) / 2)
                                + params.leftMargin - params.rightMargin;
                        break;
                    case Gravity.RIGHT:
                        childLeft = childRight - childWidth - params.rightMargin;
                        break;

                    case Gravity.LEFT:
                    default:
                        childLeft = mPaddingLeft + params.leftMargin;
                        break;
                
                childLeft += params.leftMargin;
                childTop += params.topMargin;
                child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
                childLeft = mPaddingTop;
                childTop += childHeight + params.bottomMargin;
            
        
    

以上已经完成了具有orientation和gravity属性,并且考虑padding,以及子view的padding和margin的容器

接下来是让它能够滚动
只需要重写onTouchEnvent,在ACTION_MOVE中scrollBy(-offsetX, 0);就可以滚动了

下面是完整代码

package ai.houzi.xiao.widget;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;

import com.juxin.common.utils.Logg;

import ai.houzi.xiao.R;

/**
 * 自定义容器(LinearLayout),支持padding,margin,设置水平垂直方向,和gravity
 */
public class MyViewGroup extends ViewGroup 
    private int mOrientation;
    private int mGravity;
    private int mPaddingTop;
    private int mPaddingLeft;
    private int mPaddingRight;
    private int mPaddingBottom;
    private int childWidth;
    private int childHeight;

    public static final int HORIZONTAL = 0;
    public static final int VERTICAL = 1;
    private static final int[] ORIENTATION_FLAGS = 
            HORIZONTAL, VERTICAL
    ;

    private Scroller mScroller;
    private int mTouchSlop;
    private boolean sameDirection;//是否同向(嵌套时)

    private float downX, downY, moveX, moveY, lastX, lastY;
    private boolean isFirst;
    private VelocityTracker mVelocityTracker;
    private int mPointerId;
    private int mMaxVelocity;//最大速度

    public MyViewGroup(Context context, AttributeSet attrs) 
        super(context, attrs);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyViewGroup);
        mOrientation = ORIENTATION_FLAGS[a.getInt(R.styleable.MyViewGroup_orientation, 0)];
        mGravity = a.getInt(R.styleable.MyViewGroup_gravity, Gravity.START | Gravity.TOP);
        a.recycle();

        mMaxVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity();
        mScroller = new Scroller(context);
        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) 
        if (mOrientation == VERTICAL) 
            layoutVertical(l, t, r, b);
         else 
            layoutHorizontal(l, t, r, b);
        
        //摆放好后getParent()才有值
        if (getParent() instanceof MyViewGroup) 
            sameDirection = ((MyViewGroup) getParent()).getOrientation() == getOrientation();
        
    

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    void layoutHorizontal(int l, int t, int r, int b) 
        int childLeft = mPaddingLeft;
        int childTop = mPaddingTop;

        final int height = b - t;
        int childBottom = height - mPaddingBottom;
        int childSpace = height - mPaddingTop - mPaddingBottom;
        final int childCount = getChildCount();
        final int majorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
        final int minorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;

        final int layoutDirection = getLayoutDirection();
        switch (Gravity.getAbsoluteGravity(majorGravity, layoutDirection)) 
            case Gravity.RIGHT:
                childLeft = mPaddingLeft + r - l - childWidth;
                break;
            case Gravity.CENTER_HORIZONTAL:
                childLeft = mPaddingLeft + (r - l - childWidth) / 2;
                break;
            case Gravity.LEFT:
            default:
                childLeft = mPaddingLeft;
                break;
        
        for (int i = 0; i < childCount; i++) 
            View child = getChildAt(i);
            if (child.getVisibility() != View.GONE) 
                LayoutParams params = (LayoutParams) child.getLayoutParams();
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();
                int gravity = params.gravity;
                if (gravity < 0) 
                    gravity = minorGravity;
                

                switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) 
                    case Gravity.TOP:
                        childTop = mPaddingTop + params.topMargin;
                        break;
                    case Gravity.CENTER_VERTICAL:
                        childTop = mPaddingTop + ((childSpace - childHeight) / 2)
                                + params.topMargin - params.bottomMargin;
                        break;
                    case Gravity.BOTTOM:
                        childTop = childBottom - childHeight - params.bottomMargin;
                        break;
                    default:
                        childTop = mPaddingTop;
                        break;
                
                childLeft += params.leftMargin;
                childTop += params.topMargin;
                child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
                childLeft += childWidth + params.rightMargin;
                childTop = mPaddingTop;
            
        
    

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    void layoutVertical(int l, int t, int r, int b) 
        int childLeft = mPaddingLeft;
        int childTop = mPaddingTop;

        final int width = r - l;
        int childRight = width - mPaddingRight;
        int childSpace = width - mPaddingLeft - mPaddingRight;
        final int childCount = getChildCount();
        final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
        final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;

        switch (majorGravity) 
            case Gravity.BOTTOM:
                childTop = mPaddingTop + b - t - childHeight;
                break;
            case Gravity.CENTER_VERTICAL:
                childTop = mPaddingTop + (b - t - childHeight) / 2;
                break;
            case Gravity.TOP:
            default:
                childTop = mPaddingTop;
                break;
        
        for (int i = 0; i < childCount; i++) 
            View child = getChildAt(i);
            if (child == null) 
                childTop += 0;
             else if (child.getVisibility() != GONE) 
                LayoutParams params = (LayoutParams) child.getLayoutParams();
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();
                int gravity = params.gravity;
                if (gravity < 0) 
                    gravity = minorGravity;
                
                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) 
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = mPaddingLeft + ((childSpace - childWidth) / 2)
                                + params.leftMargin - params.rightMargin;
                        break;
                    case Gravity.RIGHT:
                        childLeft = childRight - childWidth - params.rightMargin;
                        break;

                    case Gravity.LEFT:
                    default:
                        childLeft = mPaddingLeft + params.leftMargin;
                        break;
                
                childLeft += params.leftMargin;
                childTop += params.topMargin;
                child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
                childLeft = mPaddingTop;
                childTop += childHeight + params.bottomMargin;
            
        
    

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mPaddingTop = getPaddingTop();
        mPaddingLeft = getPaddingLeft();
        mPaddingRight = getPaddingRight();
        mPaddingBottom = getPaddingBottom();

        childWidth = 0;//所有child占用的总宽高
        childHeight = 0;
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) 
            View child = getChildAt(i);
            if (child.getVisibility() != View.GONE) 
                //为了添加child的margin属性值
                LayoutParams lp = (LayoutParams) child.getLayoutParams();

                //▲变化1:将measureChild改为measureChildWithMargin
                measureChildWithMargins(child, widthMeasureSpec, 0,
                        heightMeasureSpec, 0);
              /*原来:  measureChild(child, widthMeasureSpec,
                        heightMeasureSpec);*/
                if (mOrientation == HORIZONTAL) 
                    childWidth += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
                    childHeight = Math.max(childHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                 else 
                    childWidth = Math.max(childWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                    childHeight += child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
                
            
        
        childWidth += mPaddingLeft + mPaddingRight;
        childHeight += mPaddingTop + mPaddingBottom;
        setMeasuredDimension(resolveSize(childWidth, widthMeasureSpec), resolveSize(childHeight, heightMeasureSpec));
    

    @Override
    protected android.view.ViewGroup.LayoutParams generateDefaultLayoutParams() 
        return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT);
    

    @Override
    public android.view.ViewGroup.LayoutParams generateLayoutParams(
            AttributeSet attrs) 
        return new LayoutParams(getContext(), attrs);
    

    @Override
    protected android.view.ViewGroup.LayoutParams generateLayoutParams(
            android.view.ViewGroup.LayoutParams p) 
        return new LayoutParams(p);
    

    public static class LayoutParams extends MarginLayoutParams 
        public int gravity = -1;

        public LayoutParams(Context c, AttributeSet attrs) 
            super(c, attrs);

            TypedArray ta = c.obtainStyledAttributes(attrs,
                    R.styleable.MyViewGroup);

            gravity = ta.getInt(R.styleable.MyViewGroup_gravity, -1);

            ta.recycle();
        

        public LayoutParams(int width, int height) 
            this(width, height, -1);
        

        public LayoutParams(int width, int height, int gravity) 
            super(width, height);
            this.gravity = gravity;
        

        public LayoutParams(android.view.ViewGroup.LayoutParams source) 
            super(source);
        

        public LayoutParams(MarginLayoutParams source) 
            super(source);
        
    

    public int getOrientation() 
        return mOrientation;
    

    public void setOrientation(int orientation) 
        this.mOrientation = orientation;
        if (getParent() instanceof MyViewGroup) 
            sameDirection = ((MyViewGroup) getParent()).getOrientation() == orientation;
        
        requestLayout();
    

    //------------------------我是快乐的分割线------------------------------------------------------


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) 
        int action = ev.getAction();
        if (action == MotionEvent.ACTION_DOWN) 
            downX = lastX = ev.getX();
            downY = lastY = ev.getY();
         else if (action == MotionEvent.ACTION_MOVE) 
            //拦截move事件
            return true;
        
        return false;
    

    @Override
    public boolean onTouchEvent(MotionEvent event) 
        obtainVelocityTracker(event);
        int action = event.getAction();
        float x = event.getX();
        float y = event.getY();
        if (action == MotionEvent.ACTION_DOWN) 
            if (!mScroller.isFinished()) 
                mScroller.abortAnimation();
            
            mPointerId = event.getPointerId(0);
            lastX = x;
            lastY = y;
            getParent().requestDisallowInterceptTouchEvent(true);
         else if (action == MotionEvent.ACTION_MOVE) 
            if (isFirst) 
                lastX = x;
                lastY = y;
                isFirst = false;
            
            if (mOrientation == HORIZONTAL) 
                touchMoveHorizontal(event);
             else 
                touchMoveVertical(event);
            
            if (scrollChangeListener != null) //滚动距离的回调
                scrollChangeListener.onScrollChange(getScrollX(), getScrollY());
            
            mScrollState = SCROLLING;
         else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) 
            //计算1000ms的速度
            mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
            //获取x,y在mPointerId上的的速度
            final float velocityX = mVelocityTracker.getXVelocity(mPointerId);
            final float velocityY = mVelocityTracker.getYVelocity(mPointerId);
            if (mOrientation == HORIZONTAL) 
                if (getScrollX() < 0) //超出起始边界,弹回起始位置
                    mScroller.startScroll(getScrollX(), 0, -getScrollX(), 0, 300);
                 else if (getScrollX() + getWidth() > childWidth) //超过结尾边界同理
                    mScroller.startScroll(getScrollX(), 0, (int) -(getScrollX() + getWidth() - childWidth), 0, 300);
                 else //中间时候,按最后的瞬时速度抛出,不超过剩下的距离
                    mScroller.fling(getScrollX(), 0, (int) -velocityX, 0, 0, childWidth - getWidth() + 100, 0, 0);
                
             else 
                if (getScrollY() < 0) 
                    mScroller.startScroll(0, getScrollY(), 0, -getScrollY(), 300);
                 else if (getScrollY() + getHeight() > childHeight) 
                    mScroller.startScroll(0, getScrollY(), 0, (int) -(getScrollY() + getHeight() - childHeight), 300);
                 else 
                    mScroller.fling(0, getScrollY(), 0, (int) -velocityY, 0, 0, 0, childHeight - getHeight() + 100);
                
            
            isFirst = true;
            recycleVelocityTracker();
            postInvalidate();//调用重绘才会调用computeScroll方法,形成动画
        
        return true;
    

    /**
     * 水平滚动
     */
    private void touchMoveHorizontal(MotionEvent event) 
        float x = event.getX();
        int offsetX = 0;
        moveX = x;
        //超出界限时,增加阻力
        if (getScrollX() < 0 || getScrollX() + getWidth() > childWidth) 
            offsetX = (int) ((moveX - lastX) / 2.5);
         else 
            offsetX = (int) (moveX - lastX);
        
        if (!sameDirection) 
//          不同向
            if (Math.abs(x - downX) + mTouchSlop > Math.abs(event.getY() - downY) && childWidth > getWidth()) 
                getParent().requestDisallowInterceptTouchEvent(true);
             else 
                getParent().requestDisallowInterceptTouchEvent(false);
            
         else 
//          同向
            if (lastX >= downX) 
                if (getScrollX() <= 0) 
                    getParent().requestDisallowInterceptTouchEvent(false);
                 else 
                    getParent().requestDisallowInterceptTouchEvent(true);
                
             else 
                if (getScrollX() >= childWidth - getWidth()) 
                    getParent().requestDisallowInterceptTouchEvent(false);
                 else 
                    getParent().requestDisallowInterceptTouchEvent(true);
                
            
        
        if (Math.abs(downX - x) > mTouchSlop) 
            scrollBy(-offsetX, 0);
        
        lastX = moveX;
    

    /**
     * 垂直滚动
     */
    private void touchMoveVertical(MotionEvent event) 
        float y = event.getY();
        int offsetY = 0;
        moveY = y;
        //超出界限时,增加阻力
        if (getScrollY() < 0 || getScrollY() + getHeight() > childHeight) 
            offsetY = (int) ((moveY - lastY) / 2.5);
         else 
            offsetY = (int) (moveY - lastY);
        
        Logg.e(sameDirection);
        if (!sameDirection) 
//          不同向
            if (Math.abs(event.getX() - downX) < Math.abs(y - downY) + mTouchSlop && childHeight > getHeight()) 
                getParent().requestDisallowInterceptTouchEvent(true);
             else 
                getParent().requestDisallowInterceptTouchEvent(false);
            
         else 
//          同向
            if (lastY >= downY) 
                if (getScrollY() <= 0) 
                    getParent().requestDisallowInterceptTouchEvent(false);
                 else 
                    getParent().requestDisallowInterceptTouchEvent(true);
                
             else 
                if (getScrollY() >= childHeight - getHeight()) 
                    getParent().requestDisallowInterceptTouchEvent(false);
                 else 
                    getParent().requestDisallowInterceptTouchEvent(true);
                
            
        
        //有效滑动,滚动-offsetY距离
        if (Math.abs(downY - y) > mTouchSlop) 
            scrollBy(0, -offsetY);
        
        lastY = moveY;
    

    /**
     * 创建新的速度监视对象
     *
     * @param event 滑动事件
     */
    private void obtainVelocityTracker(MotionEvent event) 
        if (null == mVelocityTracker) 
            mVelocityTracker = VelocityTracker.obtain();
        
        mVelocityTracker.addMovement(event);
    

    /**
     * 释放资源
     */
    private void recycleVelocityTracker() 
        if (null != mVelocityTracker) 
            mVelocityTracker.clear();
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        
    

    @Override
    public void computeScroll() 
        super.computeScroll();

        if (mScroller.computeScrollOffset()) 
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();//当没滚动到需要的位置时,不断的重绘,形成动画
            mScrollState = SCROLLING;
         else 
            mScrollState = IDLE;
        
    

    /**
     * 滚动距离监听器
     */
    interface ScrollChangeListener 
        void onScrollChange(int scrollX, int scrollY);
    

    private ScrollChangeListener scrollChangeListener;

    /**
     * 设置滚动监听
     *
     * @param l 回调
     */
    public void setScrollChangeListener(ScrollChangeListener l) 
        this.scrollChangeListener = l;
    

    private int mScrollState;
    public static final int IDLE = 0;//闲置状态
    public static final int SCROLLING = 1;//滚动状态

    /**
     * @return 当前滚动状态
    

以上是关于自定义ViewGroup学习(LinearLayout的布局方式,可以滚动和嵌套)的主要内容,如果未能解决你的问题,请参考以下文章

一个FlowLayout带你学会自定义ViewGroup

Android 自定义ViewGroup之实现FlowLayout-标签流容器

自定义View_1_关于View,ViewGroup的测量和绘制流程

android自定义ViewGroup(侧滑菜单)

自定义控件自定义ViewGroup 在ViewGroup中显示TextView

自定义ViewGroup