关于React Native加载Android原生封装的动态ViewGroup组件失效的解决方案

前言

近期,项目中需要在原生端封装一个ViewGroup组件,给RN那边使用,写完了之后发现一些动态的东西显示会有问题,比如最简单的visibility,但是同样的代码在原生里边运行没有问题,就很奇怪!由此觉得是RN的问题

问题分析

因为觉得是RN的问题,所以就去GitHub上搜了RN项目的issue,果然这样的问题还是有挺多人遇到的,
参考:Android native UI components are not re-layout on dynamically added views #17968

在这个issue里边,根据@irgendeinich的回答

We are running into the same problem while developing the react-native wrapper of our Android SDK.

As far as I can tell the issue is that if you attach a ViewGroup to the react-native view hierarchy any change you make inside this ViewGroup causes a layout pass to be triggered. Layouting on Android happens top-down so the ReactRootView gets the layout calls which it will drop since layouting is handled by the UIManagerModule.

ViewGroupManager already has a needsCustomLayoutForChildren method which tells react-native to call the regular Android layout for any react views you add. There also needs to be a way to tell react-native that we have added a ViewGroup which needs all regular layout calls forwarded. All this would need to do is to call layout() with the already calculated values on your views when ever layout is requested.

总结一下,就是说当你把一个viewgroup添加到rn的布局层级里边,当其中发生变化时,触发的布局传递最终是由UIManagerModule来处理。这一点,我们可以看一下ReactViewGroup的源码:

  @Override
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    // No-op since UIManagerModule handles actually laying out children.
  }

  @Override
  public void requestLayout() {
    // No-op, terminate `requestLayout` here, UIManagerModule handles laying out children and
    // `layout` is called on all RN-managed views by `NativeViewHierarchyManager`
  }

而且,我们也可以看到就算你手动调用requestLayout(),也没有用!

另外,在ViewGroupManager已经有一个needsCustomLayoutForChildren方法,该方法告诉react-native当你添加一个react view时,会调用Android原生的layout方法。但是现在没有这样一个方法,当你添加一个ViewGroup,这个ViewGroup里边的view发生变化时,没有方法去告诉react-native去执行layout方法。所以到这样我们就明白了,原因就是我们封装的ViewGroup内部的view的变化(具体指位置的变化,即layout),react-native 不会重新测量并布局。

解决方案

知道了原因,那么下边也看到了一些回答的方案!

方案一

在@irgendeinich的回答中也提到了,在封装的ViewGroup的初始化方法中添加如下代码:

private void init(){
    ....
    Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                child.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
                child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
            }
            getViewTreeObserver().dispatchOnGlobalLayout();
            Choreographer.getInstance().postFrameCallback(this);
        }
    });
}       

我们可以去参考一下@irgendeinich提到的封装的Pdf的sdk:PSPDFKit/PdfView.java#L144

方案二

还有就是在另一个issue中,@tlcheah2的回答,重写requestLayout()方法:

@Override
public void requestLayout() {
    super.requestLayout();
    post(measureAndLayout);
}

private final Runnable measureAndLayout = new Runnable() {
    @Override
    public void run() {
        measure(
                MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
        layout(getLeft(), getTop(), getRight(), getBottom());
    }
};

结语

那么最后想说一句使用RN进行混合的开发的同学的心声:我太难了!

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值