ScrollView 源码分析(二)
来源:互联网 发布:ubuntu安装wps office 编辑:程序博客网 时间:2024/06/05 05:32
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 spaceif (!sHelperRect.intersect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom())) { outputRect.setEmpty(); return;}// Now we move the coordinates to the View's coordinate spacesHelperRect.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++; }} }
- ScrollView 源码分析(二)
- 【android】ScrollView源码分析
- Scrollview源码分析
- ScrollView源码分析
- android ScrollView smoothScrollTo源码分析
- Scroll分析-附ScrollView源码分析
- Scroll原理-附ScrollView源码分析
- Mangos源码分析(二)
- ConcurrentHashMap 源码分析 (二)
- Notepad++源码分析(二)
- gSOAP 源码分析(二)
- shopqi源码分析二
- VLC源码分析(二)
- mongodb源码分析(二)
- VLC源码分析(二)
- tomcat源码分析二
- Struts2源码分析二
- gSOAP 源码分析(二)
- AJAX请求头Content-type
- 自动化测试框架Cucumber和RobotFramework的实战对比
- form提交action和url提交action有什么区别?
- Java学习【经典面试题: 写出你见过的运行时异常。】
- numpy中的基本数据类型
- ScrollView 源码分析(二)
- 20170728学习问题
- 如何利用opencv c++徒手写BP神经网络识别数字(一)前言及准备
- React 疑难点-Props和State的区别
- swift_037(Swift之Swift和OC混编)
- 如何加入统计概率思维社群?
- 前端常见算法的JS实现
- MySQL必知必会笔记(六)插入数据 创建和操纵表
- mysql消息队列/定时任务实现思路(一)