ReactNative组件的borderColor和borderRadius属性踩坑记录

问题

今天小程序平台项目中发现Android上自定义的ReactNative的Text组件样式显示不正确,具体表现如下图所示:
ReactNative组件的borderColor和borderRadius属性踩坑记录_第1张图片
可以看到图中组件显示主要存在两个问题:
1、左右黑边;2、左右锯齿;

好嘛,这里自定义的组件是继承RN原有组件生成的,于是果断把同样的样式代码放到RN自己的Text组件里写个demo一看,竟然也有这两个问题。再换View组件也试一下,好吧,表现一样,demo代码如下:

render() {
    return (
        大幅度反对
    );
  }

显示结果如下:
ReactNative组件的borderColor和borderRadius属性踩坑记录_第2张图片
好吧,看来rn自己没有处理好这一块儿的内容啊……只好自己解决了,emmm~~

问题一:左右两边框颜色显示不正常

原因与解决方案

经多次调整样式代码后发现,左右黑边原因是borderColor与borderLeftColor和borderRightColor颜色值冲突造成的,这里的表现是同时设置borderColor和四边框的颜色值后,左右两边的颜色是borderColor的颜色值、上下两边是单独设置的色值。
好吧,作为一个平台级项目,我们是没法控制对方是如何来写js代码的,因此也没法限制borderColor和四边框色值不同时存在。如此就只能通过原生端代码来控制了。
商量后决定,当两者同时存在时,就以四边框色值为准。

ReactNative中borderColor的实现

先来看RN自己是如何实现borderColor相关内容的吧。
Text组件对应的Android上的ViewManager是ReactTextViewManager,在ReactTextViewManager的父类ReactTextAnchorViewManager的源码里面找到了设置borderColor属性的代码,内容如下:

 @ReactPropGroup(
    names = {
      "borderColor",
      "borderLeftColor",
      "borderRightColor",
      "borderTopColor",
      "borderBottomColor"
    },
    customType = "Color"
  )
  public void setBorderColor(ReactTextView view, int index, Integer color) {
    float rgbComponent =
        color == null ? YogaConstants.UNDEFINED : (float) ((int) color & 0x00FFFFFF);
    float alphaComponent = color == null ? YogaConstants.UNDEFINED : (float) ((int) color >>> 24);
    view.setBorderColor(SPACING_TYPES[index], rgbComponent, alphaComponent);

代码意思仔细看看就明白了,不做过多解释,其中SPACING_TYPES代码如下:

 private static final int[] SPACING_TYPES = {
    Spacing.ALL, Spacing.LEFT, Spacing.RIGHT, Spacing.TOP, Spacing.BOTTOM,
  };}

点setBorderColor函数,一路往上寻找,最终在ReactViewBackgroundDrawable类里找到了setBorderColor的具体实现:

public void setBorderColor(int position, float rgb, float alpha) {
    this.setBorderRGB(position, rgb);
    this.setBorderAlpha(position, alpha);
  }

  private void setBorderRGB(int position, float rgb) {
    // set RGB component
    if (mBorderRGB == null) {
      mBorderRGB = new Spacing(DEFAULT_BORDER_RGB);
    }
    if (!FloatUtil.floatsEqual(mBorderRGB.getRaw(position), rgb)) {
      mBorderRGB.set(position, rgb);
      invalidateSelf();
    }
  }

  private void setBorderAlpha(int position, float alpha) {
    // set Alpha component
    if (mBorderAlpha == null) {
      mBorderAlpha = new Spacing(DEFAULT_BORDER_ALPHA);
    }
    if (!FloatUtil.floatsEqual(mBorderAlpha.getRaw(position), alpha)) {
      mBorderAlpha.set(position, alpha);
      invalidateSelf();
    }
  }

打断点调试,发现这部分代码所有内容都会执行,其中invalidateSelf是其父类Drawable里实现的函数:

/**
     * Use the current {@link Callback} implementation to have this Drawable
     * redrawn.  Does nothing if there is no Callback attached to the
     * Drawable.
     *
     * @see Callback#invalidateDrawable
     * @see #getCallback()
     * @see #setCallback(android.graphics.drawable.Drawable.Callback)
     */
    public void invalidateSelf() {
        final Callback callback = getCallback();
        if (callback != null) {
            callback.invalidateDrawable(this);
        }
    }

好吧,看完这一路代码,明白设置bordercolor的过程应该是每次设置完透明度和色值后,就刷新下重新绘制。
仔细思考后,决定将整体的bordercolor属性拆分为上下左右四边,控制四边的绘制。

控制

属性是并发调用的,顺序没法控制,就先添加后等view加载完成后,再手动控制。view加载完后执行onAfterUpdateTransaction。涉及到并发访问用ConcurrentHashMap存储数据。

/**************************修复同时bordercolor和borderRightColor、borderLeftColor,后两者不生效的问题***********************/
    protected static final int[] SPACING_TYPES = {
            Spacing.ALL,
            Spacing.LEFT,
            Spacing.RIGHT,
            Spacing.TOP,
            Spacing.BOTTOM,
            Spacing.START,
            Spacing.END,
    };

    @ReactPropGroup(
            names = {
                    ViewProps.BORDER_COLOR,
                    ViewProps.BORDER_LEFT_COLOR,
                    ViewProps.BORDER_RIGHT_COLOR,
                    ViewProps.BORDER_TOP_COLOR,
                    ViewProps.BORDER_BOTTOM_COLOR,
                    ViewProps.BORDER_START_COLOR,
                    ViewProps.BORDER_END_COLOR
            },
            customType = "Color"
    )
    public void setBorderColor(ReactViewGroup view, int index, Integer color) {
        //将所有的bordercolor设置进color,因为属性调用是并行的,无法在这里控制渲染顺序
        SBaseViewTag sBaseViewTag = (SBaseViewTag) view.getTag();
        if (sBaseViewTag != null) {
            sBaseViewTag.getBorderColors().put(index, color);
        }
    }

    @Override
    protected void onAfterUpdateTransaction(T view) {
        super.onAfterUpdateTransaction(view);
        //属性已经设置完成,在这里控制bordercolor的显示
        //如果即有BORDER_COLOR又有上下左右四边的颜色,则以四边颜色为准
        //下面的0~6对应setBorderColor的属性index
        SBaseViewTag sBaseViewTag = (SBaseViewTag) view.getTag();
        if (sBaseViewTag != null) {
            //真实去设置的颜色集合
            Map realBorderColors = new HashMap<>();

            Map borderColors = sBaseViewTag.getBorderColors();
            for (Map.Entry entry : borderColors.entrySet()) {
                //将非BORDER_COLOR添加进待设置集合里
                if (entry.getKey() != 0) {
                    realBorderColors.put(entry.getKey(), entry.getValue());
                }
            }

            //如果包含BORDER_COLOR,则替换待设置集合里未设置的边框的颜色
            if (borderColors.containsKey(0)) {
                Integer allColor = borderColors.get(0);

                if (!realBorderColors.containsKey(1)) {
                    realBorderColors.put(1, allColor);
                }
                if (!realBorderColors.containsKey(2)) {
                    realBorderColors.put(2, allColor);
                }
                if (!realBorderColors.containsKey(3)) {
                    realBorderColors.put(3, allColor);
                }
                if (!realBorderColors.containsKey(4)) {
                    realBorderColors.put(4, allColor);
                }
            }

            for (Map.Entry entry : realBorderColors.entrySet()) {
                setRealBorderColor((ReactViewGroup) view, entry.getKey(), entry.getValue());
            }

        }
    }

    /**
     * 设置border颜色
     * @param view
     * @param index
     * @param color
     */
    private void setRealBorderColor(ReactViewGroup view, int index, Integer color) {
        float rgbComponent = color == null ? YogaConstants.UNDEFINED : (float) ((int)color & 0x00FFFFFF);
        float alphaComponent = color == null ? YogaConstants.UNDEFINED : (float) ((int)color >>> 24);
        view.setBorderColor(SPACING_TYPES[index], rgbComponent, alphaComponent);
    }

tag里的borderColors:

    //map的key为border的index,value为color
    //需并发访问
    private Map borderColors = new ConcurrentHashMap<>();

    public Map getBorderColors() {
        return borderColors;
    }

跑代码后,表现如下:
ReactNative组件的borderColor和borderRadius属性踩坑记录_第3张图片
左右两边黑框消失了,达成目标。
(逐一去设置自定义的viewmanager,同时对比继承的父类,发现rn自己的borderColor、borderColor等属性也是在各自的viewmanager里都实现了一遍,并没有什么统一的复用方式。)

问题二:左右两边圆角处锯齿

圆角边框的实现,同样是在ReactViewBackgroundDrawable类里,该类有1300+行,其中后面1000+行是在说绘制圆角的事情,看得头大,也看不出什么问题。
最后到GitHub上查看issue,发现也有其他人遇见锯齿的问题。
ReactNative组件的borderColor和borderRadius属性踩坑记录_第4张图片
看搜索到的第一个issue中,有个官方团队的回答:
ReactNative组件的borderColor和borderRadius属性踩坑记录_第5张图片
说是0.57版本已经修复了此问题。
好吧,与团队里其他成员商量后,考虑到还有其他问题也需要通过升级新版本来解决,最终决定升级版本。一通操作升级到新版本,再解决掉若干兼容问题,终于修复此问题。

你可能感兴趣的:(ReactNative)