RN中Android定位问题

前言

在RN开发中仅仅使用flex布局,也满足不了我们日常的需求开发;RN官方也提供了定位布局,flexbox定位和position定位可以同时使用,同时生效;

position

RN提供了两种布局方式:absolute和relative

  • relative
    • 相对于 上一个兄弟节点
    • 不可以浮动(尽管偏移了,还是占了一个位置)
  • absolute
    • 相对于 父视图
    • 浮动的

绝对布局是脱离文档流的,不过RN依旧在文档层次结构里面,这个和html的position也很大不一样。另外还有一个和html不一样的是,html中position:absolute要求父容器的position必须是absolute或者relative,如果第一层父容器position不是absolute或者relative,在html会依次向上递归查询直到找到为止,然后居于找到的父容器绝对定位。

如果你之前是搞安卓开发的会觉得RN设计非常怪异,在我们安卓原生开发中,决定用什么布局的是由parent决定的,如:AbsoluteLayout和RelativeLayout,而在RN开发中,决定用什么布局的是有child来决定的。

比如:

<View><Text style={{position:'absolute'}}></View> 

这样Text组件相对于外层parent组件是相对布局,同理我们可以修改成relative。

可以使用left、top、right、bottom来改变偏移量。如下图:

absolute:

       view1的样式:  { position:'absolute',left:5,top:5 }.

       view2的样式:  { position:'absolute',right:10,bottom:10 }.

  relative:

      view1的样式:  { position:'relative',left:5,top:5 }.

      view2的样式:  { position:'relative',left:50,top:30 }.

zIndex层级

zIndex是rn在0.30开始支持的属性,是可以生效的;

一般是zIndex层级大的在上面

对于Android,两个同一层级的定位组件(position:”absolute”)

情况 在z轴的层叠关系
既没有ZIndex属性,又没有elevation 属性 由其摆放位置决定的,放在下面的组件会在上层
两个组件只有zIndex没有elevation属性时 zIndex大的在上层
两个组件有elevation属性 elevation大的在上层
两个组件既有zIndex属性elevation属性 以elevation为准

注:对于IOS,同层级的组件,z轴的层叠关系只与摆放顺序与zIndex有关,与elevation无关

Android和iOS position差异

如果使用position:”absolute”想要把子元素浮出到父元素外面一部分,在html中的常规处理可以把偏移量top、left、right、bottom设置成负值即可;此种方案在RN的iOS端可是可行的,但是Android端浮出父元素外面的部分是不显示的;

RN对absolute的官方解释就是:以父元素的边框为基准进行偏移,也就是说只能在父元素包裹的范围内偏移;

一下是iOS和Android上的差异:

iOS

Android

如上图想要给其中的一个模块打上一个推荐位的标,实现代码如下:

priceGroup: {
    paddingHorizontal: 15,
    flexDirection: "row",
    justifyContent: "space-between",
    alignItems: "center",
    marginTop: 25,
    position: "relative",
    zIndex: 99998,
    elevation: 99998
},
//父级容器
commotView: {
    width: 109,
    height: 100,
    justifyContent: "center",
    alignItems: "center",
    borderWidth: 1
},
//推荐logo
recIcon: {
    width: 38,
    height: 24,
    position: "absolute",
    top: -18,
    right: -5,
    justifyContent: "center",
    alignItems: "center",
    zIndex: 99999,
    elevation: 99999
}

let priceDom = list.map((item, index) => {
    //价格列表数据对象 dealPrice折扣价 originPrice原价 desc标题
    let { id, dealPrice, originPrice, desc, recommand } = item;
    //是否选中
    let isChecked = id == sel.id ? true : false;
    //是否使能 免费使用不选点击选择
    let dis = discountStatus == "0" ? true : false;
    return (
        <TouchableOpacity
            activeOpacity={1}
            key={index}
            onPress={() => {
                if (discountStatus) {
                    this.setState((pre) => {
                        return {
                            selected: item
                        };
                    });
                }
            }}
            style={[
                styles.commotView,
                //选中状态和非选中状态以及免费不能点击状态字体颜色区分
                isChecked
                    ? styles.activePriceView
                    : dis
                    ? styles.disableView
                    : styles.priceView
            ]}>
            {/* 折扣状态和全价状态下会显示推荐tag */}
            {discountStatus && recommand ? (
                <Image
                    source={require("./../img/recommand.png")}
                    style={styles.recIcon}
                />
            ) : null}
            {/* 订阅列表标题栏 */}
            <Text
                style={{
                    color: isChecked
                        ? COLOR.ActivetilColor
                        : dis
                        ? COLOR.disColor
                        : COLOR.priceColor,
                    fontSize: 15
                }}>
                {desc}
            </Text>
            {/* 订阅列表当前价格 折扣状态显示折扣之后的价格
            免费试用显示全价中间价贯穿线 全价直接展示原始价格 */}
            <Text
                style={{
                    color: isChecked
                        ? COLOR.ActivetilColor
                        : dis
                        ? COLOR.disColor
                        : COLOR.priceColor,
                    fontSize: 18
                }}>
                ¥
                <Text
                    style={[
                        { fontSize: 36, fontWeight: "700" },
                        discountStatus ? null : styles.lineThrough
                    ]}>
                    {discountStatus == 1 ? parseInt(dealPrice) : parseInt(originPrice)}
                </Text>
            </Text>
            {/* 折扣状态下的原始价格展示 */}
            {discountStatus == 1 ? (
                <Text
                    style={[
                        styles.lineThrough,
                        isChecked ? styles.activeDiscountText : styles.discountText
                    ]}>
                    {parseInt(originPrice)}</Text>
            ) : null}
        </TouchableOpacity>
    );
});
return <View style={styles.priceGroup}>{priceDom}</View>;

整体思路如下:

此种布局就是常规思路,把要logo作为一个子元素放在要打标的模块里面,作为一个子元素,然后使用定位达到UI设计效果;二父元素的尺寸又是严格按照UI要求的尺寸来固定大小了;此时超出父元素的部分在Android上就不会显示了;

解决方案:

//原布局方式
父级标签里面直接渲染当前全部内容,把推荐标签页包裹在内
<View parent>
    <Image tag/>
    //渲染内容content
</View>

//修改后
<View parent>
    <Image child1/>
    <View child2>
        //渲染内容content
    </View>
</View>

修改方式:

  • 把原父级区块放在一个视图里面 复用原父级样式 成一个新模块 child2
  • 把tag推荐图片作为一个兄弟节点 child1
  • 把child1和child2放到一个视图里面 parent
  • parent布局:
    • 如果是如上图所示的需要上浮和右浮出
    • parent大小设置 width = child2.width + child1右浮宽度
      height = child2.widt + child1上浮高度
    • 设置完parent容器大小之后,容器内布局
      水平方向:从左往右布局
      垂直方向:从下往上布局
    • 其余方向同上 自行使用

以上是本人在开发中的一些拙见,有不足支持欢迎指正。


   转载规则


《RN中Android定位问题》 浅夏晴空 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录