recyclerview 简单布局小试牛刀

这是  https://blog.csdn.net/handsonn/article/details/52850815 的第二篇

简单的recyclerview 运用,实现的效果如下,类似一个简易的课表:

activity_main.xml 布局如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:background="@color/colorPrimary">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/tv_schedule"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_centerInParent="true"
                android:gravity="center_vertical"
                android:textColor="@android:color/white"
                android:textSize="16sp"
                android:text="no select"/>
        </RelativeLayout>
    </android.support.v7.widget.Toolbar>


    <com.example.z.scheduleroom.view.ScheduleRoom
        android:id="@+id/content_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:schedule_left_cell_width="18dp"
        app:schedule_top_cell_height="44dp">
    </com.example.z.scheduleroom.view.ScheduleRoom>

</LinearLayout>

ScheduleRoom 就是 toolbar 下面的布局,是一个简单的组合控件

那么,需要解决几个问题:

1、首先,如何让 RecyclerView 任意方向滚动呢,要感谢这里作为参考(太牛了,大神啊~~~):

Building a RecyclerView LayoutManager

2、其次,如何获取 ScrollView 的滚动距离?

这个比较简单,继承 ScrollView,抛出一个回调即可,代码如下:

public class ObserverScrollView extends ScrollView {

    private ScrollViewListener scrollViewListener;

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

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

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


    public void setScrollViewListener(ScrollViewListener scrollViewListener) {
        this.scrollViewListener = scrollViewListener;
    }


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

    @Override
    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
        super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
        if (scrollViewListener != null) {
            scrollViewListener.onOverScrolled(this, scrollX, scrollY, clampedX, clampedY);
        }
    }

    public interface ScrollViewListener {

        void onScrollChanged(ObserverScrollView scrollView, int newX, int newY, int oldX, int oldY);

        void onOverScrolled(ObserverScrollView scrollView, int scrollX, int scrollY, boolean clampedX, boolean clampedY);

    }
    
}

3、接着,如何让 ScrollView  和 RecyclerView 、RecyclerView 与 RecyclerView 之间互相联动 ?

什么意思呢?

a、当 ScrollView 滚动时,通过捕获 ScrollView 滚动让 RecyclerView 也同时滚动

b、同理,当 RecyclerView 滚动时,通过捕获 RecyclerView 滚动让 ScrollView 也同时滚动

那么,问题来了,ScrollView  和 RecyclerView 都是要监听的,当 ScrollView 滚动时,RecyclerView就会跟着一起联动,这个时候又会触发 RecyclerView 的滚动监听,里面又会让 ScrollView 跟着联动,就这样互相联动,相当于造成了无限递归了,如下:

        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                topRecyclerView.scrollBy(dx, 0);
                leftScrollView.scrollBy(0, dy);
            }
        });

        topRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                mRecyclerView.scrollBy(dx, 0);
            }
        });

        leftScrollView.setScrollViewListener(new ObserverScrollView.ScrollViewListener() {
            @Override
            public void onScrollChanged(ObserverScrollView scrollView, int newX, int newY, int oldX, int oldY) {
                mRecyclerView.scrollBy(0, newY - oldY);
            }

            @Override
            public void onOverScrolled(ObserverScrollView scrollView, int scrollX, int scrollY, boolean clampedX, boolean clampedY) {

            }
        });

那么,肯定是需要一些条件的,这个条件可以让我们完成这些事:

a、当拖动 recyclerview 滚动时候,scrollView 跟着 recyclerview 滚动,但是 ScrollView 里面滚动监听的 recyclerview 不会跟着联动;

b、同理,当拖动 ScrollView 滚动时候,recyclerview 跟着 ScrollView 滚动,但是 recyclerview 里面滚动监听的 ScrollView 不会跟着联动;

(1)一种方法,通过判断手指 touch 在哪个view,添加标记位来记录,不过这样有个坏处,onTouch 是异步的,所以肯定还有 a、b的问题;

(2)另一种,通过查看 RecyclerView 里面的代码,可以发现,系统已经提供了这些状态码:

可以看到  RecyclerView  默认 mScrollState  是  SCROLL_STATE_IDLE,也就是 recyclerview 不自己主动滚动的时候就是这个状态,

当手指拖动 recyclerview 滚动的时候  mScrollState 是 1,手指离开 recyclerview 但是 recyclerview 还在继续滚动的时候,mScrollState  是 2 ,那么就可以根据这些状态来判断了。

添加条件之后如下:

        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                if (topRecyclerView.getScrollState() == 0) {
                    topRecyclerView.scrollBy(dx, 0);
                }
                if (mRecyclerView.getScrollState() != 0) {
                    leftScrollView.scrollBy(0, dy);
                }
            }
        });

        topRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                if (mRecyclerView.getScrollState() == 0) {
                    mRecyclerView.scrollBy(dx, 0);
                }
            }
        });

        leftScrollView.setScrollViewListener(new ObserverScrollView.ScrollViewListener() {
            @Override
            public void onScrollChanged(ObserverScrollView scrollView, int newX, int newY, int oldX, int oldY) {
                if (mRecyclerView.getScrollState() == 0) {
                    mRecyclerView.scrollBy(0, newY - oldY);
                }
            }

            @Override
            public void onOverScrolled(ObserverScrollView scrollView, int scrollX, int scrollY, boolean clampedX, boolean clampedY) {

            }
        });

4、然后,ScrollView 和 RecyclerView 联动滚动错位问题 ,即 拖动  ScrollView 滚动的时候,RecyclerView 跟着滚动,这个时候会有明显的错位问题,两个控件无法相同时间内滚动相同距离,有快有慢,这个时候需要将 继承的 ScrollView 修改成 NestedScrollView,修改后的  ObserverScrollView 如下:

public class NestedObserverScrollView extends NestedScrollView {

    private ScrollViewListener scrollViewListener;

    public NestedObserverScrollView(@NonNull Context context) {
        super(context);
    }

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

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

    public void setNestedScrollViewListener(ScrollViewListener scrollViewListener) {
        this.scrollViewListener = scrollViewListener;
    }



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

    @Override
    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
        super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
        if (scrollViewListener != null) {
            scrollViewListener.onOverScrolled(this, scrollX, scrollY, clampedX, clampedY);
        }
    }

    public interface ScrollViewListener {

        void onScrollChanged(NestedObserverScrollView scrollView, int newX, int newY, int oldX, int oldY);

        void onOverScrolled(NestedObserverScrollView scrollView, int scrollX, int scrollY, boolean clampedX, boolean clampedY);

    }

}

5、接下来就是选择时间段的问题了,嗯,是的,计算就好了,当然有一点需要注意的是,计算 dp2px 的时候,

例如:

public static int dp2px(Context context, double dp) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dp * scale + 0.5f);
    }

 num *  dp2px() 和  dp2px(dp * num)  和 是不一样的,如果数字 num 较大的话,误差就大了,后者是正确的做法,因为不同设备的 density 不一样,而且 还加了 0.5f,就有  0.5 * num 的误差

关于为什么转换的时候要加 0.5f 呢,很明显是为了 四舍五入,通过看系统一些做法也是这么做的,比如这里:

里面的 applyDimension 就是 先计算 value*density,后面的已经不属于这里讨论的了

6、记录状态,可以定义一个实体类,如下:

附上demo地址: https://github.com/laymanZ/ScheduleRoom

好了,一共就这么多,很简单,作为一个记录,写的不好的地方欢迎指正,共同学习。

猜你喜欢

转载自blog.csdn.net/handsonn/article/details/80849445