RecycleView实现横向带指示器翻页滑动和越界回弹

首先看需求效果
这里写图片描述
在acvitity中有一个控件,需要实现这种分页效果,还要指示器,并且每页的两列不能太分散,使用GridView就很不好实现,这里用RecycleView展示,先看成型后需要用到的结构(需要用到哪些自定义的东西)
这里写图片描述
自己在Java下建一个新的包,需要这三个自定义的东西,一个用来展示GridView布局样式自定义的GridViewLayoutManger,一个指示器的自定义控件,第三个就是自定义RecleView,实现翻页效果,另外还有一个工具类,实现获取屏幕尺寸,并且让px和dp相互转换的功能,网上有很多,没有几行代码,之前也专门写过,不再赘述,接下来就看着三个自定义东西怎么写

1、AutoGridLayoutManager

package inditor.recycleview.horizontal;

import android.content.Context;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by YTF on 2017/9/16.
 */

public class AutoGridLayoutManager extends GridLayoutManager {
    private int measuredWidth = 0;
    private int measuredHeight = 0;

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

    public AutoGridLayoutManager(Context context, int spanCount) {
        super(context, spanCount);
    }

    public AutoGridLayoutManager(Context context, int spanCount,
                                 int orientation, boolean reverseLayout) {
        super(context, spanCount, orientation, reverseLayout);
    }

    @Override
    public void onMeasure(RecyclerView.Recycler recycler,
                          RecyclerView.State state, int widthSpec, int heightSpec) {
        if (measuredHeight <= 0) {
            int count = state.getItemCount();
            if(count>0){
                View view = recycler.getViewForPosition(0);
                if (view != null) {
                    measureChild(view, widthSpec, heightSpec);
                    measuredWidth = View.MeasureSpec.getSize(widthSpec);
                    measuredHeight = view.getMeasuredHeight() * getSpanCount();
                }
            }

        }
        setMeasuredDimension(measuredWidth, measuredHeight);
    }

}

2、PageIndicatorView

package inditor.recycleview.horizontal;

import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.LinearLayout;

import com.lab.web.entity.UtilDynamicWidth;

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


/**
 * Created by shichaohui on 2015/7/10 0010.
 * <p/>
 * 页码指示器类,获得此类实例后,可通过{@link PageIndicatorView#initIndicator(int)}方法初始化指示器
 * </P>
 */

public class PageIndicatorView extends LinearLayout {

    private Context mContext = null;
    private int dotSize = 10; // 指示器的大小(dp)
    private int margins = 10; // 指示器间距(dp)
    private List<View> indicatorViews = null; // 存放指示器

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

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

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

    private void init(Context context) {
        this.mContext = context;

        setGravity(Gravity.CENTER);
        setOrientation(HORIZONTAL);

        dotSize = UtilDynamicWidth.dip2px(context, dotSize);
        margins = UtilDynamicWidth.dip2px(context, margins);
    }

    /**
     * 初始化指示器,默认选中第一页
     *
     * @param count 指示器数量,即页数
     */
    public void initIndicator(int count) {

        if (indicatorViews == null) {
            indicatorViews = new ArrayList<>();
        } else {
            indicatorViews.clear();
            removeAllViews();
        }
        View view;
        LayoutParams params = new LayoutParams(dotSize, dotSize);
        params.setMargins(margins, margins, margins, margins);
        for (int i = 0; i < count; i++) {
            view = new View(mContext);
            view.setBackgroundResource(android.R.drawable.presence_invisible);
            addView(view, params);
            indicatorViews.add(view);
        }
        if (indicatorViews.size() > 0) {
            indicatorViews.get(0).setBackgroundResource(android.R.drawable.presence_online);
        }
    }

    /**
     * 设置选中页
     *
     * @param selected 页下标,从0开始
     */
    public void setSelectedPage(int selected) {
        for (int i = 0; i < indicatorViews.size(); i++) {
            if (i == selected) {
                indicatorViews.get(i).setBackgroundResource(android.R.drawable.presence_online);
            } else {
                indicatorViews.get(i).setBackgroundResource(android.R.drawable.presence_invisible);
            }
        }
    }

}

3、PageRecyclerView

package inditor.recycleview.horizontal;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;

import com.lab.web.entity.UtilDynamicWidth;

import java.util.List;
import java.util.Map;

/**
 * Created by shichaohui on 2015/7/9 0009.
 * <p>
 * 横向分页的GridView效果
 * </p>
 * <p>
 * 默认为1行,每页3列,如果要自定义行数和列数,请在调用{@link PageRecyclerView#setAdapter(Adapter)}方法前调用
 * {@link PageRecyclerView#setPageSize(int, int)}方法自定义行数
 * </p>
 */
public class PageRecyclerView extends RecyclerView {

    private Context mContext = null;

    private PageAdapter myAdapter = null;

    private int shortestDistance; // 超过此距离的滑动才有效
    private float slideDistance = 0; // 滑动的距离
    private float scrollX = 0; // X轴当前的位置

    private int spanRow = 1; // 行数
    private int spanColumn = 2; // 每页列数
    private int totalPage = 0; // 总页数
    private int currentPage = 1; // 当前页

    private int pageMargin = 0; // 页间距

    private PageIndicatorView mIndicatorView = null; // 指示器布局

    private int realPosition = 0; // 真正的位置(从上到下从左到右的排列方式变换成从左到右从上到下的排列方式后的位置)

    /*
     * 0: 停止滚动且手指移开; 1: 开始滚动; 2: 手指做了抛的动作(手指离开屏幕前,用力滑了一下)
	 */
    private int scrollState = 0; // 滚动状态

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

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

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

    // 默认初始化
    private void defaultInit(Context context) {
        this.mContext = context;
        setLayoutManager(new AutoGridLayoutManager(
                mContext, spanRow, AutoGridLayoutManager.HORIZONTAL, false));
        setOverScrollMode(OVER_SCROLL_NEVER);
    }

    /**
     * 设置行数和每页列数
     *
     * @param spanRow    行数,<=0表示使用默认的行数
     * @param spanColumn 每页列数,<=0表示使用默认每页列数
     */
    public void setPageSize(int spanRow, int spanColumn) {
        this.spanRow = spanRow <= 0 ? this.spanRow : spanRow;
        this.spanColumn = spanColumn <= 0 ? this.spanColumn : spanColumn;
        setLayoutManager(new AutoGridLayoutManager(
                mContext, this.spanRow, AutoGridLayoutManager.HORIZONTAL, false));
    }

    /**
     * 设置页间距
     *
     * @param pageMargin 间距(px)
     */
    public void setPageMargin(int pageMargin) {
        this.pageMargin = pageMargin;
    }

    /**
     * 设置指示器
     *
     * @param indicatorView 指示器布局
     */
    public void setIndicator(PageIndicatorView indicatorView) {
        this.mIndicatorView = indicatorView;
    }

    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        super.onMeasure(widthSpec, heightSpec);
        shortestDistance = getMeasuredWidth() / 3;
    }

    @Override
    public void setAdapter(Adapter adapter) {
        super.setAdapter(adapter);
        this.myAdapter = (PageAdapter) adapter;
        update();
    }

    // 更新页码指示器和相关数据
    private void update() {
        // 计算总页数
        int temp = ((int) Math.ceil(myAdapter.dataList.size() / (double) (spanRow * spanColumn)));
        if (temp != totalPage) {
            mIndicatorView.initIndicator(temp);
            // 页码减少且当前页为最后一页
            if (temp < totalPage && currentPage == totalPage) {
                currentPage = temp;
                // 执行滚动
                smoothScrollBy(-getWidth(), 0);
            }
            mIndicatorView.setSelectedPage(currentPage - 1);
            totalPage = temp;
        }
    }

    @Override
    public void onScrollStateChanged(int state) {
        switch (state) {
            case 2:
                scrollState = 2;
                break;
            case 1:
                scrollState = 1;
                break;
            case 0:
                if (slideDistance == 0) {
                    break;
                }
                scrollState = 0;
                if (slideDistance < 0) { // 上页
                    currentPage = (int) Math.ceil(scrollX / getWidth());
                    if (currentPage * getWidth() - scrollX < shortestDistance) {
                        currentPage += 1;
                    }
                } else { // 下页
                    currentPage = (int) Math.ceil(scrollX / getWidth()) + 1;
                    if (currentPage <= totalPage) {
                        if (scrollX - (currentPage - 2) * getWidth() < shortestDistance) {
                            // 如果这一页滑出距离不足,则定位到前一页
                            currentPage -= 1;
                        }
                    } else {
                        currentPage = totalPage;
                    }
                }
                // 执行自动滚动
                smoothScrollBy((int) ((currentPage - 1) * getWidth() - scrollX), 0);
                // 修改指示器选中项
                mIndicatorView.setSelectedPage(currentPage - 1);
                slideDistance = 0;
                break;
        }
        super.onScrollStateChanged(state);
    }

    @Override
    public void onScrolled(int dx, int dy) {
        scrollX += dx;
        if (scrollState == 1) {
            slideDistance += dx;
        }

        super.onScrolled(dx, dy);
    }

    /**
     * 数据适配器
     */
    public class PageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

        private List<Map<String, Object>> dataList = null;
        private CallBack mCallBack = null;
        private int itemWidth = 0;
        private int itemCount = 0;

        /**
         * 实例化适配器
         *
         * @param data
         * @param callBack
         */
        public PageAdapter(List<Map<String, Object>> data, CallBack callBack) {
            this.dataList = data;
            this.mCallBack = callBack;
            itemCount = dataList.size(); //+ spanRow * spanColumn;
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if (itemWidth <= 0) {
                // 计算Item的宽度
//                parent.post(new Runnable() {
//                    @Override
//                    public void run() {
//                        itemWidth = (getWidth() - pageMargin * 2) / spanColumn;
//                    }
//                });
//                itemWidth = (parent.getWidth() - pageMargin * 2) / spanColumn;
                //获取手机屏幕宽度px
                WindowManager wm = (WindowManager) mContext
                        .getSystemService(Context.WINDOW_SERVICE);
                DisplayMetrics outMetrics = new DisplayMetrics();
                wm.getDefaultDisplay().getMetrics(outMetrics);
                int withScreen=outMetrics.widthPixels;
                itemWidth = withScreen/6;
            }

            RecyclerView.ViewHolder holder = mCallBack.onCreateViewHolder(parent, viewType);

            holder.itemView.measure(0, 0);
            holder.itemView.getLayoutParams().width = itemWidth;
            holder.itemView.getLayoutParams().height = holder.itemView.getMeasuredHeight();

            return holder;
        }

        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {

            WindowManager wm = (WindowManager) mContext
                    .getSystemService(Context.WINDOW_SERVICE);
            DisplayMetrics outMetrics = new DisplayMetrics();
            wm.getDefaultDisplay().getMetrics(outMetrics);
            int withScreen=outMetrics.widthPixels;
            if (spanColumn == -1) {
                // 每个Item距离左右两侧各pageMargin
//                holder.itemView.getLayoutParams().width = itemWidth + pageMargin * 2;
//                holder.itemView.setPadding(pageMargin, 0, pageMargin, 0);
            } else {

                int m = position % (spanRow * spanColumn);
                if (m < spanRow) {
                    // 每页左侧的Item距离左边pageMargin
//                    holder.itemView.getLayoutParams().width = itemWidth + pageMargin;
//                    mContext.getWindowManager().getDefaultDisplay().getWidth();//获取手机屏幕宽度px

                    holder.itemView.getLayoutParams().width =  withScreen/2;
                    holder.itemView.setPadding(pageMargin*7, 0, 0, 0);
                } else if (m >= spanRow * spanColumn - spanRow) {
                    // 每页右侧的Item距离右边pageMargin
//                    holder.itemView.getLayoutParams().width = itemWidth + pageMargin;
                    holder.itemView.getLayoutParams().width =  withScreen/2;
                    holder.itemView.setPadding(0, 0, pageMargin*7, 0);
                } else {
                    // 中间的正常显示
//                    holder.itemView.getLayoutParams().width = itemWidth;
//                    holder.itemView.setPadding(0, 0, 0, 0);
                }
            }

            countRealPosition(position);

            holder.itemView.setTag(realPosition);

            setListener(holder);
            mCallBack.onBindViewHolder(holder, position);
//            if (realPosition < dataList.size()) {
//                holder.itemView.setVisibility(View.VISIBLE);
//                mCallBack.onBindViewHolder(holder, realPosition);
//            } else {
//                holder.itemView.setVisibility(View.INVISIBLE);
//            }

        }

        @Override
        public int getItemCount() {
            return itemCount;
        }

        private void countRealPosition(int position) {
            // 为了使Item从左到右从上到下排列,需要position的值
//            int m = position % (spanRow * spanColumn);
            realPosition = position;
//            switch (m) {
//
//                case 1:
//                case 5:
//                    realPosition = position + 2;
//                    break;
//                case 3:
//                case 7:
//                    realPosition = position - 2;
//                    break;
//                case 2:
//                    realPosition = position + 4;
//                    break;
//                case 6:
//                    realPosition = position - 4;
//                    break;
//                case 0:
//                case 4:
//                case 8:
//                    realPosition = position;
//                    break;
//            }
        }

        private void setListener(ViewHolder holder) {
            // 设置监听
            holder.itemView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    mCallBack.onItemClickListener(v, (Integer) v.getTag());
                }
            });

            holder.itemView.setOnLongClickListener(new OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    mCallBack.onItemLongClickListener(v, (Integer) v.getTag());
                    return true;
                }
            });
        }

        /**
         * 删除Item
         *
         * @param position 位置
         */
        public void remove(int position) {
            if (position < dataList.size()) {
                // 删除数据
                dataList.remove(position);
                itemCount--;
                // 删除Item
                notifyItemRemoved(position);
                // 更新界面上发生改变的Item
                notifyItemRangeChanged((currentPage - 1) * spanRow * spanColumn, currentPage * spanRow * spanColumn);
                // 更新页码指示器
                update();
            }
        }

    }

    public interface CallBack {

        /**
         * 创建VieHolder
         *
         * @param parent
         * @param viewType
         */
        RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);

        /**
         * 绑定数据到ViewHolder
         *
         * @param holder
         * @param position
         */
        void onBindViewHolder(RecyclerView.ViewHolder holder, int position);

        void onItemClickListener(View view, int position);

        void onItemLongClickListener(View view, int position);

    }
}

4、使用

写完了上边的123,就是相当于实现了一整个自定义的控件,这个控件可以实现横向滑动翻页,可以设置行数和列数,并且可以自定义设置指定两个item之间的距离,避免一页中显示连个item显示间距太大的问题,并且翻页的同时会显示指示器
4-1:xml文件使用控件:

   <!--自定义RecycleView加指示器横向滑动布局-->
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="85dp">
                <inditor.recycleview.horizontal.PageRecyclerView
                    android:id="@+id/cusom_swipe_view"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">

                </inditor.recycleview.horizontal.PageRecyclerView>

                <inditor.recycleview.horizontal.PageIndicatorView
                    android:id="@+id/indicator"
                    android:layout_width="match_parent"
                    android:layout_height="12dp"
                    android:layout_alignParentBottom="true"
                    android:layout_marginBottom="3dp"></inditor.recycleview.horizontal.PageIndicatorView>
            </RelativeLayout>

4-2 java 代码绑定数据并展示,添加item的点击和长按事件

      mRecyclerView = (PageRecyclerView) findViewById(R.id.cusom_swipe_view);
                                // 设置指示器
                                mRecyclerView.setIndicator((PageIndicatorView) findViewById(R.id.indicator));
                                // 设置行数和列数
                                mRecyclerView.setPageSize(1, 2);

                                // 设置页间距
                                mRecyclerView.setPageMargin(30);
                                // 设置数据
                                mRecyclerView.setAdapter(myAdapter=mRecyclerView.new PageAdapter(list_tequan1, new PageRecyclerView.CallBack() {
                                    @Override
                                    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                                        View view = LayoutInflater.from(HuiYuanCenterActivity.this).inflate(R.layout.tequan_item, parent, false);
                                        return new MyHolder(view);
                                    }

                                    @Override
                                    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
                                        Map<String,Object> map= (Map<String, Object>) list_tequan1.get(position);
                                        Glide.with(HuiYuanCenterActivity.this).load(map.get("img").toString()).into( ((MyHolder) holder).iv_huanyuan_zuo);
                                        ((MyHolder) holder).gv_text.setText((String) map.get("txt"));
                                    }

                                    @Override
                                    public void onItemClickListener(View view, int position) {
//                                        Toast.makeText(HuiYuanCenterActivity.this, "点击:"
//                                                + list_tequan1.get(position), Toast.LENGTH_SHORT).show();

                                    }

                                    @Override
                                    public void onItemLongClickListener(View view, int position) {
//                                        Toast.makeText(HuiYuanCenterActivity.this, "删除:"
//                                                + list_tequan1.get(position), Toast.LENGTH_SHORT).show();
//                                        myAdapter.remove(position);
                                    }
                                }));

RcycleView横向左右越界回弹

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.TranslateAnimation;
import android.widget.LinearLayout;

import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

public class OverScrollLayout extends LinearLayout {
    private static final int ANIM_TIME = 400;

    private RecyclerView childView;

    private Rect original = new Rect();

    private boolean isMoved = false;

    private float startXpos;
    
    private static final float DAMPING_COEFFICIENT = 0.3f;

    private boolean isSuccess = false;

    private ScrollListener mScrollListener;

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

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

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

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        childView = (RecyclerView) getChildAt(0);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        original.set(childView.getLeft(), childView.getTop(), childView.getRight(), childView.getBottom());
    }

    public void setScrollListener(ScrollListener listener) {
        mScrollListener = listener;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        float touchXpos = ev.getX();
        if (touchXpos >= original.right || touchXpos <= original.left) {
            if (isMoved) {
                recoverLayout();
            }
            return true;
        }
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startXpos = ev.getX();
            case MotionEvent.ACTION_MOVE:
                int scrollXpos = (int) (ev.getX() - startXpos);
                boolean pullDown = scrollXpos > 0 && canPullDown();
                boolean pullUp = scrollXpos < 0 && canPullUp();
                if (pullDown || pullUp) {
                    cancelChild(ev);
                    int offset = (int) (scrollXpos * DAMPING_COEFFICIENT);
                    childView.layout(original.left+ offset, original.top , original.right+ offset, original.bottom );
                    if (mScrollListener != null) {
                        mScrollListener.onScroll();
                    }
                    isMoved = true;
                    isSuccess = false;
                    return true;
                } else {
                    scrollXpos = (int) ev.getX();
                    isMoved = false;
                    isSuccess = true;
                    return super.dispatchTouchEvent(ev);
                }
            case MotionEvent.ACTION_UP:
                if (isMoved) {
                    recoverLayout();
                }
                return !isSuccess || super.dispatchTouchEvent(ev);
            default:
                return true;
        }
    }

    /**
     * 取消子view已经处理的事件
     *
     * @param ev event
     */
    private void cancelChild(MotionEvent ev) {
        ev.setAction(MotionEvent.ACTION_CANCEL);
        super.dispatchTouchEvent(ev);
    }

    /**
     * 位置还原
     */
    private void recoverLayout() {
        TranslateAnimation anim = new TranslateAnimation(childView.getLeft() - original.left, 0, 0, 0);
        anim.setDuration(ANIM_TIME);
        childView.startAnimation(anim);
        childView.layout(original.left, original.top, original.right, original.bottom);
        isMoved = false;
    }

    /**
     * 判断是否可以下拉
     *
     * @return true:可以,false:不可以
     */
    private boolean canPullDown() {
        final int firstVisiblePosition = ((LinearLayoutManager) childView.getLayoutManager()).findFirstVisibleItemPosition();
        if (firstVisiblePosition != 0 && childView.getAdapter().getItemCount() != 0) {
            return false;
        }
        int mostTop = (childView.getChildCount() > 0) ? childView.getChildAt(0).getTop() : 0;
        return mostTop >= 0;
    }

    /**
     * 判断是否可以上拉
     *
     * @return true:可以,false:不可以
     */
    private boolean canPullUp() {
        final int lastItemPosition = childView.getAdapter().getItemCount() - 1;
        final int lastVisiblePosition = ((LinearLayoutManager) childView.getLayoutManager()).findLastVisibleItemPosition();
        if (lastVisiblePosition >= lastItemPosition) {
            final int childIndex = lastVisiblePosition - ((LinearLayoutManager) childView.getLayoutManager()).findFirstVisibleItemPosition();
            final int childCount = childView.getChildCount();
            final int index = Math.min(childIndex, childCount - 1);
            final View lastVisibleChild = childView.getChildAt(index);
            if (lastVisibleChild != null) {
                return lastVisibleChild.getBottom() <= childView.getBottom() - childView.getTop();
            }
        }
        return false;
    }


    public interface ScrollListener {
        /**
         * 滚动事件回调
         */
        void onScroll();
    }

}

RcycleView纵向上下越界回弹

package com.by.rvapp;

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.TranslateAnimation;
import android.widget.LinearLayout;

import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

public class OverScrollLayout1 extends LinearLayout {
    private static final int ANIM_TIME = 400;

    private RecyclerView childView;

    private Rect original = new Rect();

    private boolean isMoved = false;

    private float startYpos;

    /**
     * 阻尼系数
     */
    private static final float DAMPING_COEFFICIENT = 0.3f;

    private boolean isSuccess = false;

    private ScrollListener mScrollListener;

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

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

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

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        childView = (RecyclerView) getChildAt(0);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        original.set(childView.getLeft(), childView.getTop(), childView.getRight(), childView.getBottom());
    }

    public void setScrollListener(ScrollListener listener) {
        mScrollListener = listener;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        float touchYpos = ev.getY();
        if (touchYpos >= original.bottom || touchYpos <= original.top) {
            if (isMoved) {
                recoverLayout();
            }
            return true;
        }
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startYpos = ev.getY();
            case MotionEvent.ACTION_MOVE:
                int scrollYpos = (int) (ev.getY() - startYpos);
                boolean pullDown = scrollYpos > 0 && canPullDown();
                boolean pullUp = scrollYpos < 0 && canPullUp();
                if (pullDown || pullUp) {
                    cancelChild(ev);
                    int offset = (int) (scrollYpos * DAMPING_COEFFICIENT);
                    childView.layout(original.left, original.top + offset, original.right, original.bottom + offset);
                    if (mScrollListener != null) {
                        mScrollListener.onScroll();
                    }
                    isMoved = true;
                    isSuccess = false;
                    return true;
                } else {
                    startYpos = ev.getY();
                    isMoved = false;
                    isSuccess = true;
                    return super.dispatchTouchEvent(ev);
                }
            case MotionEvent.ACTION_UP:
                if (isMoved) {
                    recoverLayout();
                }
                return !isSuccess || super.dispatchTouchEvent(ev);
            default:
                return true;
        }
    }

    /**
     * 取消子view已经处理的事件
     *
     * @param ev event
     */
    private void cancelChild(MotionEvent ev) {
        ev.setAction(MotionEvent.ACTION_CANCEL);
        super.dispatchTouchEvent(ev);
    }

    /**
     * 位置还原
     */
    private void recoverLayout() {
        TranslateAnimation anim = new TranslateAnimation(0, 0, childView.getTop() - original.top, 0);
        anim.setDuration(ANIM_TIME);
        childView.startAnimation(anim);
        childView.layout(original.left, original.top, original.right, original.bottom);
        isMoved = false;
    }

    /**
     * 判断是否可以下拉
     *
     * @return true:可以,false:不可以
     */
    private boolean canPullDown() {
        final int firstVisiblePosition = ((LinearLayoutManager) childView.getLayoutManager()).findFirstVisibleItemPosition();
        if (firstVisiblePosition != 0 && childView.getAdapter().getItemCount() != 0) {
            return false;
        }
        int mostTop = (childView.getChildCount() > 0) ? childView.getChildAt(0).getTop() : 0;
        return mostTop >= 0;
    }

    /**
     * 判断是否可以上拉
     *
     * @return true:可以,false:不可以
     */
    private boolean canPullUp() {
        final int lastItemPosition = childView.getAdapter().getItemCount() - 1;
        final int lastVisiblePosition = ((LinearLayoutManager) childView.getLayoutManager()).findLastVisibleItemPosition();
        if (lastVisiblePosition >= lastItemPosition) {
            final int childIndex = lastVisiblePosition - ((LinearLayoutManager) childView.getLayoutManager()).findFirstVisibleItemPosition();
            final int childCount = childView.getChildCount();
            final int index = Math.min(childIndex, childCount - 1);
            final View lastVisibleChild = childView.getChildAt(index);
            if (lastVisibleChild != null) {
                return lastVisibleChild.getBottom() <= childView.getBottom() - childView.getTop();
            }
        }
        return false;
    }


    public interface ScrollListener {
        /**
         * 滚动事件回调
         */
        void onScroll();
    }

}

越界回弹使用

使用自定义布局包含RecycleView即可,自定义的recycleview逻辑不需要改动

    <com.by.rvapp.OverScrollLayout
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:orientation="horizontal"
        android:background="@android:color/holo_green_light">
        <com.by.rvapp.MyRecyclerView
            android:id="@+id/card_rv"
            android:background="@android:color/transparent"
            android:layout_width="match_parent"
            android:layout_height="300dp"
            android:layout_margin="10dp"/>
    </com.by.rvapp.OverScrollLayout>
以下是 Android 中结合 ViewPager 和 RecyclerView 实现横向翻页无限循环的代码示例: 1. 在布局文件中定义 ViewPager 和 RecyclerView: ```xml <androidx.viewpager.widget.ViewPager android:id="@+id/view_pager" android:layout_width="match_parent" android:layout_height="200dp" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="wrap_content" /> ``` 2. 在 Activity 或 Fragment 中初始化 ViewPager 和 RecyclerView: ```java private ViewPager mViewPager; private MyPagerAdapter mPagerAdapter; private RecyclerView mRecyclerView; private LinearLayoutManager mLinearLayoutManager; private List<MyData> mDataList; private MyAdapter mAdapter; private int mCurrentPosition = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mViewPager = findViewById(R.id.view_pager); mRecyclerView = findViewById(R.id.recycler_view); // 设置 ViewPager 适配器 mPagerAdapter = new MyPagerAdapter(this); mViewPager.setAdapter(mPagerAdapter); // 初始化数据 mDataList = new ArrayList<>(); for (int i = 0; i < 20; i++) { mDataList.add(new MyData("Item " + i)); } // 初始化适配器 mAdapter = new MyAdapter(this, mDataList); // 初始化布局管理器 mLinearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); mRecyclerView.setLayoutManager(mLinearLayoutManager); // 设置适配器 mRecyclerView.setAdapter(mAdapter); // 监听 RecyclerView 滚动事件 mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); // 计算当前显示在 ViewPager 中的位置 int position = mLinearLayoutManager.findFirstCompletelyVisibleItemPosition(); if (position != RecyclerView.NO_POSITION && position != mCurrentPosition) { mCurrentPosition = position; mViewPager.setCurrentItem(mCurrentPosition, false); } } }); // 监听 ViewPager 页面切换事件 mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { // 设置 RecyclerView 滚动到对应位置 mRecyclerView.smoothScrollToPosition(position); mCurrentPosition = position; } @Override public void onPageScrollStateChanged(int state) { } }); // 初始化第一个页面 mViewPager.setCurrentItem(mCurrentPosition); mRecyclerView.scrollToPosition(mCurrentPosition); } ``` 3. 编写 ViewPager 和 RecyclerView 的适配器和数据类: ```java public class MyPagerAdapter extends PagerAdapter { private Context mContext; public MyPagerAdapter(Context context) { mContext = context; } @Override public int getCount() { return 20; } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } @Override public Object instantiateItem(ViewGroup container, int position) { View view = LayoutInflater.from(mContext).inflate(R.layout.item_my_pager, container, false); TextView textView = view.findViewById(R.id.tv_text); textView.setText("Page " + position); container.addView(view); return view; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View) object); } } public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> { private Context mContext; private List<MyData> mDataList; public MyAdapter(Context context, List<MyData> dataList) { mContext = context; mDataList = dataList; } @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(mContext).inflate(R.layout.item_my_data, parent, false); return new MyViewHolder(view); } @Override public void onBindViewHolder(MyViewHolder holder, int position) { MyData data = mDataList.get(position); holder.mTextView.setText(data.getText()); } @Override public int getItemCount() { return mDataList.size(); } } public class MyViewHolder extends RecyclerView.ViewHolder { public TextView mTextView; public MyViewHolder(View itemView) { super(itemView); mTextView = itemView.findViewById(R.id.tv_text); } } public class MyData { private String mText; public MyData(String text) { mText = text; } public String getText() { return mText; } } ``` 在这个示例中,当用户滑动 RecyclerView 时,会自动滑动到对应的 ViewPager 页面,当用户滑动 ViewPager 页面时,会自动滑动到对应的 RecyclerView 列表项,实现横向翻页无限循环的效果。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值