react native 自适应位置的下拉选择框(react-native-selfadapt-modal)

react-native-selfadapt-modal

在实际开发过程中,下拉选择应该是比较常用的功能。我在项目开发中先后使用了以下两种第三方的下拉选择框插件:

react-native-popup-menu
react-native-modal-dropdown

但是这两款插件对我来说都不是特别理想。

首先react-native-popup-menu自定义样式是比较自由的,但是编码相当繁复,而且代码复用率太低了,尤其是使用该组件需要在最外层使用MenuProvider替换View

其次react-native-modal-dropdown自定义样式不是很友好,举个栗子?:因为该组件的背景色是透明的,在需要设置背景色为半透明的情况下,令人抓狂?!此外,如果想令弹框自适应高度也不太好使。

所以,在下希冀通过封装react native自有的Modal组件,实现复用率较高且能自定义样式的可定位下拉框组件。

先看看效果:

获取定位参数

先说下基本思路吧:要使Modal在特定的位置展开,就必须获取到一些定位参数,在Modal中使用绝对布局,并按照一定的规则传入这些参数,才能正确的定位弹窗位置,除此之外还需要判断Modal到底应该是向上展开还是向下展开。

示意图:

根据上述思路可知,必要参数是Y轴坐标(y)、Modal控制按钮的高度(height)和屏幕高度。弹框的起点位置

首先,我想的是如何获取到Modal的展开位置,并需要根据这个位置判断应该向下展开还是向上展开。

一开始我准备使用onLayout方法,但是发现了几个问题,现总结如下:

1.onLayout是 y 轴起点是根据父布局来计算的。也就是说如果不以根布局为父布局则无法获取到绝对y轴坐标。
2. 如果onLayout用于第二级元素,Android 和 iOS 的 y 轴的起点位置是不同的,Android是从状态栏以下开始计算的,而iOS是从屏幕顶端开始计算的。

关于第二点,有示例图如下:

左边为Android,右边为iOS。

经过一番查阅,发现使用react native提供API —— NativeMethodsMixin 中的 measure 方法可以获取到某组件在屏幕中的绝对位置。该方法通过ref调用。具体是使用示例如下:

this.listRow&&this.listRow.measure((x, y, width, height, left, top) => {
    console.log('===x===',x); //组件相对父布局的X坐标??
    console.log('===y===',y); //组件相对父布局的Y坐标??
    console.log('===width===',width); //组件的宽度
    console.log('===height===',height); //组件的高度
    console.log('===left===',left); //组件在屏幕中的X坐标
    console.log('===top===',top); //组件在屏幕中的Y坐标
});

根据查阅API,我们知道该方法有6个回调参数,在这里我们需要使用的就是 topheight。至于前两个参数,我不太确定是啥意思,有点小尴尬。欢迎有了解的童鞋进行补充。
在这里插入图片描述

关键代码

接下来就是实际的编码了。核心技术点如下:

第一步,通过measure获取到Modal的y轴坐标和点击组件(可点击的组件较多,例如Button、TouchableOpacity等,这里和下文都将此类组件笼统的成为“点击组件”)的高度,需要注意的measure是一个异步方法,因此在这里需要传入一个回调参数,示例如下:

getPosition = (callback) => {
	this.listRow&&this.listRow.measure((x, y, width, height, left, top) => {
	    this.yaxis = top;
	    this.itemHeight = height;
	    callback&&callback();
	});
}

第二步,判断Modal是向上展开还是向下展开,当y轴坐标大于屏幕高度的一半时,向上展开,反之向下展开。

/** 获取默认的弹出位置,通过样式的形式返回 */
getPositionStyle = () => {
    let positionStyle = {};
    if (parseInt(this.state.yNumber) > parseInt(screenHeight / 2)) {
        positionStyle = {bottom: screenHeight - (this.state.yNumber + AndroidStatusBar) };
    } else {
        positionStyle = {top: this.state.yNumber + this.state.itemHeight};
    }
    return positionStyle;
}

在这一步中,需要注意的是,因为Android的Y轴坐标是从状态栏以下开始计算的,而屏幕高度是包含了状态栏在内的,因此如果遇到向上展开的情况,就需要减去相应的高度。

Android可以通过 StatusBarcurrentHeight 方法获取到状态栏高度。示例:

const AndroidStatusBar = Platform.OS == 'android'? StatusBar.currentHeight: 0;

最终效果如下所示:

扩展点

上文提到如何获取到Android端状态栏的高度,但是这一方法在iOS端却是无效的。经过一番查阅,我找到了以下方法:

import { NativeModules } from 'react-native';

const { StatusBarManager } = NativeModules;

componentWillMount = () => {
    if (Platform.OS == 'ios') {
        StatusBarManager.getHeight((statusBarHeight) => {
            this.iosHeight = statusBarHeight && statusBarHeight.height;
        });
    }
}

getHeight方法是只对iOS端有效,调用时请务必加上平台判断,不然会报错。

其它问题

在开发过程可能会遇到measure的回调参数全部为undefined的情况,这时你可能需要在创建ref的元素上添加collapsable={false}

<View 
    collapsable={false}
    ref={(o) => this.listRow = o}>
    ……
</View>

然后,经过试验,发现使用measureInWindow比起measure更适合本地开发的需求,使用示例:

this.listRow&&this.listRow.measureInWindow((x, y, width, height) => {
   console.log('===x===',x);
   console.log('===y===',y);
   console.log('===width===',width);
   console.log('===height===',height);
});

总结

在本次开发中学习和使用了一些平时没有使用过的API和方法,例如measuremeasureInWindowcollapsableStatusBarManager等。

发现了onLayout方法的使用局限以及其获取到的参数的意义。

熟悉了对于异步函数(例如measure)的处理方法。

备注

我已经将该组件发布到npm,欢迎有需要的童鞋引用。

npm install react-native-selfadapt-modal --save

同时代码库也同步到github,希望各位不吝Star

react-native-selfadapt-modal

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值