ReactNative利用Navigator实现登录跳转

看完全文并且do it你将收获:

  1. 导航器组件Navigator的使用
  2. 文本输入组件TextInput的使用(当然不重点说这个,注释里见!
  3. 安卓特有组件ToastAndroid(iOS直接用alert替代吧

老套路,来先看看今天要实现一个怎么样的效果:

今天要实现的Demo

OK,现在开始来实现:

那个username和password的小图标我放在文末,先去另存为一下吧~

界面布局

组件的布局样式使用简直就是小菜一碟对不对?那我们直接把布局给写出来!(千万记得import需要的组件噢)


export default class Login extends Component {
  render() {
    return (
      <View 
        style={styles.container}>
        <View
          style={styles.inputBox}>
          <Image
            style={styles.img}
            source={require('./image/username.png')}/>//注意你的图片引用路径
          <TextInput
            style={styles.input}/>
        </View>
        <View
          style={styles.inputBox}>
          <Image
            source={require('./image/pwd.png')}//注意你的图片引用路径
            style={styles.img}/>
          <TextInput
            style={styles.input}/>
        </View>
        <TouchableOpacity
          style={styles.button}>
          <Text
            style={styles.btText}>登录</Text>
        </TouchableOpacity>
        <TouchableOpacity
          style={styles.button}>
          <Text
            style={styles.btText}>注册</Text>
        </TouchableOpacity>
      </View>

    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  img: {
    width: 30,
    height: 30,
  },
  input: {
    width: 200,
    height: 40,
    color: '#fff',//输入框输入的文本为白色
  },
  inputBox: {
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    width: 280,
    height: 50,
    borderRadius: 8,
    backgroundColor: '#66f',
    marginBottom: 8,
  },
  button: {
    height: 50,
    width: 280,
    justifyContent: 'center',
    alignItems: 'center',
    borderRadius: 8,
    backgroundColor: '#66f',    
    marginBottom: 8,
  },
  btText: {
    color: '#fff',
  }
});

初步写成的界面是这样:

一步到位!

这个TextInput下划线那么丑?!不行,我得把他干掉:

......
        <View
          style={styles.inputBox}>
          <Image
            style={styles.img}
            source={require('./image/username.png')}/>
          <TextInput
            style={styles.input}
            //secureTextEntry={true}  如果是密码框,请开启这个属性!!
            placeholderTextColor={'#fff'}//提示文本的颜色
            placeholder={'username'}//提示文本内容
            underlineColorAndroid={'transparent'}/>{/*设置下划线颜色为透明,就相当于不见了*/}
        </View>
......

OK,这样就把账号的输入框搞定了,那密码框就一样的了,把secureTextEntry设置true,然后placeholder内容改成password就行了!

来,看看效果:

比较完美了

PS.饶了我吧,我也不知道那个光标的颜色要怎么改/(ㄒoㄒ)/~~

不过界面效果确实很理想,接下来咱们把登录按钮的功能实现一下,预想功能是这样的:(简单判断账号密码是否与预设相同)

  1. 点击登录,如果账号密码不匹配,则弹出提示密码错误
  2. 点击登录,如果账号密码匹配,则跳转到应用主界面

那么现在问题来了,我要怎么在点击登录的时候获取到TextInput的文本值呢?

原来TextInput有一个onChangeText属性,该属性接收一个”参数为当前文本的回调函数”,会在文本内容发生变化的时候回调此函数。

既然这样,那就可以在state中设置两个变量分别为username和password,在onChangeText回调函数内setState来更新他们的值!

这里就得说一下了,每次调用setState的时候,都会触发render重新渲染界面(请参考组件生命周期相关知识),这样太消耗资源了,既然这样,那还是不把username和password写在state中吧

username、password和onChangeText:

export default class Login extends Component {

  username = '';
  password = '';

  //账号框文本变化的回调函数,该回调函数接收的参数为:输入框当前文本内容
  //通过绑定此函数给onChangeText就实现实时更新username变量
  onUsernameChanged = (newUsername) => {
    console.log(newUsername);//运行后可以在输入框随意输入内容并且查看log验证!
    this.username = newUsername;
  };

  //密码框文本变化的回调函数,该回调函数接收的参数为:输入框当前文本内容
  //通过绑定此函数给onChangeText就实现实时更新password变量
  onPasswordChanged = (newPassword) => {
    console.log(newUsername);//运行后可以在输入框随意输入内容并且查看log验证!
    this.password = newPassword;
  };

  render() {
    return (
          ......
          <TextInput
            onChangeText={this.onUsernameChanged}//绑定文本变化的回调函数
            style={styles.input}
            placeholderTextColor={'#fff'}
            placeholder={'username'}
            underlineColorAndroid={'transparent'}/>
          ......
          <TextInput
            onChangeText={this.onPasswordChanged}//绑定文本变化的回调函数
            placeholderTextColor={'#fff'}
            placeholder={'password'}
            underlineColorAndroid={'transparent'}
            secureTextEntry={true}
            style={styles.input}/>
            ......
    );
  }
}

现在能获取到输入框的内容了,接下来就给登陆按钮绑定一个点击事件吧!

  ......
  login = () => {
    if (this.username == 'admin' && this.password == '123') {
      //其实安卓的Toast...简直简单到我不用说...(记得引入ToastAndroid)
      ToastAndroid.show('登录成功',ToastAndroid.SHORT);
    } else {
      //iOS直接用alert就行了
      ToastAndroid.show('登录失败',ToastAndroid.SHORT);
    }
  };

  render() {
    return (
      ......
      <TouchableOpacity
          onPress={this.login}//绑定一下点击事件
          style={styles.button}>
          <Text
            style={styles.btText}>登录</Text>
      </TouchableOpacity>
      ......
    )
  }

好了,写了这么一坨代码,现在来看看效果:double r

登陆按钮搞定

还不错,那么下一个目标就是如果账号密码正确,不仅仅是弹一个吐司那么简单,咱们进入一个新的界面(场景Scene)

Navigator导航器可以帮我们实现场景(Scene)的跳转,那么第一个肯定问题肯定要问啥是场景(Scene)呢:来自RN中文网的解释:

无论是View中包含Text,还是一个排满了图片的ScrollView,渲染各种组件现在对你来说应该已经得心应手了。这些摆放在一个屏幕中的组件,就共同构成了一个“场景(Scene)”。场景简单来说其实就是一个全屏的React组件。与之相对的是单个的Text、Image又或者是你自定义的什么组件,仅仅占据页面中的一部分。

原来场景(Scene)就是一个全屏的React组件!也就是说我们刚刚写的那个界面,就是一个场景(Scene)咯!

在使用Navigator之前,咱们先把这个登录的界面做成一个“可复用的组件(场景)”,关于怎么制作一个可复用的组件,可以参考我前面的文章ReactNative制作Component控件并且复用

这里我们在项目根目录新建一个scene文件夹,里面用来存放各个scene,接着在文件夹内新建一个LoginScene.js文件,将刚刚写的代码迁移过去(这个过程中注意修改各个和路径有关的地方),最后再index.android.js中引用:【index.android.js】

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

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

import LoginScene from './scene/LoginScene';

export default class Login extends Component {

  render() {
    return (
      <LoginScene/>
    );
  }
}

AppRegistry.registerComponent('Login', () => Login);

完美,效果和之前完全一样!(不放图预览啦,反正一样的)

Navigator的使用极其简单!他是一个组件,所以使用他也就是一个标签就行了: 当然你要注意引入这个组件

Navigator有两个必要属性renderScene和initialRoute

  1. renderScene属性接收一个”返回需要渲染的场景的回调函数”,”该回调函数接收两个参数”—(数据集合/即路由,这个集合中要包含需要展示的scene)和(navigator对象实例),
  2. initialRoute属性接收一个数据对象,其中必须包含默认展示的场景(启动默认显示的界面),当然你可以在其中添加其他数据(用于数据传递)。注意了,这个数据对象就是第一次传给renderScene的回调函数的第一个参数!!

OK,现在知道Navigator需要的东西,那咱们就给他准备好:

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

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

import LoginScene from './scene/LoginScene';

export default class Login extends Component {

  //第一次调用的时候,第一个参数route就是initialRoute
  renderScene = (route, navigator) => {
    return(
      <route.scene //这里返回route中包含的场景,在界面上渲染该场景
        navigator={navigator}/>//并且将navigator作为一个参数传递给这个场景,以便在这个场景中做场景跳转
    );
  }

  //默认的route数据,其中必须包含第一次需要渲染的场景,不然显示啥?
  initialRoute = {
    scene: LoginScene,
    //你也可以在这里继续添加其他数据,然后在renderScene中取出,用于场景的数据传递,不展开叙述这个了!
  }

  render() {
    return (
      <Navigator
        initialRoute={this.initialRoute}
        renderScene={this.renderScene}/>
    );
  }
}

AppRegistry.registerComponent('Login', () => Login);

这个时候还是来double r看一看效果:

好吧由于界面效果和之前完全一样,没有变化,就不贴图了。(没红就是最好的效果啦)

那么怎么控制场景切换,在哪里控制场景切换呢?在上面的代码我们看到,navigator对象实例传递给了每一个Scene,而场景切换正是由navigator实例对象来控制的:

navigator拥有一系列方法,用于场景切换RN中文网Navigator API

  • getCurrentRoutes() - 获取当前栈里的路由,也就是push进来,没有pop掉的那些。
  • jumpBack() - 跳回之前的路由,当然前提是保留现在的,还可以再跳回来,会给你保留原样。
  • jumpForward() - 上一个方法不是调到之前的路由了么,用这个跳回来就好了。
  • jumpTo(route) - 跳转到已有的场景并且不卸载。
  • push(route) - 跳转到新的场景,并且将场景入栈,你可以稍后跳转过去
  • pop() - 跳转回去并且卸载掉当前场景
  • replace(route) - 用一个新的路由替换掉当前场景
  • replaceAtIndex(route, index) - 替换掉指定序列的路由场景
  • replacePrevious(route) - 替换掉之前的场景
  • resetTo(route) - 跳转到新的场景,并且重置整个路由栈
  • immediatelyResetRouteStack(routeStack) - 用新的路由数组来重置路由栈
  • popToRoute(route) - pop到路由指定的场景,在整个路由栈中,处于指定场景之后的场景将会被卸载。
  • popToTop() - pop到栈中的第一个场景,卸载掉所有的其他场景。
    ######所有场景都在栈中,由navigator控制出栈入栈,这些解释我相信大家很容易就明白

在第一个显示的场景是LoginScene的情况下,场景切换的控制权当然在[LoginScene.js]中了!!不过这个时候你更应该想到的是:要切换场景就需要>=2个场景,但是现在却只有一个场景,所以现在的任务就是在scene文件夹下新建一个【HomeScene.js】文件了!

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

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


export default class HomeScene extends Component {

  render() {
    return (
      <View
        style={styles.container}>
        <Text>登录成功!这是主页!</Text>
      </View>
    );
  }

}


const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

接下来,就是需要在【LoginScene.js】中控制账号密码匹配后跳转到这个主页场景中:

.....
import HomeScene from './HomeScene';//千万别忘了引入!!
  ......
  login = () => {
    if (this.username == 'admin' && this.password == '123') {
      this.props.navigator.push({//还记得navigator作为属性传给了每一个scene吗!对了,就是这样取到他
        scene: HomeScene,//通过push方法将一个scene入栈,push方法接收一个route,其中必须包含一个scene
      });
      ToastAndroid.show('登录成功',ToastAndroid.SHORT);
    } else {
      ToastAndroid.show('登录失败',ToastAndroid.SHORT);
    }
  };
  ......

这个时候来看一下整体的效果:double r

实现了场景切换

可以看到,这个时候已经实现了场景跳转,并且可以看到在第二个场景可以通过在左边缘向右滑返回上一个场景。可是现在还有一个重大bug,就是第二个场景出来的时候,居然和第一个场景出现了图像的重叠??

经过分析和尝试,发现问题出在这儿:【HomeScene.js】

......
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    //在这里我们并没有给第二个场景设置背景颜色!如果没有设置背景颜色,默认就是透明的
    //这就导致了第二个场景在出现并叠在第一个场景之上的时候,会出现图像的重叠
    //解决方法就是给第二个场景加上一个背景颜色!
    backgroundColor: '#F5FCFF',
  },
});

这个时候就解决图像重叠的问题了!不信你double r试试看

这里有一个特别有意思的地方,就是你在登录成功并且跳转到第二个场景之后,你再按两下R重载界面试试?
是不是发现无论多疯狂的按R键,都不会重载界面?
这个时候你从左边缘向右滑动,返回上一个场景,你会发现密码框出现了一坨的r

  • 这就是有趣的地方,RN的场景切换,现在看来应该只是把第一个场景给隐藏了!可以想象成向左滑动一点距离之后设置透明度为0,这样就看不见了。
    刚刚登陆场景向左滑动并且隐藏,可是密码输入框的焦点还在,所以这个时候你按r当然就是在向密码框输入内容了!

当然另一方面,在逻辑上来说,既然用户是登录进入了第二个场景,那么自然不可能通过滑动返回到登录界面!所以再登录按钮的方法中navigator.push()应该换成navigator.replace(),这样在登录成功的情况下就无法返回到上一个场景了,因为栈中已经没有登录的场景了!

几乎是个成品了

既然看到了这儿,那么Navigator最基本的用法你已经掌握了,开头的效果,你也很容易能加上了—–无非是每个场景多加一个button来实现场景切换嘛!

接下来把图片给你们!

username.png

pwd.png

我靠,在文章里这么大…..算了,拿着用吧分辨率是510*510的…的…好吧我之前一直没注意用的是这么大的图片!!

接下来就是代码啦:

【index.android.js】

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

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

import LoginScene from './scene/LoginScene';

export default class Login extends Component {

  renderScene = (route, navigator) => {
    return(
      <route.scene 
        navigator={navigator}/>
    );
  }
  initialRoute = {
    scene: LoginScene,
  }

  render() {
    return (
      <Navigator
        initialRoute={this.initialRoute}
        renderScene={this.renderScene}/>
    );
  }
}

AppRegistry.registerComponent('Login', () => Login);

【LoginScene.js】

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  StyleSheet,
  Text,
  View,
  TextInput,
  Image,
  TouchableOpacity,
  ToastAndroid
} from 'react-native';

import HomeScene from './HomeScene.js';

export default class LoginScene extends Component {

  username = '';
  password = '';

  onUsernameChanged = (newUsername) => {
    this.username = newUsername;
  };

  onPasswordChanged = (newPassword) => {
    this.password = newPassword;
  };

  login = () => {
    if (this.username == 'admin' && this.password == '123') {
      this.props.navigator.replace({
        scene: HomeScene,
      });
      ToastAndroid.show('登录成功',ToastAndroid.SHORT);
    } else {
      ToastAndroid.show('登录失败',ToastAndroid.SHORT);
    }
  };

  render() {
    return (
      <View 
        style={styles.container}>
        <View
          style={styles.inputBox}>
          <Image
            style={styles.img}
            source={require('../image/username.png')}/>
          <TextInput
            onChangeText={this.onUsernameChanged}
            style={styles.input}
            placeholderTextColor={'#fff'}//提示文本的颜色
            placeholder={'username'}//提示文本内容
            underlineColorAndroid={'transparent'}/>{/*设置下划线颜色为透明,就相当于不见了*/}
        </View>
        <View
          style={styles.inputBox}>
          <Image
            source={require('../image/pwd.png')}
            style={styles.img}/>
          <TextInput
            onChangeText={this.onPasswordChanged}
            placeholderTextColor={'#fff'}
            placeholder={'password'}
            underlineColorAndroid={'transparent'}
            secureTextEntry={true}//密码输入框
            style={styles.input}/>
        </View>
        <TouchableOpacity
          onPress={this.login}
          style={styles.button}>
          <Text
            style={styles.btText}>登录</Text>
        </TouchableOpacity>
        <TouchableOpacity
          style={styles.button}>
          <Text
            style={styles.btText}>注册</Text>
        </TouchableOpacity>
      </View>

    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  img: {
    width: 30,
    height: 30,
  },
  input: {
    width: 200,
    height: 40,
    color: '#fff',
  },
  inputBox: {
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    width: 280,
    height: 50,
    borderRadius: 8,
    backgroundColor: '#66f',
    marginBottom: 8,
  },
  button: {
    height: 50,
    width: 280,
    justifyContent: 'center',
    alignItems: 'center',
    borderRadius: 8,
    backgroundColor: '#66f',    
    marginBottom: 8,
  },
  btText: {
    color: '#fff',
  }
});

【HomeScene.js】

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

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


export default class HomeScene extends Component {

  render() {
    return (
      <View
        style={styles.container}>
        <Text>登录成功!这是主页!</Text>
      </View>
    );
  }

}


const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
});