2016-04-11 23:08:55 p106786860 阅读数 14977
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57542 人正在学习 去看看 李宁
一、Touchable手势
1.React Native提供了4个组件来做这个事情,具体如下:
     TouchableHighlight:高亮触摸,用户点击时,会产生高亮效果;
     TouchableNativeFeedback:
     TouchableOpacity:透明触摸,用户点击时,点击的组件不会出现任何视觉变化;
     TouchableWithoutFeedback:无反馈触摸,用户点击时,点击的组件不会有任何视觉变化;
2.这4个组件,我们可以应用某个部分绑定上Touch事件,并支持一下方法:
     onPress:
     onPressIn:
     onPressOut:
     onLongPress:
3.下面我们以实例演示下,相关代码实现:
Index.android.js文件:
import React, {
  … … 
  TouchableHighlight,
} from 'react-native';

class AwesomeProject extends Component {
  show(text) {
    alert(text);
  }
  //手势相关事件的实现
  onPressIn(){
    this.start = Date.now()
    console.log("press in")
  }
  onPressOut(){
    console.log("press out")
  }
  onPress(text){
    console.log("press")
    alert(text);
  }
  onLonePress(){  AppRegistry,
    console.log("long press "+(Date.now()-this.start))
  }
  render() {
    return(
      <View style={styles.container}>
        //TouchableHighlight包裹绑定Touch手势的组件,并实现支持的4个事件
        <TouchableHighlight style={styles.touchable} onPressIn={this.onPressIn} onPressOut={this.onPressOut}
          onPress={this.onPress.bind(this,'点击了吗?')} onLongPress={this.onLonePress}>
          <View style={styles.button}></View>
        </TouchableHighlight>
      </View>
    )
  };
}
var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  button:{
    width: 200,
    height: 200,
    borderRadius: 100,
    backgroundColor: 'red'
  },
  touchable: {
    borderRadius: 100
  },
});
AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);
4.点击,我们看见具体的运行效果如下:

5.我们使用Debug模式,研究下4个手势出现的条件和顺序;
摇晃手机,选择Debug JS;

打开Chrome浏览器,在弹出的Debug页面http://localhost:8081/debugger-ui后,选择开发着工具,点击手机上的按钮就可以看见浏览器控制台的
输出内容PressIn->longPress->pressOut;

二、手势响应生命周期
1.对于大部分应用,使用以上4个Touch*组件,在配合4个Press事件就能对用户的手势进行响应。但是对于比较复杂的交互,还得使用手势响应系统;
2.响应手势的基本单位是responder,具体来说就是View组件,任何View组件都是潜在的responder;
3.一个普通的View组件成为能响应手势操作的responder,只要设置几个手响应生命周期的方法即可,具体如下:
     View.props.onStartShouldSetResponder:用户开始触摸屏幕的时候,是否愿意成为响应者;
     View.props.onMoveShouldSetResponder:在每一个触摸点开始移动的时候,再询问一次是否响应触摸交互;
     View.props.onResponderGrant:要开始响应触摸事件了;
     View.props.onResponderReject:响应者现在另有其人,而且暂时不会放权,另作安排;
     View.props.onResponderMove:用户正在屏幕上移动手指;
     View.props.onResponderRelease:触摸操作结束收触发;
     View.props.onResponderTerminationRequest:有其它组件请求接替响应者,当前View是否放权;
     View.props.onResponderTerminate:响应权已经交出;
4.一个React Native应用中同时之能存在一个responder,具体响应步骤如下:
       用户通过触摸或者滑动来“激活”某个responder,View.props.onStartShouldSetResponder以及View.props.onMoveShouldSetResponder这两个方法处理,如果返回true,则这个View能够响应触摸或者滑动手势被激活;
       如果组件被激活,View.props.onResponderGrant方法被调用,一般这个时候去改变组建的底色或者透明度,表示组件已经被激活;
       接下来,用户开始滑动手指,此时View.props.onResponderMove方法被调用;
       当用户的手指离开屏幕之后,View.props.onResponderRelease方法被调用,组件恢复被触摸之前样式,例如底色和透明度恢复之前的样式,完成一次手势操作;
正常流程:响应touch或者move手势 -> grant(被激活) -> move -> release(结束事件);

5.下面我们以实例演示下,相关代码如下:
index.android.js文件
import React, {
   … … 
} from 'react-native';

var AwesomeProject = React.createClass({
  getInitialState(){
    return {
      bg: 'white'
    };
  },

  componentWillMount(){
    this._gestureHandlers = {
      onStartShouldSetResponder: () => true,
      onMoveShouldSetResponder: ()=> true,
      onResponderGrant: ()=>{
        this.setState({bg: 'red'})
      },
      onResponderMove: ()=>{
        console.log(123)
      },
      onResponderRelease: ()=>{
        this.setState({bg: 'white'})
      }
    }
  },

  render() {
    return(
      <View style={styles.container}>
        <View {...this._gestureHandlers} style={[styles.rect, {backgroundColor:this.state.bg}]}></View>
      </View>
    );
  }
});
var styles = StyleSheet.create({
     … … 
});
AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);
6.运行效果如下,onClick为未点击,Click为点击,后面为控制台输出log:


注意:如果运行Demode的时候错误提示如下图:

使用var AwesomeProject = React.createClass();的方式创建组件;
三、手势事件传递
1.onStartShouldSetResponder于onMoveShouldSetResponder是以冒泡的形式调用的,即嵌套最深的节点最先调用;
2.意味着多个View同时在ShouldSetResponder中返回true时,最底层的View将优先”夺权“;
3.但是有些时候,某个父View会希望先能成为响应者,我们可以利用”捕获期“来解决。响应系统从最底层的组件开始冒泡前,会首先执行一个”捕获期“,在此期间会触发on*ShouldSetResponderCapture系列事件。因此,如果某个父View想要在触摸开始时阻止组件成为响应者,那就应该处理onStartShouldSetResponderCapture事件冰返回true值;
     View.props.onStartShouldSetResponderCapture:(evt)=>true;
     View.props.onMoveShouldSetReponderCapture(evt)=>ture;

4.下面我们将以实例演示一下,实现代码如下:
Index.android.js文件:
import React, {
     ... ... 
} from 'react-native';

var AwesomeProject = React.createClass({
  getInitialState(){
    return {
      bg: 'white',
      bg2: 'white'
    }
  },
  componentWillMount(){
    this._gestureHandlers = {
      //外部正方形在“捕获期”阻止底层时间成为响应者
      onStartShouldSetResponderCapture: () => true,
      onMoveShouldSetResponderCapture: ()=> true,
      onResponderGrant: ()=>{this.setState({bg: 'red'})},
      onResponderMove: ()=>{console.log(123)},
      onResponderRelease: ()=>{this.setState({bg: 'white'})},
    }
    this._gestureHandlers2 = {
      //内部正方形在即时实现了on*ShouldSetResponder也无法成为响应者
      onStartShouldSetResponder: () => true,
      onMoveShouldSetResponder: ()=> true,
      onResponderGrant: ()=>{this.setState({bg2: 'green'})},
      onResponderMove: ()=>{console.log(123)},
      onResponderRelease: ()=>{this.setState({bg2: 'white'})}
    }
  },
  render: function() {
    return (
      <View style={styles.container}>
        <View {...this._gestureHandlers} style={[styles.rect,{"backgroundColor": this.state.bg}]}>
          <View {...this._gestureHandlers2} style={[styles.rect2,{"backgroundColor": this.state.bg2}]}>
          </View>
        </View>
      </View>
    );
  }
});
var styles = StyleSheet.create({
  ... ... 
});
AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);
5.运行效果如下图,点击内部正方形,外部正方形相应事件:
 
四、evt参数
1.和Web开发中的事件参数类似,以上的每个方法都有一个evt参数,在事件发生的过程中,这个evt参数的nativeEvent属性的各个指能表示手势的状态:
     nativeEventchangedTouches:在上一次事件之后,所有发生变化的触摸事件的数组集合(即上一次事件后,所有移动过的触摸点)
     identifier:触摸点的ID
     locationX:触摸点相对于父元素的横坐标
     locationY:触摸点相对于父元素的纵坐标
     pageX:触摸点相对于根元素的横坐标
     pageY:触摸点相对于根元素的纵坐标
     target:触摸点所在的元素ID
     timestamp:触摸事件的时间戳,可用于移动速度的计算
     touches:当前屏幕上的所有触摸点的集合

五、PanResponder
1.除了手势相应系统之外,React Native还抽象出一套PanResponder方法,它的抽象成程度更高,使用起来更为方便;
2.使用PanResponder的时候,相应手势的逻辑和流程都不变,只需要根据文档对几个方法名称参数修改即可:
     第一个参数evt;
     第二个gestureState,包含手势进行过程中更多信息,比较常见如下:
          dx/dy:手势进行到现在的横向/纵向相对位移;
          vx/vy:此刻的横向/纵向速度;
          numberActiveTouches:reponder上的触摸的个数;
3.下面我们就使用PanResponder实现拖拽效果,代码实现如下:
index.android.js文件:
import React, {
  ... ... 
  PanResponder,
} from 'react-native';


var AwesomeProject = React.createClass({
  getInitialState(){
    return {
      bg: 'white',
      top: 0,
      left: 0
    }
  },
  componentWillMount(){
    this._panResponder = PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      onMoveShouldSetPanResponder: ()=> true,
      onPanResponderGrant: ()=>{
        //滑动开始时,获取矩形的左上坐标,并设置背景为红色
        this._top = this.state.top
        this._left = this.state.left
        this.setState({bg: 'red'})
      },
      onPanResponderMove: (evt,gs)=>{
        console.log(gs.dx+' '+gs.dy)
        //随着手势滑动,相应的改变矩形的位置
        this.setState({
          top: this._top+gs.dy,
          left: this._left+gs.dx
        })
      },
      onPanResponderRelease: (evt,gs)=>{
        //活动结束后,还原背景为白色
        this.setState({
          bg: 'white',
          top: this._top+gs.dy,
          left: this._left+gs.dx
        })}
    })
  },
  render: function() {
    return (
      <View style={styles.container}>
        //设置手势事件处理对象
        <View{...this._panResponder.panHandlers} style={[styles.rect,{
            "backgroundColor": this.state.bg,
            "top": this.state.top,
            "left": this.state.left}]}>
        </View>
      </View>
    );
  }
});
var styles = StyleSheet.create({
  container: {
    flex: 1,
    //开始的矩形位于中间,下图拖动到下部区域
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  rect: {
    ... ... 
  }
});
AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);
4.运行演示如下,由于只能上传图片,无法演示动画,样式中矩形位于中间,图中拖动至下方:

2017-11-17 10:10:02 beyond181 阅读数 2606
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57542 人正在学习 去看看 李宁

先来吐槽一下,React native学习的资料真的是很少啊,而且国内博客大部分都很老,还千篇一律,加了几个群,光有问的,没有回答的。简直了。。。难过

好了,现在来说一说怎么跳转到React native的某个页面:

React native现在提倡大家用React Navigation 来做导航器,如果不懂得,先去官网看看例子。这里页面跳转用StackNavigator,以下是示例代码:

 

const App = StackNavigator({
    first: {
        screen: First,
        path:'app/first'
    },
    second: {
        screen: Second,
        path:'app/second'},
}, {
    navigationOptions: {
        header: null
    },
});

大家可能知道screen,它就是要跳转的页面,但是对path可能不太清楚,下面是官网的介绍,当deep linking或者是一个web app的时候,这个就有用处了,所以今天app跳转原生就是由path来实现了。

 

 

android/app/src/main/AndroidManifest.xml中的MainActivity 下添加

<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="demo2"
          android:host="demo2" />
</intent-filter>

 

然后通过在原生app处调用

Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
Uri uri = Uri.parse("demo2://demo2/app/first");
intent.setData(uri);
startActivity(intent);

就可以打开了,同样,如果有参数的话,首先path定义的时候要定义'app/first/:name',

然后调用的时候Uri uri = Uri.parse("demo2://demo2/app/first/rou");//first后边的就是参数

后记:

之前按照此方法是可行的,可是最近有很多人问我,怎么跳转不了了,我自己也试了一下,确实不可以了!需要在StackNavigator中增加一个URI的匹配项,代码如下:

 

import React, { Component } from 'react';
import {
    StackNavigator,
} from 'react-navigation';
import Third from './page/third'
import First from './page/first'
import Second from './page/second'
const App = StackNavigator({
    first: {
        screen: First,
        path:'app/first'
    },
    second: {
        screen: Second,
        path:'app/second'
    },
    third:{
        screen: Third,
        path:'app/third'
    },
}, {
    navigationOptions: {
        header: null
    },
});
const prefix = 'demo2://demo2/';
const MainApp = () => <App uriPrefix={prefix} />

export default MainApp ;

现在就可以跳转了!

 

 

2017-01-07 22:51:44 s10141303 阅读数 1793
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57542 人正在学习 去看看 李宁

一、Image组件

(1)引用本地图片
React Native提供了一种统一的方式来管理iOS和Android应用中的图片。 要向应用程序添加静态图片,请将其放在源代码树中的某个位置,并引用它,如下所示:

<Image source={require('./my-icon.png')} />

以与解析JS模块相同的方式解析映像名称。 在上面的示例中,打包程序将在与需要它的组件相同的文件夹中查找my-icon.png。 此外,如果您有my-icon.ios.png和my-icon.android.png,打包程序将为平台选择正确的文件。

您还可以使用@ 2x和@ 3x后缀为不同的屏幕密度提供图像。 如果您具有以下文件结构:

.
├── button.js
└── img
    ├── check@2x.png
    └── check@3x.png

button.js中调用

<Image source={require('./img/check.png')} />

(2)引用网络图片

<Image source={{uri: 'https://facebook.github.io/react/img/logo_og.png'}}
       style={{width: 400, height: 400}} />

案例:
这里写图片描述
这里写图片描述

注意:
真机测试的话加载网络图片会出现不显示的问题,Google后查证,iOS9引入了新特性App Transport Security (ATS)。
新特性要求App内访问的网络必须使用HTTPS协议。
但是现在公司的项目使用的是HTTP协议,使用私有加密方式保证数据安全。现在也不能马上改成HTTPS协议传输。
重点内容
修改:
这里写图片描述

二、Text组件

React Native中文本用Text组件

import React, { Component } from 'react';
import { AppRegistry, Text, StyleSheet } from 'react-native';

class TextInANest extends Component {
  constructor(props) {
    super(props);
    this.state = {
      titleText: "Bird's Nest",
      bodyText: 'This is not really a bird nest.'
    };
  }

  render() {
    return (
        <Text style={styles.baseText}>
          <Text style={styles.titleText} onPress={this.onPressTitle}>
            {this.state.titleText}{'\n'}{'\n'}
          </Text>
          <Text numberOfLines={5}>
            {this.state.bodyText}
          </Text>
        </Text>
    );
  }
}

const styles = StyleSheet.create({
  baseText: {
    fontFamily: 'Cochin',
  },
  titleText: {
    fontSize: 20,
    fontWeight: 'bold',
  },
});

// App registration and rendering
AppRegistry.registerComponent('HelloWorld', () => TextInANest);

这里写图片描述

三、Slider滑动条组件

(1)属性和方法
maximumValue number
滑块的初始最大值。默认值为1。

minimumValue number
滑块的初始最小值。默认值为0。

onSlidingComplete函数
当用户完成更改值时调用回调(例如,释放滑块时)。

onValueChange函数
当用户拖动滑块时,连续调用回调。

滑块的步长值。该值应介于0和(maximumValue - minimumValue)之间。默认值为0。

四、View容器组件

构建UI的最基本的组件,View是一个容器,支持使用flexbox,样式,一些触摸处理和辅助功能控件的布局。 无论UIView,div,android.view等,直接查看映射到本地视图等效于任何平台上的React Native正在运行。视图被设计为嵌套在其他视图中,并且可以有0到许多任何类型的孩子。

示例:

class ViewColoredBoxesWithText extends Component {
  render() {
    return (
      <View style={{flexDirection: 'row', height: 100, padding: 20}}>
        <View style={{backgroundColor: 'blue', flex: 0.3}} />
        <View style={{backgroundColor: 'red', flex: 0.5}} />
        <Text>Hello World!</Text>
      </View>
    );
  }
}
2019-08-08 15:40:48 bojikeqian 阅读数 125
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57542 人正在学习 去看看 李宁

干刚入坑,挑选了好几种第三方下拉框,还是用了base的picker
(react-native-modal-dropdown安装之后,使用没反应,可能是我react-native版本0.62版本过高,有兴趣的朋友可以去看看)

这里还要注意可能会有黄色警告出现,提示版本内置的某个props会在下个版本删除怎么怎么地。
加上这个就不会出现warning了
官方没有提供动态加载的方法,目前只能这样实现
//消除页面黄色warning console.disableYellowBox = true;

import React, { Component } from "react";
import { Container, Header, Content, Icon, Picker, Form } from "native-base";
import {Text, View} from "react-native";
const dimensions = require('Dimensions');
const {width, height} = dimensions.get('window');
export default class PickerWithIcon extends Component {
    constructor(props) {
        super(props);
        this.state = {
            selected: undefined,
            selected1: undefined,
            dataSource: [{
                    "orderId": "6543",
                    "orderTime": "2019年8月5日14:37:27",
                    "orderName": "马云订单=================",
                    "orderState": "1"
                },
                {
                    "orderId": "gf54",
                    "orderTime": "2019年8月5日14:37:27",
                    "orderName": "马云",
                    "orderState": "2"
                },
                {
                    "orderId": "h435ds",
                    "orderTime": "2019年8月5日14:37:27",
                    "orderName": "马云",
                    "orderState": "1"
                },
                {
                    "orderId": "5jks",
                    "orderTime": "2019年8月5日14:37:27",
                    "orderName": "马云",
                    "orderState": "2"
                }],
        };
    }
    onValueChange(value: string) {
        this.setState({
            selected: value
        });
    }
    onValueChange1(value: string) {
        this.setState({
            selected1: value
        });
    }

    render() {
        let list = this.state.dataSource.map((item,i) => (
            <Picker.Item label={item.orderName} key={i} value={item.orderId} />
        ));
        return (
            <Container>
                <Content>
                    <Form>
                        <View style={{flexDirection:'row',flexWrap:'nowrap'}} >
                            <Picker style={{ width:'25%'}}  mode={'dropdown'}  //'dialog'弹窗 'dropdown'下拉
                                selectedValue={this.state.selected1}  onValueChange={this.onValueChange1.bind(this)}>
                                <Picker.Item label={'企业'} value={'企业'} />
                                    {list}
                            </Picker>
                        </View>
                    </Form>
                </Content>
            </Container>
        );
    }
}

谢谢大家的观看,有问题请留言。或者加我wx16653144918
在这里插入图片描述

2019-01-02 23:14:33 qq_33703877 阅读数 138
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57542 人正在学习 去看看 李宁

前言

在之前项目的开发过程中,下图这样的需求很是常见:
在这里插入图片描述
当键盘弹起时,某个布局正好在键盘之上,当键盘消失时,这个布局又回到页面最底部。

今天来讨论的就是这个功能我用过的方法及踩过的坑。

方案1

首先我们观察到下面的一块布局(以下简称 “A” 布局)是随着键盘的弹起和消失来改变位置的,并且距离屏幕的底部正好是键盘的高度。
所以,第一个思路,设置 A 布局的位置方式为绝对定位,并且是计算距离父布局底部的位置为键盘的高度。
代码中定义一个展示键盘高度的变量 keyboardHeight ,然后设置键盘显示与消失的事件监听,并给 keyboardHeight 变量赋值:

    componentDidMount(){
        this.keyboardDidShow = Keyboard.addListener('keyboardDidShow', this.keyboardShow.bind(this));
        this.keyboardDidHide = Keyboard.addListener('keyboardDidHide', this.keyboardHide.bind(this));
     }

     keyboardShow(e){
        this.setState({
            keyboardHeight:e.endCoordinates.height
        });
    };
    keyboardHide(){
         this.setState({
            keyboardHeight:0
          });
    }

然后在 render 方法中设置 A 布局的样式:

render() {
         return (
         <View style = {{flex:1}}>
            <TextInput
                 style={{height: 140, borderColor: '#999', borderWidth: 1,margin:15}}
                 onChangeText={(text) => this.setState({text})}
                 placeholder={'随便说点什么吧'}
                 textAlignVertical = {'top'}
             />

             <View style = {[styles.bottomView,{bottom:this.state.keyboardHeight}]}>
                <TouchableOpacity onPress = {()=>{alert(1)}}>
                  <Image style = {{width:26,height:26,marginLeft:15}} resizeMode ={'contain'}source = {require('../img/keyBoard.png')}/>
                </TouchableOpacity>
                <TouchableOpacity onPress = {()=>{alert(1)}}>
                  <Image style = {{width:26,height:26,marginRight:15}} resizeMode ={'contain'}source = {require('../img/emoji.png')}/>
                </TouchableOpacity>
             </View>
         </View>

         );
       }
       
     const styles = StyleSheet.create({

        bottomView:{
           position:'absolute',
           flexDirection:'row',
           height:48,width:'100%',
           backgroundColor:'#DD4F43',
           justifyContent:'space-between',
           alignItems:'center'
         }
       });

然后,我们来看一下效果:
在这里插入图片描述

咦,好像翻车了。
可以看到当键盘没有弹出的时候是好的,但是当键盘弹出后 A 布局就不见了。那这又有两种情况,一种是 A 布局在正确位置的上方,超出屏幕了,一种是在正确位置的下方,被键盘遮挡没弹起来。
那我们来将 A 布局的位置改为 keyboardHeight -150

看下效果:
在这里插入图片描述
嗯,A 布局出现了,并且是在键盘的上方。由此可见我们的第一个猜想是对的。
那我们不妨再大胆做个猜想,A 布局偏离正确位置的高度会不会就是一个键盘的高度呢?如果是这样的话,那我们 A 布局的 bottom 就应该为 keyboardHeight - keyboardHeight 即为 0!

改下代码,然后来看下效果
在这里插入图片描述
貌似完美! 也就是在 android 上也不需要去计算键盘的高度,布局直接在键盘之上?这个原理我还不清楚,但感觉和输入框与软键盘之间的恩怨有联系。
然后我们再看下在 ios 上的表现,嗯,这里没有测试机,就不贴图了。但事实证明会有个问题,在 ios 上如果这样设置当键盘显示的时候,A 布局不会弹起来,而是还在屏幕的底部。所以,ios 还是要计算键盘的高度。

所以,最后适配如下:

 <View style = {[styles.bottomView,{bottom:Platform.OS == 'ios'? this.state.keyboardHeight:0}]}>
                <TouchableOpacity onPress = {()=>{alert(1)}}>
                  <Image style = {{width:26,height:26,marginLeft:15}} resizeMode ={'contain'}source = {require('../img/keyBoard.png')}/>
                </TouchableOpacity>
                <TouchableOpacity onPress = {()=>{alert(1)}}>
                  <Image style = {{width:26,height:26,marginRight:15}} resizeMode ={'contain'}source = {require('../img/emoji.png')}/>
                </TouchableOpacity>
             </View>

在这种方案实行了俩星期后,测试就提出来严重的兼容性问题:

  • 在某些 android 手机上(如魅蓝),键盘弹起时,A 布局会距离键盘上方大约一个键盘的高度,即弹的太上了
  • 在某些 android 手机 或者 iPhoneX 上,键盘弹起时 A 布局弹不出来

这两个问题都是偶现的,但在某些机型上的偶现率达 50%
所以,看来方案一是有问题。
但问题究竟出在哪里,我没弄明白,但敢肯定还是和输入框与软键盘的适配有关,曾经尝试设置 activity 的 softnput 参数,未果。

方案2

那我们就来换种思路,方案一我们是采用基于 bottom 的定位。但也许由于软键盘与输入框的特殊关系,这个 bottom 的值在不同机型上表现不同。那我们能否改为基于 top 定位呢。
分析一下,如果是基于 top 定位的情况,值应该怎么算,来看一下分析图:
在这里插入图片描述

  • 当键盘没有弹起的时候,A 布局距离父布局的顶部的距离是 h- bottomHeight,即父布局高度减去 A 布局自身高度。
  • 当键盘弹起时,A 布局距离父布局的顶部的距离是 h- bottomHeight-keyboardHeight,即多减掉键盘的高度。

所以最终计算方式为:父布局的高度 - A 布局自身的高度 - 键盘的高度

代码中,我们首先需要计算一下父布局的高度,通过 onLayout 方法计算出。然后设置 A 布局的 top 属性值

_onLayout(event) {
       //获取根View的宽高,以及左上角的坐标值
        this.setState({
           layoutHeight : 1
        })
     }
      render() {
         return (
         <View style = {{flex:1}}  onLayout={()=>{this._onLayout}}>
            <TextInput
                 style={{height: 140, borderColor: '#999', borderWidth: 1,margin:15}}
                 onChangeText={(text) => this.setState({text})}
                 placeholder={'随便说点什么吧'}
                 textAlignVertical = {'top'}
             />

             <View style = {[styles.bottomView,{top:520-48-this.state.keyboardHeight}]}>
                <TouchableOpacity onPress = {()=>{alert(1)}}>
                  <Image style = {{width:26,height:26,marginLeft:15}} resizeMode ={'contain'}source = {require('../img/keyBoard.png')}/>
                </TouchableOpacity>
                <TouchableOpacity onPress = {()=>{alert(1)}}>
                  <Image style = {{width:26,height:26,marginRight:15}} resizeMode ={'contain'}source = {require('../img/emoji.png')}/>
                </TouchableOpacity>
             </View>
         </View>

         );
       }

刷新一下,看一下效果:
在这里插入图片描述

貌似没什么毛病,通过在其他机型和 ios 上测试,目前没有发现问题。

所以第二种方案可行。

总结

以后遇到这种需求,最好要避免根据 bottom 来定位,可以换种思路,根据 top 来定位。

没有更多推荐了,返回首页