HorizontalListView横向自动无限循环滚动效果

Posted dingxiansen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HorizontalListView横向自动无限循环滚动效果相关的知识,希望对你有一定的参考价值。

实现的效果品牌横向自动滚动效果,

 

HorizontalListView2.xml

 

public class HorizontalListView2 extends AdapterView<ListAdapter> {
    /**AdapterView 继承自ViewGroup, 这两个常量是为ViewGroup.addViewInLayout(View, int, LayoutParams, boolean)
     * 准备的,第二个参数如果为-1表示尾插,0表示头插,这里贴出它的官方文档说明。
     **/
    /**
     * addViewInLayout(View, int, LayoutParams, boolean) 的官方文档说明
     * Adds a view during layout. This is useful if in your onLayout() method,
     * you need to add more views (as does the list view for example).
     * <p>
     * If index is negative, it means put it at the end of the list.(-1代表尾插)
     *
     * @param child the view to add to the group
     * @param index the index at which the child must be added
     * @param params the layout parameters to associate with the child
     * @param preventRequestLayout if true, calling this method will not trigger a
     * layout request on child
     * @return true if the child was added, false otherwise
     */
    private static final int INSERT_AT_END_OF_LIST = -1;
    private static final int INSERT_AT_START_OF_LIST = 0;

    /**
     * The velocity to use for overscroll absorption
     */
    private static final float FLING_DEFAULT_ABSORB_VELOCITY = 30f;

    /**
     * The friction amount to use for the fling tracker
     */
    private static final float FLING_FRICTION = 0.009f;

    /**
     * 下面两个常量用作状态恢复  ,一个恢复mCurrentScrollX, 一个缓存父类状态
     */
    private static final String BUNDLE_ID_CURRENT_SCROLL_X = "BUNDLE_ID_CURRENT_SCROLL_X";
    private static final String BUNDLE_ID_PARENT_STATE = "BUNDLE_ID_PARENT_STATE";

    /**
     * 用于计算fling时滑动dx的Scroller,会根据你的滑动,为你计算出下一个滚动位置
     */
    protected Scroller mFlingTracker = new Scroller(getContext());

    /**
     * 检测fling等操作的回调
     */
    private final GestureListener mGestureListener = new GestureListener();

    /**
     * 检测fling等操作
     */
    private GestureDetector mGestureDetector;

    /**
     * 记录最左边可见的那个View从什么位置开始展示
     * 取值范围0到-view.getWidth()
     * 其实就是我们layout的时候需要用到的那个offset
     **/
    private int mLeftVisibleViewDisplayOffset;

    /**
     * 适配器
     */
    protected ListAdapter mAdapter;

    /**
     * 回收,缓存View的队列,因为HorizontalListView对ItemViewType做了支持,如果你还不了解的话,自己查一查ListView
     * 怎么使用Adapter的ItemViewType在一个ListView里展示不同的View,这个缓存为每种类型的View使用了独立的缓存,
     * 如果还不明白先画个问号,等到看到存取缓存的时候就明白了
     */
    private List<Queue<View>> mRemovedViewsCache = new ArrayList<Queue<View>>();

    /**
     * 标记数据集改变,你对notifyDataSetChanged一定不陌生
     */
    private boolean mDataSetChanged = false;

    /**
     * Temporary rectangle to be used for measurements
     */
    private Rect mRect = new Rect();

    /**
     * Tracks the currently touched view, used to delegate touches to the view being touched
     */
    private View mViewBeingTouched = null;

    /**
     * The width of the divider that will be used between list items
     */
    private int mDividerWidth = 0;

    /**
     * The drawable that will be used as the list divider
     */
    private Drawable mDividerDrawable = null;

    /**
     * 这个值以像素为单位,是最重要的一个值,记录当前滑动的距离里
     * 取值范围是0-最大滑动距离,也就是最后一个Item完全显示出来的时候,永远是正数
     */
    protected int mCurrentScrollX;

    /**
     * 滑动的过程是平滑的,这需要保存下一个滚动的位置,一般地, mCurrentScrollX和mNextScrollX相差很小,
     * 这样重绘UI的时候才会觉得平滑,mNextScrollX可以通过Scroller计算,亦可以通过GestureListener 计算得到,
     * 不滑动的时候mNextScrollX等于mCurrentScrollX
     */
    protected int mNextScrollX;

    /**
     * Used to hold the scroll position to restore to post rotate
     */
    private Integer mRestoreScrollX = null;

    /**
     * 记录最大的滚动距离,也就是最后一个item完全显示出来的时候的滚动距离。
     * 这个值没有办法初始化,只能在滑动的过程中动态计算,且一旦计算完成就不需要再计算,除非布局发生改变
     */
    private int mMaxScrollX = Integer.MAX_VALUE;

    /**
     * 最左边的View在adapter中的索引
     */
    private int mLeftVisibleViewAdapterIndex;

    /**
     * 最右边的View在adapter中的索引
     */
    private int mRightVisibleViewAdapterIndex;

    /**
     * This tracks the currently selected accessibility item
     */
    private int mCurrentlySelectedAdapterIndex;

    /**
     * 这个可以不用看,不影响我们解释主要逻辑,在这里解释一下,就是滑到adapter index快要到底的时候回调这个方法,
     * 有一个阀值,根据这个阀值来判断是否滑到底了。
     * Callback interface to notify listener that the user has scrolled this view to the point that it is low on data.
     */
    private RunningOutOfDataListener mRunningOutOfDataListener = null;

    /**
     * 没有数据的阀值
     */
    private int mRunningOutOfDataThreshold = 0;

    /**
     * Tracks if we have told the listener that we are running low on data. We only want to tell them once.
     */
    private boolean mHasNotifiedRunningLowOnData = false;

    /**
     * 监听滚动状态变化,有3个状态,IDLE , TOUCH_SCROLL,FLING
     */
    private OnScrollStateChangedListener mOnScrollStateChangedListener = null;

    /**
     * 当前滚动状态,默认IDLE
     */
    private OnScrollStateChangedListener.ScrollState mCurrentScrollState = OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE;

    /**
     * 滑动过程中左边的亮边
     */
    private EdgeEffectCompat mEdgeGlowLeft;

    /**
     * 滑动过程中右边的亮边
     */
    private EdgeEffectCompat mEdgeGlowRight;

    /**
     * HorizontalListView的高度测量参数 MeasureSpec,我们需要用这个参数来对child施加约束,
     * 从而测量child的高度
     */
    private int mHeightMeasureSpec;

    /**
     * Used to track if a view touch should be blocked because it stopped a fling
     */
    private boolean mBlockTouchAction = false;

    /**
     * 如果HorizontalListView放在一个scrollView等滚动的View中,用来禁用parent处理事件,从而解决滑动冲突
     */
    private boolean mIsParentVerticiallyScrollableViewDisallowingInterceptTouchEvent = false;

    /**
     * The listener that receives notifications when this view is clicked.
     */
    private OnClickListener mOnClickListener;





   /* public HorizontalListView2(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }*/

  /*  private synchronized void initView() {
        mLeftViewIndex = -1;
        mRightViewIndex = 0;
        mDisplayOffset = 0;
        mCurrentX = 0;
        mNextX = 0;
        mMaxX = Integer.MAX_VALUE;
        mScroller = new Scroller(getContext());
        mGesture = new GestureDetector(getContext(), mOnGesture);
    }*/







    public HorizontalListView2(Context context, AttributeSet attrs) {
        super(context, attrs);
        mEdgeGlowLeft = new EdgeEffectCompat(context);//左边的滑动亮边
        mEdgeGlowRight = new EdgeEffectCompat(context);//右边的滑动亮边
        mGestureDetector = new GestureDetector(context, mGestureListener);//手势检测
        bindGestureDetector();
        initView();
//        retrieveXmlConfiguration(context, attrs);
        setWillNotDraw(false);
        // If the OS version is high enough then set the friction on the fling tracker */
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            HoneycombPlus.setFriction(mFlingTracker, FLING_FRICTION);
        }
    }

    /**
     * 为当前的HorizontalListView设置onTouchListener,并把onTouch事件交给我们的手势检测对象mGestureDetector来处理
     */
    private void bindGestureDetector() {
        // Generic touch listener that can be applied to any view that needs to process gestures
        final OnTouchListener gestureListenerHandler = new OnTouchListener() {
            @Override
            public boolean onTouch(final View v, final MotionEvent event) {
                // Delegate the touch event to our gesture detector
                return mGestureDetector.onTouchEvent(event);
            }
        };

        setOnTouchListener(gestureListenerHandler);
    }

    /**
     * When this HorizontalListView is embedded within a vertical scrolling view it is important to disable the parent view from interacting with
     * any touch events while the user is scrolling within this HorizontalListView. This will start at this view and go up the view tree looking
     * for a vertical scrolling view. If one is found it will enable or disable parent touch interception.
     *
     * @param disallowIntercept If true the parent will be prevented from intercepting child touch events
     */
    private void requestParentListViewToNotInterceptTouchEvents(Boolean disallowIntercept) {
        // Prevent calling this more than once needlessly
        if (mIsParentVerticiallyScrollableViewDisallowingInterceptTouchEvent != disallowIntercept) {
            View view = this;

            while (view.getParent() instanceof View) {
                // 如果parent是 ListView , ScrollView ,那么就禁用掉他们的拦截事件能力,从而避免滑动冲突
                if (view.getParent() instanceof ListView || view.getParent() instanceof ScrollView) {
                    view.getParent().requestDisallowInterceptTouchEvent(disallowIntercept);
                    mIsParentVerticiallyScrollableViewDisallowingInterceptTouchEvent = disallowIntercept;
                    return;
                }

                view = (View) view.getParent();
            }
        }
    }

    /**
     * 获取divider 和divider宽度
     */
  /*  private void retrieveXmlConfiguration(Context context, AttributeSet attrs) {
        if (attrs != null) {
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HorizontalListView);

            // Get the provided drawable from the XML
            final Drawable d = a.getDrawable(R.styleable.HorizontalListView_android_divider);
            if (d != null) {
                // If a drawable is provided to use as the divider then use its intrinsic width for the divider width
                setDivider(d);
            }

            // If a width is explicitly specified then use that width
            final int dividerWidth = a.getDimensionPixelSize(R.styleable.HorizontalListView_dividerWidth, 0);
            if (dividerWidth != 0) {
                setDividerWidth(dividerWidth);
            }

            a.recycle();
        }
    }*/

    @Override
    public Parcelable onSaveInstanceState() {//状态保存,只保存两个状态

        Bundle bundle = new Bundle();

        // Add the parent state to the bundle
        bundle.putParcelable(BUNDLE_ID_PARENT_STATE, super.onSaveInstanceState());

        // Add our state to the bundle
        bundle.putInt(BUNDLE_ID_CURRENT_SCROLL_X, mCurrentScrollX);

        return bundle;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {//状态恢复
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;

            // Restore our state from the bundle
            mRestoreScrollX = Integer.valueOf((bundle.getInt(BUNDLE_ID_CURRENT_SCROLL_X)));

            // Restore out parent‘s state from the bundle
            super.onRestoreInstanceState(bundle.getParcelable(BUNDLE_ID_PARENT_STATE));
        }
    }

    /**
     * 设置分隔线,同时设置分隔线宽度
     *
     * @param divider The drawable to use.
     */
    public void setDivider(Drawable divider) {
        mDividerDrawable = divider;

        if (divider != null) {
            setDividerWidth(divider.getIntrinsicWidth());
        } else {
            setDividerWidth(0);
        }
    }

    /**
     * 设置分隔线宽度
     */
    public void setDividerWidth(int width) {
        mDividerWidth = width;

        // 强制重布局,重绘
        requestLayout();
        invalidate();
    }

    /**
     * 初始化默认值
     */
    private void initView() {
        mLeftVisibleViewAdapterIndex = -1;
        mRightVisibleViewAdapterIndex = -1;
        mLeftVisibleViewDisplayOffset = 0;
        mCurrentScrollX = 0;
        mNextScrollX = 0;
        mMaxScrollX = Integer.MAX_VALUE;
        setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);


    }

    /**
     * 重置默认值
     */
    private void reset() {
        initView();
        removeAllViewsInLayout();
        requestLayout();
    }

    /**
     * ListView也使用这个,可以到源码里看一下,继承的层次略深
     */
    private DataSetObserver mAdapterDataObserver = new DataSetObserver() {
        @Override
        public void onChanged() {
            mDataSetChanged = true;

            // Clear so we can notify again as we run out of data
            mHasNotifiedRunningLowOnData = false;

            unpressTouchedChild();

            // 数据集改变,我们就重绘自己
            invalidate();
            requestLayout();
        }

        @Override
        public void onInvalidated() {
            // Clear so we can notify again as we run out of data
            mHasNotifiedRunningLowOnData = false;

            unpressTouchedChild();
            reset();

            // Invalidate and request layout to force this view to completely redraw itself
            invalidate();
            requestLayout();
        }
    };

    @Override
    public void setSelection(int position) {
        mCurrentlySelectedAdapterIndex = position;
        requestLayout();
        invalidate();
    }

    @Override
    public View getSelectedView() {
        return getChild(mCurrentlySelectedAdapterIndex);
    }

    @Override
    public void setAdapter(ListAdapter adapter) {
        if (mAdapter != null) {
            mAdapter.unregisterDataSetObserver(mAdapterDataObserver);
        }

        if (adapter != null) {
            // Clear so we can notify again as we run out of data
            mHasNotifiedRunningLowOnData = false;
            //这样,当我们调用notifyDataSetChanged的时候,onChanged(),onInvalidated()才会被回调
            mAdapter = adapter;
            mAdapter.registerDataSetObserver(mAdapterDataObserver);
        }

        initializeRecycledViewCache(mAdapter.getViewTypeCount());//比较重要
        reset();//requestLayout
    }

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

    /**
     * Will create and initialize a cache for the given number of different types of views.
     *
     * @param viewTypeCount - The total number of different views supported
     */
    private void initializeRecycledViewCache(int viewTypeCount) {
        // 看到了吧,我们为每一个View的类型初始化一个Cache
        mRemovedViewsCache.clear();
        for (int i = 0; i < viewTypeCount; i++) {
            mRemovedViewsCache.add(new LinkedList<View>());
        }
    }

    /**
     * Returns a recycled view from the cache that can be reused, or null if one is not available.
     *
     * @param adapterIndex
     * @return
     */
    private View getRecycledView(int adapterIndex) {
        //从缓存中拿出一个View,但是,我们需要首先根据adapterIndex来获取view的类型。
        //这个adapterIndex就相当于getView的position参数
        int itemViewType = mAdapter.getItemViewType(adapterIndex);

        if (isItemViewTypeValid(itemViewType)) {
            return mRemovedViewsCache.get(itemViewType).poll();//pull one from Q head.
        }

        return null;
    }

    /**
     * Adds the provided view to a recycled views cache.
     *
     * @param adapterIndex
     * @param view
     */
    private void recycleView(int adapterIndex, View view) {
        // There is one Queue of views for each different type of view.
        // Just add the view to the pile of other views of the same type.
        // The order they are added and removed does not matter.
        // 划出屏幕的View都会被回收,参照getRecycledView
        int itemViewType = mAdapter.getItemViewType(adapterIndex);//getItemViewType是adapter的方法,一般开发者很少使用
        if (isItemViewTypeValid(itemViewType)) {
            mRemovedViewsCache.get(itemViewType).offer(view);//push one to Q tail.
        }
    }

    private boolean isItemViewTypeValid(int itemViewType) {
        return itemViewType < mRemovedViewsCache.size();
    }

    /**
     * Adds a child to this viewgroup and measures it so it renders the correct size
     * 第二个参数0,-1两个选项,将一个view放置到HorizontalListView里,但是View只被测量了,没有被layout 和draw
     */
    private void addAndMeasureChild(final View child, int viewPos) {
        LayoutParams params = getLayoutParams(child);
        //Me: perform addView(View) like a ViewGroup. If viewPos is negative then put view at the end.
        addViewInLayout(child, viewPos, params, true);
        measureChild(child);
    }

    /**
     * Measure the provided child.
     *
     * @param child The child.
     */
    private void measureChild(View child) {
        LayoutParams childLayoutParams = getLayoutParams(child);
        //根据HorizontalListView自身的约束mHeightMeasureSpec,来计算child的childWidthSpec
        //这个地方比较难理解,涉及到View在ViewGroup的Measure过程
        int childHeightSpec = ViewGroup.getChildMeasureSpec(
                mHeightMeasureSpec, getPaddingTop() + getPaddingBottom(), childLayoutParams.height);

        int childWidthSpec;
        if (childLayoutParams.width > 0) {
            childWidthSpec = MeasureSpec.makeMeasureSpec(childLayoutParams.width, MeasureSpec.EXACTLY);
        } else {
            //如果宽度是wrap content,就测量最大宽度
            childWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        }
        //我们并没有使用容器提供的measureChild方法,而是重载,这样,我们就能测量出真实的宽高,而不局限于容器的大小
        child.measure(childWidthSpec, childHeightSpec);
    }

    /**
     * Gets a child‘s layout parameters, defaults if not available.
     */
    private LayoutParams getLayoutParams(View child) {
        LayoutParams layoutParams = child.getLayoutParams();
        if (layoutParams == null) {
            // Since this is a horizontal list view default to matching the parents height, and wrapping the width
            //看,宽度我们用wrap content, 高度我们用 match parent,即使用容器的高度
            layoutParams = new LayoutParams(
                    LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
        }

        return layoutParams;
    }

    /**
     * 核心逻辑在这里。。。。。
     * 为了让滑动生效,我们需要每滑动一小段距离,或是fling一小段距离,就需要调用一下requestLayout来
     * 触发本方法执行,直到滑动结束或是fling完毕,
     * 你可以跳过mEdgeGlowRight的操作看核心逻辑,这个只是个锦上添花,滑动亮边而已
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        if (mAdapter == null) {
            return;
        }

        // Force the OS to redraw this view
        invalidate();//Me: I think this call is useless.

        // If the data changed then reset everything and render from scratch at the same offset as last time
        if (mDataSetChanged) {
            int oldCurrentX = mCurrentScrollX;
            initView();
            removeAllViewsInLayout();
            mNextScrollX = oldCurrentX;// mCurrentX is 0 now.
            mDataSetChanged = false;
        }

        // If restoring from a rotation
        if (mRestoreScrollX != null) {
            mNextScrollX = mRestoreScrollX;
            mRestoreScrollX = null;
        }

        // If in a fling
        //这个方法会帮你计算下一个要滑动的位置,Scroller如何使用,自己去查查
        if (mFlingTracker.computeScrollOffset()) {
            // Compute the next position
            mNextScrollX = mFlingTracker.getCurrX();
        }

        // Prevent scrolling past 0 so you can‘t scroll past the end of the list to the left
        if (mNextScrollX <= 0) {//做一个简单的非法值校验
            mNextScrollX = 0;

            // Show an edge effect absorbing the current velocity
            if (mEdgeGlowLeft.isFinished()) {
                mEdgeGlowLeft.onAbsorb((int) determineFlingAbsorbVelocity());
            }

            //滑到最左边就强制停止fling
            mFlingTracker.forceFinished(true);
            setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);
        } else if (mNextScrollX >= mMaxScrollX) {//做一个简单的非法值校验
            // Clip the maximum scroll position at mMaxX so you can‘t scroll past the end of the list to the right
            mNextScrollX = mMaxScrollX;

            // Show an edge effect absorbing the current velocity
            if (mEdgeGlowRight.isFinished()) {
                mEdgeGlowRight.onAbsorb((int) determineFlingAbsorbVelocity());
            }
            //滑到最右边就强制停止fling
            mFlingTracker.forceFinished(true);
            setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);
        }

        // Calculate our delta from the last time the view was drawn
        // fling(int startX, int startY, int velocityX, int velocityY,int minX, int maxX, int minY, int maxY)
        //计算滑动的变化量,向左滑动 dx < 0  向右滑动 dx > 0
        int dx = mCurrentScrollX - mNextScrollX;

        // Since the view has now been drawn, update our current position
        mCurrentScrollX = mNextScrollX;

        removeNonVisibleChildren(dx);
        fillList(dx);
        positionChildren(dx);


        // If we have scrolled enough to lay out all views, then determine the maximum scroll position now
        //滑动的过程当中,尝试计算mMaxScrollX
        if (determineMaxX()) {
            // Redo the layout pass since we now know the maximum scroll position
            //递归调用onLayout, 因为requestLayout()会触发onLayout
            ViewCompat.postOnAnimation(this, mDelayedLayout);
            return;
        }

        // If the fling has finished
        if (mFlingTracker.isFinished()) {
            // If the fling just ended
            if (mCurrentScrollState == OnScrollStateChangedListener.ScrollState.SCROLL_STATE_FLING) {
                setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);
            }
        } else {
            // Still in a fling so schedule the next frame
            //递归调用onLayout, 因为requestLayout()会触发onLayout
            ViewCompat.postOnAnimation(this, mDelayedLayout);
        }
    }

    @Override
    protected float getLeftFadingEdgeStrength() {
        int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();

        // If completely at the edge then disable the fading edge
        if (mCurrentScrollX == 0) {
            return 0;
        } else if (mCurrentScrollX < horizontalFadingEdgeLength) {
            // We are very close to the edge, so enable the fading edge proportional to the distance from the edge, and the width of the edge effect
            return (float) mCurrentScrollX / horizontalFadingEdgeLength;
        } else {
            // The current x position is more then the width of the fading edge so enable it fully.
            return 1;
        }
    }

    @Override
    protected float getRightFadingEdgeStrength() {
        int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();

        // If completely at the edge then disable the fading edge
        if (mCurrentScrollX == mMaxScrollX) {
            return 0;
        } else if ((mMaxScrollX - mCurrentScrollX) < horizontalFadingEdgeLength) {
            // We are very close to the edge, so enable the fading edge proportional to the distance from the ednge, and the width of the edge effect
            return (float) (mMaxScrollX - mCurrentScrollX) / horizontalFadingEdgeLength;
        } else {
            // The distance from the maximum x position is more then the width of the fading edge so enable it fully.
            return 1;
        }
    }

    /**
     * Determines the current fling absorb velocity
     */
    private float determineFlingAbsorbVelocity() {
        // If the OS version is high enough get the real velocity */
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            return IceCreamSandwichPlus.getCurrVelocity(mFlingTracker);
        } else {
            // Unable to get the velocity so just return a default.
            // In actuality this is never used since EdgeEffectCompat does not draw anything unless the device is ICS+.
            // Less then ICS EdgeEffectCompat essentially performs a NOP.
            return FLING_DEFAULT_ABSORB_VELOCITY;
        }
    }

    /**
     * Use to schedule a request layout via a runnable
     */
    private Runnable mDelayedLayout = new Runnable() {
        @Override
        public void run() {
            requestLayout();
        }
    };

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

        // Cache off the measure spec
        mHeightMeasureSpec = heightMeasureSpec;
    }

    ;

    /**
     * Determine the Max X position. This is the farthest that the user can scroll the screen. Until the last adapter item has been
     * laid out it is impossible to calculate; once that has occurred this will perform the calculation, and if necessary force a
     * redraw and relayout of this view.
     *
     * @return true if the maxx position was just determined
     */
    private boolean determineMaxX() {
        // If the last view has been laid out, then we can determine the maximum x position
        //只有最后一个,也是最右边的item滑入屏幕的时候才计算mMaxScrollX
        if (isLastItemInAdapter(mRightVisibleViewAdapterIndex)) {
            View rightView = getRightmostChild();

            if (rightView != null) {
                int oldMaxX = mMaxScrollX;

                // Determine the maximum x position
                //注意,不包括getRenderWidth()
                mMaxScrollX = mCurrentScrollX + (rightView.getRight() - getPaddingLeft()) - getRenderWidth();

                // Handle the case where the views do not fill at least 1 screen
                if (mMaxScrollX < 0) {
                    mMaxScrollX = 0;
                }

                if (mMaxScrollX != oldMaxX) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Adds children views to the left and right of the current views until the screen is full
     */
    private void fillList(final int dx) {
        // Get the rightmost child and determine its right edge
        int edge = 0;
        View child = getRightmostChild();
        if (child != null) {
            edge = child.getRight();
        }

        // Add new children views to the right, until past the edge of the screen
        fillListRight(edge, dx);//如果最右边空了,就填充,直到最右边填满屏幕为止

        // Get the leftmost child and determine its left edge
        edge = 0;
        child = getLeftmostChild();
        if (child != null) {
            edge = child.getLeft();
        }

        // Add new children views to the left, until past the edge of the screen
        //如果最左边空了,就填充,直到最左边填满屏幕为止
        fillListLeft(edge, dx);
    }

    private void removeNonVisibleChildren(final int dx) {
        View child = getLeftmostChild();
        //移除左边的滑出屏幕的view,直到最左边的view还可见,就停止
        // Loop removing the leftmost child, until that child is on the screen
        while (child != null && child.getRight() + dx <= 0) {
            // The child is being completely removed so remove its width from the display offset and its divider if it has one.
            // To remove add the size of the child and its divider (if it has one) to the offset.
            // You need to add since its being removed from the left side, i.e. shifting the offset to the right.

            // After this, mDisplayOffset will be a small negative and next to zero value.
            mLeftVisibleViewDisplayOffset += isLastItemInAdapter(mLeftVisibleViewAdapterIndex) ? child.getMeasuredWidth() : mDividerWidth + child.getMeasuredWidth();
            Log.e("zzz", "move left mLeftVisibleViewDisplayOffset " + mLeftVisibleViewDisplayOffset);
            // Add the removed view to the cache
            recycleView(mLeftVisibleViewAdapterIndex, child);

            // Actually remove the view
            removeViewInLayout(child);

            // Keep track of the adapter index of the left most child
            mLeftVisibleViewAdapterIndex++;

            // Get the new leftmost child
            child = getLeftmostChild();
        }

        child = getRightmostChild();
        //移除右边的滑出屏幕的view,直到最右边的view还可见,就停止
        // Loop removing the rightmost child, until that child is on the screen
        while (child != null && child.getLeft() + dx >= getWidth()) {
            recycleView(mRightVisibleViewAdapterIndex, child);
            removeViewInLayout(child);
            mRightVisibleViewAdapterIndex--;
            child = getRightmostChild();
        }
    }

    private void fillListRight(int rightEdge, final int dx) {
        // Loop adding views to the right until the screen is filled
        while (rightEdge + dx + mDividerWidth < getWidth() && mRightVisibleViewAdapterIndex + 1 < mAdapter.getCount()) {
            mRightVisibleViewAdapterIndex++;

            // If mLeftViewAdapterIndex < 0 then this is the first time a view is being added, and left == right
            if (mLeftVisibleViewAdapterIndex < 0) {
                mLeftVisibleViewAdapterIndex = mRightVisibleViewAdapterIndex;
            }

            // Get the view from the adapter, utilizing a cached view if one is available
            //我们写的adapter的getView,第一个参数是position,第二个参数是convertView,第三个参数是ViewGroup
            View child = mAdapter.getView(mRightVisibleViewAdapterIndex, getRecycledView(mRightVisibleViewAdapterIndex), this);
            addAndMeasureChild(child, INSERT_AT_END_OF_LIST);

            // If first view, then no divider to the left of it, otherwise add the space for the divider width
            rightEdge += (mRightVisibleViewAdapterIndex == 0 ? 0 : mDividerWidth) + child.getMeasuredWidth();

            // Check if we are running low on data so we can tell listeners to go get more
            determineIfLowOnData();
        }
    }

    private void fillListLeft(int leftEdge, final int dx) {
        // Loop adding views to the left until the screen is filled
        while (leftEdge + dx - mDividerWidth > 0 && mLeftVisibleViewAdapterIndex >= 1) {
            mLeftVisibleViewAdapterIndex--;
            View child = mAdapter.getView(mLeftVisibleViewAdapterIndex, getRecycledView(mLeftVisibleViewAdapterIndex), this);
            addAndMeasureChild(child, INSERT_AT_START_OF_LIST);

            // If first view, then no divider to the left of it
            leftEdge -= mLeftVisibleViewAdapterIndex == 0 ? child.getMeasuredWidth() : mDividerWidth + child.getMeasuredWidth();

            // If on a clean edge then just remove the child, otherwise remove the divider as well
            mLeftVisibleViewDisplayOffset -= leftEdge + dx <= 0 ? child.getMeasuredWidth() : mDividerWidth + child.getMeasuredWidth();
            Log.e("zzz", "move right mLeftVisibleViewDisplayOffset " + mLeftVisibleViewDisplayOffset);
        }
    }

    /**
     * Loops through each child and positions them onto the screen
     */
    private void positionChildren(final int dx) {
        int childCount = getChildCount();
        //这个方法就是item的摆放逻辑了,从做摆放到右,用到mLeftVisibleViewDisplayOffset这个值
        //这里需要强调一下,layout的过程,可用的宽度只有屏幕的大小,这个坐标也是相对于屏幕的
        //而mCurrentScrollX则是逻辑上的
        //其实这个逻辑就是,将item整体移动dx具体,可能向左,可能向右,取决于dx的正负
        if (childCount > 0) {
            mLeftVisibleViewDisplayOffset += dx;
            int leftOffset = mLeftVisibleViewDisplayOffset;

            // Loop each child view
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                int left = leftOffset + getPaddingLeft();//不要在paddingLeft的位置渲染哦
                int top = getPaddingTop();
                int right = left + child.getMeasuredWidth();
                int bottom = top + child.getMeasuredHeight();

                // Layout the child
                child.layout(left, top, right, bottom);

                // Increment our offset by added child‘s size and divider width
                leftOffset += child.getMeasuredWidth() + mDividerWidth;
            }
        }
    }

    /**
     * Gets the current child that is leftmost on the screen.
     */
    private View getLeftmostChild() {
        return getChildAt(0);
    }

    /**
     * Gets the current child that is rightmost on the screen.
     */
    private View getRightmostChild() {
        return getChildAt(getChildCount() - 1);
    }

    /**
     * Finds a child view that is contained within this view, given the adapter index.
     *
     * @return View The child view, or or null if not found.
     */
    private View getChild(int adapterIndex) {
        if (adapterIndex >= mLeftVisibleViewAdapterIndex && adapterIndex <= mRightVisibleViewAdapterIndex) {
            return getChildAt(adapterIndex - mLeftVisibleViewAdapterIndex);
        }

        return null;
    }

    /**
     * Returns the index of the child that contains the coordinates given.
     * This is useful to determine which child has been touched.
     * This can be used for a call to {@link #getChildAt(int)}
     *
     * @param x X-coordinate
     * @param y Y-coordinate
     * @return The index of the child that contains the coordinates. If no child is found then returns -1
     */
    private int getChildIndex(final int x, final int y) {
        int childCount = getChildCount();

        for (int index = 0; index < childCount; index++) {
            getChildAt(index).getHitRect(mRect);
            if (mRect.contains(x, y)) {
                return index;
            }
        }

        return -1;
    }

    /**
     * Simple convenience method for determining if this index is the last index in the adapter
     */
    private boolean isLastItemInAdapter(int index) {
        return index == mAdapter.getCount() - 1;
    }

    /**
     * Gets the height in px this view will be rendered. (padding removed)
     */
    private int getRenderHeight() {
        return getHeight() - getPaddingTop() - getPaddingBottom();
    }

    /**
     * Gets the width in px this view will be rendered. (padding removed)
     */
    private int getRenderWidth() {
        return getWidth() - getPaddingLeft() - getPaddingRight();
    }

    /**
     * Scroll to the provided offset
     */
    public void scrollTo(int x) {
        mFlingTracker.startScroll(mNextScrollX, 0, x - mNextScrollX, 0);
        setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_FLING);
        requestLayout();
    }

    @Override
    public int getFirstVisiblePosition() {
        return mLeftVisibleViewAdapterIndex;
    }

    @Override
    public int getLastVisiblePosition() {
        return mRightVisibleViewAdapterIndex;
    }

    /**
     * Draws the overscroll edge glow effect on the left and right sides of the horizontal list
     */
    private void drawEdgeGlow(Canvas canvas) {
        if (mEdgeGlowLeft != null && !mEdgeGlowLeft.isFinished() && isEdgeGlowEnabled()) {
            // The Edge glow is meant to come from the top of the screen, so rotate it to draw on the left side.
            final int restoreCount = canvas.save();
            final int height = getHeight();

            canvas.rotate(-90, 0, 0);
            canvas.translate(-height + getPaddingBottom(), 0);

            mEdgeGlowLeft.setSize(getRenderHeight(), getRenderWidth());
            if (mEdgeGlowLeft.draw(canvas)) {
                invalidate();
            }

            canvas.restoreToCount(restoreCount);
        } else if (mEdgeGlowRight != null && !mEdgeGlowRight.isFinished() && isEdgeGlowEnabled()) {
            // The Edge glow is meant to come from the top of the screen, so rotate it to draw on the right side.
            final int restoreCount = canvas.save();
            final int width = getWidth();

            canvas.rotate(90, 0, 0);
            canvas.translate(getPaddingTop(), -width);
            mEdgeGlowRight.setSize(getRenderHeight(), getRenderWidth());
            if (mEdgeGlowRight.draw(canvas)) {
                invalidate();
            }

            canvas.restoreToCount(restoreCount);
        }
    }

    /**
     * Draws the dividers that go in between the horizontal list view items
     */
    private void drawDividers(Canvas canvas) {
        final int count = getChildCount();

        // Only modify the left and right in the loop, we set the top and bottom here since they are always the same
        final Rect bounds = mRect;
        mRect.top = getPaddingTop();
        mRect.bottom = mRect.top + getRenderHeight();

        // Draw the list dividers
        for (int i = 0; i < count; i++) {
            // Don‘t draw a divider to the right of the last item in the adapter
            if (!(i == count - 1 && isLastItemInAdapter(mRightVisibleViewAdapterIndex))) {
                View child = getChildAt(i);

                bounds.left = child.getRight();
                bounds.right = child.getRight() + mDividerWidth;

                // Clip at the left edge of the screen
                if (bounds.left < getPaddingLeft()) {
                    bounds.left = getPaddingLeft();
                }

                // Clip at the right edge of the screen
                if (bounds.right > getWidth() - getPaddingRight()) {
                    bounds.right = getWidth() - getPaddingRight();
                }

                // Draw a divider to the right of the child
                drawDivider(canvas, bounds);

                // If the first view, determine if a divider should be shown to the left of it.
                // A divider should be shown if the left side of this view does not fill to the left edge of the screen.
                if (i == 0 && child.getLeft() > getPaddingLeft()) {
                    bounds.left = getPaddingLeft();
                    bounds.right = child.getLeft();
                    drawDivider(canvas, bounds);
                }
            }
        }
    }

    /**
     * Draws a divider in the given bounds.
     *
     * @param canvas The canvas to draw to.
     * @param bounds The bounds of the divider.
     */
    private void drawDivider(Canvas canvas, Rect bounds) {
        if (mDividerDrawable != null) {
            mDividerDrawable.setBounds(bounds);
            mDividerDrawable.draw(canvas);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawDividers(canvas);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        drawEdgeGlow(canvas);
    }

    @Override
    protected void dispatchSetPressed(boolean pressed) {
        // Don‘t dispatch setPressed to our children. We call setPressed on ourselves to
        // get the selector in the right state, but we don‘t want to press each child.
    }

    /**
     * 检测到fling事件,启动scroller,并且触发onLayout,就会触发整个fling操作
     */
    protected boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        //            fling(startX,startY,velocityX,velocityY,minX, int maxX, int minY, int maxY)
        mFlingTracker.fling(mNextScrollX, 0, (int) -velocityX, 0, 0, mMaxScrollX, 0, 0);
        setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_FLING);
        requestLayout();
        return true;
    }

    protected boolean onDown(MotionEvent e) {
        // If the user just caught a fling, then disable all touch actions until they release their finger
        mBlockTouchAction = !mFlingTracker.isFinished();

        // Allow a finger down event to catch a fling
        //滑动过程中,只要触摸就停止滑动
        mFlingTracker.forceFinished(true);
        setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);

        unpressTouchedChild();

        if (!mBlockTouchAction) {
            // Find the child that was pressed
            final int index = getChildIndex((int) e.getX(), (int) e.getY());
            if (index >= 0) {
                // Save off view being touched so it can later be released
                mViewBeingTouched = getChildAt(index);

                if (mViewBeingTouched != null) {
                    // Set the view as pressed
                    mViewBeingTouched.setPressed(true);
                    refreshDrawableState();
                }
            }
        }

        return true;
    }

    /**
     * If a view is currently pressed then unpress it
     */
    private void unpressTouchedChild() {
        if (mViewBeingTouched != null) {
            // Set the view as not pressed
            mViewBeingTouched.setPressed(false);
            refreshDrawableState();

            // Null out the view so we don‘t leak it
            mViewBeingTouched = null;
        }
    }

    private class GestureListener extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onDown(MotionEvent e) {
            return HorizontalListView2.this.onDown(e);
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            return HorizontalListView2.this.onFling(e1, e2, velocityX, velocityY);
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            // Lock the user into interacting just with this view
            requestParentListViewToNotInterceptTouchEvents(true);

            setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_TOUCH_SCROLL);
            unpressTouchedChild();
            //触摸滚动的时候,计算mNextScrollX,然后触发onLayout进行重绘,触摸滑动停止,重新布局停止
            mNextScrollX += (int) distanceX;// key operation.
            updateOverscrollAnimation(Math.round(distanceX));
            requestLayout();

            return true;
        }

        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            unpressTouchedChild();
            OnItemClickListener onItemClickListener = getOnItemClickListener();

            final int index = getChildIndex((int) e.getX(), (int) e.getY());

            // If the tap is inside one of the child views, and we are not blocking touches
            if (index >= 0 && !mBlockTouchAction) {
                View child = getChildAt(index);
                int adapterIndex = mLeftVisibleViewAdapterIndex + index;

                if (onItemClickListener != null) {
                    onItemClickListener.onItemClick(HorizontalListView2.this, child, adapterIndex, mAdapter.getItemId(adapterIndex));
                    return true;
                }
            }

            if (mOnClickListener != null && !mBlockTouchAction) {
                mOnClickListener.onClick(HorizontalListView2.this);
            }

            return false;
        }

        @Override
        public void onLongPress(MotionEvent e) {
            unpressTouchedChild();

            final int index = getChildIndex((int) e.getX(), (int) e.getY());
            if (index >= 0 && !mBlockTouchAction) {
                View child = getChildAt(index);
                OnItemLongClickListener onItemLongClickListener = getOnItemLongClickListener();
                if (onItemLongClickListener != null) {
                    int adapterIndex = mLeftVisibleViewAdapterIndex + index;
                    boolean handled = onItemLongClickListener.onItemLongClick(HorizontalListView2.this, child, adapterIndex, mAdapter
                            .getItemId(adapterIndex));

                    if (handled) {
                        // BZZZTT!!1!
                        performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
                    }
                }
            }
        }
    }

    ;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // Detect when the user lifts their finger off the screen after a touch
        if (event.getAction() == MotionEvent.ACTION_UP) {
            // If not flinging then we are idle now. The user just finished a finger scroll.
            if (mFlingTracker == null || mFlingTracker.isFinished()) {
                setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);
            }

            // Allow the user to interact with parent views
            requestParentListViewToNotInterceptTouchEvents(false);

            releaseEdgeGlow();
        } else if (event.getAction() == MotionEvent.ACTION_CANCEL) {
            unpressTouchedChild();
            releaseEdgeGlow();

            // Allow the user to interact with parent views
            requestParentListViewToNotInterceptTouchEvents(false);
        }

        return super.onTouchEvent(event);
    }

    /**
     * Release the EdgeGlow so it animates
     */
    private void releaseEdgeGlow() {
        if (mEdgeGlowLeft != null) {
            mEdgeGlowLeft.onRelease();
        }

        if (mEdgeGlowRight != null) {
            mEdgeGlowRight.onRelease();
        }
    }

    /**
     * Sets a listener to be called when the HorizontalListView has been scrolled to a point where it is
     * running low on data. An example use case is wanting to auto download more data when the user
     * has scrolled to the point where only 10 items are left to be rendered off the right of the
     * screen. To get called back at that point just register with this function with a
     * numberOfItemsLeftConsideredLow value of 10. <br>
     * <br>
     * This will only be called once to notify that the HorizontalListView is running low on data.
     * Calling notifyDataSetChanged on the adapter will allow this to be called again once low on data.
     *
     * @param listener                       The listener to be notified when the number of array adapters items left to
     *                                       be shown is running low.
     * @param numberOfItemsLeftConsideredLow The number of array adapter items that have not yet
     *                                       been displayed that is considered too low.
     */
    public void setRunningOutOfDataListener(RunningOutOfDataListener listener, int numberOfItemsLeftConsideredLow) {
        mRunningOutOfDataListener = listener;
        mRunningOutOfDataThreshold = numberOfItemsLeftConsideredLow;
    }

    /**
     * This listener is used to allow notification when the HorizontalListView is running low on data to display.
     */
    public static interface RunningOutOfDataListener {
        /**
         * Called when the HorizontalListView is running out of data and has reached at least the provided threshold.
         */
        void onRunningOutOfData();
    }

    /**
     * Determines if we are low on data and if so will call to notify the listener, if there is one,
     * that we are running low on data.
     */
    private void determineIfLowOnData() {
        // Check if the threshold has been reached and a listener is registered
        if (mRunningOutOfDataListener != null && mAdapter != null &&
                mAdapter.getCount() - (mRightVisibleViewAdapterIndex + 1) < mRunningOutOfDataThreshold) {

            // Prevent notification more than once
            if (!mHasNotifiedRunningLowOnData) {
                mHasNotifiedRunningLowOnData = true;
                mRunningOutOfDataListener.onRunningOutOfData();
            }
        }
    }

    /**
     * Register a callback to be invoked when the HorizontalListView has been clicked.
     *
     * @param listener The callback that will be invoked.
     */
    @Override
    public void setOnClickListener(OnClickListener listener) {
        mOnClickListener = listener;
    }

    /**
     * Interface definition for a callback to be invoked when the view scroll state has changed.
     */
    public interface OnScrollStateChangedListener {
        public enum ScrollState {
            /**
             * The view is not scrolling. Note navigating the list using the trackball counts as being
             * in the idle state since these transitions are not animated.
             */
            SCROLL_STATE_IDLE,

            /**
             * The user is scrolling using touch, and their finger is still on the screen
             */
            SCROLL_STATE_TOUCH_SCROLL,

            /**
             * The user had previously been scrolling using touch and had performed a fling. The
             * animation is now coasting to a stop
             */
            SCROLL_STATE_FLING
        }

        /**
         * Callback method to be invoked when the scroll state changes.
         *
         * @param scrollState The current scroll state.
         */
        public void onScrollStateChanged(ScrollState scrollState);
    }

    /**
     * Sets a listener to be invoked when the scroll state has changed.
     *
     * @param listener The listener to be invoked.
     */
    public void setOnScrollStateChangedListener(OnScrollStateChangedListener listener) {
        mOnScrollStateChangedListener = listener;
    }

    /**
     * Call to set the new scroll state.
     * If it has changed and a listener is registered then it will be notified.
     */
    private void setCurrentScrollState(OnScrollStateChangedListener.ScrollState newScrollState) {
        // If the state actually changed then notify listener if there is one
        if (mCurrentScrollState != newScrollState && mOnScrollStateChangedListener != null) {
            mOnScrollStateChangedListener.onScrollStateChanged(newScrollState);
        }

        mCurrentScrollState = newScrollState;
    }

    /**
     * Updates the over scroll animation based on the scrolled offset.
     *
     * @param scrolledOffset The scroll offset
     */
    private void updateOverscrollAnimation(final int scrolledOffset) {
        if (mEdgeGlowLeft == null || mEdgeGlowRight == null) return;

        // Calculate where the next scroll position would be
        int nextScrollPosition = mCurrentScrollX + scrolledOffset;

        // If not currently in a fling (Don‘t want to allow fling offset updates to cause over scroll animation)
        if (mFlingTracker == null || mFlingTracker.isFinished()) {
            // If currently scrolled off the left side of the list and the adapter is not empty
            if (nextScrollPosition < 0) {

                // Calculate the amount we have scrolled since last frame
                int overscroll = Math.abs(scrolledOffset);

                // Tell the edge glow to redraw itself at the new offset
                mEdgeGlowLeft.onPull((float) overscroll / getRenderWidth());

                // Cancel animating right glow
                if (!mEdgeGlowRight.isFinished()) {
                    mEdgeGlowRight.onRelease();
                }
            } else if (nextScrollPosition > mMaxScrollX) {
                // Scrolled off the right of the list

                // Calculate the amount we have scrolled since last frame
                int overscroll = Math.abs(scrolledOffset);

                // Tell the edge glow to redraw itself at the new offset
                mEdgeGlowRight.onPull((float) overscroll / getRenderWidth());

                // Cancel animating left glow
                if (!mEdgeGlowLeft.isFinished()) {
                    mEdgeGlowLeft.onRelease();
                }
            }
        }
    }

    /**
     * Checks if the edge glow should be used enabled.
     * The glow is not enabled unless there are more views than can fit on the screen at one time.
     */
    private boolean isEdgeGlowEnabled() {
        if (mAdapter == null || mAdapter.isEmpty()) return false;

        // If the maxx is more then zero then the user can scroll, so the edge effects should be shown
        return mMaxScrollX > 0;
    }

    @TargetApi(11)
    /** Wrapper class to protect access to API version 11 and above features */
    private static final class HoneycombPlus {
        static {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
                throw new RuntimeException("Should not get to HoneycombPlus class unless sdk is >= 11!");
            }
        }

        /**
         * Sets the friction for the provided scroller
         */
        public static void setFriction(Scroller scroller, float friction) {
            if (scroller != null) {
                scroller.setFriction(friction);
            }
        }
    }

    @TargetApi(14)
    /** Wrapper class to protect access to API version 14 and above features */
    private static final class IceCreamSandwichPlus {
        static {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                throw new RuntimeException("Should not get to IceCreamSandwichPlus class unless sdk is >= 14!");
            }
        }

        /**
         * Gets the velocity for the provided scroller
         */
        public static float getCurrVelocity(Scroller scroller) {
            return scroller.getCurrVelocity();
        }
    }



    public void move(int l) {
        synchronized (HorizontalListView2.this) {
            mFlingTracker.fling(mNextScrollX, 0, (int) l, 0, 0, mMaxScrollX, 0, 0);
        }
        requestLayout();
    }

 

注释也很详细,然后看调用代码

public class Main3333_BF extends Activity {
    private DemoAdapter adapter;
    private List<String> courseList;
    private ListView listViewsss;
    private HorizontalListView2 listView2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initListView();
    }

    @TargetApi(Build.VERSION_CODES.M)
    private void initListView() {
        listView2 = findViewById(R.id.history_list2);
        courseList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            courseList.add("编号" + i);
        }
        adapter = new DemoAdapter(this, courseList);
        listView2.setAdapter(adapter);
        listScrollUp();


        /*设置滑动未停止时*/
        listView2.setOnScrollStateChangedListener(new HorizontalListView2.OnScrollStateChangedListener() {
            @Override
            public void onScrollStateChanged(ScrollState scrollState) {
                switch (scrollState) {
                    case SCROLL_STATE_IDLE://滑动停止时
                        // 触摸抬起时的操作
                        handler.postDelayed(run_scroll_up, 0);
                        break;
                    case SCROLL_STATE_FLING://惯性滑动时
                        // 触摸移动时的操作
                        handler.removeCallbacks(run_scroll_up);
                        break;
                    case SCROLL_STATE_TOUCH_SCROLL://正在滚动
                        // 触摸移动时的操作
                        handler.removeCallbacks(run_scroll_up);
                        break;
                }
            }
        });


    }

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            handler.removeCallbacks(run_scroll_up);
        }
    };

    /**
     * 向上滚动
     */
    public void listScrollUp() {
        handler.postDelayed(run_scroll_up, 0);
    }

    Runnable run_scroll_up = new Runnable() {
        @Override
        public void run() {
            /**
             * public void smoothScrollBy (int distance, int duration)
             *
             * Added in API level 8 Smoothly scroll by distance pixels over duration milliseconds.
             *
             * Pameters
             *    ar distance Distance to scroll in pixels.
             *     duration Duration of the scroll animation in milliseconds.
             */
//            listView.smoothScrollBy(1, 8);
            listView2.move(50);
            handler.postDelayed(run_scroll_up, 10);
        }
    };
}

解决滑动冲突可以使用

home_head_lv_hzpp_list.getParent().getParent().requestDisallowInterceptTouchEvent(true);

 

以上是关于HorizontalListView横向自动无限循环滚动效果的主要内容,如果未能解决你的问题,请参考以下文章

去除上下移动冲突,横向ListView

横向滑动的listview和其中用到的触摸监听事件详解

使用HorizontalListView仿车来了公交时刻表

使用HorizontalListView仿车来了公交时刻表

RecyclerView里面嵌套一个无限循环的横向列表该怎么做

js 实现图片无限横向滚动效果