2018May01_ListView实现横向滚动

1Requirement

  • 有一个竖向滑动的列表和一个表头,表头每列对应显示列表项;
  • 列表的每行从第二列开始可以横向滚动,列表头的每项也可以横向滚动;
  • 表头和每行滚动一致

2 Effect Picture

这里写图片描述

3 Demo

ListHorizontalScrollActivity

4 Theory

  • ListView的addHeaderView实现列表加表头
  • 重写ListView的触摸事件,监听按下、抬起、移动、惯性滑动事件,定义VelocityTracker获取手指滑动速度
  • 将滚动事件的参数封装到一个ScrollerCompat中去
  • 表头和表项包含一个可以滚动的自定义控件
  • 自定义控件获取ScrollerCompat,设置位置

    注意:记录滚动的位置,ListView向下滑动时,itemView要设置到相应位置

5 CoreCode

(1) 监听ListView的滚动事件,获取事件类型、元素(方向、速度、距离)

/**
 * Created by Ray on 2018/5/10.
 * 获取手势:方向、速度、距离
 */

public class HSListView extends ListView {
    public HSListView(Context context) {
        super(context);
    }

    public HSListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

    private HScrollListViewListener mListener;
    /**
     * 紀錄手指按下時的x座標
     */
    private float xDown;
    /**
     * 紀錄手指按下時的y座標
     */
    private float yDown;
    /**
     * 紀錄手指移動時的x坐標
     */
    private float xMove;
    /**
     * 紀錄手指移動時的y座標
     */
    private float yMove;
    /**
     * 紀錄手指放開時的x座標
     */
    private float xUp;
    /**
     * 紀錄手指放開時的y座標
     */
    private float yUp;
    /**
     * 計算手指滑動的速度
     */
    private VelocityTracker mVelocityTracker;
    /**
     * X軸滑動到這個距離觸發水平滑動,暫停垂直滑動
     * <li>預設30
     */
    private int touchXSlop = 30;
    /**
     * Y軸滑動到這個距離觸發垂直滑動,暫停水平滑動
     * <li>預設30
     */
    private int touchYSlop = 30;
    private int REBOUND_DISTANCE_X = 30; //scroller X軸反彈效果的距離
    private int distanceY;

    private boolean isSliding, isPulling;

    public void setOnListener(HScrollListViewListener listener) {
        mListener = listener;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        createVelocityTracker(ev);
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                xDown = ev.getRawX();
                yDown = ev.getRawY();
                xMove = xDown;
                yMove = yDown;
                isSliding = false;
                isPulling = false;
                if (mListener != null) {
                    mListener.onTouchDown(ev.getX(), ev.getY());
                }
                break;
            case MotionEvent.ACTION_MOVE:
                //先判斷是否上下滑動,若上下滑動就鎖死左右滑動
                if (!isSliding && Math.abs(yDown - ev.getRawY()) >= touchYSlop) {
                    isSliding = false;
                    isPulling = true;
                }
                //若不是上下滑動,就判斷是否左右滑動,是的話就鎖死上下滑動
                if (!isPulling && Math.abs(xDown - ev.getRawX()) >= touchXSlop) {
                    isSliding = true;
                    isPulling = false;
                }
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                return super.onInterceptTouchEvent(ev);
            case MotionEvent.ACTION_MOVE:
                if (isSliding && !isPulling) {
                    return true;
                } else {
                    return super.onInterceptTouchEvent(ev);
                }
            case MotionEvent.ACTION_UP:
                if (isSliding && !isPulling) {
                    return true;
                } else {
                    return super.onInterceptTouchEvent(ev);
                }
            default:
                super.onInterceptTouchEvent(ev);
                break;
        }
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                return super.onTouchEvent(event);
            case MotionEvent.ACTION_MOVE:
                int moveDistanceX = (int) (xMove - event.getRawX());
                distanceY = (int) (yMove - event.getRawY());
                xMove = event.getRawX();
                yMove = event.getRawY();
                if (mListener != null && isSliding && !isPulling && xDown > 0 ) {
                    mListener.onSliding(moveDistanceX);
                    return true;
                } else {
                    return super.onTouchEvent(event);
                }
            case MotionEvent.ACTION_UP:
                xUp = event.getX();
                yUp = event.getY();
                if (mListener != null) {
                    mListener.onTouchUp(xUp, yUp);
                }

            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_OUTSIDE:
                if (mListener != null && isSliding && xDown > 0 ) {
                    mListener.onFling(0, 0, -getScrollVelocity() * 2 / 3, 0, 0, 0, 0, 0, REBOUND_DISTANCE_X, 0);
                    isSliding = false;
                    isPulling = false;
                    recycleVelocityTracker();
                    return true;
                } else {
                    return super.onTouchEvent(event);
                }
            default:
                super.onTouchEvent(event);
                break;
        }
        return true;
    }


    /**
     * 獲取手指在右邊layout的監聽View上的滑動速度
     * @return 滑動速度,以每秒鐘移動了多少像素為單位
     */
    private int getScrollVelocity() {
        mVelocityTracker.computeCurrentVelocity(1000);
        return (int) mVelocityTracker.getXVelocity();
    }

    /**
     * 回收VelocityTracker
     */
    private void recycleVelocityTracker() {
        if (mVelocityTracker != null) {
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    }

    private void createVelocityTracker(MotionEvent event) {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);
    }

    public  interface HScrollListViewListener {

        void onTouchDown(float x, float y);

        void onSliding(int moveDistanceX);

        void onFling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY, int overX, int overY);

        void onTouchUp(float x, float y);

    }
}

(2)可以滚动的自定义控件

/**
 * Created by Ray on 2018/5/10.
 * 横向滚动的表头和List 的item中可以滚动的组件
 */

public class HScrollLayout extends LinearLayout {

    private ScrollerCompat mScrollerCompat;

    public HScrollLayout(Context context) {
        super(context);
    }

    public HScrollLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public HScrollLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mScrollerCompat != null && mScrollerCompat.computeScrollOffset()) {
            scrollTo(mScrollerCompat.getCurrX(), 0);
            postInvalidate();
        }
    }

    public void setScroller(ScrollerCompat scroller) {
        mScrollerCompat = scroller;
    }
}

(3)实现监听手势,设置UI

scrollerCompat = ScrollerCompat.create(this);
        hsl.setScroller(scrollerCompat);
        mAdapter.setScroller(scrollerCompat);

        lv.setOnListener(new HSListView.HScrollListViewListener() {
            @Override
            public void onTouchDown(float x, float y) {
                scrollXPos = scrollerCompat.getCurrX();
                scrollerCompat.abortAnimation();

                if (scrollXPos < 0) {
                    scrollXPos = 0;
                } else if (scrollXPos >= hScrollMaxWidth - UtilsDensity.dp2px(getApplicationContext(), 3)) {
                    scrollXPos = hScrollMaxWidth;
                }

                setViewPosition(scrollXPos);
            }

            @Override
            public void onSliding(int moveDistanceX) {
                scrollXPos = scrollXPos + moveDistanceX;
                if (scrollXPos < 0) {
                    scrollXPos = 0;
                } else if (scrollXPos > hScrollMaxWidth) {
                    scrollXPos = hScrollMaxWidth;
                }

                setViewPosition(scrollXPos);
            }

            @Override
            public void onFling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY, int overX, int overY) {
                if (hsl.getScrollX() <= 0 && velocityX <= 0) {
                    scrollerCompat.fling(0, startY, 0, velocityY, minX, hScrollMaxWidth, minY, maxY, overX, overY);
                } else if (hsl.getScrollX() >= hScrollMaxWidth && velocityX >= 0) {
                    scrollerCompat.fling(hScrollMaxWidth, startY, 0, velocityY, minX, hScrollMaxWidth, minY, maxY, overX, overY);
                } else {
                    scrollerCompat.fling(hsl.getScrollX(), startY, velocityX, velocityY, minX, hScrollMaxWidth, minY, maxY, overX, overY);
                }

                hsl.postInvalidate();
                for (int i = 0; i < lv.getChildCount(); i++) {
                    if (lv.getChildAt(i).findViewById(R.id.hsl) != null) {
                        lv.getChildAt(i).findViewById(R.id.hsl).postInvalidate();
                    }
                }
                scrollXPos = scrollerCompat.getFinalX();
                if (mAdapter != null) {
                    mAdapter.setScrollXPos(scrollXPos);
                }
            }

            @Override
            public void onTouchUp(float x, float y) {
                scrollXPos = hsl.getScrollX();
                if (scrollXPos < 0) {
                    scrollXPos = 0;
                } else if (scrollXPos > hScrollMaxWidth) {
                    scrollXPos = hScrollMaxWidth;
                }
                setViewPosition(scrollXPos);
            }

            private void setViewPosition(int distance){
                hsl.scrollTo(distance, 0);
                for (int i = 0; i < lv.getChildCount(); i++) {
                    if (lv.getChildAt(i).findViewById(R.id.hsl) != null) {
                        lv.getChildAt(i).findViewById(R.id.hsl).scrollTo(distance, 0);
                    }
                }

                if (mAdapter != null) {
                    mAdapter.setScrollXPos(distance);
                }
            }
        });

(4)item实现滚动

 @Override
    public View getView(int position, View convertView, ViewGroup parent) {
      ...

        HScrollLayout hScrollLayout = convertView.findViewById(R.id.hsl);
        if(hScrollLayout != null && mScrollerCompat != null) {
            hScrollLayout.setScroller(mScrollerCompat);
        }
        if (scrollXPos != 0) {
            hScrollLayout.scrollTo(scrollXPos,0);
        } else {
            if(mScrollerCompat != null) {
                hScrollLayout.scrollTo(mScrollerCompat.getCurrX(), 0);
            }
        }


        return convertView;
    }

    public void setScrollXPos(int pos){
        this.scrollXPos = pos;
    }

猜你喜欢

转载自blog.csdn.net/baopengjian/article/details/80283786