开发者学堂课程【高校精品课-上海交通大学 -互联网应用开发技术:移动端框架 1】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/76/detail/15784
移动端框架1
内容介绍
一、搜索功能
二、认证逻辑
三、登出
四、Flutter
一、搜索功能
在页面上方搜索栏输入东西后搜索
实现逻辑,也成一个页面,展示一个构建,在 BookListScreen 上面嵌入。搜索栏本身嵌入到 BookListScreen,BookListScreen 相当于父组件。
初始化搜索的输入为空,放弃搜索为空。构造器初始化时 text 为空,isCancel 设为 false。
export class SearchBar extends React.Component{
static defaultProps = {
searchBooks:{},
cancelSearching:{}
}
constructor(props) {
super(props);
this.state ={text: ",isCancel:false};
}
render(){….}
Render 里有一个文本框,向里面输入东西。什么都不输,显示请输入关键字,值赋给 text。文本发生变化,调 textChange 函数,按下旁边的按钮,显示搜索或取消,在两者之间做切换。
输入东西,点搜索,变为取消。
文本支持点 onPress 的动作,点后调 search 函数,判断 iscancel方法。如果是 true 显示取消,否则显示搜索。构造器进来时赋false,开始显示搜索。textChange 只要有文本变化,绑定方法执行。
如果文本构造器构造时为空,isCancel 设为 false,当前对象的状态text,设为 text 值。
点搜索或取消文本,调 search 后判断。如果什么都没写,调 search没有反映。看文本是否输入东西,是否是取消状态。输入东西,搜索,再点一下,如果是取消状态,应该取消,显示所有东西。判断两个东西,false 状态并且文本不为空,反 isCancel,调 searchBooks 函数,传递输入的文本。如果 isCancel 是 true,按钮只要有东西,点把状态反。点取消,文本设为空。点 search 后点取消,设为空,调cancelSearching 方法。
<TextInput
style={styles.input}
returnKeyType="search"
placeholder="请输入关键字"
value={this.state.text}
onChangeText={this.textChange.bind(this)}/>
</View>
<View style={styles.btn}>
<Text style={styles.search}onPress={()=>this.search()}>{this.state.is
Cancel?'取消':'搜索'}</Text>
</View>
</View>
</View>
//输入框文字改变
textChange(text){
if(text==''){
this.setstate(state:{
isCancel:false
});
}
this.setstate(state:{
text:text
});
}
//搜索按钮点击
search(){
if(this.state.text!= && this.state.isCancel==false){
this.setstate(state:{
isCancel:!this.state.isCancel
}, callback:()=> {
this.props.searchBooks(this.state.text);
});
}else if(this.state.isCancel==true){
this.setstate(state:{
isCancel:!this.state.isCancel,
text:
},callback: ()=>{
this.props.cancelSearching();
});
cancelSearching 和 searchBooks 不在当前的 SearchBar 里,Sear
chBar 嵌入到 BookListScreen 里,BookListScreen 嵌入到页面里。
SearchBar 被按下执行时,是 cancelSearching 或 searchBooks,在 props 里,意味着在创建当前 SearchBar,传递的属性。创建SearchBar 时,传递出来。searchBooks 调用当前 BookListScreen的 text 方法,data 是传递的参数。拿到 text 后调 text 函数,cancelSearching 赋给一个值,如果调 cancelSearching,是调当前对象 BookListScreen 的 cancel。
<SafeAreaView style={{ flex: 2}}>
<SearchBar searchBooks={(data) => this.getText(data)}cancelSearc
hing={()=>this.cancel(()}/>
<FlatList
data={this.state.showBooks}
renderItem={this.renderBook}
style={styles.list}
keyExtractor={item => item.bookId}
/>
</SafeAreaView>
getText 拿到 data,data 是在搜索框内写进的内容。List 是当前BookList 展示的所有书,便利所有书,每本书名字、类型、作者、描述里哪个包含 data,相当于搜索。只要四个部分里,任何一个包含输入的 text,索引是>=0的数字,表示 data 字符串在前面字符串里出现索引,索引从0开始,只要>=0,表示找到。四个条件只要一个满足,表示此书满足条件,将书 push 到空的数组里。便利所有书后 arr 里包含所有满足条件的书,设置状态 showBooks 等于 arr。
getText(data){
ver arr=[];
var list=this.state.books;
for (var i = 0; i < list.length; i++) {
if (list[i].name.index0f(data)>= 0||
list[i].type.indexof(data)>= 0||
list[i].author.indexOf(data)>=0||list[i].description.indexOf(data)>= 0) {
arr.push(list[i])}
}
this.setstate( state: {
showBooks:arr
})
}
navigateToDetail({item}){
showBooks 是展示所有书时的 data,展示所有书列表里的数据。状态发生变化,部分页面重绘,包含想要的东西。
return{
<SafeAreaView style={{ flex: 2}}>
<SearchBar searchBooks={(data) => this.getText(data)}cancelSearc
hing={()=>this.cancel()}/>
<FlatList
data={this.state.showBooks}
renderItem={this.renderBook}
style={styles.list}
keyExtractor={item => item.bookId}
/>
</SafeAreaView>
cancel把showBooks恢复成所有书
cancel(){
this.setstate( state: {
showBooks:ithis.state.books
});
}
SearchBar 是构建不是完整的页面,可被复用。SearchBar 被嵌入到BookListScreen 里,BookListScreen 创建时设置了两个参数,回调当前 BookListScreen 里的函数。两个是当前构建的属性,cancelSear
Ching 和 searchBooks 属性是父类 BookListScreen 创建时所设置。按下后调属性里 cancelSearching 或 searchBooks,实际上调到父类传递进的两个方法上,调到 getText 和 cancel 上。
搜索实现最重要的是 cancelSearching 和 searchBooks 从前面创建时传递进来的,真正的业务逻辑在 getText 和 cancel 里,getText和 cancel 是当前父构建的一部分。getText 和 cancel 函数改变showBooks 的状态,状态改变,重绘,出现搜索效果。
安卓端效果
iOS端效果
二、认证逻辑
认证逻辑比较复杂,name、type、author、description四个属性,任何一个在都认为符合条件。
getText(data){
ver arr=[];
var list=this.state.books;
for (var i = 0; i < list.length; i++) {
if (list[i].name.index0f(data)>= 0||
list[i].type.indexof(data)>= 0||
list[i].author.indexOf(data)>=0||list[i].description.indexOf(data)>= 0) {
arr.push(list[i])}
}
this.setstate( state: {
showBooks:arr
})
}
React Navigation 在手机和PC机上不一样,和 Web 浏览器不一样。认证比较复杂,代码几乎没有修改,自己起名字,跳回页面不一样。链接里寻找,可直接复用。
逻辑,做认证时,根据认证的结果记录信息,包括SIGN_IN。
参考链接:https://reactnavigation.org/docs/auth-flow
const Stack=createStackNavigator();
const CHECK URL=apiUrl+"/checkSession":
export default function App() {
const [state, dispatch]=React.useReducer
((prevState,action)=> {
switch(action.type){
case 'RESTORE TOKEN':
return {
.…prevState,
userToken: action.token
isLoading: false,
};
case 'SIGN_IN':
return{
.…prevState,
isSignout: false,
userToken: action.token,
};
case 'SIGN_OUT':
return {
…prevState,
isSignout: true,
userToken: null
};
因为要 SIGN,所有 get,拿到用户的 Token。
React.useEffect(()=>{
// Fetch the token from storage then navigate to our appropriate place
const Async = async () => {
let userToken;
try {
userToken=await AsyncStorage.getltem(@Bookstore:token')
} catch (e) {
// Restoring token failed
}
// After restoring token,we may need to validate it in production apps
fetch(CHECK_URL,{
method:'POST',
headers:{'Content-Type:'application/json',}, body:JSON.stringify({}),
})
.then((response)=>{return response.json();})
.then((responseData)=>{ if(responseData.status<0){
AsyncStorage.removeltem("@Bookstore:token")
dispatch({ type: 'RESTORE TOKEN, token: null });
}else{
dispatch({type:'RESTORE_TOKEN,token:userToken})
})
.catch((error)=>{
console.error(error);
});
};
Async();
三、登出
后台发请求,登出。用 useContext 对象里 signOut 方法,调用。前面认证通过时涉及到@Bookstore:token,移除,显示按钮,推出账户,点击移除。移除后到后台 signOut 发 POST 请求,需要 logout,不需要输用户名、密码,后台消除用户关联的 session 对象,回来后什么都不做处理。登出只是告诉后台,前台手机存储里把登录后的token SIGN_IN成功后,设数据,去除 Bookstore:token 表示用户没有登录。
const LOGOUT_URL=apiUrl+"/logout";
export function Profile(L-
const{signOut }= React.useContext(AuthContext)
return(<View>
<Txt>Mey Profile</Text>
<Button title="退出账户" onPress={()=> {
AsyncStorage.removeltem("@Bookstore:token");
signOut();
fetch(LOGOUT URL,{
method:'POST'
credentials: 'include'
headers:{
'Content-Type:'application/json',
},
body:JSON.stringify({}),
})
.then((response) => {})
.then((responseData)=>{})
.catch((error)=>{});
}}/>
</View>);
}
效果,抽屉式最后一个,退出账户。退出账户后,没有登录过,重回页面。
方法定义,没有通过认证或没有身份,回到重新认证页面。
return(
<AuthContext.Providervalue={authContext}>
<NavigationContainer>
<Stack.Navigator>{state.isLoading?(
//We haven't finished checking for the token yet
<Stack.Screen name="Splash"component={SplashScreen}/>
): state.userToken== null ?(
// No token found, user isn't signed in
<Stack.Screen
name="Login"
component={LoginScreen}
options={{
title: 'Log in',
// when logging outa pop animation feels intuitive
animationTypeForReplace: stateisSignout?'pop': 'push',
headerShown:false,
}}
):(
// User is signed in
<Stack.Screen name="Home"component={HomeScreen}options={{
headerShown:false}}/>
)}
</Stack.Navigator>
</NavigationContainer>
</AuthContext.Provider>
写的例子不代表很科学,只代表一种参考。
四、Flutter
开发前端,iOS 和安卓是否都写一套。 Js React Navigation 是用框架,写出的代码是一套,一套不一定两者都写。苹果iOS,不是苹果机器不能用,要装手机模拟器。安卓只用 idea 不能跑,不能用虚拟机,虚拟器装安卓 studio 是必须的。开发手机端 app 可以不用 idea,直接用安卓 studio,安卓 studio 与 idea 操作方法几乎一样。只用idea 不能跑,idea 要安卓 studio SDK,安装步骤都操作好后,idea把安卓的 iOS 模拟器起来,应用才能跑进去。只有 idea 什么都不能跑,装安卓 studio 才能在安卓上跑。如果苹果的机器,有 ice cold,可以在 iPhone 模拟器上跑。装好后,装模拟器。默认装好安卓studio或 ice cold 不带模拟器,选一个模拟器,一个模拟器3、4G,只需装一个。如果机器不好,很难后端、两个模拟器同时跑起来,只能跑一个。
Flutter 模拟器跑 Flutter,可在安卓和 iOS 同时跑。编译装到模拟器里,跑起来。
Flutter 官网上提供的应用如下
人名随机生成,可选中一些写。选中后点列表,可看到选中的人。选中后可取消,点列表,消失。
跑 Flutter 要装 Flutter 插件,在 idea 里装插件可能遇到问题。Idea升级到最新版2020.1.1,升级后 Flutter 和 Dart 插件不能使用。升级 Flutter 和 Dart 遇到问题,Flutter 是 Google 的东西,升级报错,下载安装包,离线安装,配环境时可能遇到类似问题。Flutter加业内,Flutter 用 Dart 写,需要装 Dart。两个装好后,新建工程后可看到 Flutter 的工程,可写Flutter东西。
Flutter 是 Google 提的东西,想法在 web 和移动端的东西一样,只用单一的一套代码。Flutter 在 web 开发麻烦一点,需要一个插件,用 channel 方式联通。Dart 接近于 JavaScript,有区别。
下载 Flutter,版本升级很快,Flutter 是 Google 的东西,直接找,找不到。到 github 上找,拿到后装。
git clone https://github.com/flutter/fluttergit-b stable
装不是在 idea 里装插件,下载相应的东西,在本地装 Flutter 环境。Flutter装好后,不一定用 idea 跑,装好后可跑 Flutter doctor。
以下都通过表示没问题
找 channel stable、开发工具装的是否正确、安卓studio、idea、VS Code、Xcode、Android toolchain,新版本里跑以上不一定能通过。
跑 Flutter doctor,IDEA 里 Flutter 和 Dart 两个插件未装。
新版本的 IDEA 与 Flutter 当前版本配合的不好,报错。报错本身不影响运行,可跑起来。如果装好 Flutter 跑 doctor,出现报错,不用担心,重要的是拿 IDEA 开发 Flutter 应用。跑项目,能跑起来,报错没关系。
编译好后可能报错,没有找到解析依赖关系。
FAILURE: Build failed with an exception.
*What went wrong:
Could not determine the dependencies of task:app:compileDebu
gJavaWithJavac'.
> Could not resolve all task dependencies for configuration:app;d
ebugCompileClasspath'
> Could not resolve io.flutter:flutter_embeddingdebug:1.0.0-
c9506cb8e93e5e8879152ff5c948b175abb5b997
解决方法
三个文件里写
https://storagegoogleapis.com/download.flutterio信息,相当于在什么地反找 flutter 依赖的东西。Googleapis 可能被强入,idea 开发新 Flutter 应用后报错。把三个文件源切换到
Flutter 官网上,如果找不到,切换为中国源。
Flutter 目录下有 packages 东西,三个文件都有此地方,三个文件全部替换。
flutter/packages/flutter_tools/gradle/resolve_dependencies.gradle
flutter/packages/flutter_tools/gradle/aarinit_script.gradle flutter/packages/flutter_tools/gradle/flutter.gradle
中的:https://storage.googleapis.com/download.flutterio 替换为: http://download.flutter.io 重新编译
Flutter 装,doctor 跑完后,如果在 idea 里不能通过,idea 安装了Flutter 和 Dart 插件,报错。跑一个空应用,看是否能跑起来。编译时报错,
改 https://storage.googleapis.com/download.flutterio
如果都没问题,把 iOS 和安卓配好。两部分与 Flutter没关系,在idae里把 iOS 和安卓提起来,安卓 studio 一定要用。安卓 studio 安装好后,不想用 idea 开发移动端的东西,安卓 studio 也可开发。
安装后要配,第一个安卓虚拟设备 AVD,至少要有一个
可加,在硬盘上占11GB,随便选一个手机。
安卓 studio 可开发 Flutter,出现一样的插件。可在安卓 studio 里装插件,可开发 Flutter 项目。
第一步环境配置比较麻烦,可能跑不起来。跑起来,先在外面,不用开发工具,用命令行的方式在 Flutter 安装好的地方创建应用。进入应用后run,是否出现类似以下东西。
说明一切都没问题,如果不行说明有问题。
Flutter 跑起来后有页面端的东西,控制台如下。
如果用 idea 开发,与安卓 studio 大体操作逻辑相同。开发 Flutter项目,出来后以下内容
有相应的选项,开始进来时前两个都没有。如果把模拟器退出,里面什么都没有,open 打开。通过 open可知模拟器是否装好,open 后选一个模拟器,点运行,编译程序在模拟器里跑。
编译后放进去,跑起来。
开发时不一定两个都有,必须有一个安卓。苹果跑起来,很耗资源。两个虚拟机加后台,跑起来,非常耗资源。
运行起来后要启动模拟器运行,运行起来后在两个里面跑。如果不想跑,直接点停。两个应用版本里跑,已经把程序放到两个模拟器。两个模拟器本身不是 idea 的一部分,是两个单独的程序,三个程序跑。
什么都不做是最简单的历程,在 Flutter 官网里有编写 Flutter app的页面,从易到难开发看起来复杂的小程序,从易到难讲。目录的结构,Flutter 程序主要是在 lib 目录里写,lib 目录里有很多 Dart 文件,由 Dart 源写。
两个例子,一个 Flutter,一个 Flutterexamples。Flutterexamples里包含很多 Dart 文件,一个例子一个。Dart 文件的组织方式和开发模块化的方式相同,应该由若干个 Dart 文件。
例子从易到难,在 lib 目录里放文件 main.dart。跑起来后,任何一个 dart 工程里都有一个 main 函数,是程序的入口点。Main 函数里 runApp,run写的类,与 react 里跑一个 app 概念上一样,具体写法上有差异。用 Flutter 里 MaterialApp,写到 material.dart文件里,import 一下,与之前概念基本一样。App 是无状态的东西,意味着所有的人看到的一样,不保留特定的状态。做一个MaterialApp,进来后调 build 方法。创建 MaterialApp,title 是标题栏看到的内容,下面是 home,home 整个是一个脚手架,都在 material.dart里定义,脚手架里有 AppBar。Title 呈现 Welcome to Flutter,上面是 AppBar,下面是 body,居中显示文本 Hello World。
// Copyright 2018 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context){
return MaterialApp(
title: 'Welcome to Flutter'
home:Scaffold(
appBar:AppBar(
title: Text('Welcome to Flutter'),
),
body: Center(
child: Text('Hello World'),
),
),
Flutter 很像 JS,是 dart 语法,一种新的语言,接近于 JS。想法与其它前端构建相同,要做构建化处理,定义自己的类或用定义好的类做组装,组装页面。不同的手机上显示有一定差异,抽象起来,所有的控件都有一样的特性,实现复用、抽象、组件化功能,具体的语法或库有一定差异。
不显示 Hello World,显示 wordPair.asPascalCase,wordPair 对象通过 WordPair 调随机函数得到,随机呈现单词。Random 是依赖的第三方,第三方在 english_words 包里 english_words.dart 包含WordPAir。出现 flutter 外,第三方包的依赖。依赖在 json 文件里写,react 在 package json 里写依赖。
Flutter 在 pubspec.yaml 文件里写,有 dependencies、english_wo
rds 包,类比与 package json 写法。所有东西靠第三方 import 进来,进来后使用,import 东西在 english_words 文件里。依赖关系写在 english_words。
Json 方式写前端,后端 maven 写 pom文件。写前端文件时,gradle编译。依赖信息放到工具里,自动拉。
import'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main()=>runApp(MyApp());
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context) {
final wordPair=WordPair.random();
return MaterialApp(
title: 'Welcome to Flutter'
home: Scaffold(
appBar:AppBar(
title: Text('Welcome to Flutter'),
),
body: Center(
child: Text(wordPair.asPascalCase)),
),
);
pubspec.yaml
name: startup namer
description: A startup-namer app.
version: 1.0.0+1
# #docregion dependencies
dependencies:
flutter:
sdk: flutter
cupertino icons: ^0.1.2
english_words: ^3.1.0
# #enddocregion dependencies
dev_dependencies:
flutter_test:
sdk: flutter
pedantic: ^1.4.0
flutter:
uses-material-design: true
依赖 web 的包,每次都是随机。在安卓和 iOS 上跑,出现的字不一样,随机生成。
添加一个状态的 widget 居中的是调 RandomWords 得到的东西,
RandomWords 是一个类,放一个 RandomWords 类对象。RandomWords 有状态,创建一个状态,通过调
RandomWordsState得到。
创建出来调用方法,在 build 调 WordPair.random 得到一对值,把 WordPair 得到的一对值,按方式展示出来,变成文本。返回 text 值,值返回,作为 RandomWordsState 和 RandomWords返回值。出来效果与前面页面相同,放到 StatefulWidget 里,与有状态、无状态相关。状态是一对词与用户相关,整个应用过程中一直维护。状态相当于购物车,每个人看到的购物车不一样,不能在购物车里放东西,发现购物车里有东西,刚放进去的。每个用户要有自己独立的状态,不能与别人弄混。
void main()=> runApp());
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context){
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: Text('Welcome to Flutter'),
),
body: Center(
child:RandomWords()),
),
),
//#docregion RandomWordsStateRWS-class-only
class RandomWordsState extends State<RandomWords>{
//#enddocregion RWS-class-only
@override
Widget build(BuildContext context){
final wordPair=WordPair.random()
return Text(wordPair.asPascalCase)
}
//#docregion RWS-class-only
}
//#enddocregion RandomWordsState,RWS-class-only
//#docregion RandomWords
class RandomWords extends StatefulWidget{
@override
RandomWordsState createState()=>RandomWordsState();
}
只显示文字没有意义,显示一个能向下滚动的列表蓝视图。