文章出處

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


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()