ScrollView 源码分析(二)

ScrollView 源码分析(二)

public class ReactScrollView extends ScrollView implements ReactClippingViewGroup {
.........
}

这段代码清楚的向我们展示了 ReactScrollView 的继承关系,它继承了 ScrollView,实现了 ReactClippingViewGroup 接口,其中继承 ScrollView 的话,我们只需呀关注,React ScrollView 如何添加 Item 到 ScrollView 中(因为在React 中它更像Native的ListView的特性,有添加Item的能力,而不像Native的ScrollView只有容器能力)。

我们先来看看后面这个接口是干嘛的

 // 这里省去了所有的注释
public interface ReactClippingViewGroup {
  void updateClippingRect();
  void getClippingRect(Rect outClippingRect);
  void setRemoveClippedSubviews(boolean removeClippedSubviews);
  boolean getRemoveClippedSubviews();
}

根据注释,我们可以知道实现了这个接口的 View 组件类,就能够在 JS 中支持 removeClippedSubviews 的属性。如果一个 ViewGroup 组件类具有这个属性,就可以移除被这个 ViewGroup 边界切割的子 View。具体的区分一个子 View 是否被切割,要根据 ReactClippingViewGroupHelper 类的 calculateClippingRect()方法来计算。

简单来说,就是实现了这个接口的 ViewGroup 组件类,就可以移除不可见的子 View,回收内存(没考虑复用,因为复用的代价比较大,后面会说到)。

我们回到源码简单看一下构造函数:

public ReactScrollView(Context context, @Nullable FpsListener fpsListener) {
super(context);
mFpsListener = fpsListener;
// sTriedToGetScrollerField 默认是 false,所以一定会执行一次第一个if里面的代码。
if (!sTriedToGetScrollerField) {
  sTriedToGetScrollerField = true; // 设置为 true
  try {
// 通过反射获取 mScroller字段,mScroller其实是一个 OverScroller 对象
sScrollerField = ScrollView.class.getDeclaredField("mScroller");
sScrollerField.setAccessible(true);// 为了避免 IllegalAccessExceptions 异常
  } catch (NoSuchFieldException e) {
Log.w(
  ReactConstants.TAG,
  "Failed to get mScroller field for ScrollView! " +
"This app will exhibit the bounce-back scrolling bug :(");
  }
}
// 如果 OverScroller 对象不为空,则根据字段拿到 OverScroller 对象。
if (sScrollerField != null) {
  try {
mScroller = (OverScroller) sScrollerField.get(this);
  } catch (IllegalAccessException e) {
throw new RuntimeException("Failed to get mScroller from ScrollView!", e);
  }
} else {
  mScroller = null;
}
}

总的来说,构造函数里面就是为了获取一个 一个 OverScroller 对象。 OverScroller 是一个 Scoller 类,用于滚动的辅助计算,最早的实现是 Scroller 类,大概在 api 1的时候,后来 google 为了完善 Scroller 类,就给出了 OverScroller 这个类,用于处理超出边界的滚动及其他扩展情况。然后,我们还忘记了 mFpsListener = fpsListener;这一行代码,这个类和我们 React 的 onScroll 函数回调有关,后面有用到再解释。

ReactScrollView 对应这一个 ReactScrollViewManager 具体的位置位于 com.facebook.react.views.scroll.ReactScrollViewManager,这个类管理着 ReactScrollView 的一个示例,并且暴露了创建实例和设置属性的方法给 JS。

  @Override
  public String getName() {
return REACT_CLASS;
  }

Manager 通过 放回一个 String 的变量供 JS 调用,然后通过 @ReactProp(或@ReactPropGroup)注解来导出属性的设置方法,提供给 JS 设置,也就大概反映了着他们之间的通信模式。如下,便是一个属性设置方法的示例代码:

  @ReactProp(name = "scrollEnabled", defaultBoolean = true)
  public void setScrollEnabled(ReactScrollView view, boolean value) {
view.setScrollEnabled(value);
  }

上面的方法,暴露给 JS,JS可以通过这个方法设置 scrollEnabled 属性,来决定 ScrollView 的内容是否可以被滚动。

我们大概了解了一下 ReactScrollView 之后,就可以着手进行分析了,我们首先从 Native ScrollView 上出发,考虑的几个问题和前提:

  • ScrollView 继承子 FrameLayout,它是一个 ViewGroup.
  • ScrollView 只允许一个直接子View,然后这个子 View 在包裹这需要滚动的内容,这个子 View 一般是 LinearLayout 之类的布局类。
  • ScrollView 相当于把全被子View的内容一次显示出来,或者说,它不像 ListView 一样是动态添加的,但是 React ScrollView 却可以实现动态添加,是怎样一种实现机制?

就第三个问题来分析一下,我们知道 View的移动(内容滑动,并非View本身移动)就是调用了 View 的 scrollBy()和scrollTo()方法,而scrollBy()和scrollTo()方法最终会导致 View 的 onScrollChanged()方法的调用,那我每年进去 ReactScrollView 看下这个方法:

  @Override
  protected void onScrollChanged(int x, int y, int oldX, int oldY) {
super.onScrollChanged(x, y, oldX, oldY);// 继承了 onScrollChanged(),这里只需要知道这里完成了
// 判断移动是否有效
if (mOnScrollDispatchHelper.onScrollChanged(x, y)) {
  if (mRemoveClippedSubviews) { //默认是false,这里的 mRemoveClippedSubviews 设置值是在哪里设置的?
updateClippingRect();
  }

  if (mFlinging) {
mDoneFlinging = false;
  }

  ReactScrollViewHelper.emitScrollEvent(this);
}
  }

这个方法里面,继承了 ScrollView 的 onScrollChanged()方法,然后第一个 if 里面判断移动是否有效,然后第二个if里面,根绝 mRemoveClippedSubviews 的布尔值,如果是 true 就执行 updateClippingRect() 方法,这个 mRemoveClippedSubviews 是在哪里设置的呢? ReactScrollView 里面有个 setRemoveClippedSubviews()方法,这里对 mRemoveClippedSubviews 进行了赋值。但是又是再哪里调用这个方法的呢?

  @Override
  public void setRemoveClippedSubviews(boolean removeClippedSubviews) {
if (removeClippedSubviews && mClippingRect == null) {
  mClippingRect = new Rect();
}
mRemoveClippedSubviews = removeClippedSubviews;
updateClippingRect();
  }

一路找呀找,我们找到了,ReactScrollViewManager 类发现了下面这个方法:

  @ReactProp(name = ReactClippingViewGroupHelper.PROP_REMOVE_CLIPPED_SUBVIEWS)
  public void setRemoveClippedSubviews(ReactScrollView view, boolean removeClippedSubviews) {
view.setRemoveClippedSubviews(removeClippedSubviews);
  }

原来,只要你在 React 的ScrollView 里面设置了 removeClippedSubviews 属性值就是,我们之前的 mRemoveClippedSubviews数值了。那么现在就来考虑,updateClippingRect()方法里面的实现把,下面是具体的实现代码:

  @Override
  public void updateClippingRect() {
if (!mRemoveClippedSubviews) {
  return;// 如果 React 上的 ScrollView 的removeClippedSubviews 属性设置为 false
}

Assertions.assertNotNull(mClippingRect);
// 关键代码就在这里了
ReactClippingViewGroupHelper.calculateClippingRect(this, mClippingRect);
// 获取直接子 View 
View contentView = getChildAt(0);
//如果直接子 View 也是实现了 ReactClippingViewGroup 接口,则子 View 也会调用updateClippingRect()方法。
if (contentView instanceof ReactClippingViewGroup) {
  ((ReactClippingViewGroup) contentView).updateClippingRect();
}
  }

这个方法的详细很简单,基本上我都在上面的代码里面注释了,现在我们在重点放在 ReactClippingViewGroupHelper.calculateClippingRect(this, mClippingRect) 这个静态方法里面,先上代码:

 public static void calculateClippingRect(View view, Rect outputRect) {
ViewParent parent = view.getParent();
if (parent == null) {
  outputRect.setEmpty();
  return;
} else if (parent instanceof ReactClippingViewGroup) {
  ReactClippingViewGroup clippingViewGroup = (ReactClippingViewGroup) parent;
  if (clippingViewGroup.getRemoveClippedSubviews()) {
clippingViewGroup.getClippingRect(sHelperRect);
// Intersect the view with the parent's rectangle
// This will result in the overlap with coordinates in the parent space
if (!sHelperRect.intersect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom())) {
  outputRect.setEmpty();
  return;
}
// Now we move the coordinates to the View's coordinate space
sHelperRect.offset(-view.getLeft(), -view.getTop());
sHelperRect.offset(view.getScrollX(), view.getScrollY());
outputRect.set(sHelperRect);
return;
  }
}
view.getDrawingRect(outputRect);
  }

这里的代码也不是很长,但是单单看这里的代码还不能够说明根本问题,我之前提到过 React ScrollView 是一个 ViewGroup 类,实际上在 View 类型的子类也会实现 ReactClippingViewGroup 接口,就拿最简单的 View 来说,它在 Native 对应的类是 ReactViewGroup,先看看 updateClippingRect()方法是怎么实现的:

  @Override
  public void updateClippingRect() {
if (!mRemoveClippedSubviews) {
  return;
}

Assertions.assertNotNull(mClippingRect);
Assertions.assertNotNull(mAllChildren);

ReactClippingViewGroupHelper.calculateClippingRect(this, mClippingRect);
updateClippingToRect(mClippingRect);
  }

看到这里,我们似乎清楚了:

  private void updateClippingToRect(Rect clippingRect) {
Assertions.assertNotNull(mAllChildren);
int clippedSoFar = 0;
for (int i = 0; i < mAllChildrenCount; i++) {
  updateSubviewClipStatus(clippingRect, i, clippedSoFar);
  if (mAllChildren[i].getParent() == null) {
clippedSoFar++;
  }
}
  }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值