Android横向滑动加载更多的控件的实现---HorizontalScrollSlideView

Android横向滑动加载更多的控件的实现—HorizontalScrollSlideView

需求

之前公司业务要求做一个横向滑动的,可以加载更多的控件,第一时间想到的就是 RecyclerView 来实现 ,后面仔细想想滑动拦截不好控制等等
所以就换了个思路来实现了。

思路:

控件继承自LinearLayout,外面包裹一层HorizontalScrollView,并重写dispatchTouchEvent()事件,当横向滑动到LinearLayout的右边缘滑动到控件的右边缘时,将隐藏的侧拉头跟随手势慢慢拉出。这中间伴随着侧拉头的状态实时的改变。松手时,如果侧拉的距离已经足够多,则回调
OnSlideBottomListener 。当回调结束时,回弹底部箭头,让侧拉头再次隐藏。 

大概的思路是这样的:

这里写图片描述

先上效果图:

效果图

先说件很操蛋的事情就是 HorizontalScrollView的滑动监听事件是protected为了在外面能拿到这个滑动监听,所以先把 这个控件重写了把滑动事件先回调回来。

public class ObservableScrollView extends HorizontalScrollView {
    private OnScrollChangedListener onScrollChangedListener;

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

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

    public ObservableScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setOnScrollListener(OnScrollChangedListener onScrollChangedListener) {
        this.onScrollChangedListener = onScrollChangedListener;
    }

    @Override
    protected void onScrollChanged(int x, int y, int oldX, int oldY) {
        super.onScrollChanged(x, y, oldX, oldY);
        if (onScrollChangedListener != null) {
            onScrollChangedListener.onScrollChanged(x, y, oldX, oldY);
        }
    }

    public interface OnScrollChangedListener {
         void onScrollChanged(int x, int y, int oldX, int oldY);
    }
}

接下来便是我们的主要实现的了:

public class HorizontalScrollSlideView extends LinearLayout implements ObservableScrollView.OnScrollChangedListener {
    private static final String TAG = "ScrollSlideView";

    //移动触发步幅
    private final int MOVE_STRIDE = 6;
    //记录移动x
    private float mRecodX;
    //记录偏移量
    private float mOffsetX;

    //底部分界线位置
    private int mBottomParting;
    //底部展示区长度
    private int mBottomShow;
    //底部触发区长度
    private int mBottomAll;
    //是否有触摸
    private boolean isDown = false;

    private Handler mHandler;
    //滑动触发的监听
    private OnSlideBottomListener mOnSlideBottomListener;
    //内容外部的滑动view
    private ObservableScrollView mScroolView;
    //包裹内容view
    private LinearLayout mContentView;
    //底部展示view
    private View mBottomShowView;
    //底部触发到监听的view
    private View mBottomGoView;

    private boolean needScrollBottom = true;

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

    public HorizontalScrollSlideView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mHandler = new Handler();
        //LayoutInflater.from(context).inflate(R.layout.horizontal_scroll_slide_view, this);
        //LayoutInflater.from(context).inflate(R.layout.horizontal_scroll_slide_view_bottom, this);
        ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT);
        mScroolView = new ObservableScrollView(context);
        mContentView = new LinearLayout(context);
        mScroolView.setLayoutParams(lp);
        mContentView.setLayoutParams(new ViewGroup.LayoutParams(lp));
        mScroolView.addView(mContentView);
        mScroolView.setHorizontalScrollBarEnabled(false);
        addView(mScroolView);
    }

    /**
     * 设置滑动区的内容
     *
     * @param views
     */
    public void setContentViews(List<View> views) {
        mContentView.removeAllViews();
        for (View view : views) {
            mContentView.addView(view);
        }
    }


    public void setContentView(View view) {
        mContentView.removeAllViews();
        mContentView.addView(view);
    }


    public ViewGroup getContentContainer() {
        return mContentView;
    }

    /**
     * 设置触发goveiw的监听
     *
     * @param listener
     */
    public void setOnSlideBottomListener(OnSlideBottomListener listener) {
        mOnSlideBottomListener = listener;
    }

    /**
     * 覆盖后,返回自定义底部view
     *
     * @return 底部展现view
     */
    protected View getBottomShowView() {
        TextView textView = new TextView(getContext());
        textView.setText("继续滑动\n查看全部");
        textView.setGravity(Gravity.CENTER);
        textView.setClickable(false);
        textView.setEnabled(false);
//        textView.setBackgroundColor(getResources().getColor(R.color.colorPrimary));
        textView.setTextColor(getContext().getResources().getColor(R.color.gray_616161));
        ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(dp2px(100), ViewGroup.LayoutParams.MATCH_PARENT);
        textView.setLayoutParams(lp);
        return textView;
    }

    /**
     * 覆盖后,返回自定义底部触发view
     *
     * @return 底部触发view
     */
    protected View getBottomGoView() {
        TextView textView = new TextView(getContext());
        textView.setText("->");
        textView.setGravity(Gravity.CENTER);
//        textView.setBackgroundColor(getResources().getColor(R.color.colorAccent));
        textView.setTextColor(getContext().getResources().getColor(R.color.gray_616161));
        ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(dp2px(20), ViewGroup.LayoutParams.MATCH_PARENT);
        textView.setLayoutParams(lp);
        return textView;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
//        mScroolView = findViewById(R.id.sv);
//        mContentView = findViewById(R.id.content);
        //mBottomShowView = findViewById(R.id.bottom_show);
        //mBottomGoView = findViewById(R.id.bottom_go);
        mScroolView.setOnScrollListener(this);

        View showView = getBottomShowView();
        if (showView != null) {
            addView(showView);
            mBottomShowView = showView;
        }

        View goView = getBottomGoView();
        if (goView != null) {
            addView(goView);
            mBottomGoView = goView;
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mBottomShow = mBottomShowView.getWidth();
        mBottomAll = mBottomShow + mBottomGoView.getWidth();
        mBottomParting = mBottomShow / 2;
//        Log.i(TAG, "onmeassure: " + mBottomAll);
    }

    @Override
    public void onScrollChanged(int x, int y, int oldX, int oldY) {
        if (!isDown && x > oldX && isScrollBottom(true)) {
            setScrollX(mBottomShow);
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
//        Log.i(TAG, "dispatch: " + ev.getAction());
        if (isScrollBottom(true) || getScrollX() > 0) {
            handleTouch(ev);
        } else {
            mRecodX = ev.getX();
        }

        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            isDown = true;
        } else if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) {
            isDown = false;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //消费掉,保证dispatchTouchevent
        if (needScrollBottom) {
            ViewParent parent = this;
            while (!((parent = parent.getParent()) instanceof ViewPager))
                parent.requestDisallowInterceptTouchEvent(true);
        }
        return true;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean isIntercept = isScrollContentBottom() && ev.getAction() == MotionEvent.ACTION_MOVE;
//        Log.i(TAG, "onInterceptTouchEvent: " + ev.getAction() + "  isINtercept:" + isIntercept);
        if (isIntercept)
            getParent().requestDisallowInterceptTouchEvent(true);
        return isIntercept ? true : super.onInterceptTouchEvent(ev);
    }

    private boolean isScrollBottom(boolean isIncludeEqual) {
        int sx = mScroolView.getScrollX();
        int cwidth = mScroolView.getChildAt(0).getWidth();
        int pwidth = getWidth();
//        Log.i(TAG, "sx: " + sx + "cwidth: " + cwidth + "pwidth: " + pwidth);

        if (needScrollBottom)
            return isIncludeEqual ? sx + pwidth >= cwidth : sx + pwidth > cwidth;
        else
            return false;
    }

    public void setNeedScrollBottom(boolean needScrollBottom) {
        this.needScrollBottom = needScrollBottom;
    }


    private boolean isScrollContentBottom() {
        return getScrollX() > 0;
    }


    private boolean handleTouch(MotionEvent event) {

//        Log.i(TAG, "handletouch: " + event.getAction());
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mRecodX = event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                if (mRecodX == 0)
                    mRecodX = event.getX();
                //移动的距离
                mOffsetX = (event.getX() - mRecodX);
                //是否达移动最小值
                if (Math.abs(mOffsetX) < MOVE_STRIDE) {
                    return true;
                }
                //手指左滑
                boolean isLeft = event.getX() - mRecodX < 0;
                mRecodX = event.getX();
                if (isLeft && getScrollX() >= mBottomAll) {
                    setScrollX(mBottomAll);
                    //Log.i(TAG,"1");
                } else if (!isLeft && getScrollX() <= 0) {
                    setScrollX(0);
                    //Log.i(TAG,"2");
                } else {
                    setScrollX((int) (getScrollX() - mOffsetX));
                    //Log.i(TAG,"3");
                }
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                if (getScrollX() < mBottomParting) {
                    setScrollX(0);

                } else {
                    int delay = 0;
                    if (getScrollX() >= mBottomAll - MOVE_STRIDE) {
                        Log.i(TAG, "slide bottom!");
                        if (mOnSlideBottomListener != null) {
                            mOnSlideBottomListener.onSlideBottom();
                        }
                        delay = 1000;
                    }
                    mHandler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            setScrollX(mBottomShow);
                        }
                    }, delay);

                }
                break;
        }
        return true;
    }

    int dp2px(int dp) {
        return (int) (getContext().getResources().getDisplayMetrics().density * dp + 0.5f);
    }

    public interface OnSlideBottomListener {
        void onSlideBottom();
    }

}

使用起来也很简单,就不单独写demo了:

horScrollView.setContentView(contanteView);
horScrollView.setOnSlideBottomListener(new HorizontalScrollSlideView.OnSlideBottomListener() {
            @Override
            public void onSlideBottom() {
               //响应滑动查看更多的事件
            }
        });
了解了你的问题,以下是一个 Kotlin 实现横向滑动加载更多的示例代码,使用的是 SmartRefreshLayout 库中的 SmartRefreshHorizontal 组件: 在布局文件中添加 SmartRefreshHorizontal 组件: ```xml <com.scwang.smartrefresh.layout.SmartRefreshHorizontal android:id="@+id/refreshLayout" android:layout_width="match_parent" android:layout_height="wrap_content"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="wrap_content" /> </com.scwang.smartrefresh.layout.SmartRefreshHorizontal> ``` 在 Kotlin 中设置 SmartRefreshHorizontal 组件的相关属性和监听器: ```kotlin val refreshLayout = findViewById<SmartRefreshHorizontal>(R.id.refreshLayout) val recyclerView = findViewById<RecyclerView>(R.id.recyclerView) // 设置 Adapter 和 LayoutManager val adapter = MyAdapter() val layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false) recyclerView.adapter = adapter recyclerView.layoutManager = layoutManager // 监听刷新和加载更多事件 refreshLayout.setOnRefreshListener { // 刷新数据 adapter.refreshData() // 结束刷新 refreshLayout.finishRefresh() } refreshLayout.setOnLoadMoreListener { // 加载更多数据 adapter.loadMoreData() // 结束加载更多 refreshLayout.finishLoadMore() } ``` 其中,`MyAdapter` 是你自己实现的 RecyclerView Adapter,根据需要实现其中的 `refreshData()` 和 `loadMoreData()` 方法来进行数据刷新和加载更多的操作。 希望这个示例代码可以帮到你。如果还有问题或需要更多帮助,请随时提问。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值