カスタムView Groupラーニング(LinearLayoutのレイアウトで、スクロールとネストが可能)

61843 ワード

カスタムView Groupラーニング(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;
            }
        }
  }

基本的なビューグループが完成しました
次にpaddingとmargin、gravity、orientationプロパティを追加します.
まずattrsにorientationとgravityの2つのカスタム属性を追加します
<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       
     */
    public int getScrollState() {
        return mScrollState;
    }
}