• RecyclerView懒加载失效问题(三)


    前言:通过NestedScrollView嵌套RecyclerView可以轻松实现嵌套滑动,但我们会发现RecyclerView懒加载失效了。

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    3. android:layout_width="match_parent"
    4. android:layout_height="match_parent"
    5. xmlns:app="http://schemas.android.com/apk/res-auto">
    6. <LinearLayout
    7. android:layout_width="match_parent"
    8. android:layout_height="match_parent"
    9. android:orientation="vertical"
    10. android:descendantFocusability="blocksDescendants">
    11. <TextView
    12. android:id="@+id/tv_title"
    13. android:layout_width="match_parent"
    14. android:layout_height="100dp"
    15. android:background="@color/colorAccent"
    16. android:gravity="center"
    17. android:text="这是头部" />
    18. <androidx.recyclerview.widget.RecyclerView
    19. android:id="@+id/rv_content"
    20. android:layout_width="match_parent"
    21. android:layout_height="match_parent"
    22. app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
    23. android:orientation="vertical"/>
    24. </LinearLayout>
    25. </androidx.core.widget.NestedScrollView>

    原因:NestedScrollView高度虽然为屏幕高度,但其对子布局会进行wrap_content方式测量,这里LinearLayout即使是match_parent也不是屏幕高度,因此传递给其子RecyclerView也wrap_content方式得到的高度,导致RecyclerView一次加载出全部数据。(NestedScrollView嵌套滑动原理是还是平面式的滑动,RecyclerView由于加载了全部数据,本身不再滑动而是随着将LinearLayout移动实现的)

    方案:要使RecyclerView懒加载则不能使其高度包裹所有item,需要指定最大高度(本例中最大高度为屏幕高度,先是NestedScrollView响应滑动当LinearLayout至最底部时,此时RecyclerView正好显示全,接下来再滑动就由RecyclerView来完成,这才是真正的嵌套滑动)

    1、自定义RecyclerView使其支持设置最大高度

    1. public class MaxHeightRecyclerView extends RecyclerView {
    2. private int mMaxHeight;
    3. public MaxHeightRecyclerView(@NonNull Context context) {
    4. super(context);
    5. }
    6. public MaxHeightRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
    7. super(context, attrs);
    8. }
    9. @Override
    10. protected void onMeasure(int widthSpec, int heightSpec) {
    11. if (mMaxHeight != 0){
    12. super.onMeasure(widthSpec, View.MeasureSpec.makeMeasureSpec(mMaxHeight, View.MeasureSpec.AT_MOST));
    13. }else {
    14. super.onMeasure(widthSpec, heightSpec);
    15. }
    16. }
    17. public void setMaxHeight(int maxHeight) {
    18. this.mMaxHeight = maxHeight;
    19. }
    20. }

    2、自定义NestedScrollView,使其给RecyclerView设置最大高度

    1. public class LazyNestedScrollView extends androidx.core.widget.NestedScrollView{
    2. private int mRecyclerViewId;
    3. private MaxHeightRecyclerView mRecyclerView;
    4. public LazyNestedScrollView(@NonNull Context context) {
    5. super(context);
    6. }
    7. public LazyNestedScrollView(@NonNull Context context, @Nullable AttributeSet attrs) {
    8. super(context, attrs);
    9. TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LazyNestedScrollView);
    10. //获取嵌套RecyclerView控件的id
    11. mRecyclerViewId = typedArray.getResourceId(R.styleable.LazyNestedScrollView_recyclerview_id, 0);
    12. typedArray.recycle();
    13. }
    14. @Override
    15. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    16. //super前给RecyclerView设置最大值,然后通过super进行测量即可
    17. int height = MeasureSpec.getSize(heightMeasureSpec);
    18. if (mRecyclerView != null){
    19. mRecyclerView.setMaxHeight(height);
    20. }else {
    21. findViewById(this);
    22. if (mRecyclerView != null){
    23. mRecyclerView.setMaxHeight(height);
    24. }
    25. }
    26. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    27. }
    28. //根据id递归查询RecyclerView
    29. private void findViewById(View view){
    30. if (view instanceof ViewGroup && !(view instanceof RecyclerView)){
    31. ViewGroup viewGroup = (ViewGroup) view;
    32. int childCount = viewGroup.getChildCount();
    33. for (int i = 0; i < childCount; i++){
    34. View childView = viewGroup.getChildAt(i);
    35. findViewById(childView);
    36. }
    37. }else {
    38. if (view.getId() == mRecyclerViewId && view instanceof MaxHeightRecyclerView){
    39. mRecyclerView = (MaxHeightRecyclerView) view;
    40. }
    41. }
    42. }
    43. @Override
    44. public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
    45. if (mRecyclerView != null){
    46. View child = getChildAt(0);
    47. MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    48. //获取自己能够滑动的距离
    49. int topHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin - getMeasuredHeight();
    50. int scrollY = getScrollY();
    51. boolean topIsShow = scrollY >=0 && scrollY < topHeight;
    52. if(topIsShow) {
    53. //由自己响应滑动
    54. int remainScrollY= topHeight - scrollY;
    55. int selfScrollY = remainScrollY > dy ? dy : remainScrollY;
    56. scrollBy(0, selfScrollY);
    57. //告诉RecyclerView,自己滑动了多少距离
    58. consumed[1] = selfScrollY;
    59. } else {
    60. super.onNestedPreScroll(target, dx, dy, consumed, type);
    61. }
    62. }else {
    63. super.onNestedPreScroll(target, dx, dy, consumed, type);
    64. }
    65. }
    66. }

    注:

    (1)RecyclerView的setNestedScrollingEnabled()应为true

    (2)嵌套滑动是通过NestedScrollingParent3和NestedScrollingChild3来实现的,这里RecyclerView已经继承NestedScrollingChild3而NestedScrollView也已继承NestedScrollingParent3,RecyclerView先接收滑动事件然后先询问NestedScrollView来滑动(即onNestedPreScroll方法)然后将其滑动距离告诉RecyclerView,RecyclerView再对剩下的距离进行滑动

    3、惯性滑动(NestedScrollView滑动到底部,将其滑动速度转化成惯性距离,计算子控件应滑距离=父惯性距离-父已滑距离,将子控件应滑距离转化成速度交给子控件进行惯性滑动)

    (1)记录NestedScrollView惯性速度

    1. @Override
    2. public void fling(int velocityY) {
    3. super.fling(velocityY);
    4. mVelocityY = velocityY;
    5. }

    (2)将剩余的惯性速度传递给RecyclerView

    1. @Override
    2. protected void onScrollChanged(int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
    3. super.onScrollChanged(scrollX, scrollY, oldScrollX, oldScrollY);
    4. if (mRecyclerView != null){
    5. View child = getChildAt(0);
    6. MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    7. //判断是否滑动到底部
    8. if (scrollY == child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin - getMeasuredHeight()){
    9. if (mVelocityY > 0){
    10. //将惯性速度转化为滑动距离
    11. double distance = FlingHelper.getInstance(getContext()).getSplineFlingDistance(mVelocityY);
    12. if (distance > scrollY){
    13. //将剩余滑动距离转化为惯性速度
    14. int velocityY = FlingHelper.getInstance(getContext()).getVelocityByDistance(distance - scrollY);
    15. //将剩余惯性速度传递给RecyclerView
    16. mRecyclerView.fling(0, velocityY);
    17. //重置惯性速度
    18. mVelocityY = 0;
    19. }
    20. }
    21. }
    22. }
    23. }

    (3)惯性速度和滑动距离转化工具类

    1. public class FlingHelper {
    2. private static FlingHelper mFlingHelper;
    3. private static final double DECELERATION_RATE = Math.log(0.78) / Math.log(0.9);
    4. private float mFlingFriction = ViewConfiguration.getScrollFriction();
    5. private float mPhysicalCoeff;
    6. private FlingHelper(Context context){
    7. mPhysicalCoeff = context.getResources().getDisplayMetrics().density * 160.0f * 386.0878f * 0.84f;
    8. }
    9. public static FlingHelper getInstance(Context context){
    10. if (mFlingHelper == null){
    11. mFlingHelper = new FlingHelper(context);
    12. }
    13. return mFlingHelper;
    14. }
    15. public double getSplineFlingDistance(int i){
    16. return Math.exp(getSplineDeceleration(i) * (DECELERATION_RATE / (DECELERATION_RATE - 1.0))) * (mFlingFriction * mPhysicalCoeff);
    17. }
    18. private double getSplineDeceleration(int i){
    19. return Math.log(0.35f * Math.abs(i) / (mFlingFriction * mPhysicalCoeff));
    20. }
    21. public int getVelocityByDistance(double d){
    22. return (int) Math.abs((Math.exp(getSplineDecelerationByDistance(d)) * mFlingFriction * mPhysicalCoeff / 0.3499999940395355));
    23. }
    24. private double getSplineDecelerationByDistance(double d){
    25. return (DECELERATION_RATE - 1.0) * Math.log(d / (mFlingFriction * mPhysicalCoeff)) / DECELERATION_RATE;
    26. }
    27. }
    
                    
  • 相关阅读:
    Pandas中的宝藏函数-map
    3. const
    校园报修抢修小程序系统开发 物业小区报修预约上门维修工单系统
    第七章第三节:散列表(Hash Table)
    HCIP第十五天笔记(企业网的三层架构、VLAN以及VLAN 的配置)
    【Rust日报】2022-07-27 chrono 有了新的维护者
    好好回答下 TCP 和 UDP 的区别!
    机器人微控制器编程(CoCube)-深度融合
    【Flink实战】Flink 商品销量统计-实战Bahir Connetor实战存储 数据到Redis6.X
    希望你多出去看看,别活在短视频和文字里!
  • 原文地址:https://blog.csdn.net/yufumatou/article/details/126869993