com.lovely3x.eavlibrary.EasyAdapterView.java Source code

Java tutorial

Introduction

Here is the source code for com.lovely3x.eavlibrary.EasyAdapterView.java

Source

/*
MIT License
    
Copyright (c) 2016
    
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
    
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
    
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package com.lovely3x.eavlibrary;

import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import static android.R.attr.right;
import static android.R.attr.scrollX;
import static android.R.attr.tag;
import static android.R.attr.translateX;
import static android.R.attr.translateY;
import static com.lovely3x.eavlibrary.EasyAdapterView.ViewMode.LIST_VIEW;

/**
 * Easy adapter View
 * Easy use !
 * Like List view or recycler view And wheel view.
 * Created by lovely3x on 16/8/3.
 * :
 * <p/>
 *   ??,,,??
 *  ?
 *  Item?
 * <p/>
 * <p/>
 */
public class EasyAdapterView extends AdapterView<ListAdapter> {

    private static final String TAG = "EasyAdapterView";

    protected static final boolean DEBUG = false;

    /**
     * 
     */
    public static final int LAYOUT_DIRECTLY_VERTICAL = 1;

    /**
     * 
     */
    public static final int LAYOUT_DIRECTLY_HORIZONTAL = 2;

    /**
     * ?:
     */
    public static final int SCROLL_TRIGGER_USER = 1;
    /**
     * ?:
     */
    public static final int SCROLL_TRIGGER_SYSTEM = 2;

    /**
     * velocity
     */
    public static final float DEFAULT_SMOOTH_ADJUST_ON_FLING_VELOCITY = 70.0F;

    private static final int DEFAULT_SMOOTH_DURATION = 500;

    /**
     * Velocity
     * ?
     * ???,?
     * ?""?
     * ?,??
     * ???,??
     * ?
     */
    private float mSmoothFlingVelocity = DEFAULT_SMOOTH_ADJUST_ON_FLING_VELOCITY;

    /**
     * ??
     */
    public static final int REST = 0;

    /**
     * 
     */
    public static final int FLING = 1;

    /**
     * 
     */
    public static final int SCROLL = 2;

    /**
     * 
     */
    public static final int SPRING_BACK = 3;

    /**
     * overscroll
     */
    public static final int OVER_SCROLL = 4;

    /**
     * 
     */
    public static final int TAP = 4;

    private final int mTouchSlop;
    private final int mMinimumVelocity;
    private final int mMaximumVelocity;
    private final Paint mDebugPaint;

    private final EdgeEffect mBottomEdgeEffect;
    private final EdgeEffect mTopEdgeEffect;

    private final EdgeEffect mLeftEdgeEffect;
    private final EdgeEffect mRightEdgeEffect;

    /**
     * ?
     */
    private int mLayoutDirectly = LAYOUT_DIRECTLY_VERTICAL;

    /**
     * ,list view?
     */
    private ViewMode mViewMode = ViewMode.ENDLESS_WHEEL_VIEW;

    /**
     * ??
     */
    private int mTouchState = REST;

    /**
     * 
     */
    private boolean mInLayout;

    /**
     * ??
     */
    protected int mFirstPosition;

    /**
     * ????
     * ???
     * notifyDataSetChanged?,???????
     */
    protected int mStartOffset;

    /**
     * ??
     */
    protected int mSelectedPosition = INVALID_POSITION;

    private View mSelectedView;

    /**
     * ???
     */
    private int mItemCount;

    protected float mLastMotionX = Integer.MIN_VALUE;
    protected float mLastMotionY = Integer.MIN_VALUE;

    protected Rect mItemClickRect = new Rect();
    protected Rect mTempRect = new Rect();

    protected boolean isBeginDrag;

    protected ListAdapter mAdapter;
    protected VelocityTracker mVelocity;
    protected boolean mDataSetInvalidated;

    /**
     * 
     */
    protected Drawable mDivider;

    /**
     * 
     */
    protected int mDividerHeight;

    protected OnItemClickListener mItemClickListener;
    protected OnScrollListener mOnScrollListener;
    protected OnSelectedItemChangedListener mOnSelectedItemChangedListener;

    protected boolean mSmoothScrollbarEnabled = true;
    protected boolean mDataChanged;

    protected int mForceOffsetFromStart;

    /**
     * ?????
     */
    protected boolean mIsMutable;

    /**
     * ""???
     * ??""?? ?, "Fling" "Adjusting" "Scrolling" ? ""?!
     * <p>
     * ,-1,??
     */
    protected int mMaxAmountScrollPage = -1;

    /**
     * ????
     * -1 ??
     */
    protected int mMinPosition = -1;
    /**
     * ???
     * -1 ??
     */
    protected int mMaxPosition = -1;

    /**
     * ?
     *  {@link #SCROLL_TRIGGER_SYSTEM}  {@link #SCROLL_TRIGGER_USER}
     */
    protected int mScrollTrigger;

    protected int mDownY;
    protected int mDownX;

    protected int mYOverScrollDistance;
    protected int mXOverScrollDistance;

    /**
     * ??overscroll
     */
    protected boolean mOverScroll = true;
    protected Runnable mKeepOverScrollRunnable;
    /**
     * ??OverScroll
     */
    private EdgeEffect mActiveEffect;

    public EasyAdapterView(Context context) {
        this(context, null);
    }

    public EasyAdapterView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public EasyAdapterView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        this.mDebugPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        this.mDebugPaint.setColor(Color.RED);
        this.mDebugPaint.setStrokeWidth(5);
        this.mDebugPaint.setTextSize(
                TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics()));

        ViewConfiguration config = ViewConfiguration.get(context);
        mTouchSlop = config.getScaledTouchSlop() / 2;
        this.mVelocity = VelocityTracker.obtain();

        mMinimumVelocity = config.getScaledMinimumFlingVelocity();
        mMaximumVelocity = config.getScaledMaximumFlingVelocity();

        this.mTopEdgeEffect = new EdgeEffect(getContext());
        this.mBottomEdgeEffect = new EdgeEffect(getContext());

        this.mLeftEdgeEffect = new EdgeEffect(getContext());
        this.mRightEdgeEffect = new EdgeEffect(getContext());

        mDividerHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1f,
                getResources().getDisplayMetrics());
        mDivider = new ColorDrawable(Color.LTGRAY);

        if (attrs != null)
            initAttrs(attrs);

        setWillNotDraw(false);

        previewInEditMode();
    }

    /**
     * ??
     *
     * @param attrs ?
     */
    protected void initAttrs(AttributeSet attrs) {
        if (attrs != null) {

            TypedArray typeArray = null;
            try {
                typeArray = getContext().obtainStyledAttributes(attrs, R.styleable.EasyAdapterView);

                mLayoutDirectly = typeArray.getInteger(R.styleable.EasyAdapterView_orientation,
                        LAYOUT_DIRECTLY_VERTICAL);
                mViewMode = ViewMode.wrap(typeArray.getInteger(R.styleable.EasyAdapterView_mode, LIST_VIEW.mValue));

                mDividerHeight = typeArray.getDimensionPixelOffset(R.styleable.EasyAdapterView_dividerHeight,
                        mDividerHeight);
                Drawable divider = typeArray.getDrawable(R.styleable.EasyAdapterView_divider);
                if (divider != null)
                    mDivider = divider;

                switch (mLayoutDirectly) {
                case LAYOUT_DIRECTLY_HORIZONTAL: {
                    setHorizontalScrollBarEnabled(true);
                    setVerticalScrollBarEnabled(false);

                }
                    break;
                case LAYOUT_DIRECTLY_VERTICAL: {
                    setVerticalScrollBarEnabled(true);
                    setHorizontalScrollBarEnabled(false);
                }
                    break;
                }

            } finally {
                if (typeArray != null)
                    typeArray.recycle();
            }

        }
    }

    /**
     * ?
     */
    protected void previewInEditMode() {
        if (isInEditMode()) {
            setAdapter(
                    new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, android.R.id.text1,
                            new String[] { "C", "C++", "Java", "Python", "JavaScript", "VB", "Objective-c", "Swift",
                                    "Shell", "Perl", "Ruby", "Groovy", "Kotlin", "C#", "CSS", "ANTLR", "Pascal" }) {
                        @Override
                        public View getView(int position, View convertView, ViewGroup parent) {
                            View view = super.getView(position, convertView, parent);
                            if (view != null) {
                                LayoutParams lp = view.getLayoutParams();
                                if (lp != null) {
                                    lp.height = LayoutParams.WRAP_CONTENT;
                                    lp.width = LayoutParams.WRAP_CONTENT;
                                }
                            }
                            return view;
                        }
                    });
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (mDivider != null && mDividerHeight > 0) {
            mDivider.setBounds(0, 0, w, mDividerHeight);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int width = 0;
        int height = 0;

        View firstView = null;

        if (mAdapter != null && mAdapter.getCount() > 0) {
            View childView = firstView = obtainView(0);
            if (childView != null) {
                setupChildView(childView);
                mRecyclerBin.addRecyclerBin(0, childView);
            }
        }

        //?
        switch (MeasureSpec.getMode(widthMeasureSpec)) {
        case MeasureSpec.EXACTLY:
            width = MeasureSpec.getSize(widthMeasureSpec);
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.UNSPECIFIED:
            if (firstView != null) {
                width = firstView.getMeasuredWidth();
            } else {
                width = MeasureSpec.getSize(widthMeasureSpec);
            }
            break;
        }

        //?
        switch (MeasureSpec.getMode(heightMeasureSpec)) {
        case MeasureSpec.EXACTLY:
            height = MeasureSpec.getSize(heightMeasureSpec);
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.UNSPECIFIED:
            if (firstView != null) {
                height = firstView.getMeasuredHeight();
            } else {
                height = MeasureSpec.getSize(heightMeasureSpec);
            }
            break;
        }

        setMeasuredDimension(width, height);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        try {
            mInLayout = true;
            super.onLayout(changed, left, top, right, bottom);
            invalidLayout();
        } finally {
            mInLayout = false;
        }
    }

    @Override
    public void setOnItemClickListener(OnItemClickListener listener) {
        super.setOnItemClickListener(listener);
        this.mItemClickListener = listener;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean beginScroll = false;

        float x = ev.getX();
        float y = ev.getY();

        mVelocity.addMovement(ev);

        switch (ev.getActionMasked()) {
        case MotionEvent.ACTION_DOWN: {

            mLastMotionX = x;
            mLastMotionY = y;

            mDownX = (int) ev.getX();
            mDownY = (int) ev.getY();

            switch (mTouchState) {
            case FLING:
            case SCROLL:
            case SPRING_BACK:
                //                        beginScroll = true;
                mTouchState = REST;
                break;
            default:
                mFlingRunnable.endScrollerAnimation(false, true);
                mTouchState = TAP;
                break;
            }

            if (mMaxAmountScrollPage != -1) {//,?
                final int first = getSelectedItemPosition();
                mMaxPosition = first + mMaxAmountScrollPage;
                mMinPosition = first - mMaxAmountScrollPage;
            }
            mScrollTrigger = SCROLL_TRIGGER_USER;
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            switch (mLayoutDirectly) {
            case LAYOUT_DIRECTLY_HORIZONTAL:
                if (mLastMotionX != Integer.MIN_VALUE) {
                    float xDelta = x - mLastMotionX;
                    float absDelta = Math.abs(xDelta);
                    if (absDelta >= mTouchSlop) {
                        beginScroll = true;
                        mTouchState = SCROLL;
                        if (getParent() != null) {
                            getParent().requestDisallowInterceptTouchEvent(true);
                        }
                        reportScrollStateChanged(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
                        startScrollIfNeed(xDelta, 0);
                    } else {
                        mTouchState = TAP;
                    }
                    mLastMotionX = x;
                    mLastMotionY = y;
                }
                break;
            case LAYOUT_DIRECTLY_VERTICAL: {
                if (mLastMotionY != Integer.MIN_VALUE) {
                    float yDelta = y - mLastMotionY;
                    float absDelta = Math.abs(yDelta);
                    if (absDelta >= mTouchSlop) {
                        if (getParent() != null) {
                            getParent().requestDisallowInterceptTouchEvent(true);
                        }
                        mTouchState = SCROLL;
                        beginScroll = true;
                        reportScrollStateChanged(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
                        startScrollIfNeed(0, yDelta);
                    } else {
                        mTouchState = TAP;
                    }
                    mLastMotionX = x/*Integer.MIN_VALUE*/;
                    mLastMotionY = y;
                }
            }
                break;
            }
            break;
        }

        }

        return beginScroll;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();

        mVelocity.addMovement(event);

        switch (event.getActionMasked()) {
        case MotionEvent.ACTION_DOWN: {
            //                switch (mTouchState) {
            //                    case FLING:
            //                    case SCROLL:
            //                    case SPRING_BACK:
            //                        mTouchState = REST;
            //                        break;
            //                    default:
            //                        mTouchState = TAP;
            //                        mFlingRunnable.endScrollerAnimation(false);
            //                        break;
            //                }
            mLastMotionX = event.getX();
            mLastMotionY = event.getY();

            mDownX = (int) event.getX();
            mDownY = (int) event.getY();

            switch (mTouchState) {
            case FLING:
            case SCROLL:
            case SPRING_BACK:
                //                        beginScroll = true;
                mTouchState = REST;
                break;
            default:
                mFlingRunnable.endScrollerAnimation(false, true);
                mTouchState = TAP;
                break;
            }

            if (mMaxAmountScrollPage != -1) {//,?
                final int first = getSelectedItemPosition();
                //                    mMaxPosition = (first + mMaxAmountScrollPage) % (mItemCount - 1);
                //                    mMinPosition = (first - mMaxAmountScrollPage) % (mItemCount - 1);
                //
                mMaxPosition = (first + mMaxAmountScrollPage);
                mMinPosition = (first - mMaxAmountScrollPage);
            }

            mScrollTrigger = SCROLL_TRIGGER_USER;
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            switch (mLayoutDirectly) {
            case LAYOUT_DIRECTLY_HORIZONTAL:
                if (mLastMotionX != Integer.MIN_VALUE) {
                    float xDelta = x - mLastMotionX;
                    float absDelta = Math.abs(xDelta);
                    if (isBeginDrag) {
                        mTouchState = SCROLL;
                        reportScrollStateChanged(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
                        startScrollIfNeed(xDelta, 0);
                    } else {
                        if (absDelta >= mTouchSlop) {
                            isBeginDrag = true;
                            mTouchState = SCROLL;

                            if (getParent() != null) {
                                getParent().requestDisallowInterceptTouchEvent(true);
                            }
                            reportScrollStateChanged(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
                            startScrollIfNeed(xDelta, 0);
                        } else {
                            mTouchState = TAP;
                        }
                    }
                    mLastMotionX = x;
                    mLastMotionY = y/*Integer.MIN_VALUE*/;
                }
                break;
            case LAYOUT_DIRECTLY_VERTICAL: {
                if (mLastMotionY != Integer.MIN_VALUE) {
                    float yDelta = y - mLastMotionY;
                    float absDelta = Math.abs(yDelta);
                    if (isBeginDrag) {
                        mTouchState = SCROLL;
                        reportScrollStateChanged(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
                        startScrollIfNeed(0, yDelta);
                    } else {
                        if (absDelta >= mTouchSlop) {
                            isBeginDrag = true;
                            mTouchState = SCROLL;
                            if (getParent() != null) {
                                getParent().requestDisallowInterceptTouchEvent(true);
                            }
                            reportScrollStateChanged(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
                            startScrollIfNeed(0, yDelta);
                        } else {
                            mTouchState = TAP;
                        }
                    }
                    mLastMotionX = x/*Integer.MIN_VALUE*/;
                    mLastMotionY = y;
                }
            }
                break;
            }
            break;
        }
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL: {
            isBeginDrag = false;
            mVelocity.computeCurrentVelocity(1000, mMaximumVelocity);

            float yVelocity = mVelocity.getYVelocity();
            float xVelocity = mVelocity.getXVelocity();

            int xDelta = 0;
            if (mDownX != Integer.MIN_VALUE)
                xDelta = (int) (event.getX() - mDownX);
            int yDelta = 0;
            if (mDownY != Integer.MIN_VALUE)
                yDelta = (int) (event.getY() - mDownY);

            final int maxDelta = Math.max(Math.abs(xDelta), Math.abs(yDelta));

            boolean needAdjust = true;
            boolean isUpEvent = event.getActionMasked() == MotionEvent.ACTION_UP;

            switch (mLayoutDirectly) {
            case LAYOUT_DIRECTLY_HORIZONTAL: {
                if (Math.abs(xVelocity) > mMinimumVelocity && isUpEvent && maxDelta >= mTouchSlop) {
                    needAdjust = false;
                    mTouchState = FLING;
                    flingInner(xVelocity, 0);
                } else {
                    mFlingRunnable.endScrollerAnimation(false);
                    mScrollTrigger = SCROLL_TRIGGER_SYSTEM;

                    if (mTouchState == TAP && isUpEvent) {
                        tryToClickItem((int) x, (int) y);
                        adjustSelView();
                    } else {
                        adjustSelView();
                    }
                }
                break;
            }
            case LAYOUT_DIRECTLY_VERTICAL: {

                if (Math.abs(yVelocity) > mMinimumVelocity && isUpEvent && maxDelta >= mTouchSlop) {
                    needAdjust = false;
                    mTouchState = FLING;
                    flingInner(0, yVelocity);
                } else {
                    mFlingRunnable.endScrollerAnimation(false);
                    mScrollTrigger = SCROLL_TRIGGER_SYSTEM;

                    if (mTouchState == TAP && isUpEvent) {
                        tryToClickItem((int) x, (int) y);
                        adjustSelView();
                    } else {
                        adjustSelView();
                    }
                }
                break;
            }
            }

            if (needAdjust)
                mVelocity.clear();

            mLastMotionY = Integer.MIN_VALUE;
            mLastMotionX = Integer.MIN_VALUE;

            mDownX = Integer.MIN_VALUE;
            mDownY = Integer.MIN_VALUE;

            mYOverScrollDistance = -1;
            mXOverScrollDistance = -1;
            mActiveEffect = null;

            break;
        }
        }
        return true;
    }

    /**
     * ??
     *
     * @param x
     * @param y
     */
    protected boolean tryToClickItem(int x, int y) {
        int viewPosition = computeClickViewPosition(x, y);
        if (viewPosition != INVALID_POSITION) {
            View view = getChildAt(viewPosition);
            invokeOnItemClickListener(view, mFirstPosition + viewPosition);
            return true;
        }
        return false;
    }

    /**
     * ?
     */
    protected boolean adjustSelView() {
        return adjustSelView(-1);
    }

    /**
     * ?
     *
     * @param duration 
     */
    protected boolean adjustSelView(int duration) {
        if (DEBUG)
            Log.d(TAG, "Adjusting View duration => " + duration);
        View selectedView = getSelectedView();
        boolean adjustment = false;
        if (selectedView != null) {
            adjustment = true;
            reportScrollStateChanged(OnScrollListener.SCROLL_STATE_ADJUSTMENT);

            switch (mViewMode) {
            case END_LESS_LIST_VIEW:
            case LIST_VIEW: {//ListView ?
                //// TODO: 16/8/9
            }
                break;
            case ENDLESS_WHEEL_VIEW:
            case WHEEL_VIEW: {//?
                switch (mLayoutDirectly) {
                case LAYOUT_DIRECTLY_HORIZONTAL: {//?
                    //??
                    //?XSel??
                    //?XSel??,?????

                    final int selectedViewCenter = selectedView.getLeft()
                            + (selectedView.getRight() - selectedView.getLeft()) / 2;
                    final int xSel = getXSel();

                    int offset = 0;

                    if (selectedViewCenter > xSel) {//?
                        offset = -((selectedViewCenter - xSel));
                    } else if (selectedViewCenter < xSel) {
                        offset = (xSel - selectedViewCenter);
                    }

                    //
                    if (offset != 0) {
                        if (duration >= 0) {
                            mFlingRunnable.startSpringBack(offset, 0, duration);
                        } else {
                            mFlingRunnable.startSpringBack(offset, 0);
                        }
                    } else {
                        resetTriggerToSystem();
                        reportScrollStateChanged(OnScrollListener.SCROLL_STATE_IDLE);
                    }
                }
                    break;
                case LAYOUT_DIRECTLY_VERTICAL: {//?

                    final int selectedViewCenter = selectedView.getTop()
                            + (selectedView.getBottom() - selectedView.getTop()) / 2;
                    final int ySel = getYSel();
                    int offset = 0;

                    if (selectedViewCenter < ySel) {//??
                        offset = ySel - selectedViewCenter;
                    } else if (selectedViewCenter > ySel) {//?
                        offset = -(selectedViewCenter - ySel);
                    }

                    if (offset != 0) {
                        if (duration >= 0) {
                            mFlingRunnable.startSpringBack(0, offset, duration);
                        } else {
                            mFlingRunnable.startSpringBack(0, offset);
                        }
                    } else {
                        resetTriggerToSystem();
                        reportScrollStateChanged(OnScrollListener.SCROLL_STATE_IDLE);
                    }
                }
                    break;
                }
            }
                break;
            }
        } else {
            reportScrollStateChanged(OnScrollListener.SCROLL_STATE_IDLE);
            mTouchState = REST;
        }

        return adjustment;
    }

    /**
     * ?
     *
     * @param xVelocity
     * @param yVelocity
     */
    protected void flingInner(float xVelocity, float yVelocity) {
        if (DEBUG)
            Log.d(TAG, "Fling xVelocity " + " , " + xVelocity + " yVelocity " + yVelocity);
        mFlingRunnable.startFling(-(int) (xVelocity * 1.5f), -(int) (yVelocity * 1.5f));
    }

    /**
     * ??
     */
    protected void resetTriggerToSystem() {
        mScrollTrigger = SCROLL_TRIGGER_SYSTEM;
    }

    /**
     * ???topbottom
     *
     * @param offset ??????
     */
    public void offsetChildrenTopAndBottom(int offset) {
        final int count = getChildCount();

        for (int i = 0; i < count; i++) {
            final View v = getChildAt(i);
            v.setTop(v.getTop() + offset);
            v.setBottom(v.getBottom() + offset);
        }

        invalidate();
    }

    /**
     * ???leftright
     *
     * @param offset ??????
     */
    public void offsetChildrenLeftAndRight(int offset) {
        if (DEBUG)
            Log.i(TAG, "OffsetChildrenLeftAndRight => " + offset);
        final int count = getChildCount();

        for (int i = 0; i < count; i++) {
            final View v = getChildAt(i);
            v.setLeft(v.getLeft() + offset);
            v.setRight(v.getRight() + offset);
        }
        invalidate();
    }

    @Override
    public boolean canScrollVertically(int direction) {
        switch (mViewMode) {
        case LIST_VIEW: {//ListView ?
            if (direction > 0) {//
                return !(mFirstPosition == 0 && getChildAt(0).getTop() >= 0/*getTop()*/);
            } else if (direction < 0) {//
                return !(mFirstPosition + getChildCount() >= mItemCount
                        && getChildAt(getChildCount() - 1).getBottom() <= (getBottom() - getTop()));
            }
        }
            break;
        case WHEEL_VIEW: {//?
            if (direction > 0) {//?
                if (mFirstPosition == 0) {//??
                    View firstChild = getChildAt(0);
                    int ySel = getYSel() - (firstChild.getBottom() - firstChild.getTop()) / 2;
                    if (firstChild.getTop() >= ySel) {
                        return false;
                    } else {
                        return true;
                    }
                } else {
                    return true;
                }
            } else if (direction < 0) {//?
                if (mFirstPosition + getChildCount() >= mItemCount) {//??
                    View lastChild = getChildAt(getChildCount() - 1);
                    int ySel = getBottom() - getYSel() + ((lastChild.getBottom() - lastChild.getTop()) / 2);
                    if (lastChild.getBottom() < ySel) {
                        return false;
                    } else {
                        return true;
                    }
                } else {
                    return true;
                }
            }
        }
            break;
        case END_LESS_LIST_VIEW: //ListView?
        case ENDLESS_WHEEL_VIEW: {//?
            return true;
        }
        }
        return super.canScrollVertically(direction);
    }

    @Override
    public boolean canScrollHorizontally(int direction) {
        switch (mViewMode) {
        case LIST_VIEW: {///ListView ?
            if (direction > 0) {//?
                return !(mFirstPosition == 0 && getChildAt(0).getLeft() >= getLeft());
            } else if (direction < 0) {//
                return !(mFirstPosition + getChildCount() >= mItemCount
                        && getChildAt(getChildCount() - 1).getRight() <= getRight());
            }
        }
            break;
        case WHEEL_VIEW: {//?
            if (direction > 0) {//?
                if (mFirstPosition == 0) {
                    final View firstChild = getChildAt(0);
                    final int xSel = getXSel();
                    // ?  ??
                    // 0 ?
                    // ((lastChild.getRight() - lastChild.getLeft()) /2) 
                    final int firstChildCenter = ((firstChild.getRight() - firstChild.getLeft()) / 2);
                    if (firstChild.getLeft() + firstChildCenter >= xSel) {
                        return false;
                    } else {
                        return true;
                    }
                } else {
                    return true;
                }
            } else if (direction < 0) {//
                if (mFirstPosition + getChildCount() >= mItemCount) {//?
                    View lastChild = getChildAt(getChildCount() - 1);
                    int xSel = getXSel();
                    //?  ??
                    // 0 ??
                    //((lastChild.getRight() - lastChild.getLeft()) /2) 
                    int lastChildCenter = ((lastChild.getRight() - lastChild.getLeft()) / 2);
                    if (lastChild.getRight() - lastChildCenter <= xSel) {
                        return false;
                    } else {
                        return true;
                    }
                } else {
                    return true;
                }
            }
        }
            break;
        case END_LESS_LIST_VIEW://ListView?
        case ENDLESS_WHEEL_VIEW://?
            return true;
        }
        return super.canScrollHorizontally(direction);
    }

    /**
     * ??????
     *
     * @param yDelta ???
     * @return ??
     */
    protected int verticalAdjustWithMaxScrollRestrict(int yDelta) {
        if (DEBUG)
            Log.i(TAG, "VerticalAdjustWithMaxScrollRestrict Scroll trigger " + mScrollTrigger);

        if (mScrollTrigger == SCROLL_TRIGGER_USER && mMaxAmountScrollPage != -1) {//?? User ,??

            final int selViewIndex = getSelectedViewPosition();
            if (selViewIndex == INVALID_POSITION)
                return 0;

            mMinPosition %= mItemCount;
            if (mMinPosition < 0)
                mMinPosition = mItemCount + mMinPosition;
            mMaxPosition %= mItemCount;

            boolean isLooping = mMaxPosition < mMinPosition;//?

            View view = getChildAt(selViewIndex);
            final int firstPosition = (selViewIndex + mFirstPosition) % mItemCount;
            if (yDelta > 0) {//
                if (firstPosition < mMinPosition && !isLooping) {
                    return 0;
                } else if (firstPosition == mMinPosition) {
                    switch (mViewMode) {
                    case LIST_VIEW:
                    case END_LESS_LIST_VIEW: {
                        View firstView = getChildAt(0);
                        if (firstView != null) {
                            return Math.min(yDelta, Math.max(0, getTop() - firstView.getTop()));
                        }
                    }
                        break;
                    case WHEEL_VIEW:
                    case ENDLESS_WHEEL_VIEW: {
                        if (mSelectedView != null) {
                            return Math.max(0, Math.min(
                                    getYSel() - mSelectedView.getBottom() + mSelectedView.getHeight() / 2, yDelta));
                        } else {
                            return 0;
                        }
                    }
                    }
                } else {
                    return Math.min(yDelta, view.getHeight() / 2);
                }

            } else if (yDelta < 0) {//??
                if (firstPosition > mMaxPosition && !isLooping) {
                    return 0;
                } else if (firstPosition == mMaxPosition) {
                    switch (mViewMode) {
                    case LIST_VIEW:
                    case END_LESS_LIST_VIEW: {
                        View firstView = getChildAt(0);
                        if (firstView != null) {
                            return Math.max(yDelta, Math.min(0, getTop() - firstView.getBottom()));
                        }
                    }
                        break;
                    case WHEEL_VIEW:
                    case ENDLESS_WHEEL_VIEW: {
                        if (mSelectedView != null) {
                            return Math.max(yDelta, -Math.max(0,
                                    (mSelectedView.getTop() + mSelectedView.getHeight() / 2) - getYSel()));
                        } else {
                            return 0;
                        }
                    }
                    }
                } else {
                    return Math.max(yDelta, -view.getHeight() / 2);
                }
            } else {
                return 0;
            }
        }
        return yDelta;
    }

    /**
     * ??????
     *
     * @param xDelta ???
     * @return ??
     */
    protected int horizontalAdjustWithMaxScrollRestrict(int xDelta) {
        if (DEBUG)
            Log.i(TAG, "horizontalAdjustWithMaxScrollRestrict Scroll trigger " + mScrollTrigger);

        if (mScrollTrigger == SCROLL_TRIGGER_USER && mMaxAmountScrollPage != -1) {//?? User ,??

            final int selViewIndex = getSelectedViewPosition();
            if (selViewIndex == INVALID_POSITION)
                return 0;

            mMinPosition %= mItemCount;
            if (mMinPosition < 0) {
                mMinPosition = mItemCount + mMinPosition;
            }
            mMaxPosition %= mItemCount;
            boolean isLooping = mMaxPosition < mMinPosition;//?

            View view = getChildAt(selViewIndex);
            final int firstPosition = (selViewIndex + mFirstPosition) % mItemCount;

            if (xDelta > 0) {//?
                if (firstPosition < mMinPosition && !isLooping) {
                    return 0;
                } else if (firstPosition == mMinPosition) {
                    switch (mViewMode) {
                    case LIST_VIEW:
                    case END_LESS_LIST_VIEW: {
                        View firstView = getChildAt(0);
                        if (firstView != null) {
                            return Math.min(xDelta, Math.max(0, getLeft() - firstView.getLeft()));
                        }
                    }
                        break;
                    case WHEEL_VIEW:
                    case ENDLESS_WHEEL_VIEW: {
                        if (mSelectedView != null) {
                            return Math.max(0, Math.min(
                                    getXSel() - mSelectedView.getRight() + mSelectedView.getWidth() / 2, xDelta));
                        } else {
                            return 0;
                        }
                    }
                    }
                } else {
                    return Math.min(xDelta, view.getWidth() / 2);
                }

            } else if (xDelta < 0) {//
                if (firstPosition > mMaxPosition && !isLooping) {
                    return 0;
                } else if (firstPosition == mMaxPosition) {
                    switch (mViewMode) {
                    case LIST_VIEW:
                    case END_LESS_LIST_VIEW: {
                        View firstView = getChildAt(0);
                        if (firstView != null) {
                            return Math.max(xDelta, Math.min(0, getLeft() - firstView.getRight()));
                        }
                    }
                        break;
                    case WHEEL_VIEW:
                    case ENDLESS_WHEEL_VIEW: {
                        if (mSelectedView != null) {
                            return Math.max(xDelta, -Math.max(0,
                                    (mSelectedView.getLeft() + mSelectedView.getWidth() / 2) - getXSel()));
                        } else {
                            return 0;
                        }
                    }
                    }
                } else {
                    return Math.max(xDelta, -view.getWidth() / 2);
                }
            } else {
                return 0;
            }
        }
        return xDelta;
    }

    /**
     * ?
     *
     * @param xDelta
     * @return ??
     */
    protected boolean horizontalScroll(int xDelta) {
        if (DEBUG)
            Log.i(TAG, "HorizontalScroll " + xDelta);
        final int childCount = getChildCount();

        View firstChild = getChildAt(0);
        View lastChild = getChildAt(childCount - 1);

        int offScreenChildStart = 0;
        int offScreenChildCount = 0;

        if (xDelta > 0) {//?
            for (int i = getChildCount() - 1; i >= 0; i--) {
                final View child = getChildAt(i);
                if (child.getLeft() + xDelta - mDividerHeight < (getRight() - getLeft())/*getRight()*/) {//
                    break;
                } else {
                    offScreenChildStart = i;//,???
                    offScreenChildCount++;
                    int position = mFirstPosition + i;
                    mRecyclerBin.addRecyclerBin(position, child);
                }
            }
        } else if (xDelta < 0) {//?
            for (int i = 0; i < getChildCount(); i++) {
                final View child = getChildAt(i);
                if (child.getRight() + xDelta + mDividerHeight > 0/*getLeft()*/) {
                    break;
                } else {
                    offScreenChildCount++;
                    int position = mFirstPosition + i;
                    mRecyclerBin.addRecyclerBin(position, child);
                }
            }
        } else {
            //Ignored
        }

        if (offScreenChildCount > 0) {
            //?
            detachViewsFromParent(offScreenChildStart, offScreenChildCount);
        }

        offsetChildrenLeftAndRight(xDelta);

        if (!awakenScrollBars()) {
            invalidate();
        }

        if (xDelta < 0) {
            fillRightView(mFirstPosition + childCount, lastChild.getRight() + mDividerHeight, offScreenChildCount);
            correctTooRight(getChildCount());
        } else {
            fillLeftView(mFirstPosition - 1, firstChild.getLeft() - mDividerHeight);
            correctTooLeft(getChildCount());
        }

        return xDelta != 0;
    }

    /**
     * ?
     *
     * @param yDelta
     */
    protected boolean verticalScroll(int yDelta) {
        if (DEBUG)
            Log.i(TAG, "VerticalScroll " + yDelta);

        final int childCount = getChildCount();

        View firstChild = getChildAt(0);
        View lastChild = getChildAt(childCount - 1);

        int offScreenChildStart = 0;
        int offScreenChildCount = 0;

        if (yDelta > 0) {//
            for (int i = getChildCount() - 1; i >= 0; i--) {
                final View child = getChildAt(i);
                if (child.getTop() + yDelta - mDividerHeight < (getBottom() - getTop())) {//
                    break;
                } else {
                    Log.i(TAG, "View OffScreen => " + i);
                    offScreenChildStart = i;
                    offScreenChildCount++;
                    int position = mFirstPosition + i;
                    mRecyclerBin.addRecyclerBin(position, child);
                }
            }
        } else if (yDelta < 0) {//?
            for (int i = 0; i < getChildCount(); i++) {
                final View child = getChildAt(i);
                if (child.getBottom() + yDelta + mDividerHeight > 0/*getTop()*/) {
                    break;
                } else {
                    Log.i(TAG, "View OffScreen => " + i);
                    offScreenChildCount++;
                    int position = mFirstPosition + i;
                    mRecyclerBin.addRecyclerBin(position, child);
                }
            }
        } else {
            //Ignored
        }

        if (offScreenChildCount > 0) {
            //?
            detachViewsFromParent(offScreenChildStart, offScreenChildCount);
        }

        //?,
        //        if (yDelta < 0) {//
        //            //?,,??,
        //            if (mFirstPosition + getChildCount() == mItemCount) {
        //                int scrollMaxDistance = getChildAt(getChildCount() - 1).getBottom() - getBottom();
        //                yDelta = Math.max(-scrollMaxDistance, yDelta);
        //            }
        //        } else if (yDelta > 0) {//
        //            if (mFirstPosition == 0) {
        //                //,,??
        //                int scrollMaxDistance = getTop() - getChildAt(0).getTop();
        //                yDelta = Math.min(scrollMaxDistance, yDelta);
        //            }
        //        }

        if (!awakenScrollBars()) {
            invalidate();
        }

        offsetChildrenTopAndBottom(yDelta);

        if (yDelta < 0) {
            fillDownView(mFirstPosition + childCount, lastChild.getBottom() + mDividerHeight, offScreenChildCount);
            correctTooHigh(getChildCount());
        } else {
            fillUpView(mFirstPosition - 1, firstChild.getTop() - mDividerHeight);
            correctTooLow(getChildCount());
        }
        return yDelta != 0;
    }

    protected void invalidateTopGlow() {
        if (mTopEdgeEffect == null) {
            return;
        }
        final int top = 0;
        final int left = 0;
        final int right = getWidth();
        invalidate(left, top, right, top + mTopEdgeEffect.getMaxHeight());
    }

    protected void invalidateBottomGlow() {
        if (mBottomEdgeEffect == null) {
            return;
        }
        final int bottom = getHeight();
        final int left = 0;
        final int right = getWidth();
        invalidate(left, bottom - mBottomEdgeEffect.getMaxHeight(), right, bottom);
    }

    protected void invalidateRightGlow() {
        if (mRightEdgeEffect == null) {
            return;
        }

        final int bottom = getHeight();
        final int top = 0;
        final int right = getWidth();
        invalidate(/*right - mRightEdgeEffect.getMaxHeight(), top, right, bottom*/);
    }

    protected void invalidateLeftGlow() {
        if (mLeftEdgeEffect == null) {
            return;
        }

        final int top = 0;
        final int left = 0;
        final int bottom = getHeight();

        invalidate(left, top, left + mLeftEdgeEffect.getMaxHeight(), bottom);
    }

    /**
     * ??overscroll
     *
     * @param overScroll
     */
    public void setOverScroll(boolean overScroll) {
        this.mOverScroll = overScroll;
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);

        //?overscrolloverscroll
        if (mOverScroll)
            drawOverScrollEffect(canvas);
    }

    /**
     * overscroll
     *
     * @param canvas
     */
    protected void drawOverScrollEffect(Canvas canvas) {
        final int scrollY = getScrollY();
        final int scrollX = getScrollX();

        final int width;
        final int height;
        final int translateX;
        final int translateY;

        width = getWidth();
        height = getHeight();

        translateX = 0;
        translateY = 0;

        if (mTopEdgeEffect != null) {//
            if (!mTopEdgeEffect.isFinished()) {
                final int restoreCount = canvas.save();
                canvas.clipRect(translateX, translateY, translateX + width,
                        translateY + mTopEdgeEffect.getMaxHeight());
                final int edgeY = Math.min(0, scrollY) + translateY;
                canvas.translate(translateX, edgeY);
                mTopEdgeEffect.setSize(width, height);
                if (mTopEdgeEffect.draw(canvas)) {
                    invalidateTopGlow();
                }
                canvas.restoreToCount(restoreCount);
            }
        }

        if (mBottomEdgeEffect != null) {//
            if (!mBottomEdgeEffect.isFinished()) {
                final int restoreCount = canvas.save();
                canvas.clipRect(translateX, translateY + height - mBottomEdgeEffect.getMaxHeight(),
                        translateX + width, translateY + height);
                final int edgeX = -width + translateX;
                final int edgeY = Math.max(getHeight(), scrollY);
                canvas.translate(edgeX, edgeY);
                canvas.rotate(180, width, 0);
                mBottomEdgeEffect.setSize(width, height);
                if (mBottomEdgeEffect.draw(canvas)) {
                    invalidateBottomGlow();
                }
                canvas.restoreToCount(restoreCount);
            }
        }

        if (mLeftEdgeEffect != null) {//
            if (!mLeftEdgeEffect.isFinished()) {
                canvas.clipRect(translateX, translateY, translateX + mLeftEdgeEffect.getMaxHeight(),
                        translateY + height);
                final int restoreCount = canvas.save();
                canvas.translate(-width, getHeight() - getWidth());//?
                canvas.rotate(270, width, 0);//
                mLeftEdgeEffect.setSize(height, width);//?
                if (mLeftEdgeEffect.draw(canvas)) {
                    invalidateLeftGlow();
                }
                canvas.restoreToCount(restoreCount);
            }
        }

        if (mRightEdgeEffect != null) {//?
            if (!mRightEdgeEffect.isFinished()) {
                //     canvas.clipRect(translateX, width - mRightEdgeEffect.getMaxHeight(), width, translateY + height);
                final int restoreCount = canvas.save();
                canvas.translate(0, getWidth());//?
                canvas.rotate(-270, width, 0);//
                mRightEdgeEffect.setSize(height, width);//?
                if (mRightEdgeEffect.draw(canvas)) {
                    invalidateRightGlow();
                }
                canvas.restoreToCount(restoreCount);
            }
        }
    }

    /**
     * 
     * ?
     * ?
     *
     * @param childCount ??
     */
    protected void correctTooHigh(int childCount) {
        switch (mViewMode) {
        case LIST_VIEW: {//ListView ?
            correctTooHighForListView(childCount);
        }
            break;
        case WHEEL_VIEW: {//?
            correctTooHighForWheelView(childCount);
        }
            break;
        }

    }

    /**
     * WheelView?
     *
     * @param childCount
     */
    protected void correctTooHighForWheelView(int childCount) {
        //????
        //???,??
        int lastPosition = mFirstPosition + childCount - 1;

        //??,?
        if (lastPosition == mItemCount - 1 && childCount > 0) {

            //???
            final View lastChild = getChildAt(childCount - 1);

            //???
            final int lastBottom = lastChild.getBottom();

            //?
            final int end = getBottom() - getYSel() + ((lastChild.getBottom() - lastChild.getTop()) / 2) - getTop();

            //??
            int bottomOffset = end - lastBottom;

            //??
            View firstChild = getChildAt(0);
            //??
            final int firstTop = firstChild.getTop();

            //1) ? ""
            //2) ??
            //?
            if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < getTop())) {

                if (mFirstPosition == 0) {
                    //??
                    bottomOffset = Math.min(bottomOffset, getTop() - firstTop);
                }

                // ??
                offsetChildrenTopAndBottom(bottomOffset);
                //                startScrollIfNeed(0, bottomOffset);
                if (mFirstPosition > 0) {
                    //?
                    fillUpView(mFirstPosition - 1, firstChild.getTop() - mDividerHeight);
                    //
                    adjustViewsUpOrDown();
                }

            }
        }
    }

    /**
     * ListView?
     *
     * @param childCount
     */
    protected void correctTooHighForListView(int childCount) {
        //????
        //???,??
        int lastPosition = mFirstPosition + childCount - 1;

        //??,?
        if (lastPosition == mItemCount - 1 && childCount > 0) {

            //???
            final View lastChild = getChildAt(childCount - 1);

            //???
            final int lastBottom = lastChild.getBottom();

            //?
            final int end = getBottom() - getTop();

            //??
            int bottomOffset = end - lastBottom;

            //??
            View firstChild = getChildAt(0);
            //??
            final int firstTop = firstChild.getTop();

            //1) ? ""
            //2) ??
            //?
            if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < getTop())) {

                if (mFirstPosition == 0) {
                    //??
                    bottomOffset = Math.min(bottomOffset, getTop() - firstTop);
                }

                // ??
                offsetChildrenTopAndBottom(bottomOffset);
                //                startScrollIfNeed(0, bottomOffset);
                if (mFirstPosition > 0) {
                    //?
                    fillUpView(mFirstPosition - 1, firstChild.getTop() - mDividerHeight);
                    adjustViewsUpOrDown();
                }

            }
        }
    }

    /**
     * 
     * ? ??
     *
     * @param childCount ??
     */
    private void correctTooLow(int childCount) {
        switch (mViewMode) {
        case LIST_VIEW: {//ListView ?
            correctTooLowForListView(childCount);
        }
            break;
        case WHEEL_VIEW: {//?
            correctTooLowForWheelView(childCount);
        }
            break;
        }

    }

    /**
     * WheelView?
     *
     * @param childCount
     */
    protected void correctTooLowForWheelView(int childCount) {
        //,????
        //???
        //??
        //???
        if (mFirstPosition == 0 && childCount > 0) {

            // ?
            final View firstChild = getChildAt(0);

            //?
            final int firstTop = firstChild.getTop();

            //?
            final int start = getTop();

            //?
            final int end = (getBottom() - getTop());

            //?
            int topOffset = firstTop - getYSel() + ((firstChild.getBottom() - firstChild.getTop()) / 2) - start;

            View lastChild = getChildAt(childCount - 1);

            final int lastBottom = lastChild.getBottom();

            int lastPosition = mFirstPosition + childCount - 1;

            if (topOffset > 0) {
                if (lastPosition < mItemCount - 1 || lastBottom > end) {
                    if (lastPosition == mItemCount - 1) {
                        // Don't pull the bottom too far up
                        topOffset = Math.min(topOffset, lastBottom - end);
                    }

                    //??
                    offsetChildrenTopAndBottom(-topOffset);
                    //                    startScrollIfNeed(0, -topOffset);

                    if (lastPosition < mItemCount - 1) {
                        // Fill the gap that was opened below the last position with more rows, if
                        // possible
                        fillDownView(lastPosition + 1, lastChild.getBottom() + mDividerHeight, 0);
                        // Close up the remaining gap
                        adjustViewsUpOrDown();
                    }
                } else if (lastPosition == mItemCount - 1) {
                    adjustViewsUpOrDown();
                }
            }
        }
    }

    /**
     * ListView?
     *
     * @param childCount
     */
    protected void correctTooLowForListView(int childCount) {
        //,????
        //???
        //??
        //???
        if (mFirstPosition == 0 && childCount > 0) {

            // ?
            final View firstChild = getChildAt(0);

            //?
            final int firstTop = firstChild.getTop();

            //?
            final int start = /*getTop();*/0;

            //?
            final int end = (getBottom() - getTop());

            //?
            int topOffset = firstTop - start;

            View lastChild = getChildAt(childCount - 1);

            final int lastBottom = lastChild.getBottom();

            int lastPosition = mFirstPosition + childCount - 1;

            // Make sure we are 1) Too low, and 2) Either there are more rows below the
            // last row or the last row is scrolled off the bottom of the drawable area
            if (topOffset > 0) {
                if (lastPosition < mItemCount - 1 || lastBottom > end) {
                    if (lastPosition == mItemCount - 1) {
                        // Don't pull the bottom too far up
                        topOffset = Math.min(topOffset, lastBottom - end);
                    }

                    //??
                    offsetChildrenTopAndBottom(-topOffset);
                    //                    startScrollIfNeed(0, -topOffset);
                    if (lastPosition < mItemCount - 1) {
                        // Fill the gap that was opened below the last position with more rows, if
                        // possible
                        fillDownView(lastPosition + 1, lastChild.getBottom() + mDividerHeight, 0);
                        // Close up the remaining gap
                        adjustViewsUpOrDown();
                    }
                } else if (lastPosition == mItemCount - 1) {
                    adjustViewsUpOrDown();
                }
            }
        }
    }

    /**
     * ???
     * ??
     * 
     */
    protected void adjustViewsUpOrDown() {
        //// TODO: 16/8/9
        Log.i(TAG, "AdjustViewsUpOrDown");
    }

    /**
     * ????
     * ??
     */
    protected void adjustViewLeftOrRight() {
        //// TODO: 16/8/9
    }

    /**
     * 
     *
     * @param childCount ???
     */
    private void correctTooLeft(int childCount) {
        switch (mViewMode) {
        case LIST_VIEW: {//list view ?
            correctTooLeftForListView(childCount);
        }
            break;
        case WHEEL_VIEW: {
            correctTooLeftForWheelView(childCount);
        }
            break;
        }
    }

    /**
     * WheelView?
     *
     * @param childCount
     */
    protected void correctTooLeftForWheelView(int childCount) {
        //,????
        //???
        //??
        //???
        if (mFirstPosition == 0 && childCount > 0) {

            // ?
            final View firstChild = getChildAt(0);

            //?
            final int firstLeft = firstChild.getLeft();

            //?
            final int start = getLeft() + getXSel();

            //?
            final int end = (getRight() - getLeft());

            //?
            int startOffset = firstLeft - start;

            View lastChild = getChildAt(childCount - 1);

            final int lastRight = lastChild.getRight();

            int lastPosition = mFirstPosition + childCount - 1;

            // Make sure we are 1) Too low, and 2) Either there are more rows below the
            // last row or the last row is scrolled off the bottom of the drawable area
            if (startOffset > 0) {
                if (lastPosition < mItemCount - 1 || lastRight > end) {
                    if (lastPosition == mItemCount - 1) {
                        // Don't pull the bottom too far up
                        startOffset = Math.min(startOffset, lastRight - end);
                    }

                    offsetChildrenLeftAndRight(-startOffset);
                    //??
                    //                    startScrollIfNeed(-startOffset, 0);

                    if (lastPosition < mItemCount - 1) {
                        // Fill the gap that was opened below the last position with more rows, if
                        // possible
                        fillRightView(lastPosition + 1, lastChild.getRight() + mDividerHeight, 0);
                        // Close up the remaining gap
                        adjustViewsUpOrDown();
                    }
                } else if (lastPosition == mItemCount - 1) {
                    adjustViewsUpOrDown();
                }
            }
        }

    }

    /**
     * ListView?
     *
     * @param childCount
     */
    protected void correctTooLeftForListView(int childCount) {
        //,????
        //???
        //??
        //???
        if (mFirstPosition == 0 && childCount > 0) {

            // ?
            final View firstChild = getChildAt(0);

            //?
            final int firstLeft = firstChild.getLeft();

            //?
            final int start = getLeft();

            //?
            final int end = (getRight() - getLeft());

            //?
            int startOffset = firstLeft - start;

            View lastChild = getChildAt(childCount - 1);

            final int lastRight = lastChild.getRight();

            int lastPosition = mFirstPosition + childCount - 1;

            // Make sure we are 1) Too low, and 2) Either there are more rows below the
            // last row or the last row is scrolled off the bottom of the drawable area
            if (startOffset > 0) {
                if (lastPosition < mItemCount - 1 || lastRight > end) {
                    if (lastPosition == mItemCount - 1) {
                        // Don't pull the bottom too far up
                        startOffset = Math.min(startOffset, lastRight - end);
                    }

                    //???
                    offsetChildrenLeftAndRight(-startOffset);
                    //                    startScrollIfNeed(-startOffset, 0);

                    if (lastPosition < mItemCount - 1) {
                        // Fill the gap that was opened below the last position with more rows, if
                        // possible
                        fillRightView(lastPosition + 1, lastChild.getRight() + mDividerHeight, 0);
                        // Close up the remaining gap
                        adjustViewsUpOrDown();
                    }
                } else if (lastPosition == mItemCount - 1) {
                    adjustViewsUpOrDown();
                }
            }
        }
    }

    /**
     * ?
     *
     * @param childCount ???
     */
    private void correctTooRight(int childCount) {
        switch (mViewMode) {
        case LIST_VIEW: {//list view ?
            correctTooRightForListView(childCount);
        }
            break;
        case WHEEL_VIEW: {//?
            correctTooRightForWheelView(childCount);
        }
            break;
        }
    }

    /**
     *  WheelView??
     *
     * @param childCount
     */
    protected void correctTooRightForWheelView(int childCount) {
        //????
        //???,??
        int lastPosition = mFirstPosition + childCount - 1;

        //??,?
        if (lastPosition == mItemCount - 1 && childCount > 0) {

            //???
            final View lastChild = getChildAt(childCount - 1);

            //????
            final int lastRight = lastChild.getRight();

            //?
            int xSel = getRight() - getXSel() + (lastChild.getRight() - lastChild.getLeft()) / 2;

            //? EAV  right  1080
            //? XSel   500
            //            ? LastChild width = 800
            // ??
            //?: LastChildRight ? XSel
            //Xsel?,?
            //Xsel = EAV.right - Xsel + lastchildwidth /2 = 980 = (1080 - 500) + 800 /2
            //Offset = Xsel - lastChild.right

            //???
            int rightOffset = xSel - lastRight;

            //??
            View firstChild = getChildAt(0);

            //??
            final int firstLeft = firstChild.getLeft();

            //1) ? "?"
            if (rightOffset > 0 && (mFirstPosition > 0 || firstLeft < getLeft())) {

                if (mFirstPosition == 0) {
                    //???
                    rightOffset = Math.min(rightOffset, getLeft() - firstLeft);
                }

                // ???
                offsetChildrenLeftAndRight(rightOffset);
                //                startScrollIfNeed(rightOffset, 0);

                if (mFirstPosition > 0) {
                    //?
                    fillRightView(lastPosition + 1, lastChild.getRight() + mDividerHeight, 0);
                    //
                    adjustViewsUpOrDown();
                }

            }
        }
    }

    /**
     * ListView??
     *
     * @param childCount
     */
    protected void correctTooRightForListView(int childCount) {
        //????
        //???,??
        int lastPosition = mFirstPosition + childCount - 1;

        //??,?
        if (lastPosition == mItemCount - 1 && childCount > 0) {

            //???
            final View lastChild = getChildAt(childCount - 1);

            //????
            final int lastRight = lastChild.getRight();

            //?
            final int end = getRight() - getLeft();

            //???
            int rightOffset = end - lastRight;

            //??
            View firstChild = getChildAt(0);
            //??
            final int firstLeft = firstChild.getLeft();

            //1) ? ""
            //2) ??
            //?
            if (rightOffset > 0 && (mFirstPosition > 0 || firstLeft < getLeft())) {

                if (mFirstPosition == 0) {
                    //??
                    rightOffset = Math.min(rightOffset, getLeft() - firstLeft);
                }

                // ???
                offsetChildrenLeftAndRight(rightOffset);
                //                startScrollIfNeed(rightOffset, 0);

                if (mFirstPosition > 0) {
                    //?
                    fillRightView(lastPosition + 1, lastChild.getRight() + mDividerHeight, 0);
                    if (firstChild.getLeft() > getLeft()) {//Need fill left views.
                        fillLeftView(mFirstPosition - 1, firstChild.getLeft() + mDividerHeight);
                    }
                    //mFirstPosition - 1, firstChild.getLeft() - mDividerHeight
                    //
                    adjustViewsUpOrDown();
                }

            }
        }
    }

    @Override
    protected void detachViewsFromParent(int start, int count) {
        if (DEBUG) {
            Log.i(TAG,
                    String.format(Locale.US, "Detach views from parent => " + "Start %d <==> %d ", start, count));
            for (int i = 0; i < count; i++) {
                View child = getChildAt(i);
                Log.i(TAG,
                        String.format(Locale.US,
                                "Detach views bounds top = [%d] left = [%d] bottom = [%d] right=[%d]",
                                child.getTop(), child.getLeft(), child.getBottom(), child.getRight()));
            }
        }
        super.detachViewsFromParent(start, count);
    }

    /**
     * 
     *
     * @param xDelta
     * @param yDelta
     */
    protected boolean startScrollIfNeed(float xDelta, float yDelta) {
        if (DEBUG)
            Log.d(TAG, String.format(Locale.US, "startScrollIfNeed xDelta %.2f yDelta %.2f ", xDelta, yDelta));

        if (getChildCount() == 0)
            return false;

        boolean moved = false;

        switch (mLayoutDirectly) {
        case LAYOUT_DIRECTLY_HORIZONTAL: {
            boolean canScroll = canScrollHorizontally((int) xDelta);
            if (DEBUG)
                Log.i(TAG, "Can scroll => " + canScroll);
            if (canScroll) {
                moved = horizontalScroll(horizontalAdjustWithMaxScrollRestrict((int) xDelta));
            } else {//At end
                if (mLastMotionY != Integer.MIN_VALUE) {
                    pullHorizontalDirectionGlowEffect(xDelta);
                }
            }
            break;
        }
        case LAYOUT_DIRECTLY_VERTICAL: {
            boolean canVerticalScroll = canScrollVertically((int) yDelta);
            if (DEBUG)
                Log.d(TAG, "Can vertical scroll => " + canVerticalScroll);
            if (canVerticalScroll)
                moved = verticalScroll(verticalAdjustWithMaxScrollRestrict((int) yDelta));
            else {//At end
                if (mLastMotionX != Integer.MIN_VALUE) {
                    pullVerticalDirectionGlowEffect(yDelta);
                }
            }
            break;
        }
        }

        //?,?
        if (moved) {
            invokeOnItemScrollListener();
            tryInvokeSelectItemChangeListener();
        }
        return moved;
    }

    /**
     * ??
     *
     * @param yDelta ?y?,overscroll?
     */
    public void pullVerticalDirectionGlowEffect(float yDelta) {
        if (DEBUG)
            Log.i(TAG, "Pull vertical direction glow effect " + yDelta);
        if (yDelta > 0) {//?

            //??,?,??overscroll?

            mTouchState = OVER_SCROLL;
            mActiveEffect = mTopEdgeEffect;

            if (mYOverScrollDistance < 0)
                mYOverScrollDistance = -1;

            // "" overscroll ?
            if (mYOverScrollDistance == -1)
                mYOverScrollDistance = (int) yDelta;
            else
                mYOverScrollDistance += yDelta;//????

            // ""
            float displacement = (mLastMotionX / getWidth());

            //?
            mTopEdgeEffect.onPull(mYOverScrollDistance * 1.0f / getHeight(), displacement);

            //?
            //,
            if (!mTopEdgeEffect.isFinished()) {
                mTopEdgeEffect.onRelease();
            }

            //
            invalidateTopGlow();

        } else if (yDelta < 0) {//?

            if (mYOverScrollDistance > 0)
                mYOverScrollDistance = -1;//??,?,??overscroll?

            float displacement = 1.0f - (mLastMotionX / getWidth());// ""

            // "" overscroll ?
            if (mYOverScrollDistance == -1)
                mYOverScrollDistance = (int) yDelta;
            else
                mYOverScrollDistance += yDelta;//????

            //?
            mBottomEdgeEffect.onPull(mYOverScrollDistance * 1.0f / getHeight(), displacement);

            //?
            //,
            if (!mBottomEdgeEffect.isFinished())
                mBottomEdgeEffect.onRelease();

            //
            invalidateBottomGlow();
        } else {

        }
    }

    /**
     * ??
     *
     * @param xDelta ?x?,overscroll?
     */
    public void pullHorizontalDirectionGlowEffect(float xDelta) {
        if (xDelta > 0) {//??

            //??,?,??overscroll?
            if (mXOverScrollDistance < 0)
                mXOverScrollDistance = -1;

            // "" overscroll ?
            if (mXOverScrollDistance == -1)
                mXOverScrollDistance = (int) xDelta;
            else
                mXOverScrollDistance += xDelta;//????

            // ""
            float displacement = 1.0f - (mLastMotionY / getHeight());

            //?
            mLeftEdgeEffect.onPull(mXOverScrollDistance * 1.0f / getWidth(), displacement);

            //?
            //,
            if (!mLeftEdgeEffect.isFinished())
                mLeftEdgeEffect.onRelease();

            //
            invalidateLeftGlow();
        } else if (xDelta < 0) {//?

            if (mYOverScrollDistance > 0)
                mYOverScrollDistance = -1;//??,?,??overscroll?

            float displacement = 1.0f - (mLastMotionY / getHeight());// ""

            // "" overscroll ?
            if (mXOverScrollDistance == -1)
                mXOverScrollDistance = (int) xDelta;
            else
                mXOverScrollDistance += xDelta;//????

            //?
            mRightEdgeEffect.onPull(mXOverScrollDistance * 1.0f / getWidth(), displacement);

            //?
            //,
            if (!mRightEdgeEffect.isFinished())
                mRightEdgeEffect.onRelease();

            //?
            invalidateRightGlow();
        }
    }

    protected void tryInvokeSelectItemChangeListener() {
        if (!mIsMutable) {
            int vp = getSelectedViewPosition();
            if (vp != INVALID_POSITION) {
                final View selectedView = getChildAt(vp);
                int sel = vp + mFirstPosition;
                if (selectedView != null) {
                    //? selectedView != mSelectedView ?
                    //  ?,?????
                    if (selectedView != mSelectedView || mSelectedPosition != sel) {
                        final int oldSel = mSelectedPosition;
                        final View oldSelView = mSelectedView;

                        mSelectedPosition = sel;
                        mSelectedView = selectedView;

                        invokeOnSelectedItemListener(oldSel % mItemCount, oldSelView,
                                mSelectedPosition % mItemCount, selectedView);
                    }

                }
            }
        }
    }

    /**
     * 
     */
    protected void fillView() {
        switch (mLayoutDirectly) {
        case LAYOUT_DIRECTLY_HORIZONTAL:
            fillRightView(mFirstPosition + getChildCount(), 0, 0);
            break;
        case LAYOUT_DIRECTLY_VERTICAL:
            fillDownView(mFirstPosition + getChildCount(), 0, 0);
            break;
        }
    }

    /***
     * 
     *
     * @param position  ?position
     * @param y         y??
     * @param x         ?x??
     * @param stuffMode ?
     * @return
     */
    protected View makeAndAddView(int position, int y, int x, StuffMode stuffMode) {

        if (DEBUG)
            Log.i(TAG, String.format("Make and add view position == %d x == %d y == %d ", position, x, y));

        View view = obtainView(position);

        LayoutParams lp = setupChildView(view);

        int childWidth = view.getMeasuredWidth();
        int childHeight = view.getMeasuredHeight();

        switch (stuffMode) {
        case flowDown: {//?
            addViewInLayout(view, -1, lp, true);
            view.layout(0, y, childWidth, y + childHeight);
        }
            break;
        case flowUp: {//?
            addViewInLayout(view, 0, lp, true);
            view.layout(0, y - childHeight, childWidth, y);
        }
            break;
        case flowRight: {//??
            addViewInLayout(view, -1, lp, true);
            view.layout(x, y, x + childWidth, y + childHeight);
        }
            break;
        case flowLeft: {//?
            addViewInLayout(view, 0, lp, true);
            view.layout(x - childWidth, y, x, y + childHeight);
        }
            break;

        }
        return view;
    }

    /**
     * ???
     * ???? ???
     *
     * @param view
     * @return
     */
    protected LayoutParams setupChildView(View view) {
        LayoutParams lp = view.getLayoutParams();
        if (lp == null)
            lp = generateDefaultLayoutParams();

        int widthMeasureSpec = 0;
        int heightMeasureSpec = 0;

        final int width = lp.width < 0 ? getWidth() : lp.width;
        final int height = lp.height < 0 ? getHeight() : lp.height;

        switch (mLayoutDirectly) {
        case LAYOUT_DIRECTLY_HORIZONTAL: {
            //?,?????
            //??
            //? Match_parent
            //........
            //widthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.UNSPECIFIED);
            //                heightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.UNSPECIFIED);
            int widthMode = (lp.width == LayoutParams.MATCH_PARENT && width > 0 || lp.width > 0)
                    ? MeasureSpec.EXACTLY
                    : MeasureSpec.UNSPECIFIED;
            int heightMode = (lp.height == LayoutParams.MATCH_PARENT && height > 0 || lp.width > 0)
                    ? MeasureSpec.EXACTLY
                    : MeasureSpec.UNSPECIFIED;

            widthMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(width, Integer.MAX_VALUE / 2), widthMode);
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(height, Integer.MAX_VALUE / 2), heightMode);
        }
            break;
        case LAYOUT_DIRECTLY_VERTICAL: {
            //widthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.UNSPECIFIED);
            //                heightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, lp.height > 0 ? MeasureSpec.EXACTLY : MeasureSpec.UNSPECIFIED);

            int widthMode = (lp.width == LayoutParams.MATCH_PARENT && width > 0 || lp.width > 0)
                    ? MeasureSpec.EXACTLY
                    : MeasureSpec.UNSPECIFIED;
            int heightMode = (lp.height == LayoutParams.MATCH_PARENT && height > 0 || lp.width > 0)
                    ? MeasureSpec.EXACTLY
                    : MeasureSpec.UNSPECIFIED;

            widthMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(width, Integer.MAX_VALUE / 2), widthMode);
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(height, Integer.MAX_VALUE / 2), heightMode);
        }
            break;
        }

        view.measure(widthMeasureSpec, heightMeasureSpec);
        return lp;
    }

    /**
     * ??
     *
     * @param position ???
     * @return
     */
    protected View obtainView(int position) {
        return mAdapter.getView(position, mRecyclerBin.findRecyclerBin(position), this);
    }

    /**
     * ????
     *
     * @param pos         ??
     * @param startOffset ????
     */
    protected void fillSpecView(int pos, int startOffset) {
        switch (mLayoutDirectly) {
        case LAYOUT_DIRECTLY_HORIZONTAL:
            fillRightSpecView(pos, startOffset);
            break;
        case LAYOUT_DIRECTLY_VERTICAL:
            fillDownSpecView(pos, startOffset);
            break;
        }
    }

    /**
     * ??????
     *
     * @param pos
     * @param xOffset
     */
    protected void fillRightSpecView(int pos, int xOffset) {
        View temp = makeAndAddView(pos, 0, xOffset, StuffMode.flowRight);
        mFirstPosition = pos;
        fillRightView(pos + 1, temp.getRight() + mDividerHeight, 0);
    }

    /**
     * ?????
     *
     * @param pos
     * @param yOffset
     */
    protected void fillDownSpecView(int pos, int yOffset) {
        View temp = makeAndAddView(pos, yOffset, 0, StuffMode.flowDown);
        mFirstPosition = pos;
        fillDownView(pos + 1, temp.getBottom() + mDividerHeight, 0);
    }

    /**
     * ?
     *
     * @param pos     ?
     * @param nextTop y
     */
    protected void fillDownView(int pos, int nextTop, int offScreenChildCount) {
        switch (mViewMode) {
        case LIST_VIEW:
        case WHEEL_VIEW: {
            while (nextTop < (getBottom() - getTop() + mDividerHeight/**Fix like list view divider bugs.*/
            ) && pos < mAdapter.getCount()) {
                View topView = makeAndAddView(pos, nextTop, 0, StuffMode.flowDown);
                nextTop = topView.getBottom() + mDividerHeight;
                pos++;
            }
            mFirstPosition += offScreenChildCount;
        }

            break;
        case END_LESS_LIST_VIEW:
        case ENDLESS_WHEEL_VIEW: {
            //?,???
            if (pos >= mItemCount) {
                pos = pos % mItemCount;
            }

            while (nextTop < (getBottom() - getTop() + mDividerHeight /**Fix like list view divider bugs.*/
            ) && mItemCount > 0 /*&& pos < mItemCount*/) {
                if (DEBUG)
                    Log.d(TAG, "Fill down => " + pos);
                View topView = makeAndAddView(pos, nextTop, 0, StuffMode.flowDown);
                nextTop = topView.getBottom() + mDividerHeight;
                pos++;

                if (pos >= mItemCount) {
                    if (DEBUG)
                        Log.d(TAG, String.format(Locale.US, "Fill down pos[%d] >= ItemCount[%d] re-pos = [%d]", pos,
                                mItemCount, pos - mItemCount));
                    pos = pos - mItemCount;
                }
            }
            mFirstPosition += offScreenChildCount;
            mFirstPosition %= mItemCount;
        }
            break;

        }
    }

    /**
     * ?
     *
     * @param pos            ?
     * @param previousBottom y
     */
    protected void fillUpView(int pos, int previousBottom) {
        if (DEBUG)
            Log.d(TAG, String.format("Fill up view position == %d Previous bottom ==  %d", pos, previousBottom));

        switch (mViewMode) {
        case LIST_VIEW:
        case WHEEL_VIEW: {//?
            while (previousBottom + mDividerHeight/**Fix like list view divider bugs.*/
            > 0 && pos >= 0 && pos < mItemCount) {
                View leftView = makeAndAddView(pos, previousBottom, 0, StuffMode.flowUp);
                previousBottom = leftView.getTop() - mDividerHeight;
                pos--;
            }
            mFirstPosition = pos + 1;
        }
            break;
        case END_LESS_LIST_VIEW:
        case ENDLESS_WHEEL_VIEW: {//?
            if (pos < 0)
                pos = mItemCount - 1;
            while (previousBottom + mDividerHeight/**Fix like list view divider bugs.*/
            > 0 && pos < mItemCount && mItemCount > 0) {
                View leftView = makeAndAddView(pos, previousBottom, 0, StuffMode.flowUp);
                previousBottom = leftView.getTop() - mDividerHeight;
                pos--;
                if (pos < 0)
                    pos = mItemCount - 1;
            }
            mFirstPosition = pos + 1;
            mFirstPosition %= mItemCount;
        }

        }

    }

    /**
     * ??
     *
     * @param offsetScreenNum
     * @param pos             
     * @param nextLeft        ?
     */
    protected void fillRightView(int pos, int nextLeft, int offsetScreenNum) {
        switch (mViewMode) {
        case LIST_VIEW:
        case WHEEL_VIEW: {//?
            while (nextLeft < (getRight() - getLeft() + mDividerHeight) && pos < mItemCount) {
                View topView = makeAndAddView(pos, 0, nextLeft, StuffMode.flowRight);
                nextLeft = topView.getRight() + mDividerHeight;
                pos++;
            }
            mFirstPosition += offsetScreenNum;
        }
            break;
        case END_LESS_LIST_VIEW:
        case ENDLESS_WHEEL_VIEW: {//?

            //?,???
            if (pos >= mItemCount) {
                pos = pos % mItemCount;
            }

            while (nextLeft < (getRight() - getLeft() + mDividerHeight) && mItemCount > 0 /*&& pos < mItemCount*/) {
                if (DEBUG)
                    Log.d(TAG, "Fill right => " + pos);
                View topView = makeAndAddView(pos, 0, nextLeft, StuffMode.flowRight);
                nextLeft = topView.getRight() + mDividerHeight;
                pos++;

                if (pos >= mItemCount) {
                    if (DEBUG)
                        Log.d(TAG, String.format(Locale.US, "Fill right pos[%d] >= ItemCount[%d] re-pos = [%d]",
                                pos, mItemCount, pos - mItemCount));
                    pos = pos - mItemCount;
                }
            }
            mFirstPosition += offsetScreenNum;
            mFirstPosition %= mItemCount;
        }
            break;
        }
    }

    /**
     * ?
     *
     * @param pos           
     * @param previousRight ?
     */
    protected void fillLeftView(int pos, int previousRight) {
        if (DEBUG)
            Log.d(TAG, String.format("Fill left view position == %d Previous right ==  %d", pos, previousRight));

        switch (mViewMode) {
        case LIST_VIEW://?
        case WHEEL_VIEW: {

            while (previousRight + mDividerHeight/**Fix like list view divider bugs.*/
            > 0 && pos >= 0 && pos < mItemCount) {
                View leftView = makeAndAddView(pos, 0, previousRight, StuffMode.flowLeft);
                previousRight = leftView.getLeft() - mDividerHeight;
                pos--;
            }
            mFirstPosition = pos + 1;
        }
            break;
        case END_LESS_LIST_VIEW:
        case ENDLESS_WHEEL_VIEW: {//?
            if (DEBUG)
                Log.d(TAG, "Previous right => " + previousRight + " , Pos => " + pos + " mFirstPosition => "
                        + mFirstPosition);
            if (pos < 0)
                pos = mItemCount - 1;
            while (previousRight + mDividerHeight/**Fix like list view divider bugs.*/
            > 0 && pos < mItemCount && mItemCount > 0) {
                View leftView = makeAndAddView(pos, 0, previousRight, StuffMode.flowLeft);
                previousRight = leftView.getLeft() - mDividerHeight;
                pos--;
                if (pos < 0)
                    pos = mItemCount - 1;
            }
            mFirstPosition = pos + 1;
            mFirstPosition %= mItemCount;
        }
            break;
        }
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        switch (mLayoutDirectly) {
        case LAYOUT_DIRECTLY_HORIZONTAL:
            return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        case LAYOUT_DIRECTLY_VERTICAL:
            return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        }
        return null;
    }

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

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

    @Override
    public ListAdapter getAdapter() {
        return mAdapter;
    }

    @Override
    public void setAdapter(ListAdapter adapter) {
        if (mAdapter != null)
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        this.mAdapter = adapter;
        if (mAdapter != null)
            adapter.registerDataSetObserver(mDataSetObserver);
        requestLayout();
    }

    /**
     * 
     */
    protected void invalidLayout() {
        if (getHeight() == 0 && getWidth() == 0)
            return;
        mIsMutable = true;

        try {
            if (mAdapter == null) {
                reset();
                invokeOnItemScrollListener();
            }

            mItemCount = mAdapter == null ? 0 : mAdapter.getCount();

            if (mDataSetInvalidated || mDataChanged) {
                removeAllViewsInLayout();
            }

            if (mDataSetInvalidated) {
                mRecyclerBin.clearRecyclerBins();
            }

            final int N = getChildCount();

            if (N > 0) {
                switch (mLayoutDirectly) {
                case LAYOUT_DIRECTLY_HORIZONTAL: {//?
                    mStartOffset = getChildAt(0).getLeft();
                }
                    break;
                case LAYOUT_DIRECTLY_VERTICAL: {//?
                    mStartOffset = getChildAt(0).getTop();
                }
                    break;
                }
            }

            for (int i = 0; i < N; i++)
                mRecyclerBin.addRecyclerBin(mFirstPosition + i, getChildAt(i));

            detachAllViewsFromParent();
            if (mItemCount > 0) {
                mStartOffset += mForceOffsetFromStart;

                if (mStartOffset == 0) {//No offset
                    fillView();
                } else {
                    fillSpecView(mFirstPosition, mStartOffset);
                }

                if (mTouchState == REST)
                    adjustSelView(0);
            }
        } finally {
            mIsMutable = false;
            invokeOnItemScrollListener();
            tryInvokeSelectItemChangeListener();
        }
    }

    /**
     * ?
     */
    protected void reset() {
        mItemCount = 0;
        mFirstPosition = 0;
        mScrollTrigger = SCROLL_TRIGGER_SYSTEM;
        mSelectedPosition = INVALID_POSITION;
        if (mFlingRunnable != null)
            mFlingRunnable.endScrollerAnimation(true);
    }

    public int getSelectedViewPosition() {
        switch (mViewMode) {
        case END_LESS_LIST_VIEW:
        case LIST_VIEW: {//ListView ?
            return 0;
        }
        case ENDLESS_WHEEL_VIEW:
        case WHEEL_VIEW: {//?
            switch (mLayoutDirectly) {
            case LAYOUT_DIRECTLY_HORIZONTAL: {//?
                final int N = getChildCount();
                final int xSel = getXSel();
                for (int i = 0; i < N; i++) {
                    View child = getChildAt(i);
                    //// TODO: 16/8/6 May have bugs.
                    if (child.getLeft() <= xSel && child.getRight() >= xSel) {
                        if (DEBUG)
                            Log.i(TAG, "Current Selected position, on divider => " + i);
                        return i;
                    } else if (child.getTop() <= xSel && child.getRight() + mDividerHeight >= xSel) {
                        if (DEBUG)
                            Log.i(TAG, "Current Selected position, on divider => " + i);
                        return i;
                    }
                    // TODO: 16/8/18
                }
            }
                break;
            case LAYOUT_DIRECTLY_VERTICAL: {//?
                final int N = getChildCount();
                final int ySel = getYSel();
                for (int i = 0; i < N; i++) {
                    View child = getChildAt(i);
                    //// TODO: 16/8/6 May have bugs.
                    if (child.getTop() <= ySel && child.getBottom() >= ySel) {
                        if (DEBUG)
                            Log.i(TAG, "Current Selected position => " + i);
                        return i;
                    } else if (child.getTop() <= ySel && child.getBottom() + mDividerHeight >= ySel) {
                        if (DEBUG)
                            Log.i(TAG, "Current Selected position, on divider => " + i);
                        return i;
                    }
                }
            }
                break;
            }
        }
            break;
        }
        return INVALID_POSITION;
    }

    @Override
    public int getSelectedItemPosition() {
        int viewPosition = getSelectedViewPosition();
        if (viewPosition != INVALID_POSITION) {
            return (viewPosition + mFirstPosition) % mItemCount;
        }
        return INVALID_POSITION;
    }

    @Override
    public View getSelectedView() {
        int viewPosition = getSelectedViewPosition();
        if (viewPosition != INVALID_POSITION) {
            return getChildAt(viewPosition);
        }
        return null;
    }

    @Override
    public void setSelection(int position) {
        setSelectionInner(position, 0);
    }

    private void setSelectionInner(int position, int startOffset) {

        if (mAdapter == null || mItemCount == 0)
            return;

        position = Math.min(Math.max(0, position), mItemCount - 1);
        if (DEBUG)
            Log.i(TAG, "SetSelection " + position + " Start Offset " + startOffset);

        if (getSelectedItemPosition() == position)
            return;

        switch (mLayoutDirectly) {
        case LAYOUT_DIRECTLY_HORIZONTAL: {
            switch (mViewMode) {
            case LIST_VIEW:
            case END_LESS_LIST_VIEW: {
                reset();
                mFirstPosition = position;

                mForceOffsetFromStart = 0;
                //??
                if (getChildCount() > 0) {
                    View firstChild = getChildAt(0);
                    mForceOffsetFromStart = -firstChild.getLeft();
                }

                mForceOffsetFromStart -= startOffset;//??

                invalidLayout();

                mForceOffsetFromStart = 0;
                mStartOffset = 0;
                correctTooRight(getChildCount());
                correctTooLeft(getChildCount());

            }
                break;
            case WHEEL_VIEW: {
                reset();
                mFirstPosition = position;
                mForceOffsetFromStart = getXSel();

                if (getChildCount() > 0)
                    mForceOffsetFromStart -= getChildAt(0).getLeft();

                invalidLayout();
                mForceOffsetFromStart = 0;
                mStartOffset = 0;
                if (getChildCount() > 0) {
                    fillLeftView(mFirstPosition - 1, getChildAt(0).getLeft() - mDividerHeight);
                    correctTooLeft(getChildCount());
                    correctTooRight(getChildCount());

                }
                adjustSelView(0);
            }
                break;
            case ENDLESS_WHEEL_VIEW: {
                reset();
                mFirstPosition = position;
                mForceOffsetFromStart = getXSel();
                invalidLayout();
                mForceOffsetFromStart = 0;
                mStartOffset = 0;
                if (getChildCount() > 0) {
                    fillLeftView(mFirstPosition - 1, getChildAt(0).getLeft() - mDividerHeight);
                    correctTooLeft(getChildCount());
                    correctTooRight(getChildCount());
                }
                adjustSelView(0);
            }
                break;
            }
        }
            break;
        case LAYOUT_DIRECTLY_VERTICAL: {
            switch (mViewMode) {
            case LIST_VIEW:
            case END_LESS_LIST_VIEW: {
                reset();
                mFirstPosition = position;
                mForceOffsetFromStart = 0;

                //??
                if (getChildCount() > 0) {
                    View firstChild = getChildAt(0);
                    mForceOffsetFromStart = -firstChild.getTop();
                }

                mForceOffsetFromStart -= startOffset;//??

                invalidLayout();

                mForceOffsetFromStart = 0;
                mStartOffset = 0;
                correctTooHigh(getChildCount());
                correctTooLow(getChildCount());

            }
                break;
            case WHEEL_VIEW: {
                reset();
                mFirstPosition = position;

                mForceOffsetFromStart = getYSel();

                View view = mAdapter.getView(position, mRecyclerBin.findRecyclerBin(position), this);
                setupChildView(view);
                int itemHeight = view.getMeasuredHeight();
                mForceOffsetFromStart -= (itemHeight / 2.0f);

                invalidLayout();

                mForceOffsetFromStart = 0;
                mStartOffset = 0;
                if (getChildCount() > 0) {
                    int selViewIndex = getSelectedViewPosition();
                    if (selViewIndex != -1) {
                        View selView = getChildAt(selViewIndex);
                        int selPos = (selViewIndex + mFirstPosition) % mItemCount;
                        fillUpView(selPos - 1, selView.getTop() - mDividerHeight);
                    } else {
                        fillUpView(mFirstPosition - 1, getChildAt(0).getTop() - mDividerHeight);
                    }
                    correctTooLow(getChildCount());
                    correctTooHigh(getChildCount());
                }

                adjustSelView(0);
            }
                break;
            case ENDLESS_WHEEL_VIEW: {
                reset();
                mFirstPosition = position;
                mForceOffsetFromStart = getYSel();

                View view = mAdapter.getView(position, mRecyclerBin.findRecyclerBin(position), this);
                setupChildView(view);
                int itemHeight = view.getMeasuredHeight();
                mForceOffsetFromStart -= (itemHeight / 2.0f);
                invalidLayout();
                mForceOffsetFromStart = 0;
                mStartOffset = 0;
                if (getChildCount() > 0) {
                    fillUpView(mFirstPosition - 1, getChildAt(0).getTop() - mDividerHeight);
                    correctTooLow(getChildCount());
                    correctTooHigh(getChildCount());
                }
                adjustSelView(0);
            }
                break;
            }
        }
            break;
        }
        invalidate();
    }

    /**
     * ???
     * ?:??{@link ViewMode#LIST_VIEW  ViewMode#END_LESS_LIST_VIEW}?
     *
     * @param position    ?
     * @param startOffset ???
     */
    public void setSelectionFromStart(int position, int startOffset) {
        switch (mViewMode) {
        case LIST_VIEW:
        case END_LESS_LIST_VIEW: {
            //todo May have bugs.
            setSelectionInner(position, startOffset);
        }
            break;
        case WHEEL_VIEW:
        case ENDLESS_WHEEL_VIEW: {
            throw new UnsupportedOperationException();
        }
        }
    }

    /**
     * ??
     * ?:??
     *
     * @param position ?
     * @return null ?
     * @throws IndexOutOfBoundsException ?
     */
    public View getVisibleViewAtPosition(int position) {

        if (position < 0 || position >= mItemCount)
            throw new IndexOutOfBoundsException(String.format(Locale.US, "0 >= [%s] < %s ", position, mItemCount));

        final int N = getChildCount();

        final int minPosition = mFirstPosition;
        final int maxPosition = mFirstPosition + N - 1;

        if (position < minPosition || position > maxPosition)
            throw new IndexOutOfBoundsException(
                    String.format(Locale.US, "%s >= [%s] < %s ", minPosition, position, maxPosition));

        return getChildAt(position - minPosition);
    }

    /**
     * ?
     *
     * @param position ?
     * @param duration 
     */
    public void smoothToPosition(int position, int duration) {
        if (mItemCount == 0)
            return;
        //
        position = Math.max(0, Math.min(position, mItemCount - 1));
        mSmoothScrollRunnable.scrollToPosition(position, 0, duration);
    }

    /**
     * ?,?????
     *
     * @param position ?
     * @param offset   ??
     * @param duration 
     */
    public void smoothToPositionFromStart(int position, int offset, int duration) {
        if (mItemCount == 0)
            return;
        switch (mViewMode) {
        case LIST_VIEW:
        case END_LESS_LIST_VIEW: {
            mSmoothScrollRunnable.scrollToPosition(position, offset, duration);
        }
            break;
        case WHEEL_VIEW:
        case ENDLESS_WHEEL_VIEW: {
            throw new UnsupportedOperationException("#smoothToPositionFromStart can use on wheel mode.");
        }
        }
    }

    /**
     * ?,?????
     *
     * @param position ?
     * @param offset   ??
     */
    public void smoothToPositionFromStart(int position, int offset) {
        smoothToPositionFromStart(position, offset, DEFAULT_SMOOTH_DURATION);
    }

    /**
     * {@link #DEFAULT_SMOOTH_DURATION},?
     *
     * @param position ??
     */
    public void smoothToPosition(int position) {
        smoothToPosition(position, DEFAULT_SMOOTH_DURATION);
    }

    /**
     * 
     *
     * @param distance ?
     * @param duration 
     * @param linear   ?
     */
    public void smoothScroll(int distance, int duration, boolean linear) {
        mFlingRunnable.startScroll(distance, duration, linear);
    }

    /**
     * ??
     *
     * @return
     */
    protected int getXSel() {
        return getMeasuredWidth() / 2;
    }

    /**
     * ??
     *
     * @return
     */
    protected int getYSel() {
        return getMeasuredHeight() / 2;
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        drawDivider(canvas);
        if (DEBUG) {
            switch (mLayoutDirectly) {
            case LAYOUT_DIRECTLY_HORIZONTAL:
                canvas.drawLine(getXSel(), 0, getXSel(), getHeight(), mDebugPaint);
                break;
            case LAYOUT_DIRECTLY_VERTICAL:
                canvas.drawLine(0, getYSel(), getWidth(), getYSel(), mDebugPaint);
                break;
            }
            canvas.drawText(String.valueOf(mFirstPosition), 100, 100, mDebugPaint);
        }

    }

    protected void drawDivider(Canvas canvas) {
        if (mDivider == null || mDividerHeight == 0)
            return;

        final int N = getChildCount();
        final Rect bounds = mTempRect;

        switch (mLayoutDirectly) {
        case LAYOUT_DIRECTLY_HORIZONTAL: {
            if (N > 1) {
                bounds.top = 0;
                bounds.bottom = getBottom() - getTop();

                for (int i = 0; i < N; i++) {
                    final View child = getChildAt(i);
                    int right = child.getRight();
                    final boolean isLastItem = (i == (N - 1));
                    final int listRight = getRight();

                    if ((right < listRight) && !isLastItem) {
                        bounds.left = right;
                        bounds.right = right + mDividerHeight;
                        drawDivider(canvas, bounds);
                    }
                }
            }
        }
            break;
        case LAYOUT_DIRECTLY_VERTICAL: {
            if (N > 1) {
                bounds.left = 0;
                bounds.right = getRight() - getLeft();

                for (int i = 0; i < N; i++) {
                    final View child = getChildAt(i);
                    int bottom = child.getBottom();
                    final boolean isLastItem = (i == (N - 1));
                    final int listBottom = getBottom();

                    if ((bottom < listBottom) && !isLastItem) {
                        bounds.top = bottom;
                        bounds.bottom = bottom + mDividerHeight;
                        drawDivider(canvas, bounds);
                    }
                }
            }
        }
            break;
        }
    }

    void drawDivider(Canvas canvas, Rect bounds) {
        // This widget draws the same divider for all children
        final Drawable divider = mDivider;
        divider.setBounds(bounds);
        divider.draw(canvas);
    }

    /**
     * ??
     */
    protected DataSetObserver mDataSetObserver = new AdapterDataSetObserver();

    /**
     * 
     */
    protected RecyclerBin mRecyclerBin = new RecyclerBin();

    /**
     * 
     */
    protected FlingRunnable mFlingRunnable = new FlingRunnable();

    /**
     * Runnable
     */
    protected SmoothScrollRunnable mSmoothScrollRunnable = new SmoothScrollRunnable();

    /**
     * ??
     *
     * @param mode ?
     */
    public void setMode(ViewMode mode) {
        if (mViewMode != mode) {
            this.mViewMode = mode;
            mDataSetInvalidated = true;
            requestLayout();
        }
    }

    public Drawable getDivider() {
        return mDivider;
    }

    public void setDivider(Drawable mDivider) {
        this.mDivider = mDivider;
        invalidLayout();
    }

    public void setDividerHeight(int dividerHeight) {
        this.mDividerHeight = dividerHeight;
    }

    /**
     * Recycler
     */
    protected class RecyclerBin {

        private final SparseArray<List<View>> mCachedViews = new SparseArray<List<View>>();

        public void clearRecyclerBins() {
            mCachedViews.clear();
        }

        /**
         * ?Position?
         *
         * @param position
         * @return
         */
        public View findRecyclerBin(int position) {
            int type = mAdapter.getItemViewType(position);
            List<View> cacheViews = mCachedViews.get(type);
            View cacheView = null;
            if (cacheViews != null && !cacheViews.isEmpty())
                cacheView = cacheViews.remove(0);
            if (DEBUG)
                Log.i(TAG,
                        String.format(Locale.US, "Pull recycler bin %s of type [%d] from pool ", cacheView, type));
            return cacheView;
        }

        /**
         * 
         *
         * @param position
         * @param scrapView
         */
        public void addRecyclerBin(int position, View scrapView) {
            int type = mAdapter.getItemViewType(position);
            List<View> cacheViews = mCachedViews.get(type);
            if (cacheViews == null)
                mCachedViews.put(type, cacheViews = new ArrayList<>());
            if (DEBUG)
                Log.i(TAG, String.format(Locale.US, "Push recycler bin %s of type [%d] to pool ", scrapView, type));
            cacheViews.add(scrapView);
        }
    }

    /**
     * ??
     */
    protected class AdapterDataSetObserver extends DataSetObserver {

        @Override
        public void onChanged() {
            mDataChanged = true;
            invalidLayout();
        }

        @Override
        public void onInvalidated() {
            mDataSetInvalidated = true;
            reset();
            invalidLayout();
        }
    }

    /**
     * ?????
     *
     * @param newState ????
     */
    protected void reportScrollStateChanged(int newState) {
        if (mOnScrollListener != null) {
            mOnScrollListener.onScrollStateChanged(this, newState);
        }
    }

    /**
     * ?
     *
     * @param x x
     * @param y y
     * @return ?, ??position
     */
    protected int computeClickViewPosition(int x, int y) {
        int position = INVALID_POSITION;
        final int N = getChildCount();
        if (N > 0) {
            for (int i = 0; i < N; i++) {
                View child = getChildAt(i);
                child.getHitRect(mItemClickRect);
                if (mItemClickRect.contains(x, y)) {
                    position = i;
                    break;
                }
            }
        }
        return position;
    }

    /**
     * 
     *
     * @param view     
     * @param position ?
     */
    protected void invokeOnItemClickListener(View view, int position) {
        if (mItemClickListener != null) {
            mItemClickListener.onItemClick(this, view, position, getItemIdAtPosition(position));
        }
    }

    /**
     * 
     * ???
     */
    protected void invokeOnItemScrollListener() {
        if (mOnScrollListener != null) {
            mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
        }
    }

    /**
     * ??
     *
     * @param oldSel
     * @param oldSelView
     * @param newSel
     * @param selectedView
     */
    protected void invokeOnSelectedItemListener(int oldSel, View oldSelView, int newSel, View selectedView) {
        if (mOnSelectedItemChangedListener != null) {
            mOnSelectedItemChangedListener.onSelectedItemChanged(this, oldSel, oldSelView, newSel, selectedView);
        }
    }

    /**
     * ?
     *
     * @param onScrollListener
     */
    public void setOnScrollListener(OnScrollListener onScrollListener) {
        this.mOnScrollListener = onScrollListener;
    }

    /**
     * ???
     *
     * @param onSelectedItemChangedListener
     */
    public void setOnSelectedItemChangedListener(OnSelectedItemChangedListener onSelectedItemChangedListener) {
        this.mOnSelectedItemChangedListener = onSelectedItemChangedListener;
    }

    @Override
    protected int computeVerticalScrollExtent() {
        final int count = getChildCount();
        if (count > 0) {
            if (mSmoothScrollbarEnabled) {
                int extent = count * 100;

                View view = getChildAt(0);
                final int top = view.getTop();
                int height = view.getHeight();
                if (height > 0) {
                    extent += (top * 100) / height;
                }

                view = getChildAt(count - 1);
                final int bottom = view.getBottom();
                height = view.getHeight();
                if (height > 0) {
                    extent -= ((bottom - getHeight()) * 100) / height;
                }
                return extent;
            } else {
                return 1;
            }
        }
        return 0;
    }

    @Override
    protected int computeHorizontalScrollExtent() {
        final int count = getChildCount();
        if (count > 0) {
            if (mSmoothScrollbarEnabled) {
                int extent = count * 100;

                View view = getChildAt(0);
                final int left = view.getLeft();
                int width = view.getWidth();
                if (width > 0) {
                    extent += (left * 100) / width;
                }
                view = getChildAt(count - 1);
                final int right = view.getRight();
                width = view.getWidth();
                if (width > 0) {
                    extent -= ((right - getWidth()) * 100) / width;
                }
                return extent;
            } else {
                return 1;
            }
        }
        return 0;
    }

    @Override
    protected int computeVerticalScrollOffset() {
        final int firstPosition = mFirstPosition;
        final int childCount = getChildCount();

        if (firstPosition >= 0 && childCount > 0) {
            if (mSmoothScrollbarEnabled) {
                final View view = getChildAt(0);
                final int top = view.getTop();
                int height = view.getHeight();
                if (height > 0) {
                    return Math.max(firstPosition * 100 - (top * 100) / height
                            + (int) ((float) getScrollY() / getHeight() * mItemCount * 100), 0);
                }
            } else {
                int index;
                final int count = mItemCount;
                if (firstPosition == 0) {
                    index = 0;
                } else if (firstPosition + childCount == count) {
                    index = count;
                } else {
                    index = firstPosition + childCount / 2;
                }
                return (int) (firstPosition + childCount * (index / (float) count));
            }
        }

        return 0;
    }

    @Override
    protected int computeHorizontalScrollOffset() {
        final int firstPosition = mFirstPosition;
        final int childCount = getChildCount();

        if (firstPosition >= 0 && childCount > 0) {
            if (mSmoothScrollbarEnabled) {
                final View view = getChildAt(0);
                final int left = view.getLeft();
                int width = view.getWidth();
                if (width > 0) {
                    return Math.max(firstPosition * 100 - (left * 100) / width
                            + (int) ((float) getScrollX() / getWidth() * mItemCount * 100), 0);
                }
            } else {
                int index;
                final int count = mItemCount;
                if (firstPosition == 0) {
                    index = 0;
                } else if (firstPosition + childCount == count) {
                    index = count;
                } else {
                    index = firstPosition + childCount / 2;
                }
                return (int) (firstPosition + childCount * (index / (float) count));
            }
        }

        return 0;
    }

    @Override
    protected int computeVerticalScrollRange() {
        int result;
        if (mSmoothScrollbarEnabled) {
            result = Math.max(mItemCount * 100, 0);
            if (getScrollY() != 0) {
                result += Math.abs((int) ((float) getScrollY() / getHeight() * mItemCount * 100));
            }
        } else {
            result = mItemCount;
        }
        return result;
    }

    @Override
    protected int computeHorizontalScrollRange() {
        int result;
        if (mSmoothScrollbarEnabled) {
            result = Math.max(mItemCount * 100, 0);
            if (getScrollX() != 0) {
                result += Math.abs((int) ((float) getScrollX() / getWidth() * mItemCount * 100));
            }
        } else {
            result = mItemCount;
        }
        return result;
    }

    /**
     * ??
     *
     * @param maxAmountScrollPage ????
     */
    public void setScrollPageLimit(int maxAmountScrollPage) {
        if (maxAmountScrollPage != -1 && maxAmountScrollPage < 0)
            throw new IllegalArgumentException("Number of pages should be a positive number or -1.");
        this.mMaxAmountScrollPage = maxAmountScrollPage;
    }

    /**
     * ???
     *
     * @return -1 ??
     */
    public int getScrollPageLimit() {
        return mMaxAmountScrollPage;
    }

    public ViewMode getViewMode() {
        return mViewMode;
    }

    @Override
    public int getCount() {
        return mItemCount;
    }

    /**
     * ?
     *
     * @param layoutDirectly
     */
    public void setLayoutDirectly(int layoutDirectly) {
        this.mLayoutDirectly = layoutDirectly;
        reset();
        invalidLayout();
    }

    public int getLayoutDirectly() {
        return mLayoutDirectly;
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        Parcelable superInstanceState = super.onSaveInstanceState();
        return new InstanceStateHolder(this, superInstanceState);
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (state instanceof InstanceStateHolder) {
            InstanceStateHolder instanceStateHolder = ((InstanceStateHolder) state);
            super.onRestoreInstanceState(((InstanceStateHolder) state).superParcelable);
            mFirstPosition = instanceStateHolder.firstPos;
            mStartOffset = instanceStateHolder.startOffset;
        } else {
            super.onRestoreInstanceState(state);
        }
    }

    /**
     *  Runnable
     * ?{@link android.widget.AbsListView#FlingRunnable}
     */
    protected class FlingRunnable implements Runnable {

        private final OverScroller mSpringBackScroller;

        private int mLastFlingY;
        private int mLastFlingX;

        private int mLastSpringBackX;
        private int mLastSpringBackY;

        private OverScroller mScroller;
        private Interpolator sLinearInterpolator = new LinearInterpolator();

        private int mSpringDuration;

        private boolean mWaitingToScrollerFinish;

        public FlingRunnable() {
            mScroller = new OverScroller(getContext(), null);
            mSpringBackScroller = new OverScroller(getContext(), new LinearInterpolator());
        }

        public void startFling(int xVelocity, int yVelocity) {
            endScrollerAnimation(false);//?,??

            switch (mLayoutDirectly) {
            case LAYOUT_DIRECTLY_HORIZONTAL: {
                int initialX = xVelocity < 0 ? Integer.MAX_VALUE : 0;
                mLastFlingX = initialX;
                mScroller.setInterpolator(null);
                mScroller.fling(initialX, 0, xVelocity, 0, 0, Integer.MAX_VALUE, 0, 0);
                mTouchState = FLING;
            }
                break;
            case LAYOUT_DIRECTLY_VERTICAL: {
                int initialY = yVelocity < 0 ? Integer.MAX_VALUE : 0;
                mLastFlingY = initialY;
                mScroller.setInterpolator(null);
                mScroller.fling(0, initialY, 0, yVelocity, 0, 0, 0, Integer.MAX_VALUE);
                mTouchState = FLING;
            }
                break;
            }

            if (mTouchState == FLING)
                reportScrollStateChanged(OnScrollListener.SCROLL_STATE_FLING);

            postOnAnimation(this);
        }

        public void startScroll(int distance, int duration, boolean linear) {

            endScrollerAnimation(false);

            switch (mLayoutDirectly) {
            case LAYOUT_DIRECTLY_VERTICAL: {
                int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
                mLastFlingY = initialY;
                mScroller.setInterpolator(linear ? sLinearInterpolator : null);
                mTouchState = FLING;
                mWaitingToScrollerFinish = true;

                mScroller.startScroll(0, initialY, 0, distance, duration);

                postOnAnimation(this);
            }
                break;
            case LAYOUT_DIRECTLY_HORIZONTAL: {
                int initialX = distance < 0 ? Integer.MAX_VALUE : 0;
                mLastFlingX = initialX;
                mScroller.setInterpolator(linear ? sLinearInterpolator : null);
                mTouchState = FLING;
                mWaitingToScrollerFinish = true;

                mScroller.startScroll(initialX, 0, distance, 0, duration);

                postOnAnimation(this);
            }
                break;
            }

            if (mTouchState == FLING)
                reportScrollStateChanged(OnScrollListener.SCROLL_STATE_FLING);
        }

        /**
         * ?
         */
        public void startSpringBack(int x, int y) {
            startSpringBack(x, y, (int) (Math.abs(x) > Math.abs(y) ? Math.abs(x) * 1.5f : Math.abs(y) * 1.5f));
        }

        /**
         * ?
         */
        public void startSpringBack(int x, int y, int duration) {
            endScrollerAnimation(false);

            switch (mLayoutDirectly) {
            case LAYOUT_DIRECTLY_HORIZONTAL: {///?
                mSpringBackScroller.startScroll(0, 0, x, 0, duration);
            }
                break;
            case LAYOUT_DIRECTLY_VERTICAL: {//?
                mSpringBackScroller.startScroll(0, 0, 0, y, duration);
            }
                break;
            }

            mTouchState = SPRING_BACK;
            mSpringDuration = duration;

            if (duration > 0) {
                postOnAnimation(this);
            } else {
                run();
            }
        }

        public void endScrollerAnimation(boolean idle) {
            endScrollerAnimation(idle, idle);
        }

        public void endScrollerAnimation(boolean reportIdleEvent, boolean resetState) {
            removeCallbacks(this);
            mScroller.abortAnimation();
            mSpringBackScroller.abortAnimation();

            mLastFlingX = 0;
            mLastFlingY = 0;
            mLastSpringBackX = 0;
            mLastSpringBackY = 0;
            mWaitingToScrollerFinish = false;

            if (resetState) {
                mScrollTrigger = SCROLL_TRIGGER_SYSTEM;
                mTouchState = REST;
            }

            if (reportIdleEvent) {
                reportScrollStateChanged(OnScrollListener.SCROLL_STATE_IDLE);
            }

            if (DEBUG)
                Log.i(TAG, "End Scroller anim => " + reportIdleEvent);
        }

        @Override
        public void run() {
            switch (mTouchState) {
            case FLING: {//
                switch (mLayoutDirectly) {
                case LAYOUT_DIRECTLY_VERTICAL: {
                    if (mItemCount == 0 || getChildCount() == 0) {
                        endScrollerAnimation(true);
                        return;
                    } else {
                        flingWithVerticalDirection();
                    }
                }
                    break;
                case LAYOUT_DIRECTLY_HORIZONTAL: {
                    if (mItemCount == 0 || getChildCount() == 0) {
                        endScrollerAnimation(true);
                        return;
                    } else {
                        flingWithHorizontalDirection();
                    }
                }
                    break;
                }
            }
                break;
            case SPRING_BACK: {//
                switch (mLayoutDirectly) {
                case LAYOUT_DIRECTLY_VERTICAL: {
                    if (mItemCount == 0 || getChildCount() == 0) {
                        endScrollerAnimation(true);
                        return;
                    }
                    springBackWithVerticalDirection();
                }
                    break;
                case LAYOUT_DIRECTLY_HORIZONTAL: {
                    if (mItemCount == 0 || getChildCount() == 0) {
                        endScrollerAnimation(true);
                        return;
                    }
                    springBackWithHorizontalDirection();
                }
                    break;
                }
            }
                break;
            }

        }

        /**
         * ?SpringBack?
         */
        private void springBackWithHorizontalDirection() {

            final OverScroller scroller = mSpringBackScroller;

            if (scroller.computeScrollOffset()) {
                int x = scroller.getCurrX();
                int delta = x - mLastSpringBackX;
                mLastSpringBackX = x;
                startScrollIfNeed(delta, 0);
                if (mSpringDuration > 0) {
                    postOnAnimation(this);
                } else {
                    run();
                }
            } else {
                int x = scroller.getCurrX();
                int delta = x - mLastSpringBackX;
                //                offsetChildrenLeftAndRight(delta);
                startScrollIfNeed(delta, 0);
                endScrollerAnimation(true);
                //                if (DEBUG) {
                //                    ALog.i(TAG, "Child count == " + getChildCount() + " Left == " + getLeft() + " Right == " + getRight());
                //                    for (int i = 0; i < getChildCount(); i++) {
                //                        View child = getChildAt(i);
                //                        ALog.i(TAG, String.format(Locale.US, "Child [%d] left => %d right => %d ", i, child.getLeft(), child.getRight()));
                //                    }
                //                }
            }
        }

        /**
         * ?SpringBack?
         */
        private void springBackWithVerticalDirection() {
            final OverScroller scroller = mSpringBackScroller;

            if (scroller.computeScrollOffset()) {
                int y = scroller.getCurrY();
                int delta = y - mLastSpringBackY;
                mLastSpringBackY = y;
                startScrollIfNeed(0, delta);
                if (mSpringDuration > 0) {
                    postOnAnimation(this);
                } else {
                    run();
                }
            } else {
                int y = scroller.getCurrY();
                int delta = y - mLastSpringBackY;
                startScrollIfNeed(0, delta);
                //                offsetChildrenLeftAndRight(delta);
                endScrollerAnimation(true);
                //                if (DEBUG) {
                //                    ALog.i(TAG, "Child count == " + getChildCount() + " Left == " + getLeft() + " Right == " + getRight());
                //                    for (int i = 0; i < getChildCount(); i++) {
                //                        View child = getChildAt(i);
                //                        ALog.i(TAG, String.format(Locale.US, "Child [%d] left => %d right => %d ", i, child.getLeft(), child.getRight()));
                //                    }
                //                }
            }
        }

        /**
         * 
         */
        private void flingWithHorizontalDirection() {
            final OverScroller scroller = mScroller;

            boolean more = scroller.computeScrollOffset();

            final int x = scroller.getCurrX();

            //,bug
            if (x == 0 && more) {
                postOnAnimation(this);
                return;
            }

            // Flip sign to convert finger direction to list items direction
            // (e.g. finger moving down means list is moving towards the top)
            int xDelta = mLastFlingX - x;

            if (xDelta > 0) {
                // Don't flingInner more than 1 screen
                xDelta = Math.min(getRight() - getLeft() - 1, xDelta);
            } else {
                // Don't flingInner more than 1 screen
                xDelta = Math.max(-(getRight() - getLeft() - 1), xDelta);
            }

            boolean hasMoved = startScrollIfNeed(xDelta, 0);

            mLastFlingX = x;

            if (x != scroller.getFinalX() && more
                    && ((hasMoved && Math.abs(xDelta) > 0) || mWaitingToScrollerFinish)) {
                postOnAnimation(this);
            } else {
                if (DEBUG)
                    Log.i(TAG, "Prepare adjust view Velocity => " + scroller.getCurrVelocity());
                endScrollerAnimation(false);
                adjustSelView();
            }
        }

        /**
         * ?
         */
        private void flingWithVerticalDirection() {
            final OverScroller scroller = mScroller;

            boolean more = scroller.computeScrollOffset();

            final int y = scroller.getCurrY();

            //bug
            if (y == 0 && more) {
                postOnAnimation(this);
                return;
            }

            // Flip sign to convert finger direction to list items direction
            // (e.g. finger moving down means list is moving towards the top)
            int yDelta = mLastFlingY - y;

            if (DEBUG) {
                Log.i(TAG, String.format(Locale.US,
                        "flingWithVerticalDirection Last flingInner Y => %s Current Y %s ", mLastFlingY, y));
                Log.i(TAG, String.format("flingWithVerticalDirection yDelta => %s  Has More %s", yDelta, more));
            }

            if (yDelta > 0) {
                // Don't flingInner more than 1 screen
                yDelta = Math.min(getBottom() - getTop() - 1, yDelta);
            } else {
                // Don't flingInner more than 1 screen
                yDelta = Math.max(-(getBottom() - getTop() - 1), yDelta);
            }

            boolean hasMoved = startScrollIfNeed(0, yDelta);

            mLastFlingY = y;

            if (more && ((hasMoved && Math.abs(yDelta) > 0) || mWaitingToScrollerFinish)) {
                postOnAnimation(this);
            } else {
                endScrollerAnimation(false);
                mScrollTrigger = SCROLL_TRIGGER_SYSTEM;
                adjustSelView();
            }
        }
    }

    /**
     * Runnable
     */
    protected class SmoothScrollRunnable implements Runnable {

        private int mTargetPosition;
        private int mDuration;
        private int mLastSeenPos;
        private int mStartOffset;

        public void scrollToPosition(int position, int offset, int duration) {
            stop();

            this.mTargetPosition = position;
            mStartOffset = offset;

            switch (mViewMode) {
            case LIST_VIEW:
            case END_LESS_LIST_VIEW: {
                int firstPos = mFirstPosition;

                final int lastPosition = firstPos + getChildCount() - 1;

                int viewTravelCount;
                if (mTargetPosition < firstPos) {
                    viewTravelCount = firstPos - mTargetPosition;
                } else if (mTargetPosition > lastPosition) {
                    viewTravelCount = mTargetPosition - lastPosition;
                } else {
                    //In screen
                    View targetChild = getChildAt(mTargetPosition - firstPos);
                    final int targetStart = mLayoutDirectly == LAYOUT_DIRECTLY_VERTICAL ? targetChild.getTop()
                            : targetChild.getLeft();
                    smoothScroll(targetStart + offset, duration, true);
                    stop();
                    return;
                }

                // Estimate how many screens we should travel
                final float screenTravelCount = (float) viewTravelCount / getChildCount();
                mDuration = screenTravelCount < 1 ? duration : (int) (duration / screenTravelCount);
                mStartOffset = offset;
                mLastSeenPos = INVALID_POSITION;
            }
                break;
            case WHEEL_VIEW:
            case ENDLESS_WHEEL_VIEW: {

                final int selViewPosition = getSelectedViewPosition();

                int firstPos = (mFirstPosition + selViewPosition) % mItemCount;

                final int lastPosition = firstPos + getChildCount() - selViewPosition - 1;

                int viewTravelCount;
                if (mTargetPosition < firstPos) {
                    viewTravelCount = firstPos - mTargetPosition;
                } else if (mTargetPosition > lastPosition) {
                    viewTravelCount = mTargetPosition - lastPosition;
                } else {
                    //In screen

                    boolean isVertical = mLayoutDirectly == LAYOUT_DIRECTLY_VERTICAL;

                    int selPos = getSelectedViewPosition();

                    if (selPos == INVALID_POSITION)
                        selPos = 0;

                    View targetChild = getChildAt(selPos + mTargetPosition - firstPos);

                    if (targetChild != null) {
                        int distance = 0;

                        if (position < firstPos) {//?,
                            distance = isVertical
                                    ? (getYSel() + ((targetChild.getBottom() - targetChild.getTop()) / 2))
                                    : (getXSel() + ((targetChild.getRight() - targetChild.getLeft()) / 2));

                        } else if (position > firstPos) {//?,
                            distance = isVertical
                                    ? targetChild.getBottom()
                                            - ((targetChild.getBottom() - targetChild.getTop()) / 2) - getYSel()
                                    : targetChild.getRight()
                                            - ((targetChild.getRight() - targetChild.getLeft()) / 2) - getXSel();

                        } else {//target child 
                            distance = isVertical
                                    ? targetChild.getTop() + (targetChild.getBottom() - targetChild.getTop()) / 2
                                            - getYSel()
                                    : targetChild.getLeft() + (targetChild.getRight() - targetChild.getLeft()) / 2
                                            - getXSel();
                        }

                        smoothScroll(distance, duration, true);

                        stop();
                    }
                    return;
                }

                // Estimate how many screens we should travel
                final float screenTravelCount = (float) viewTravelCount / getChildCount();
                mDuration = screenTravelCount < 1 ? duration : (int) (duration / screenTravelCount);
                mLastSeenPos = INVALID_POSITION;
            }
            }

            postOnAnimation(this);
        }

        /**
         * ?
         */
        private void stop() {

            removeCallbacks(this);
            invalidate();
        }

        @Override
        public void run() {

            switch (mViewMode) {
            case LIST_VIEW:
            case END_LESS_LIST_VIEW: {
                int firstPos = mFirstPosition;

                mLastSeenPos = firstPos;

                final int childCount = getChildCount();
                final int position = mTargetPosition;
                final int lastPos = firstPos + childCount - 1;

                int viewTravelCount = 0;
                if (position < firstPos) {
                    viewTravelCount = firstPos - position + 1;
                } else if (position > lastPos) {
                    viewTravelCount = position - lastPos;
                }

                // Estimate how many screens we should travel
                final float screenTravelCount = (float) viewTravelCount / childCount;

                final boolean isVertical = mLayoutDirectly == LAYOUT_DIRECTLY_VERTICAL;

                final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
                if (position < firstPos) {
                    final int distance = (int) (-(isVertical ? getHeight() : getWidth()) * modifier);
                    final int duration = (int) (mDuration * modifier);
                    smoothScroll(distance, duration, true);
                    postOnAnimation(this);
                } else if (position > lastPos) {
                    final int distance = (int) (isVertical ? getHeight() : getWidth() * modifier);
                    final int duration = (int) (mDuration * modifier);
                    smoothScroll(distance, duration, true);
                    postOnAnimation(this);
                } else {
                    // On-screen, just scroll.

                    View targetChild = getChildAt(mTargetPosition - firstPos);
                    final int distance = isVertical ? targetChild.getTop() : targetChild.getLeft();
                    final int duration = (int) (mDuration * ((float) Math.abs(distance) / getHeight()));
                    smoothScroll(distance + mStartOffset, duration, true);
                }
            }
                break;
            case WHEEL_VIEW:
            case ENDLESS_WHEEL_VIEW: {

                final int selViewPosition = getSelectedViewPosition();

                int firstPos = (mFirstPosition + selViewPosition) % mItemCount;

                if (DEBUG)
                    Log.i(TAG, String.format(Locale.US, "Smooth scroll => First position [%s] TargetPosition  [%s]",
                            firstPos, mTargetPosition));

                mLastSeenPos = firstPos;

                final int position = mTargetPosition;

                final int childCount = getChildCount() - selViewPosition;

                final int lastPos = firstPos + childCount - 1;

                int viewTravelCount = 0;
                if (position < firstPos) {
                    viewTravelCount = firstPos - position + 1;
                } else if (position > lastPos) {
                    viewTravelCount = position - lastPos;
                }

                // Estimate how many screens we should travel
                final float screenTravelCount = (float) viewTravelCount / childCount;

                final boolean isVertical = mLayoutDirectly == LAYOUT_DIRECTLY_VERTICAL;

                final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
                if (position < firstPos) {
                    final int distance = (int) (-(isVertical ? /*getHeight()*//*selView.getTop()*/getYSel()
                            : /*getWidth()*/ /*selView.getLeft()*/getXSel()) * modifier);
                    final int duration = (int) (mDuration * modifier);
                    smoothScroll(distance, duration, true);
                    postOnAnimation(this);
                } else if (position > lastPos) {
                    final int distance = (int) ((isVertical ? (getHeight() - getYSel()) : (getWidth() - getXSel()))
                            * modifier);
                    final int duration = (int) (mDuration * modifier);
                    smoothScroll(distance, duration, true);
                    postOnAnimation(this);
                } else {
                    // On-screen, just scroll.

                    View targetChild = getChildAt(selViewPosition + mTargetPosition - firstPos);

                    int distance = 0;
                    int duration = 0;

                    if (position < firstPos) {//?,
                        distance = isVertical ? (getYSel() + ((targetChild.getBottom() - targetChild.getTop()) / 2))
                                : (getXSel() + ((targetChild.getRight() - targetChild.getLeft()) / 2));

                    } else if (position > firstPos) {//?,
                        distance = isVertical
                                ? targetChild.getBottom() - ((targetChild.getBottom() - targetChild.getTop()) / 2)
                                        - getYSel()
                                : targetChild.getRight() - ((targetChild.getRight() - targetChild.getLeft()) / 2)
                                        - getXSel();

                    } else {//target child 
                        //todo May have bugs.
                        distance = isVertical
                                ? targetChild.getTop() + (targetChild.getBottom() - targetChild.getTop()) / 2
                                        - getYSel()
                                : targetChild.getLeft() + (targetChild.getRight() - targetChild.getLeft()) / 2
                                        - getXSel();

                    }

                    duration = (int) (mDuration
                            * ((float) Math.abs(distance) / (isVertical ? getYSel() : getXSel())));

                    smoothScroll(distance, duration, true);
                }
            }
                break;
            }

        }
    }

    /**
     * ?
     */
    protected enum StuffMode {
        /**
         * ?
         */
        flowDown,
        /**
         * ?
         */
        flowUp,
        /**
         * ?
         */
        flowLeft,
        /**
         * ??
         */
        flowRight
    }

    /**
     * ?
     */
    public static enum ViewMode {
        /**
         * list view ?
         */
        LIST_VIEW(1),
        /**
         * ?? list view ?
         */
        END_LESS_LIST_VIEW(2),
        /**
         *  ?
         * list view 
         * ,?,
         * ??,??
         * ?
         * 
         */
        WHEEL_VIEW(3),
        /**
         * ?
         * ??
         */
        ENDLESS_WHEEL_VIEW(4);

        private final int mValue;

        ViewMode(int value) {
            this.mValue = value;
        }

        public static ViewMode wrap(int value) {
            switch (value) {
            case 1:
                return LIST_VIEW;
            case 2:
                return END_LESS_LIST_VIEW;
            case 3:
                return WHEEL_VIEW;
            case 4:
                return ENDLESS_WHEEL_VIEW;
            }
            return null;
        }
    }

    /**
     * ?
     */
    public static interface OnScrollListener {

        /**
         * ?:
         */
        public static int SCROLL_STATE_IDLE = 0;

        /**
         * ?:?
         */
        public static int SCROLL_STATE_TOUCH_SCROLL = 1;

        /**
         * ?:
         */
        public static int SCROLL_STATE_FLING = 2;

        /**
         * ?:?
         */
        public static int SCROLL_STATE_ADJUSTMENT = 3;

        /**
         * ????
         *
         * @param view        
         * @param scrollState ??
         */
        public void onScrollStateChanged(EasyAdapterView view, int scrollState);

        /**
         * ?
         *
         * @param view             
         * @param firstVisibleItem ????
         * @param visibleItemCount ????
         * @param totalItemCount   ??
         */
        public void onScroll(EasyAdapterView view, int firstVisibleItem, int visibleItemCount, int totalItemCount);
    }

    public static interface OnSelectedItemChangedListener {

        void onSelectedItemChanged(EasyAdapterView easyAdapterView, int oldSel, View oldSelView, int position,
                View selectedView);
    }

    /**
     * ??holder
     */
    private static class InstanceStateHolder implements Parcelable {
        private int firstPos;
        private int amountMax;
        private ViewMode mode;
        private int orientation;
        private int startOffset;

        private Parcelable superParcelable;

        public InstanceStateHolder(EasyAdapterView easyAdapterView, Parcelable superParcelable) {
            this.firstPos = easyAdapterView.mFirstPosition;
            this.amountMax = easyAdapterView.mMaxAmountScrollPage;
            this.mode = easyAdapterView.mViewMode;
            this.orientation = easyAdapterView.mLayoutDirectly;
            this.startOffset = easyAdapterView.mStartOffset;
            this.superParcelable = superParcelable;
        }

        public InstanceStateHolder() {
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(this.firstPos);
            dest.writeInt(this.amountMax);
            dest.writeInt(this.mode == null ? -1 : this.mode.ordinal());
            dest.writeInt(this.orientation);
            dest.writeParcelable(this.superParcelable, flags);
        }

        protected InstanceStateHolder(Parcel in) {
            this.firstPos = in.readInt();
            this.amountMax = in.readInt();
            int tmpMode = in.readInt();
            this.mode = tmpMode == -1 ? null : ViewMode.values()[tmpMode];
            this.orientation = in.readInt();
            this.superParcelable = in.readParcelable(Parcelable.class.getClassLoader());
        }

        public static final Creator<InstanceStateHolder> CREATOR = new Creator<InstanceStateHolder>() {
            @Override
            public InstanceStateHolder createFromParcel(Parcel source) {
                return new InstanceStateHolder(source);
            }

            @Override
            public InstanceStateHolder[] newArray(int size) {
                return new InstanceStateHolder[size];
            }
        };
    }
}