ReactNative入门(安卓)——API(下)

LayoutAnimation - layout动画

当布局发生改变时的动画模块,它有两个方法:

1. 最常用的方法是 LayoutAnimation.configureNext(conf<Object>),用于设置布局变化时的动画类型,在调用 setState 之前使用。

其中 conf 参数格式为:

            {
                duration: 700,   //持续时间
                create: {    //若是新布局的动画类型
                    type: 'linear',
                    property: 'opacity'
                },
                update: {  //若是布局更新的动画类型
                    type: 'spring',
                    springDamping: 0.4
                }
            }

其实 LayoutAnimation 模块中已经帮我们写好了许多的动画预设,我们直接使用即可,例如上方的 conf 可写为 LayoutAnimation.Presets.spring

LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);

顺便说一下,LayoutAnimation 有以下三种动画效果类型:

  spring  //弹跳
  linear  //线性
  easeInEaseOut  //缓入缓出
  easeIn  //缓入
  easeOut  //缓出
  keyboard  //键入

不过这里我们要注意的是,安卓平台使用 LayoutAnimation 动画必须加上这么一句代码:

UIManager.setLayoutAnimationEnabledExperimental

否则动画将失效(官方文档也没提这个,真想掀桌)。

2. LayoutAnimation模块还提供了一个没啥卵用的 LayoutAnimation.create(duration<number>, type<String>, creationProp<String>) 接口,只是一个语法糖用来简化动画配置(就是上方的conf啦)的创建,看下源码就知道怎么用了:

function create(duration: number, type, creationProp): Config {
  return {
    duration,
    create: {
      type,
      property: creationProp,
    },
    update: {
      type,
    },
  };
}

我们来个官方示例看下这个布局动画是怎样的:

'use strict';

var React = require('react-native');
var {
    AppRegistry,
    StyleSheet,
    Text,
    View,
    UIManager,
    TouchableOpacity,
    LayoutAnimation,
    } = React;
//注意安卓平台一定要加上这一句!!!
UIManager.setLayoutAnimationEnabledExperimental(true);

var AwesomeProject = React.createClass({
    componentWillMount() {
        LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
    },

    getInitialState() {
        return { w: 100, h: 100 }
    },

    _onPress() {
        LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
        this.setState({w: this.state.w + 15, h: this.state.h + 15})
    },

    render: function() {
        return (
            <View style={styles.container}>
                <View style={[styles.box, {width: this.state.w, height: this.state.h}]} />
                <TouchableOpacity onPress={this._onPress}>
                    <View style={styles.button}>
                        <Text style={styles.buttonText}>Press me!</Text>
                    </View>
                </TouchableOpacity>
            </View>
        );
    }
});

var styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
    },
    box: {
        backgroundColor: 'red',
    },
    button: {
        marginTop: 10,
        paddingVertical: 10,
        paddingHorizontal: 20,
        backgroundColor: 'black',
    },
    buttonText: {
        color: 'white',
        fontSize: 16,
        fontWeight: 'bold',
    },
});

AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);
View Code

效果如下:

Linking - 链接处理接口

用于链接处理,比如获取从外部进入APP的链接,或者从APP打开一个外部链接。

1. 通过 Linking.openURL(url<String>) 可以打开一个外部链接:

    class AwesomeProject extends Component {
        constructor(props) {
            super(props);
        }
        _onPress() {
            Linking.openURL('http://4g.qq.com')
        }
        render() {
            return (
                <View style={styles.container}>
                    <TouchableOpacity onPress={this._onPress}>
                        <View style={styles.button}>
                            <Text style={styles.buttonText}>Press me!</Text>
                        </View>
                    </TouchableOpacity>

                    <TouchableOpacity onPress={InteractionManager.abc()}>
                        <View style={styles.button}>
                            <Text style={styles.buttonText}>reload</Text>
                        </View>
                    </TouchableOpacity>
                </View>
            );
        }
    }
View Code

效果如下:

也可以把url换成地理位置,比如“geo:37.484847,-122.148386”,它会自行去拉谷歌地图:

当然url的格式还是很多元的,通过它能够打开通讯录名片、已安装应用(伪协议)等

2. 通过 canOpenURL(url<String>) 可以判断所传的url是否是可以被打开的,返回一个Promise对象。建议在使用上述的 openURL 接口前可以先利用该接口做判断:

Linking.canOpenURL(url).then(supported => {
  if (!supported) {
    console.log('Can\'t handle url: ' + url);
  } else {
    return Linking.openURL(url);
  }
}).catch(err => console.error('An error occurred', err));

3. 如果应用是被一个链接调起的,则可以通过 getInitialURL() 接口获得该链接地址(若非链接调起则返回 null)。

NativeMethodsMixin - 原生组件方法调用接口

用于调用底层原生组件的一些组件方法,缺省是mixin到我们代码中的,可以直接作为原生组件的方法来调用而无须引入额外模块。

该接口功能要求你得了解 React 的 refs

1. 通过 measure(MeasureOnSuccessCallback<Fuction>) 获取组件在屏幕上的宽高和位置,通过回调参数可获得如下数据:

x
y
width
height
pageX
pageY

来个示例:

    class AwesomeProject extends Component {
        constructor(props) {
            super(props);
        }
        _onPress(){
            this.refs.view.measure(this.logWelcomeLayout);
        }
        logWelcomeLayout(ox, oy, width, height, px, py) {
            Alert.alert(
                'View的measure数据',
                ox + ' ' + oy + ' ' + width + ' ' + height + ' ' + px + ' ' + py,
                [
                    {text: 'OK', onPress: () => console.log('OK Pressed')}
                ]
            )
        }
        render() {
            return (
                <View style={styles.container}>
                    <TouchableOpacity onPress={this._onPress.bind(this)} >
                        <View style={styles.button}>
                            <Text style={styles.buttonText} ref="view">getMeasure</Text>
                        </View>
                    </TouchableOpacity>
                </View>
            );
        }
    }

点击按钮后的弹窗:

2. 有时候我们希望能获取一个原生组件相对于其某个祖先组件的位置,那我们可以使用 measureLayout(relativeToNativeNode<Number>, MeasureLayoutOnSuccessCallback<Function>, onFail<Function>) 方法。

注意这里的 relativeToNativeNode 传入的是祖先原生节点的ID,我们可以通过 React.findNodeHandle(component) 来获取。

示例:

    class AwesomeProject extends Component {
        constructor(props) {
            super(props);
        }
        componentDidMount() {

        }
        _onPress(){
            var cid = React.findNodeHandle(this.refs.view);
            this.refs.text.measureLayout(cid, this.logWelcomeLayout);
        }
        logWelcomeLayout(ox, oy, width, height, px, py) {
            Alert.alert(
                'View的measure数据',
                ox + ' ' + oy + ' ' + width + ' ' + height + ' ' + px + ' ' + py,
                [
                    {text: 'OK', onPress: () => console.log('OK Pressed')}
                ]
            )
        }
        render() {
            return (
                <View style={styles.container}>
                    <TouchableOpacity onPress={this._onPress.bind(this)} >
                        <View style={styles.button} ref="view">
                            <Text style={styles.buttonText} ref="text">getMeasure</Text>
                        </View>
                    </TouchableOpacity>
            );
        }
    }

点击按钮后:

3. 有时候我们想设置原生组件的某些原生特性,例如通过修改 TouchableOpacity 的 opacity 属性来修改该组件的透明度,这种形式的修改不会触发重绘,性能要高于常规通过 setState 然后间接修改 stsyle 的形式。

通过 setNativeProps(nativeProps<Object>) 接口我们可以轻松做到这一点:

    class AwesomeProject extends Component {
        constructor(props) {
            super(props);
        }
        _onPress(){
            this.refs.view.setNativeProps({
                style: {transform: [{rotate:'50deg'}]}
            });
        }
        render() {
            return (
                <View style={styles.container}>
                    <TouchableOpacity onPress={this._onPress.bind(this)} >
                        <View style={styles.button} ref="view">
                            <Text style={styles.buttonText}>setRoate</Text>
                        </View>
                    </TouchableOpacity>

                    <TouchableOpacity>
                        <View style={styles.button}>
                            <Text style={styles.buttonText}>somebtn</Text>
                        </View>
                    </TouchableOpacity>
                </View>
            );
        }
    }

4. 另外对于部分组件还提供了原生的 .focus().blur() 方法来唤起聚焦、失焦功能:

    class AwesomeProject extends Component {
        constructor(props) {
            super(props);
        }
        _onPress(){
            this.refs.input.focus();
        }
        render() {
            return (
                <View style={styles.container}>
                    <TextInput ref="input"
                        style={{height: 40, borderColor: 'gray', borderWidth: 1, marginBottom: 10}}
                    />

                    <TouchableOpacity onPress={this._onPress.bind(this)} >
                        <View style={styles.button}>
                            <Text style={styles.buttonText}>focus</Text>
                        </View>
                    </TouchableOpacity>
                </View>
            );
        }
    }

NetInfo - 获取设备当前网络状态

注意若希望应用能异步抓取到设备的网络状态,需要在 AndroidManifest.xml 文件中添加下方代码:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

1. 我们可以通过 NetInfo.fetch 接口来捕获设备当前的网络类型,能捕获到的联网类型有如下几种:

NONE - 设备处于离线状态
BLUETOOTH - 蓝牙数据连接
DUMMY - 模拟数据连接
ETHERNET - 以太网数据连接
MOBILE - 移动网络数据连接
MOBILE_DUN - 拨号移动网络数据连接
MOBILE_HIPRI - 高优先级移动网络数据连接
MOBILE_MMS - 彩信移动网络数据连接
MOBILE_SUPL - 安全用户面定位(SUPL)数据连接
VPN - 虚拟网络连接。需要Android5.0以上
WIFI - WIFI数据连接
WIMAX - WiMAX数据连接
UNKNOWN - 未知数据连接

来个示例:

        constructor(props) {
            super(props);
            this.state = {
                network : 'checked...'
            }
        }
        componentDidMount(){
            NetInfo.fetch().done(this.handleFirstConnectivityChange.bind(this));

            NetInfo.addEventListener(
                'change',
                this.handleFirstConnectivityChange.bind(this)
            );
        }
        componentWillUnmount() {
            NetInfo.removeEventListener(
                'change',
                this.handleFirstConnectivityChange.bind(this)
            );
        }
        handleFirstConnectivityChange(reach){
            this.setState({
                network : reach
            })
        }

注意其中还使用了 NetInfo.addEventListener/removeEventListener 来挂载/卸载网络状态的变化监听事件。

2. NetInfo还提供了 isConnectionExpensive 接口来识别当前网络是否处于移动运营计费模式:

NetInfo.isConnectionExpensive((isConnectionExpensive) => {
  console.log('当前网络是否会产生运营费用: ' + (isConnectionExpensive ? '会' : '不会'));
});

另外 NetInfo 模块也提供了一个属性 isConnected 来识别当前设备是否连上网络,该属性返回一个 Boolean 值:

var isConnected = NetInfo.isConnected;

PanResponder - 手势模块

通过该模块可监听组件上的手势并触发回调,通过 PanResponder.create(config<Object>) 可创建手势响应器,并将其以 prop 形式放入组件中可监听组件手势事件。

其中 config 参数属性有如下可选方法(具体含义写在后面示例注释中):

onMoveShouldSetPanResponder: (e, gestureState) => {...}
onMoveShouldSetPanResponderCapture: (e, gestureState) => {...}
onStartShouldSetPanResponder: (e, gestureState) => {...}
onStartShouldSetPanResponderCapture: (e, gestureState) => {...}
onPanResponderReject: (e, gestureState) => {...}
onPanResponderGrant: (e, gestureState) => {...}
onPanResponderStart: (e, gestureState) => {...}
onPanResponderEnd: (e, gestureState) => {...}
onPanResponderRelease: (e, gestureState) => {...}
onPanResponderMove: (e, gestureState) => {...}
onPanResponderTerminate: (e, gestureState) => {...}
onPanResponderTerminationRequest: (e, gestureState) => {...}
onShouldBlockNativeResponder: (e, gestureState) => {...}

其中 e 表示事件对象,gestureState 表示捕获到的手势对象:

//e - 事件对象:
changedTouches - 在上一次事件之后,所有发生变化的触摸事件的数组集合(即上一次事件后,所有移动过的触摸点)
identifier - 触摸点的ID
locationX - 触摸点相对于父元素的横坐标
locationY - 触摸点相对于父元素的纵坐标
pageX - 触摸点相对于根元素的横坐标
pageY - 触摸点相对于根元素的纵坐标
target - 触摸点所在的元素ID
timestamp - 触摸事件的时间戳,可用于移动速度的计算
touches - 当前屏幕上的所有触摸点的集合

//gestureState - 手势状态对象:
stateID - 触摸状态的ID。在屏幕上有至少一个触摸点的情况下,这个ID会一直有效。
moveX - 最近一次移动时的屏幕横坐标
moveY - 最近一次移动时的屏幕纵坐标
x0 - 当响应器产生时的屏幕坐标
y0 - 当响应器产生时的屏幕坐标
dx - 从触摸操作开始时的累计横向路程
dy - 从触摸操作开始时的累计纵向路程
vx - 当前的横向移动速度
vy - 当前的纵向移动速度
numberActiveTouches - 当前在屏幕上的有效触摸点的数量

示例:

    class AwesomeProject extends Component {
        constructor(props) {
            super(props);
            this.state = {
                gbtn : '手势区',
                coordinate : 'listening...'
            };
            this._panResponder = {}
        }

        //注意绑定到componentDidMount的话会失效,需要在componentWillMount处预先创建手势响应器
        componentWillMount() {
            this._panResponder = PanResponder.create({
                //类似 shouldComponentUpdate,监听手势开始按下的事件,返回一个boolean决定是否启用当前手势响应器
                onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder.bind(this),
                //监听手势移动的事件,返回一个boolean决定是否启用当前手势响应器
                onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder.bind(this),
                //手势开始处理
                onPanResponderGrant: this._handlePanResponderGrant.bind(this),
                //手势移动时的处理
                onPanResponderMove: this._handlePanResponderMove.bind(this),
                //用户放开所有触点时的处理
                onPanResponderRelease: this._handlePanResponderRelease.bind(this),
                //另一个组件成了手势响应器时(当前组件手势结束)的处理
                onPanResponderTerminate: this._handlePanResponderEnd.bind(this)
            });
        }

        _handleStartShouldSetPanResponder(e, gestureState) {
            //返回一个boolean决定是否启用当前手势响应器
            return true;
        }

        _handleMoveShouldSetPanResponder(e, gestureState) {
            return true;
        }

        _handlePanResponderGrant(e, gestureState) {
            this.setState({
                gbtn : '手势开始'
            })
        }

        _handlePanResponderEnd(e, gestureState) {
            this.setState({
                gbtn : '手势结束'
            })
        }

        _handlePanResponderRelease(e, gestureState) {
            this.setState({
                gbtn : '手势释放'
            })
        }

        _handlePanResponderMove(e, gestureState) {
            var coordinate = 'x:' + gestureState.moveX + ',y:' + gestureState.moveY;
            this.setState({
                coordinate : coordinate
            })
        }
        render() {
            return (
                <View style={styles.container}>
                    <TouchableOpacity>
                        <View style={styles.button} {...this._panResponder.panHandlers}>
                            <Text style={styles.buttonText}>{this.state.gbtn}</Text>
                        </View>
                    </TouchableOpacity>

                    <TouchableOpacity>
                        <View style={styles.button}>
                            <Text style={styles.buttonText}>{this.state.coordinate}</Text>
                        </View>
                    </TouchableOpacity>
                </View>
            );
        }
    }
View Code

效果:

PixelRatio - 设备像素密度模块

现在的主流移动设备基本都是高清屏,比如 GalaxyS5 的像素密度就达到了3,这意味着我们要在视觉上表达一个线宽(最细边框),就得设置 1/3 pt 的边框值。

1. 通过 PixelRatio.get() 方法我们可以轻松获得这个像素密度值:

var styles = StyleSheet.create({
    button: {
        borderWidth : 1/PixelRatio.get(),
        borderColor : 'red',
        marginTop: 10,
        paddingVertical: 10,
        paddingHorizontal: 20,
        backgroundColor: 'yellow'
    }
});

不过这里顺带提一下,RN中的 StyleSheet 模块(没错就是咱通过 StyleSheet.create() 来创建组件样式的模块)带有一个参数 hairlineWidth,它能直接返回当前设备的最细边框值,即上方代码其实等价于:

var styles = StyleSheet.create({
    button: {
        borderWidth : StyleSheet.hairlineWidth,
        borderColor : 'red',
        marginTop: 10,
        paddingVertical: 10,
        paddingHorizontal: 20,
        backgroundColor: 'yellow'
    }
});

2. 通过 PixelRatio.getFontScale() 方法可以获取到字体的尺寸缩放比值,默认跟像素密度值一致,但如果用户在 “设置 > 显示 > 字体大小” 中修改了字体尺寸:

如上图我们在 GalaxyS5 修改字体尺寸为 Large,那么通过 PixelRatio.getFontScale() 获得到的值将由“3”变为“3.4499998”。

另外 PixelRatio 还有一个非常重要的方法 getPixelSizeForLayoutSize(layoutSize<Number>) ,它可以把一个布局尺寸转换为匹配当前像素密度的(四舍五入后的)像素值。

比如一台设备的像素密度是3,那么 getPixelSizeForLayoutSize(100) 会返回 3*100 即 300 的像素值,

比如一台设备的像素密度是2.4999,那么 getPixelSizeForLayoutSize(100) 会返回 Math.round(2.4999*100) 即 250 的像素值。

该方法的好处是,如果我们希望一张图片的尺寸能在各种分辨率的设备上看到的视觉比例是一致的,例如希望该图片在分辨率宽度都是320的普通设备与高清设备上,看到的图片宽度没有不同(不会因为高清设备的像素密度高就导致图片视觉尺寸被压缩),那么我们可以这么设置图片尺寸(假设图片宽高为300 * 200):

var imageSize = {
    width : PixelRatio.getPixelSizeForLayoutSize(300),
    height : PixelRatio.getPixelSizeForLayoutSize(200)
}

TimePickerAndroid - 时间选择器

通过 TimePickerAndroid.open(options<Object>) 方法可以打开一个标准的Android时间选择器的对话框,并返回一个Promise对象。

其中 options 参数参考如下:

hour (0-23) - 要显示的小时,默认为当前时间。
minute (0-59) - 要显示的分钟,默认为当前时间。
is24Hour (boolean) - 如果设为true,则选择器会使用24小时制。如果设为false,则会额外显示AM/PM的选项。如果不设定,则采取当前地区的默认设置。

Promise的回调参数为:

action - 对应动作,若为取消对话框,该值为 TimePickerAndroid.dismissedAction
hour (0-23) - 选中的小时值,若为取消对话框,该值为undefined
minute (0-59) - 选中的分钟值,若为取消对话框,该值为undefined

因此我们可以通过判断 Promise 回调中的 action 是否等价于 TimePickerAndroid.dismissedAction,来得知用户是否做了取消对话框的行为:

    class AwesomeProject extends Component {
        constructor(props) {
            super(props);
        }
        _onPress(){
            TimePickerAndroid.open({
                hour: 13,
                minute: 21,
                is24Hour: true // Will display '2 PM'
            }).done(function(params){
                var content = '';
                if(params.action !== TimePickerAndroid.dismissedAction){
                    content = '你选中了' + params.hour + ':' + params.minute
                } else {
                    content = '你退出了时间选择对话框'
                }

                Alert.alert(
                    '时间选择结果',
                    content,
                    [
                        {text: 'OK', onPress: () => console.log('OK Pressed')}
                    ]
                )
            })
        }

        render() {
            return (
                <View style={styles.container}>
                    <TouchableOpacity onPress={this._onPress}>
                        <View style={styles.button}>
                            <Text style={styles.buttonText}>打开时间选择器</Text>
                        </View>
                    </TouchableOpacity>

                    <TouchableOpacity>
                        <View style={styles.button}>
                            <Text style={styles.buttonText}>somebtn</Text>
                        </View>
                    </TouchableOpacity>
                </View>
            );
        }
    }

效果:

ToastAndroid - 小黑条Toast提示接口

通过 ToastAndroid.show(content<String>, showTime<ToastAndroid.SHORT/ToastAndroid.LONG>) 可拉起原生 Toast小黑条提示。

其中 content 表示要展示的提示内容,showTime 则表示Toast提示时间的长短,其值须为ToastAndroid.SHORT 或者 ToastAndroid.LONG:

    class AwesomeProject extends Component {
        constructor(props) {
            super(props);
        }

        showToast(isLong) {
            var showTime = isLong ? ToastAndroid.LONG : ToastAndroid.SHORT;
            ToastAndroid.show('呵呵呵', showTime)
        }
        render() {
            return (
                <View style={styles.container}>
                    <TouchableOpacity onPress={this.showToast.bind(this, false)}>
                        <View style={styles.button}>
                            <Text style={styles.buttonText}>短时间Toast</Text>
                        </View>
                    </TouchableOpacity>

                    <TouchableOpacity onPress={this.showToast.bind(this, true)}>
                        <View style={styles.button}>
                            <Text style={styles.buttonText}>长时间Toast</Text>
                        </View>
                    </TouchableOpacity>

                    <TouchableOpacity>
                        <View style={styles.button}>
                            <Text style={styles.buttonText}>somebtn</Text>
                        </View>
                    </TouchableOpacity>
                </View>
            );
        }
    }

稍微算了下,ToastAndroid.SHORT 大概持续了4秒,而ToastAndroid.LONG 则持续了6秒的提示时间。

Vibration - 振动接口

该模块仅有一个 .vibrate() 方法异步唤起设备的振动功能(如果设备支持的话)。

注意安卓平台需要在 AndroidManifest.xml 文件下加入一行代码:

<uses-permission android:name="android.permission.VIBRATE"/>

如果你希望能调试模拟效果,也只能在真机上模拟了,虚拟机没法做出振动反馈。

示例:

      <TouchableHighlight  onPress={() => Vibration.vibrate()}>
        <View style={styles.button}>
          <Text>Vibrate</Text>
        </View>
      </TouchableHighlight>

 

安卓的API就算都描述完了,边测试边撸文章真的辛苦,所以看本文的盆友就是不评论也请点下右下方的吧么么哒~

donate

posted @ 2016-03-13 12:35  vajoy  阅读(...)  评论(... 编辑 收藏
Copyright © 2014 - 2017 VaJoy Studio