美文网首页优秀案例
〔两行哥〕自定义分屏控件及联动RecyclerView控件

〔两行哥〕自定义分屏控件及联动RecyclerView控件

作者: 两行哥 | 来源:发表于2017-07-01 21:09 被阅读209次

本项目已经上传至码云,有兴趣的哥们可以参考。

码云:https://git.oschina.net/xuzhaoyu/customviewdemo.git

跟大家分享一下最近写的两个自定义控件。
一个公职教育类的App,材料题有一个分屏的自定义控件;
另外一个金融股票类的App,股票列表界面有一个联动的RecyclerView控件,同理可以拓展到N级联动。

先看看效果图。
不要吐槽UI,咱们是程序员,重要的是逻辑的实现。UI不就是在实现基础逻辑的基础上换上美美素材嘛,给我们一套美美的素材:

咱就能飞上天,和太阳肩并肩。

效果图

分屏自定义控件与传统的DrawerLayout不同,它从下方划出,并且可以跟随手指停留到一定位置。而Android原生的通常是根据手势惯性滑动到最末端,占据整个屏幕。

联动RecyclerView,左边的RecyclerView可以上下滑动,右边的RecyclerView可以上下滑动,也可以单独左右滑动。两个RecyclerView上下滑动是一致的。
接下来分析一下代码的实现。

一、自定义分屏控件
在自定义分屏控件中,上部分为ViewPager,可以左右滑动,ViewPager的每个ItemView为可以上下滑动的ScrollView。下部分为自定义横向滑动的RecyclerView(呆会讲述为啥不用ViewPager),RecyclerView的每一个ItemView为可以上下滑动的ScrollView。

本人项目的要求是,分屏控件的下方,页面数量可变,页面内容也可以即时更新。如果使用了ViewPager,由于其缓存机制,默认情况下无法对当前页面、前一个页面(如果有的话)、后一个页面(如果有的话)即时刷新数据。本人在更改了ViewPager的缓存机制后,仅缓存当前页面,虽然可以即时刷新前一个页面和后一个页面的数据,但是当前页面依旧无法即时更新数据。因此选择自定义RecyclerView来实现类似ViewPager的效果。本人写了Helper类绑定RecyclerView来仿ViewPager,实现的代码如下:

public class PagingScrollHelper {

    //需要绑定的RecyclerView
    RecyclerView mRecyclerView;
    //自定义的监听
    private MyOnScrollListener mOnScrollListener = new MyOnScrollListener();
    private MyOnFlingListener mOnFlingListener = new MyOnFlingListener();
    //当前位置,RecyclerView相对于屏幕的偏移量
    private int mOffsetY;
    private int mOffsetX;
    //前一个位置所在的位置(页面Index)
    private int mPreIndex;

    //可以根据需要自定义这个RecyclerView仿的ViewPager是垂直滚动还是水平滚动,默认为水平
    enum ORIENTATION {
        HORIZONTAL, VERTICAL, NULL
    }

    ORIENTATION mOrientation = ORIENTATION.HORIZONTAL;

    public void setUpRecycleView(RecyclerView recycleView) {
        if (recycleView == null) {
            throw new IllegalArgumentException("recycleView must be not null");
        }
        mRecyclerView = recycleView;
        //处理滑动
        recycleView.setOnFlingListener(mOnFlingListener);
        //设置滚动监听,记录滚动的状态,和总的偏移量
        recycleView.setOnScrollListener(mOnScrollListener);
        //获取滚动的方向
        updateLayoutManger();
    }
    
    //更新布局管理器
    public void updateLayoutManger() {
        RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
        if (layoutManager != null) {
            if (layoutManager.canScrollVertically()) {
                mOrientation = ORIENTATION.VERTICAL;
            } else if (layoutManager.canScrollHorizontally()) {
                mOrientation = ORIENTATION.HORIZONTAL;
            } else {
                mOrientation = ORIENTATION.NULL;
            }
            if (mAnimator != null) {
                mAnimator.cancel();
            }
            mOffsetX = 0;
            mOffsetY = 0;
        }
    }

    ValueAnimator mAnimator = null;
    
    //自定义的惯性滑动监听
    public class MyOnFlingListener extends RecyclerView.OnFlingListener {

        @Override
        public boolean onFling(int velocityX, int velocityY) {
            if (mOrientation == ORIENTATION.NULL) {
                return false;
            }
            //获取开始滚动时所在页面的index
            mPreIndex = getPageIndex();
            //记录滚动开始和结束的位置
            int endPoint = 0;
            int startPoint = 0;
            //如果是垂直方向
            if (mOrientation == ORIENTATION.VERTICAL) {
                startPoint = mOffsetY;
                if (velocityY > 0) {
                    endPoint = (mPreIndex + 1) * mRecyclerView.getHeight();
                } else {
                    endPoint = mPreIndex * mRecyclerView.getHeight();
                }
            } else {
                startPoint = mOffsetX;
                if (velocityX > 0) {
                    endPoint = (mPreIndex + 1) * mRecyclerView.getWidth();
                } else {
                    endPoint = mPreIndex * mRecyclerView.getWidth();
                }
            }
            //使用动画处理滚动
            if (mAnimator == null) {
                mAnimator = ValueAnimator.ofInt(startPoint, endPoint);
                mAnimator.setDuration(200);
                mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        int nowPoint = (int) animation.getAnimatedValue();
                        if (mOrientation == ORIENTATION.VERTICAL) {
                            int dy = nowPoint - mOffsetY;
                            //这里通过RecyclerView的scrollBy方法实现滚动。
                            mRecyclerView.scrollBy(0, dy);
                        } else {
                            int dx = nowPoint - mOffsetX;
                            mRecyclerView.scrollBy(dx, 0);
                        }
                    }
                });
                mAnimator.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        //回调监听
                        if (null != mOnPageChangeListener) {
                            int curIndex = getPageIndex();
                            if (mPreIndex != curIndex) {
                                mOnPageChangeListener.onPageChange(curIndex);
                            }
                        }
                    }
                });
            } else {
                mAnimator.cancel();
                mAnimator.setIntValues(startPoint, endPoint);
            }
            mAnimator.start();
            return true;
        }
    }

    //自定义的滑动监听
    public class MyOnScrollListener extends RecyclerView.OnScrollListener {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            //newState==0表示滚动停止,此时需要处理回滚
            if (newState == 0 && mOrientation != ORIENTATION.NULL) {
                boolean moveToNext;
                int vX = 0, vY = 0;
                if (mOrientation == ORIENTATION.VERTICAL) {
                    int absY = Math.abs(mOffsetY % recyclerView.getHeight());
                    //如果滑动的距离超过屏幕的一半表示需要滑动到下一页
                    moveToNext = absY > recyclerView.getHeight() / 2;
                    vY = moveToNext ? 1000 : 0;
                } else {
                    int absX = Math.abs(mOffsetX % recyclerView.getWidth());
                    moveToNext = absX > recyclerView.getWidth() / 2;
                    vX = moveToNext ? 1000 : 0;
                }
                mOnFlingListener.onFling(vX, vY);
            }
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            //滚动结束记录滚动的偏移量
            mOffsetY += dy;
            mOffsetX += dx;
        }
    }
    
    //获取RecyclerVeiw当前所在的位置(索引Index)
    private int getPageIndex() {
        int p = 0;
        if (mOrientation == ORIENTATION.VERTICAL) {
            p = mOffsetY / mRecyclerView.getHeight();
        } else {
            p = mOffsetX / mRecyclerView.getWidth();
        }
        return p;
    }
    
    //定义页面切换的回调监听
    onPageChangeListener mOnPageChangeListener;

    public void setOnPageChangeListener(onPageChangeListener listener) {
        mOnPageChangeListener = listener;
    }

    public interface onPageChangeListener {
        void onPageChange(int index);
    }

这里涉及到一个水平滑动的控件嵌套一个垂直滑动的控件。到底是水平滑动控件先响应,还是垂直滑动控件先响应,取决于用户落下手指的那一瞬间,相对于屏幕的滑动向量增量(类似于数学上导数),但有时候这个向量的增量方向为水平,而用户实际上想垂直滑动,但是垂直滑动控件是不会响应的,需要用户抬起手指,重新落下,并且落下的那一瞬间,相对于屏幕的滑动向量增量方向垂直。这样的体验很不友好。

为了优化用户体验,我们通常会做如下逻辑:判断用户滑动的手势的总向量,如果在水平方向上的位移大于垂直方向上的位移,那么让水平滑动控件响应触摸事件,否则让垂直滑动控件响应触摸事件。因此,本人在自定义分屏控件中,下方的RecyclerView也进行了处理,覆写了onInterceptTouchEvent( )方法,对手势进行拦截,代码如下:

public class SoftRecyclerView extends RecyclerView {

    private float mStartX;
    private float mStartY;

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

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

    public SoftRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mStartX = ev.getX();
                mStartY = ev.getY();
                break;
            case MotionEvent.ACTION_UP:
                float endX = ev.getX();
                float endY = ev.getY();
                float diffX = Math.abs(endX - mStartX);
                float diffY = Math.abs(endY - mStartY);
                mStartX = endX;
                mStartY = endY;
                if (diffX > diffY) {
                    return true;
                } else {
                    return false;
                }
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean fling(int velocityX, int velocityY) {
        return false;
    }
}

可能有哥们发现我同时覆写了fling( )方法,在这里讲述一下原因。

这里存在另外一个用户体验不佳的地方。不管任何垂直与水平滑动控件的嵌套,都会存在的问题。图片不好表达,只有亲身体验一下才会明白,简要叙述一下:

假设,一个水平滚动的ViewPager,ViewPager的每一个子页面为垂直滚动的ScrollView。理论上这两个滑动控件没有手势冲突。但是实际上,Android中有一种滑动状态叫“Fling”(飞行中,即惯性滑动)。如上假设,ScrollView为子的滑动控件,当用户垂直滚动它的时候,如果滚动的速度非常大,突然间用户抬起手指,想进行水平滚动(即让水平滚动的ViewPager响应用户操作),而此时ViewPager是不会响应的,因为ScrollView处于惯性滑动中。为什么会出现这种现象呢?我们节选一部分Android系统的源码中ScrollView对onTouchEvent( )中处理。

            case MotionEvent.ACTION_DOWN: {
                if (getChildCount() == 0) {
                    return false;
                }
                if ((mIsBeingDragged = !mScroller.isFinished())) {
                    final ViewParent parent = getParent();
                    if (parent != null) {
                        parent.requestDisallowInterceptTouchEvent(true);
                    }
                }

                /*
                 * If being flinged and user touches, stop the fling. isFinished
                 * will be false if being flinged.
                 */
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                    if (mFlingStrictSpan != null) {
                        mFlingStrictSpan.finish();
                        mFlingStrictSpan = null;
                    }
                }

                // Remember where the motion event started
                mLastMotionY = (int) ev.getY();
                mActivePointerId = ev.getPointerId(0);
                startNestedScroll(SCROLL_AXIS_VERTICAL);
                break;
            }

原来源码中对Fling状态进行了判断(判断滚动Scroller的isFinished( ),如果未完成,就requestDisallowInterceptTouchEvent( ),即请求父控件不要拦截触摸事件)。
同理,RecyclerView的源码中也有类似的逻辑,截取onInterceptTouchEvent( )中的处理。

            case MotionEvent.ACTION_DOWN:
                if (mIgnoreMotionEventTillDown) {
                    mIgnoreMotionEventTillDown = false;
                }
                mScrollPointerId = e.getPointerId(0);
                mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
                mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);

                if (mScrollState == SCROLL_STATE_SETTLING) {
                    getParent().requestDisallowInterceptTouchEvent(true);
                    setScrollState(SCROLL_STATE_DRAGGING);
                }

                // Clear the nested offsets
                mNestedOffsets[0] = mNestedOffsets[1] = 0;

                int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
                if (canScrollHorizontally) {
                    nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
                }
                if (canScrollVertically) {
                    nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
                }
                startNestedScroll(nestedScrollAxis);
                break;

同样也是在惯性滑动的时候,请求父容器requestDisallowInterceptTouchEvent( ),不要拦截触摸手势。
根据源码的逻辑,本人将SoftRecyclerView中fling( )中逻辑去除,直接返回false,避免此RecyclerVeiw在fling状态下,内部ScrollVeiw不响应用户的触摸事件。 如果各位看官使用本人的Demo,建议把内部的ScrollView也自定义,并覆写其中的逻辑,本人不再累述。

接下来看看分屏控件的封装,先看一下布局文件。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <LinearLayout
        android:id="@+id/ll_bottom_drawer_title"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:background="#999999"
        android:gravity="end|center_vertical"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/iv_arrow_btn"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_marginEnd="10dp"
            android:background="#999999"
            android:src="@mipmap/arrow"/>

    </LinearLayout>

    <com.demo.xuzhaoyu.customviewdemo.view.SoftRecyclerView
        android:id="@+id/rv_bottom_drawer_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

这是分屏控件的下半部分,实现的代码逻辑如下:

public class BottomDrawer extends LinearLayout {

    private View mView;
    private float mTitleStartY;
    private ViewGroup.LayoutParams mLayoutParams;
    private SoftRecyclerView mRvContent;
    private RecyclerView.LayoutManager mLayoutManager;
    private PagingScrollHelper mScrollHelper = new PagingScrollHelper();
    private ArrayList<String> mData = new ArrayList<>();
    private BottomDrawerAdapter mAdapter;
    private float mArrowDegree;

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

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

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

    private void initView() {
        final ScreenUtil screenUtil = ScreenUtil.getInstance(getContext());
        mView = LayoutInflater.from(getContext()).inflate(R.layout.view_bottom_drawer, this, true);
        View flTitle = mView.findViewById(R.id.ll_bottom_drawer_title);
        final View ivArrow = mView.findViewById(R.id.iv_arrow_btn);
        mRvContent = (SoftRecyclerView) mView.findViewById(R.id.rv_bottom_drawer_content);
        flTitle.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        mLayoutParams = BottomDrawer.this.getLayoutParams();
                        mTitleStartY = event.getRawY();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        float endY = event.getRawY();
                        float diffY = endY - mTitleStartY;
                        mTitleStartY = endY;
                        mLayoutParams.height -= diffY;
                        if (mLayoutParams.height <= screenUtil.dip2px(40)) {
                            mLayoutParams.height = screenUtil.dip2px(40);
                        }
                        if (mLayoutParams.height >= screenUtil.getScreenHeight()) {
                            mLayoutParams.height = screenUtil.getScreenHeight();
                        }
                        mArrowDegree = (mLayoutParams.height - screenUtil.dip2px(40)) * 180f / (screenUtil.getScreenHeight() - screenUtil.dip2px(40));
                        ivArrow.setRotation(mArrowDegree);
                        BottomDrawer.this.setLayoutParams(mLayoutParams);
                        break;
                }
                return true;
            }
        });

        mAdapter = new BottomDrawerAdapter(getContext(), mData);
        mAdapter.setItemClickedListener(new BottomDrawerAdapter.ItemClickedListener() {
            @Override
            public void onItemClicked(View view, int position, List<String> data) {
                if (mOnItemClickedListener != null) {
                    mOnItemClickedListener.onItemClicked(view, position, data);
                }
            }
        });

        mAdapter = new BottomDrawerAdapter(getContext(), mData);
        mRvContent.setAdapter(mAdapter);
        mScrollHelper.setUpRecycleView(mRvContent);
        mScrollHelper.setOnPageChangeListener(new PagingScrollHelper.onPageChangeListener() {
            @Override
            public void onPageChange(int index) {
                // TODO: 2017/7/2  
            }
        });
        mLayoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false);
        mRvContent.setLayoutManager(mLayoutManager);
        mScrollHelper.updateLayoutManger();

    }

    public void setData(ArrayList<String> data) {
        mData.clear();
        mData.addAll(data);
        mAdapter.notifyDataSetChanged();
    }

    public interface OnItemClickedListener {
        void onItemClicked(View view, int position, List<String> data);
    }

    private OnItemClickedListener mOnItemClickedListener;

    public void setOnItemClickedListener(OnItemClickedListener onItemClickedListener) {
        mOnItemClickedListener = onItemClickedListener;
    }

}

这里的主要逻辑就是对分屏控件中的灰色条的触摸事件进行覆写,flTitle.setOnTouchListener( )。思路是当用户触摸到这个灰色条的时候,动态改变下方SoftRecyclerVeiw的高度。高度的改变量与手指触摸在垂直方向上移动的距离一致。同时根据高度的改变量,动态地控制箭头图标的旋转。

最后看一下分屏控件在Activity中的使用。这是Activity中的布局文件,一个ViewPager和一个分屏控件。

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v4.view.ViewPager
        android:id="@+id/vp_top_content"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

    <com.demo.xuzhaoyu.customviewdemo.view.BottomDrawer
        android:id="@+id/bottom_drawer"
        android:layout_width="match_parent"
        android:layout_height="40dp"/>

</LinearLayout>

Activity中的主要代码逻辑如下:

public class BottomDrawerActivity extends AppCompatActivity {

    private ViewPager mVpContent;
    private BottomDrawer mBottomDrawer;
    private ViewPagerAdapter mViewPagerAdapter;

    public static void startAction(Context context) {
        Intent intent = new Intent(context, BottomDrawerActivity.class);
        context.startActivity(intent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bottom_drawer);
        initView();
        initData();
    }

    private void initView() {
        mVpContent = (ViewPager) findViewById(R.id.vp_top_content);
        mBottomDrawer = (BottomDrawer) findViewById(R.id.bottom_drawer);
    }

    private void initData() {
        ArrayList<View> views = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            View view = LayoutInflater.from(this).inflate(R.layout.view_page_item, null);
            TextView tvTitle = (TextView) view.findViewById(R.id.tv_title);
            tvTitle.setText(new StringBuilder("这是第").append(i).append("篇阅读材料"));
            views.add(view);
        }
        mViewPagerAdapter = new ViewPagerAdapter(views);
        mVpContent.setAdapter(mViewPagerAdapter);

        ArrayList<String> strings = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            strings.add(new StringBuilder("这是第").append(i).append("个相关问题\n\n")
                    .append("材料是谁写的?\n")
                    .append("他身高多少?\n")
                    .append("他体重多少?\n")
                    .append("他从事什么岗位?\n\n")
                    .append("材料是谁写的?\n")
                    .append("他身高多少?\n")
                    .append("他体重多少?\n")
                    .append("他从事什么岗位?\n\n")
                    .append("材料是谁写的?\n")
                    .append("他身高多少?\n")
                    .append("他体重多少?\n")
                    .append("他从事什么岗位?\n\n")
                    .append("材料是谁写的?\n")
                    .append("他身高多少?\n")
                    .append("他体重多少?\n")
                    .append("他从事什么岗位?\n\n")
                    .toString()
            );
        }

        mBottomDrawer.setData(strings);

        mBottomDrawer.setOnItemClickedListener(new BottomDrawer.OnItemClickedListener() {
            @Override
            public void onItemClicked(View view, int position, List<String> data) {
                Toast.makeText(BottomDrawerActivity.this, new StringBuilder("点击了").append(position).append("个相关问题"), Toast.LENGTH_SHORT).show();
            }
        });

    }
}

这个控件到此为止,逻辑已经讲解完毕。

二、联动RecyclerView
这个控件相对比较简单。主题部分包括:左右两个部分,左边是一个垂直方向的RecyclerVeiw,右边是一个水平方向的ScrollVeiw,这个ScrollView中嵌套一个垂直方向的RecyclerView。这两个RecyclerVeiw在垂直滚动的方向上完全一致。
先看一下主体布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="horizontal">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <include layout="@layout/view_self_select_title_head"/>

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="1px"
            android:background="#CDCDCD"/>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv_title"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_marginTop="5dp"/>
    </LinearLayout>

    <HorizontalScrollView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:overScrollMode="never"
        android:scrollbars="none">

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <include layout="@layout/view_self_select_content_head"/>

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="1px"
                android:background="#CDCDCD"/>

            <android.support.v7.widget.RecyclerView
                android:id="@+id/rv_content"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_marginTop="5dp"/>

        </LinearLayout>

    </HorizontalScrollView>

</LinearLayout>

其中有两个head不属于RecyclerView,view_self_select_title_head和view_self_select_content_head,就是效果图中联动RecyclerView最上面的那行,不再累述。
看一下自定义控件的代码逻辑:

public class DoubleRecyclerView extends LinearLayout {

    private RecyclerView mRvTitle;
    private RecyclerView mRvContent;
    private View mView;

    private TextView mTvEditBtn;
    private TextView mTvLastTrade;
    private TextView mTvChangePercent;
    private TextView mTvChange;
    private TextView mTvBid;
    private TextView mTvAsk;
    private TextView mTvPreClose;
    private TextView mTvOpen;
    private TextView mTvVolume;
    private TextView mTvTurOver;

    private ArrayList<SelfSelectBean> mData = new ArrayList<>();
    private SelfSelectAdapter mTitleAdapter;
    private SelfSelectAdapter mContentAdapter;


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

    public DoubleRecyclerView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

    private void initData() {

        mRvTitle.setLayoutManager(new LinearLayoutManager(getContext()));
        mRvContent.setLayoutManager(new LinearLayoutManager(getContext()));

        mTitleAdapter = new SelfSelectAdapter(getContext(), mData, SelfSelectAdapter.TYPE_TITLE);
        mContentAdapter = new SelfSelectAdapter(getContext(), mData, SelfSelectAdapter.TYPE_CONTENT);

        mRvTitle.setAdapter(mTitleAdapter);
        mRvContent.setAdapter(mContentAdapter);

        syncScroll(mRvTitle, mRvContent);

    }

    private void initView() {
        mView = LayoutInflater.from(getContext()).inflate(R.layout.view_double_recycler_view, this, true);
        mRvTitle = (RecyclerView) mView.findViewById(R.id.rv_title);
        mRvContent = (RecyclerView) mView.findViewById(R.id.rv_content);
        mTvEditBtn = (TextView) mView.findViewById(R.id.tv_edit_btn);
        mTvLastTrade = (TextView) mView.findViewById(R.id.tv_head_last_trade);
        mTvChangePercent = (TextView) mView.findViewById(R.id.tv_head_change_percent);
        mTvChange = (TextView) mView.findViewById(R.id.tv_head_change);
        mTvBid = (TextView) mView.findViewById(R.id.tv_head_bid);
        mTvAsk = (TextView) mView.findViewById(R.id.tv_head_ask);
        mTvPreClose = (TextView) mView.findViewById(R.id.tv_head_pre_close);
        mTvOpen = (TextView) mView.findViewById(R.id.tv_head_open);
        mTvVolume = (TextView) mView.findViewById(R.id.tv_head_volume);
        mTvTurOver = (TextView) mView.findViewById(R.id.tv_head_tur_over);
    }

    private void syncScroll(final RecyclerView leftList, final RecyclerView rightList) {
        leftList.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                if (recyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
                    rightList.scrollBy(dx, dy);
                }
            }
        });

        rightList.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                if (recyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
                    leftList.scrollBy(dx, dy);
                }
            }
        });
    }

    public void setData(ArrayList<SelfSelectBean> data) {
        if (data != null && data.size() > 0) {
            mData.clear();
            mData.addAll(data);
            mTitleAdapter.notifyDataSetChanged();
            mContentAdapter.notifyDataSetChanged();
        }
    }

    public void addData(ArrayList<SelfSelectBean> data) {
        if (data != null && data.size() > 0) {
            mData.addAll(data);
            mTitleAdapter.notifyDataSetChanged();
            mContentAdapter.notifyDataSetChanged();
        }
    }

}

这里最主要的逻辑就是syncScroll( )方法,同步滚动的方法,看代码即可理解,同理,也可以扩充到N级联动。
就到此结束吧,有需要的同志可以在码云上下载Demo修改并使用,欢迎大家继续完善Demo。

相关文章

网友评论

    本文标题:〔两行哥〕自定义分屏控件及联动RecyclerView控件

    本文链接:https://www.haomeiwen.com/subject/lyugcxtx.html