manbetx客户端应用下载 关于移动端技术 | Android&iOS Development Manager | 这里是 CrazyCodeBoy@贾鹏辉 的个人博客,分享manbetx客户端应用。 http://localhost:4000/ Sun, 08 Dec 2019 20:41:07 +0800 Sun, 08 Dec 2019 20:41:07 +0800 Jekyll v4.0.0 新版React Native发布APP之签名打包APK

React Native发布APP之签名打包APK

本文出自《React Native学习笔记》系列文章。 了解更多,可以关注我的GitHub和加入: React Native学习交流群 React Native学习交流群


用React Native开发好APP之后,如何将APP发布以供用户使用呢?一款APP的发布流程无外乎:签名打包—>发布到各store这两大步骤。本文将向大家分享如何签名打包一款React Native APP。

众所周知,Android要求所有的APP都需要进行数字签名后,才能够被安装到相应的设备上。签名打包一个Android APP已经是每一位Android开发者的家常便饭了。 那么如何签名打包一款用React Native开发的APP呢? 既然Android Studio中可以进行APP的签名打包,那我们可不可以用它进行打包呢,实践表明用Android Studio打包React Native APP不是一种推荐的方案。

为什么不用Android Studio打包React Native APP?

在发这篇博文前我曾试着用Android Studio打包React Native APP,编译,打包,安装各项指数正常,当我欣喜在手机上打开APP看一下效果时,APP在启动时闪退了。多试几次依然如此,这时让我想起每次通过terminal安装APP到模拟器上时,launchPboke.nrnlibrary.com终端都会输出http://localhost:8081/index.android.bundle?platform=android&dev=true&hot=false&minify=false这样一行信息,然后APP在启动页加载一会才进入应用。通过浏览器访问上面的链接,发现链接返回的是一个js文件,打开该文件发现文件中的代码其实是我们写的 React Native 的 JS 代码。 PS.

  1. 在开发环境下,每次启动APP,都会连接JS Server将项目中编写的js文件代码加载到APP(这也是React Native的动态更新的精髓)。
  2. 签名打包后的APK已经从开发环境变成了生产环境,自然不会在每次启动的时候连接JS Server加载相应的js文件。所以导致APP因缺少相应的js而无法启动。

既然Android Stuio打包行不通,那么我们采用React Native官方推荐的方式进行签名打包(下文会重点讲解“通过官方推荐的方式签名打包”),打包过程很顺利,将打包好的APK安装到手机上后,发现能正常运行。 对比用Android Studio签名打包生成的APK与用官方推荐方式签名打包生成的APK,发现了它们在大小上和内容上都有所差别,如图: 大小上的差别: 两种打包方式apk大小差异.png

对比两种打包方式发现,它们所生成的apk在大小上相差几百k。为什么会相差那么大呢,带着这个疑问我们就将两个apk解压之后看看他们内部具体有什么不同。 apk内部差别: 两种方式签名打包的APK内部差别.png

上图是解压之后apk的内部细节,发现通过官方推荐的方式打包的apk多了两个文件“index.android.bundle”与“index.android.bundle.meta”,打开“index.android.bundle”发现其和从http://localhost:8081/index.android.bundle?platform=android&dev=true&hot=false&minify=false获取的文件内容是一样的,都是我们写的 React Native 的 JS 代码。

结论

  1. 在开发环境下,为方便调试,APP会在启动时从JS Server服务器将index.android.bundle文件加载到APP。
  2. 签名打包后的APP变成了生产环境,此时APP会默认从本地加载 index.android.bundle文件,由于通过Android Studio打包的APK没有将index.android.bundle打包进apk,所以会因缺少index.android.bundle而无法启动。

通过官方推荐的方式签名打包APK

第一步:生成Android签名证书

如果你已经有签名证书可以绕过此步骤。 签名APK需要一个证书用于为APP签名,生成签名证书可以Android Studio以可视化的方式生成,也可以使用终端采用命令行的方式生成,需要的可以自行Google这里不再敖述。

第二步:设置gradle变量

  1. 将你的签名证书copy到 android/app目录下。
  2. 编辑~/.gradle/boke.nrnlibrary.com../android/boke.nrnlibrary.com(一个是全局boke.nrnlibrary.com,一个是项目中的boke.nrnlibrary.com,大家可以根据需要进行修改) ,加入如下代码:
MYAPP_RELEASE_STORE_FILE=your keystore filename MYAPP_RELEASE_KEY_ALIAS=your keystore alias MYAPP_RELEASE_STORE_PASSWORD=***** MYAPP_RELEASE_KEY_PASSWORD=***** 

提示:用正确的证书密码、alias以及key密码替换掉 *****。

第三步:在gradle配置文件中添加签名配置

编辑 android/app/build.gradle文件添加如下代码:

... android { ... defaultConfig { ... } signingConfigs { release { storeFile file(MYAPP_RELEASE_STORE_FILE) storePassword MYAPP_RELEASE_STORE_PASSWORD keyAlias MYAPP_RELEASE_KEY_ALIAS keyPassword MYAPP_RELEASE_KEY_PASSWORD } } buildTypes { release { ... signingConfig signingConfigs.release } } } ... 

第四步:签名打包APK

terminal进入项目下的android目录,运行如下代码: ./gradlew assembleRelease

签名打包成功.png

签名打包成功后你会在 “android/app/build/outputs/apk/”目录下看到签名成功后的app-release.apk文件。 提示:如果你需要对apk进行混淆打包 编辑android/app/build.gradle:

/** * Run Proguard to shrink the Java bytecode in release builds. */ def enableProguardInReleaseBuilds = true 

如何在gradle中不使用明文密码?

上文中直接将证书密码以明文的形式写在了boke.nrnlibrary.com文件中,虽然可以将此文件排除在版本控制之外,但也无法保证密码的安全,下面将向大家分享一种方法避免在gradle中直接使用明文密码。

通过“钥匙串访问(Keychain Access)”工具保护密码安全

下面阐述的方法只在OS X上可行。 我们可以通过将发布证书密码委托在“钥匙串访问(Keychain Access)”工具中,然后通过gradle访问“钥匙串访问”工具来获取证书密码。

具体步骤:

  1. cmd+space打开“钥匙串访问(Keychain Access)”工具。
  2. 在登录选项中新钥匙串,如图: 通过“钥匙串访问(Keychain Access)”工具保护密码安全  .png

提示: 你可以在terminal中运行如下命令检查新建的钥匙串是否成功。 security find-generic-password -s android_keystore -w

  1. 在build.gradle中访问你的秘钥串,将下列代码编辑到android/app/build.gradle中:
def getPassword(String currentUser, String keyChain) { def stdout = new ByteArrayOutputStream() def stderr = new ByteArrayOutputStream() exec { commandLine 'security', '-q', 'find-generic-password', '-a', currentUser, '-s', keyChain, '-w' standardOutput = stdout errorOutput = stderr ignoreExitValue true } //noinspection GroovyAssignabilityCheck stdout.toString().trim() } 
// Add this line def pass = getPassword("YOUR_USER_NAME","android_keystore") ... android { ... defaultConfig { ... } signingConfigs { release { storeFile file(MYAPP_RELEASE_STORE_FILE) storePassword pass // Change this keyAlias MYAPP_RELEASE_KEY_ALIAS keyPassword pass // Change this } } buildTypes { release { ... signingConfig signingConfigs.release } } } ... 

注意事项 钥匙串访问(Keychain Access)工具只是帮我们托管了,证书密码,证书明和alias还是需要我们在boke.nrnlibrary.com中设置一下的。

Fri, 08 Nov 2019 00:00:00 +0800 http://localhost:4000/2019/11/08/react-native-Release-APP-Signature-Package-APK/ http://localhost:4000/2019/11/08/react-native-Release-APP-Signature-Package-APK/ React Native Android iOS
新版React Native发布APP之打包iOS应用

React Native发布APP之打包iOS应用

了解更多,可学习《React Native视频教程》 ,或关注我的GitHub和加入: React Native学习交流群

React Native学习交流群


用React Native开发好APP之后,如何将APP发布以供用户使用呢?一款APP的发布流程无外乎:签名打包—>发布到各store这两大步骤。本文将向大家分享如何签名打包一款React Native APP。

在本文中我将为大家讲解如何打包和发布React Native iOS App。

第一步:导出js bundle包和图片资源

和打包React Native Android应用不同的是,我们无法通过命令一步进行导出React Native iOS应用。我们需要将JS部分的代码和图片资源等打包导出,然后通过XCode将其添加到iOS项目中。

导出js bundle的命令

在React Native项目的根目录下执行:

react-native bundle --platform ios --entry-file index.js --bundle-output ./bundles/main.jsbundle --assets-dest ./bundles --dev false 

通过上述命令,我们可以将JS部分的代码和图片资源等打包导出到release_ios目录下:

生成jsbundle)

其中,assets为项目中的JS部分所用到的图片资源(不包括原生模块中的图片资源),main.jsbundle是JS部分的代码。

在执行打包命令之前,我们需要先确保在我们项目的根目录有release_ios文件夹,没有的话创建一个。

第二步:将js bundle包和图片资源导入到iOS项目中

这一步我们需要用到XCode,选择assets文件夹与main.jsbundle文件将其拖拽到XCode的项目导航面板中即可。

导入jsbundle

然后,修改AppDelegate.m文件,添加如下代码:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSURL *jsCodeLocation; //jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"boke.nrnlibrary.com" fallbackResource:nil]; +jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; #endif ... return YES; } 

上述代码的作用是让React Native去使用我们刚才导入的jsbundle,这样以来我们就摆脱了对本地nodejs服务器的依赖。

提示:如果在项目中使用了CodePush热更新,那么我们需要就可以直接通过CodePush来读取本地的jsbundle,方法如下:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSURL *jsCodeLocation; #ifdef DEBUG  jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"boke.nrnlibrary.com" fallbackResource:nil]; #else  jsCodeLocation = [CodePush bundleURL]; #endif ... return YES; } 

到目前为止呢,我们已经将js bundle包和图片资源导入到iOS项目中,接下来我们就可以发布我们的iOS应用了。

第三步:发布iOS应用

发布iOS应用我们需要有一个99美元的账号用于将App上传到AppStore,或者是299美元的企业级账号用于将App发布到自己公司的服务器或第三方公司的服务器。

接下来我们就需要进行申请APPID ➜ 在Tunes Connect创建应用 ➜ 打包程序 ➜ 将应用提交到app store等几大步骤。

因为官方文档中有详细的说明,在这我就不再重复了。

如果,大家在打包发布React Native iOS应用的过程中遇到问题可以在本文的下方进行留言,我看到了后会及时回复的哦。 另外也可以关注我的新浪微博,或者关注我的Github来获取更多有关React Native开发的manbetx客户端应用

推荐学习:视频教程《React Native开发跨平台GitHub App》

Fri, 08 Nov 2019 00:00:00 +0800 http://localhost:4000/2019/11/08/React-Native-releases-packaged-iOS-apps-for-apps/ http://localhost:4000/2019/11/08/React-Native-releases-packaged-iOS-apps-for-apps/ React Native Android iOS
React Native列表之FlatList开发实用教程

为大家倾力打造的课程《新版React Native+Redux打造高质量上线App》上线了,解锁React Native开发应用新姿势,一网打尽React Native新版本热门技术 点我Get!!!

在APP开发过程中,列表可谓是页面最重要的一种展现形式了,几乎每一个APP都离不了列表,那么在这篇文章中将向大家分享在React Native中该如何实现列表,以及FlatList的原理和实用指南。

本文出自教程《新版React Native+Redux打造高质量上线App》——将带你解锁React Native开发应用新姿势,一网打尽React Native新版本热门技术。

在React Native的早期版本中列表通常使用ListView来实现,新版React Native推荐我们使用FlatList来实现列表,那么为什么推荐使用FlatList列表呢?接下来就让我从FlatList的由来说起:

  • 在大家React Native开发环境过程中遇到无法解决的问题可以在课程问答区进行提问,课程老师会对你进行辅导和帮助;

FlatList的由来?

在React Native0.43版本中引入了FlatList,SectionListVirtualizedList,其中VirtualizedList是FlatList 与 SectionList 的底层实现。

FlatList

可能有人要问了,既然有了ListView,那为什么还要设计一个FlatList出来呢?

经常使用ListView的同学都知道: ListView的性能是比较差的,尤其是当有大量的数据需要展示的时候,ListView对内存的占用是相当可观的、丢帧卡顿那是常有的事。

为什么ListView对于大数据量的情况下性能会很差呢?

深入ListView的原理你会发现,ListView对列表中的Item是全量渲染的,并且没有复用机制,这就难以避免当让ListView渲染大数据量的时候会发生以下两个问题:

  • 第一次打开与切换Tab时会出现卡顿或白屏的情况:这是因为ListView对所有的Item都是全量渲染的,比如:ListView中有100条Item,只有等这100条Item都渲染完成,ListView中的内容才会展示,这就难以避免卡顿白屏的问题;
  • 滑动列表时会出现卡顿与不跟手:当因ListView中展示了大量数据的时候,滑动列表你会发现没有少量数据的时候的跟手与流畅,这是因为ListView为了渲染大量数据需要大量的内存和计算,这对手机资源是一个很大的消耗,尤其是在一些低端机上甚至会出现OOM;

ListView的这种性能问题一直困扰着React Native开发者。有能力的公司、团队都纷纷对ListView做优化,封装自己的列表组件,然对性能的提升并不大,所以现在急需一个高性能的列表组件,于是便有了设计FlatList的构想;

那FlatList都有哪些特性能呢?

FlatList是基于VirtualizedList的,要说FlatList的特性还要从VirtualizedList说起:

VirtualizedList

VirtualizedList 是FlatList 与 SectionList 的底层实现。Vritualization 通过维护一个有限的渲染窗口(其中包含可见的元素),并将渲染窗口之外的元素全部用合适的定长空白空间代替的方式,极大的改善了内存消耗以及在有大量数据情况下的使用性能。这个渲染窗口能响应滚动行为。当一个元素离可视区太远时,它就有一个较低优先级;否则就获得一个较高的优先级。渲染窗口通过这种方式逐步渲染其中的元素(在进行了任何交互之后),以尽量减少出现空白区域的可能性。

render-window

特性

VirtualizedList有以下特性:

  • 支持滚动加载(具体可以借助onEndReached的回调,做数据动态加载);
  • 支持下拉刷新(借助onRefresh / refreshing属性实现);
  • 支持可配置的可见性(VPV)回调(借助onViewableItemsChanged / viewabilityConfig实现)
  • 滑动方向增加对Horizontal(水平)方向的支持;
  • 更加智能的Item以及section separators支持;
  • 支持Multi-column(借助numColumns属性实现);
  • 添加scrollToEnd, scrollToIndex, 和 scrollToItem方法的支持;
  • 对 Flow更加友好;

性能

VirtualizedList除了简化API之外,新的列表组件还具有显着的性能增强,主要的是对于任意数量的行(Item)的增加不会带着内存的增加。 它主要是通过虚拟元素也就是在渲染窗口之外的元素将会被从组件结构上卸载以达到回收内存目的。这样会带来一个问题,即内部组件状态不会被保留,因此请确保你跟踪组件本身以外的任何重要状态,例如, 在Relay或Redux或Flux store。

限制渲染窗口还可以减少React和本地平台的工作量,例如View遍历。 即使你渲染了最后的一百万个元素,用这些新的列表也不需要渲染所有的元素来完成遍历。比如:你可以使用scrollToIndex跳至中间位置,而无需过多渲染。

另外VirtualizedList还对调度进行了一些改进,这对应用程序的响应很有帮助。 在任何手势或动画或其他交互完成后,呈现在窗口边缘的Item不会被频繁的渲染,并且渲染优先级比较低。

高级使用

  • 与ListView不同的是,渲染窗口中的所有Item在任何props改变时都会重新渲染,这在通常情况下是比较好的,因为渲染窗口的Item数量是不变的,但是如果Item比较复杂的话,你因该应确保遵循React最佳性能实践,并在适当情况下使用React.PureComponent和/或shouldComponentUpdate来限制你的组件以及子组件的渲染次数,减少不必要的渲染以及递归渲染等。
  • 如果你不需要渲染就知道内容的高度的话,可以通过getItemLayout 属性来改善用户体验,这使得通过例如滚动到具体Item更平滑。比如使用 scrollToIndex滚动到指定的Item。
  • 如果你有另一种数据类型比如immutable的list, 那么使用VirtualizedList是个不错的选择. 它提供一个getItem属性来让你为任何给定的index返回item数据。

注意事项

  • 当某行滑出渲染区域之外后,其内部状态将不会保留。请确保你在行组件以外的地方保留了数据。
  • 本组件继承自PureComponent而非通常的Component,这意味着如果其props在浅比较中是相等的,则不会重新渲染。所以请先检查你的renderItem函数所依赖的props数据(包括data属性以及可能用到的父组件的state),如果是一个引用类型(Object或者数组都是引用类型),则需要先修改其引用地址(比如先复制到一个新的Object或者数组中),然后再修改其值,否则界面很可能不会刷新。(译注:这一段不了解的朋友建议先学习下js中的基本类型和引用类型。)
  • 为了优化内存占用同时保持滑动的流畅,列表内容会在屏幕外异步绘制。这意味着如果用户滑动的速度超过渲染的速度,则会先看到空白的内容。这是为了优化不得不作出的妥协,而我们也在设法持续改进。
  • 默认情况下每行都需要提供一个不重复的key属性。你也可以提供一个keyExtractor函数来生成key。
  • 另外如果你有一些特殊的需求或用例,你也通过调整一些参数来实现。 例如,你可以使用windowSize来平衡内存使用情况与用户体验,使用maxToRenderPerBatch调整填充率与响应度,使用onEndReachedThreshold以控制何时发生滚动加载等等。

React Native列表的未来规划

  • 完成现有的迁移(最终弃用ListView)。
  • 实现一些看到或听到的好的功能。
  • 粘滞头部支持。
  • 更多的性能优化。
  • 支持具有状态的功能Item组件。

了解完VirtualizedList之后,接下来就让我们来认识一下FlatList的一些特性吧:

FlatList的特性

高性能的且使用简单的列表组件,支持一些特性:

  • 完全跨平台;
  • 支持水平布局模式;
  • 行组件显示或隐藏时可配置回调事件;
  • 支持单独的头部组件;
  • 支持单独的尾部组件;
  • 支持自定义行间分隔线;
  • 支持下拉刷新;
  • 支持上拉加载;
  • 支持跳转到指定行(ScrollToIndex);

如果需要分组/类/区(section)的功能,请使用

简单使用

<FlatList data={[{key: 'a'}, {key: 'b'}]} renderItem={({item}) => <Text>{item.key}</Text>} /> 

查看全部完整代码

注意事项

FlatList组件实质是基于 组件的封装,因此除了 需要注意的事项之外还有下面这些需要注意的事项:

  • removeClippedSubviews属性目前是不必要的,而且可能会引起问题。如果你在某些场景碰到内容不渲染的情况(比如使用LayoutAnimation时),尝试设置removeClippedSubviews={false}。我们可能会在将来的版本中修改此属性的默认值。

属性

data: ?Array

为了简化起见,data属性目前只支持普通数组。如果需要使用其他特殊数据结构,例如immutable数组,请直接使用更底层的VirtualizedList组件。

renderItem: (info: {item: ItemT, index: number}) => ?React.Element

根据行数据data渲染每一行的组件。典型用法:

_renderItem = ({item}) => ( <TouchableOpacity onPress={() => this._onPress(item)}> <Text>{item.title}}</Text> 
          
           TouchableOpacity> ); ... <FlatList data={[{title: 'Title Text', key: 'item1'}]} renderItem={this._renderItem} /> 

查看全部完整代码

除data外还有第二个参数index可供使用。

onRefresh?: ?() => void

如果设置了此选项,则会在列表头部添加一个标准的RefreshControl控件,以便实现“下拉刷新”的功能。同时你需要正确设置refreshing属性。

refreshing?: ?boolean

在等待加载新数据时将此属性设为true,列表就会显示出一个正在加载的符号。

horizontal?: ?boolean

设置为true则变为水平布局模式。

initialNumToRender: number

指定一开始渲染的元素数量,最好刚刚够填满一个屏幕,这样保证了用最短的时间给用户呈现可见的内容。注意这第一批次渲染的元素不会在滑动过程中被卸载,这样是为了保证用户执行返回顶部的操作时,不需要重新渲染首批元素。

keyExtractor: (item: ItemT, index: number) => string

此函数用于为给定的item生成一个不重复的key。Key的作用是使React能够区分同类元素的不同个体,以便在刷新时能够确定其变化的位置,减少重新渲染的开销。若不指定此函数,则默认抽取item.key作为key值。若item.key也不存在,则使用数组下标。

ItemSeparatorComponent?: ?ReactClass

行与行之间的分隔线组件。不会出现在第一行之前和最后一行之后。

ListFooterComponent?: ?ReactClass

通过它设置尾部组件

ListHeaderComponent?: ?ReactClass

通过它设置头部组件

columnWrapperStyle?: StyleObj

如果设置了多列布局(即将numColumns值设为大于1的整数),则可以额外指定此样式作用在每行容器上。

extraData?: any

如果有除data以外的数据用在列表中(不论是用在renderItem还是Header或者Footer中),请在此属性中指定。同时此数据在修改时也需要先修改其引用地址(比如先复制到一个新的Object或者数组中),然后再修改其值,否则界面很可能不会刷新。

getItem?:

获取指定的Item;

getItemCount?:

用于获取总共有多少Item;

getItemLayout?: (data: ?Array , index: number) => {length: number, offset: number, index: number}

getItemLayout是一个可选的优化,用于避免动态测量内容尺寸的开销,不过前提是你可以提前知道内容的高度。如果你的行高是固定的,getItemLayout用起来就既高效又简单,类似下面这样:

getItemLayout={(data, index) => ( {length: 行高, offset: 行高 * index, index} )} 

注意如果你指定了SeparatorComponent,请把分隔线的尺寸也考虑到offset的计算之中。

legacyImplementation?: ?boolean

设置为true则使用旧的ListView的实现。

numColumns: number

多列布局只能在非水平模式下使用,即必须是horizontal={false}。此时组件内元素会从左到右从上到下按Z字形排列,类似启用了flexWrap的布局。组件内元素必须是等高的——暂时还无法支持瀑布流布局。

onEndReached?: ?(info: {distanceFromEnd: number}) => void

当列表被滚动到距离内容最底部不足onEndReachedThreshold的距离时调用。

onEndReachedThreshold?: ?number

决定当距离内容最底部还有多远时触发onEndReached回调。注意此参数是一个比值而非像素单位。比如,0.5表示距离内容最底部的距离为当前列表可见长度的一半时触发。

onViewableItemsChanged?: ?(info: {viewableItems: Array , changed: Array }) => void

在可见行元素变化时调用。可见范围和变化频率等参数的配置请设置viewabilityconfig属性

viewabilityConfig?: ViewabilityConfig

可参考ViewabilityHelper的源码来了解具体的配置。

方法

scrollToEnd(params?: object)

滚动到底部。如果不设置getItemLayout属性的话,可能会比较卡。

scrollToIndex(params: object)

滚动到指定位置,如果不设置getItemLayout属性的话,可能会比较卡。

scrollToItem(params: object)

需要线性扫描数据 - 如果可能,请使用scrollToIndex。如果不设置getItemLayout属性的话只能滚动到当前渲染窗口的某个位置。

scrollToOffset(params: object)

滚动到列表中的特定内容像素偏移量。

recordInteraction()

复杂使用

下面是一个较复杂的例子,其中演示了如何利用PureComponent来进一步优化性能和减少bug产生的可能:

  • 对于MyListItem组件来说,其onPressItem属性使用箭头函数而非bind的方式进行绑定,使其不会在每次列表重新render时生成一个新的函数,从而保证了props的不变性(当然前提是 id、selected和title也没变),不会触发自身无谓的重新render。换句话说,如果你是用bind来绑定onPressItem,每次都会生成一个新的函数,导致props在===比较时返回false,从而触发自身的一次不必要的重新render。
  • 给FlatList指定extraData={this.state}属性,是为了保证state.selected变化时,能够正确触发FlatList的更新。如果不指定此属性,则FlatList不会触发更新,因为它是一个PureComponent,其props在===比较中没有变化则不会触发更新。
  • keyExtractor属性指定使用id作为列表每一项的key。
class MyListItem extends React.PureComponent { _onPress = () => { this.props.onPressItem(this.props.id); }; render() { retuReact Native ( <SomeOtherWidget {...this.props} onPress={this._onPress} />  ) } } class MyList extends React.PureComponent { state = {selected: (new Map(): Map<string, boolean>)}; _keyExtractor = (item, index) => item.id; _onPressItem = (id: string) => { // updater functions are preferred for transactional updates this.setState((state) => { // copy the map rather than modifying state. const selected = new Map(state.selected); selected.set(id, !selected.get(id)); // toggle retuReact Native {selected}; }); }; _renderItem = ({item}) => ( <MyListItem id={item.id} onPressItem={this._onPressItem} selected={!!this.state.selected.get(item.id)} title={item.title} />  ); render() { retuReact Native ( <FlatList data={this.props.data} extraData={this.state} keyExtractor={this._keyExtractor} renderItem={this._renderItem} />  ); } } 

查看全部完整代码

实例:上拉加载更多,下拉刷新,自定义刷新组件

FlatListDemo

const CITY_NAMES = ['北京', '上海', '广州', '深圳', '杭州', '苏州', '成都', '武汉', '郑州', '洛阳', '厦门', '青岛', '拉萨']; export default class FlatListDemo extends Component<Props> { constructor(props) { super(props); this.state = { dataArray: CITY_NAMES, isLoading: false } } _renderItem(data) { retuReact Native <View style={styles.item}> <Text style={styles.text}>{data.item}</Text>  </View>  } loadData(refresh) { if (refresh) { this.setState({ isLoading: true }); } setTimeout(() => { let dataArray = []; if (refresh) { for (let i = this.state.dataArray.length - 1; i >= 0; i--) { dataArray.push(this.state.dataArray[i]) } } else { dataArray = this.state.dataArray.concat(CITY_NAMES); } this.setState({ dataArray: dataArray, isLoading: false }); }, 2000); } genIndicator() { retuReact Native <View style={styles.indicatorContainer}> <ActivityIndicator style={styles.indicator} size='large' animating={true} />  <Text>正在加载更多</Text>  </View>  } render() { retuReact Native ( <View style={styles.container}> <FlatList data={this.state.dataArray} renderItem={(data => this._renderItem(data))} // refreshing={this.state.isLoading} // onRefresh={() => { // this.loadData(); // }} refreshControl={ <RefreshControl title='Loading...' colors={['red']} refreshing={this.state.isLoading} onRefresh={() => this.loadData(true)} tintColor={'orange'} />  } ListFooterComponent={() => this.genIndicator()} onEndReached={() => { this.loadData() }} />  </View>  ); } } const styles = StyleSheet.create({ container: { flex: 1, }, item: { height: 200, backgroundColor: '#169', marginLeft: 15, marginRight: 15, marginBottom: 15, alignItems: 'center', justifyContent: 'center' }, text: { color: 'white', fontSize: 20 }, indicatorContainer: { alignItems: "center" }, indicator: { color: 'red', margin: 10 } }); 

查看全部完整代码

  • 本节学习过程中遇到无法解决的问题可以在课程问答区进行提问,课程老师会对你进行辅导和帮助;
  • 欢迎加入课程官方群:579379877 和讲师以及其他师兄弟们一起学习交流;

参考资料

Sun, 19 May 2019 00:00:00 +0800 http://localhost:4000/2019/05/19/flatlist/ http://localhost:4000/2019/05/19/flatlist/ React Native Android iOS 教程 开发指导
Flutter异步编程Future与FutureBuilder的实用技巧

Flutter异步编程Future与FutureBuilder的实用技巧

为大家倾力打造的课程《Flutter从入门到进阶-实战携程网App》上线了,解锁Flutter开发新姿势,一网打尽Flutter核心技术 点我Get!!!

在这篇文章中,将向大家分享异步编程FutureFutureBuilder的一些实用知识和技巧,首先会带着大家认识什么是Future?Future的常见用法?、以及什么是FutureBuilder?,以及FutureBuilder常见的用法?等。

  • 在大家Flutter开发环境过程中遇到无法解决的问题可以在课程问答区进行提问,课程老师会对你进行辅导和帮助;

目录

  • 什么是Future?
  • Future的常见用法?
    • 获取Future的结果?
    • 捕获Future的异常?
    • 结合async,await?
    • future.whenComplete?
    • future.timeout?
  • 什么是FutureBuilder?
  • FutureBuilder常见的用法?

什么是Future?

Future表示在接下来的某个时间的值或错误,借助Future我们可以在Flutter实现异步操作。

它类似于ES6中的Promise,提供thencatchError的链式调用;

Futuredart:async包中的一个类,使用它时需要导入dart:async包,Future有两种状态:

  • pending - 执行中;
  • completed - 执行结束,分两种情况要么成功要么失败;

Future的常见用法?

使用future.then获取future的值与捕获future的异常

import 'dart:async'; Future<String> testFuture() { // throw new Error(); return Future.value('success'); // return Future.error('error'); } main() { testFuture().then((s) { print(s); }, onError: (e) { print('onError:'); print(e); }).catchError((e) { print('catchError:'); print(e); }); } 

如果catchErroronError同时存在,则会只调用onError

Future的then的原型:

Future<R> then<R>(FutureOr<R> onValue(T value), {Function onError}); 

第一个参数会成功的结果回调,第二个参数onError可选表示执行出现异常。

练一练

结合async await

Future是异步的,如果我们要将异步转同步,那么可以借助async await来完成。

import 'dart:async'; test() async { int result = await Future.delayed(Duration(milliseconds: 2000), () { return Future.value(123); }); print('t3:' + DateTime.now().toString()); print(result); } main() { print('t1:' + DateTime.now().toString()); test(); print('t2:' + DateTime.now().toString()); } 

练一练

future.whenComplete

有时候我们需要在Future结束的时候做些事情,我们知道then().catchError()的模式类似于try-catchtry-catch有个finally代码块,而future.whenComplete就是Future的finally。

import 'dart:async'; import 'dart:math'; void main() { var random = Random(); Future.delayed(Duration(seconds: 3), () { if (random.nextBool()) { return 100; } else { throw 'boom!'; } }).then(print).catchError(print).whenComplete(() { print('done!'); }); } 

future.timeout

完成一个异步操作可能需要很长的时间,比如:网络请求,但有时我们需要为异步操作设置一个超时时间,那么,如何为Future设置超时时间呢?

import 'dart:async'; void main() { new Future.delayed(new Duration(seconds: 3), () { return 1; }).timeout(new Duration(seconds: 2)).then(print).catchError(print); } 

运行上述代码会看到:TimeoutException after 0:00:02.000000: Future not completed

manbetx客户端 ios-练一练

什么是FutureBuilder?

FutureBuilder是一个将异步操作和异步UI更新结合在一起的类,通过它我们可以将网络请求,数据库读取等的结果更新的页面上。

FutureBuilder的构造方法

FutureBuilder({Key key, Future<T> future, T initialData, @required AsyncWidgetBuilder<T> builder }) 
  • future: Future对象表示此构建器当前连接的异步计算;
  • initialData: 表示一个非空的Future完成前的初始化数据;
  • builder: AsyncWidgetBuilder类型的回到函数,是一个基于异步交互构建widget的函数;

这个builder函数接受两个参数BuildContext contextAsyncSnapshot snapshot ,它返回一个widget。AsyncSnapshot包含异步计算的信息,它具有以下属性:

connectionState - 枚举ConnectionState的值,表示与异步计算的连接状态,ConnectionState有四个值:none,waiting,active和done; data - 异步计算接收的最新数据; error - 异步计算接收的最新错误对象;

AsyncSnapshot还具有hasDatahasError属性,以分别检查它是否包含非空数据值或错误值。

现在我们可以看到使用FutureBuilder的基本模式。 在创建新的FutureBuilder对象时,我们将Future对象作为要处理的异步计算传递。 在构建器函数中,我们检查connectionState的值,并使用AsyncSnapshot中的数据或错误返回不同的窗口小部件。

https://boke.nrnlibrary.com/async-in-flutter-futurebuilder/

FutureBuilder的使用?

... class _MyAppState extends State<MyApp> { String showResult = ''; Future<CommonModel> fetchPost() async { final response = await http .get('https://boke.nrnlibrary.com/io/flutter_app/json/test_common_model.json'); Utf8Decoder utf8decoder = Utf8Decoder(); //fix 中文乱码 var result = json.decode(utf8decoder.convert(response.bodyBytes)); return CommonModel.fromJson(result); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: Text('Future与FutureBuilder实用技巧'), ), body: FutureBuilder<CommonModel>( future: fetchPost(), builder: (BuildContext context, AsyncSnapshot<CommonModel> snapshot) { switch (snapshot.connectionState) { case ConnectionState.none: return new Text('Input a URL to start'); case ConnectionState.waiting: return new Center(child: new CircularProgressIndicator()); case ConnectionState.active: return new Text(''); case ConnectionState.done: if (snapshot.hasError) { return new Text( '${snapshot.error}', style: TextStyle(color: Colors.red), ); } else { return new Column(children: <Widget>[ Text('icon:${snapshot.data.icon}'), Text('statusBarColor:${snapshot.data.statusBarColor}'), Text('title:${snapshot.data.title}'), Text('url:${snapshot.data.url}') ]); } } }), ), ); } } class CommonModel { final String icon; final String title; final String url; final String statusBarColor; final bool hideAppBar; CommonModel( {this.icon, this.title, this.url, this.statusBarColor, this.hideAppBar}); factory CommonModel.fromJson(Map<String, dynamic> json) { return CommonModel( icon: json['icon'], title: json['title'], url: json['url'], statusBarColor: json['statusBarColor'], hideAppBar: json['hideAppBar'], ); } } 

查看全部完整代码

  • 本节学习过程中遇到无法解决的问题可以在课程问答区进行提问,课程老师会对你进行辅导和帮助;
  • 欢迎加入课程官方群:795410523 和讲师以及其他师兄弟们一起学习交流;

参考资料

Mon, 06 May 2019 00:00:00 +0800 http://localhost:4000/2019/05/06/flutter-future-futurebuilder/ http://localhost:4000/2019/05/06/flutter-future-futurebuilder/ Flutter Android iOS
Flutter Http网络操作实用教程

 Flutter Http网络操作实用教程

为大家倾力打造的课程《Flutter从入门到进阶-实战携程网App》上线了,解锁Flutter开发新姿势,一网打尽Flutter核心技术 点我Get!!!

在这篇文章中,将向大家分享Flutter网络操作的一些实用知识和技巧,包括如何用Http库做get请求?如何用Http库做post请求?如何将Response转换成Dart object?,以及如何将请求结果展示在界面上?等。

  • 在大家Flutter开发环境过程中遇到无法解决的问题可以在课程问答区进行提问,课程老师会对你进行辅导和帮助;

目录

  • 如何用Http库做get请求?
  • 如何用Http库做post请求?
  • 如何将Response转换成Dart object
  • 如何将请求结果展示在界面上?

网络请求是开发APP必不可少的一部分,比如获取用户订单数据,获取商品列表,提交表单等等都离不了网络请求,那么在Flutter中如何进行网络请求呢?

Flutter官方推荐我们在Flutter中用Http进行网络请求。

什么是Http?

Http 是Flutter社区开发的一个可组合的、跨平台的用于Flutter的网络请求插件。

如何用http库做get请求?

dependencies: http: <latest_version> 
Future<http.Response> fetchPost() { return http.get('https://boke.nrnlibrary.com/posts/1'); } 

http.get()返回一个包含http.ResponseFuture

  • Future:是与异步操作一起工作的核心Dart类。它用于表示未来某个时间可能会出现的可用值或错误;
  • http.Response:类包含一个成功的HTTP请求接收到的数据;

在上一节讲解了Future的用法,以及如何从Future中获取服务端具体的返回数据,如果你对Flutter中的Future还不熟悉的话可以去学习下。

如何用http库做post请求?

dependencies: http: <latest_version> 
Future<http.Response> fetchPost() { return http.post('https://boke.nrnlibrary.com/posts/1'); } 

http.post()返回一个包含http.ResponseFuture

  • Future:是与异步操作一起工作的核心Dart类。它用于表示未来某个时间可能会出现的可用值或错误;
  • http.Response:类包含一个成功的HTTP请求接收到的数据;

在上一节讲解了Future的用法,以及如何从Future中获取服务端具体的返回数据,如果你对Flutter中的Future还不熟悉的话可以去学习下。

如何将Response转换成Dart object?

虽然发出网络请求很简单,但如果要使用原始的Future 并不简单。为了让我们可以开开心心的写代码,我们可以将http.Response转换成我们自己的Dart对象。

创建一个CommonModel类

首先,我们需要创建一个CommonModel类,它包含我们网络请求的数据。它还将包括一个工厂构造函数,它允许我们可以通过json创建一个CommonModel对象。

class CommonModel { final String icon; final String title; final String url; final String statusBarColor; final bool hideAppBar; CommonModel({this.icon, this.title, this.url, this.statusBarColor, this.hideAppBar}); factory CommonModel.fromJson(Map<String, dynamic> json) { return CommonModel( icon: json['icon'], title: json['title'], url: json['url'], statusBarColor: json['statusBarColor'], hideAppBar: json['hideAppBar'], ); } } 

http.Response转换成一个CommonModel对象

现在,我们将更新fetchPost函数以返回一个Future 。为此,我们需要:

  1. 使用dart:convert package将响应内容转化为一个json Map;
  2. 使用fromJson工厂函数,将json Map 转化为一个CommonModel对象;
Future<CommonModel> fetchPost() async { final response = await http.get('https://boke.nrnlibrary.com/io/flutter_app/json/test_common_model.json'); final result = json.decode(response.body); return new CommonModel.fromJson(result); } 

如何将请求结果展示在界面上?

http_get_test

... class _MyAppState extends State<MyApp> { String showResult = ''; Future<CommonModel> fetchPost() async { final response = await http .get('https://boke.nrnlibrary.com/io/flutter_app/json/test_common_model.json'); final result = json.decode(response.body); return CommonModel.fromJson(result); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: Text('Http'), ), body: Column( children: <Widget>[ InkWell( onTap: () { fetchPost().then((CommonModel value) { setState(() { showResult = '请求结果:\nhideAppBar:${value.hideAppBar}\nicon:${value.icon}'; }); }); }, child: Text( '点我', style: TextStyle(fontSize: 26), ), ), Text(showResult) ], ), ), ); } } class CommonModel { final String icon; final String title; final String url; final String statusBarColor; final bool hideAppBar; CommonModel( {this.icon, this.title, this.url, this.statusBarColor, this.hideAppBar}); factory CommonModel.fromJson(Map<String, dynamic> json) { return CommonModel( icon: json['icon'], title: json['title'], url: json['url'], statusBarColor: json['statusBarColor'], hideAppBar: json['hideAppBar'], ); } } 

查看全部完整代码

在上述代码中我们通过fetchPost().then获取Fluter的返回结果,其实Future可以理解为ES5中的Promise,在接来下的课程中会有对Future的详细讲解。

  • 本节学习过程中遇到无法解决的问题可以在课程问答区进行提问,课程老师会对你进行辅导和帮助;
  • 欢迎加入课程官方群:795410523 和讲师以及其他师兄弟们一起学习交流;

manbetx客户端应用下载-参考资料

Sat, 27 Apr 2019 00:00:00 +0800 http://localhost:4000/2019/04/27/flutter-http/ http://localhost:4000/2019/04/27/flutter-http/ Flutter Android iOS
Flutter 本地存储实用教程

 Flutter Http网络操作实用教程

为大家倾力打造的课程《Flutter从入门到进阶-实战携程网App》上线了,解锁Flutter开发新姿势,一网打尽Flutter核心技术 点我Get!!!

在这篇文章中,我将向大家分享Flutter 本地存储的一些实用知识和技巧。首先会带你一起认识什么是shared_preferences如何使用shared_preferences、以及shared_preferences有那些常用的API?,最后会通过一个计数器的例子来巩固Flutter 中本地存储的知识点等。

  • 在你学习Flutter 本地存储过程中遇到无法解决的问题或疑问,都可以在课程问答区进行提问,课程老师会对你进行辅导和帮助;

目录

  • shared_preferences 是什么?
  • 如何使用shared_preferences
  • shared_preferences有那些常用的API?
  • 基于shared_preferences实现计数器Demo

数据存储是开发APP必不可少的一部分,比如页面缓存,从网络上获取数据的本地持久化等,那么在Flutter中如何进行数据存储呢?

Flutter官方推荐我们用shared_preferences进行数据存储,它类似于React Native中的AsyncStorage

什么是shared_preferences?

shared_preferences是Flutter社区开发的一个本地数据存取插件,它有以下特性:

如何使用shared_preferences?

首先在pubspec.yaml文件中添加:

dependencies: shared_preferences: ^0.5.1+ 

记得运行安装哦:flutter packages get

在需要用到的文件中导入:

import 'package:shared_preferences/shared_preferences.dart'; 

存储数据

final prefs = await SharedPreferences.getInstance(); // set value prefs.setInt('counter', counter); 

查看全部完整代码

读取数据

final prefs = await SharedPreferences.getInstance(); // Try reading data from the counter key. If it does not exist, return 0. final counter = prefs.getInt('counter') ?? 0;} 

查看全部完整代码

删除数据

final prefs = await SharedPreferences.getInstance(); prefs.remove('counter'); 

查看全部完整代码

shared_preferences有那些常用的API?

存储相关

shared_preferences

如上图shared_preferences支持int, double, bool, stringstringList类型的数据存储;

读取相关

shared_preferences

上图shared_preferences中所提供的读取相关的API;

基于shared_preferences实现计数器Demo

shared_preferences

... class _CounterWidget extends StatefulWidget { @override _CounterState createState() => _CounterState(); } class _CounterState extends State<_CounterWidget> { String countString = ''; String localCount = ''; @override Widget build(BuildContext context) { return Center( child: Column( children: <Widget>[ RaisedButton( onPressed: _incrementCounter, child: Text('Increment Counter')), RaisedButton(onPressed: _getCounter, child: Text('Get Counter')), Text( countString, style: TextStyle(fontSize: 20), ), Text( 'result:' + localCount, style: TextStyle(fontSize: 20), ), ], ), ); } _incrementCounter() async { SharedPreferences prefs = await SharedPreferences.getInstance(); setState(() { countString = countString + " 1"; }); int counter = (prefs.getInt('counter') ?? 0) + 1; await prefs.setInt('counter', counter); } _getCounter() async { SharedPreferences prefs = await SharedPreferences.getInstance(); setState(() { localCount = prefs.getInt('counter').toString(); }); } } 

查看全部完整代码

以上便是Flutter 本地存储的一些实用知识和技巧,你Get到了吗!

  • 本文学习过程中遇到无法解决的问题可以在课程问答区进行提问,课程老师会对你进行辅导和帮助;
  • 欢迎加入课程官方群:795410523 和讲师以及其他师兄弟们一起学习交流;

参考资料

Wed, 24 Apr 2019 00:00:00 +0800 http://localhost:4000/2019/04/24/flutter-shared_preferences/ http://localhost:4000/2019/04/24/flutter-shared_preferences/ Flutter Android iOS shared_preferences
Flutter Hero动画开发实用教程

Flutter 动画开发实用教程

为大家倾力打造的课程《Flutter从入门到进阶-实战携程网App》上线了,解锁Flutter开发新姿势,一网打尽Flutter核心技术 点我Get!!!

在这篇文章中,将向大家分享Flutter动画中的重要一员Hero动画,以及一些Hero动画的开发技巧和经验

  • 在大家Flutter开发环境过程中遇到无法解决的问题可以在课程问答区进行提问,课程老师会对你进行辅导和帮助;

精心设计的动画会让用户界面感觉更直观、流畅,能改善用户体验。 Flutter的动画支持可以轻松实现各种动画类型。许多widget,特别是Material Design widgets, 都带有在其设计规范中定义的标准动画效果,但也可以自定义这些效果,在开始学习之前呢,我们先来快速过一下本篇文章的目录:

目录

  • 什么是Hero动画?
  • 如何实现标准Hero动画?
  • Hero的函数原型的函数原型是什么?
  • 如何实现径向Hero动画?

什么是Hero动画?

在 Flutter中可以用 Hero widget创建这个动画。当 Hero 通过动画从源页面飞到目标页面时,目标页面逐渐淡入视野。通常, Hero 是用户界面的一小部分,如图片,它通常在两个页面都有。从用户的角度来看, Hero 在页面之间“飞翔”。接下来我们一起来学习如何创建manbetx客户端应用下载-Hero动画:

实现标准Hero动画

Standard-Hero-Animation

... class PhotoHero extends StatelessWidget { const PhotoHero({ Key key, this.photo, this.onTap, this.width }) : super(key: key); final String photo; final VoidCallback onTap; final double width; Widget build(BuildContext context) { return SizedBox( width: width, child: Hero( tag: photo, child: Material( color: Colors.transparent, child: InkWell( onTap: onTap, child: Image.network( photo, fit: BoxFit.contain, ), ), ), ), ); } } class HeroAnimation extends StatelessWidget { Widget build(BuildContext context) { timeDilation = 10.0; // 1.0 means normal animation speed. return Scaffold( appBar: AppBar( title: const Text('Basic Hero Animation'), ), body: Center( child: PhotoHero( photo: 'https://boke.nrnlibrary.com/flutter/website/master/examples/_animation/Hero_animation/images/flippers-alpha.png', width: 300.0, onTap: () { Navigator.of(context).push(MaterialPageRoute<void>( builder: (BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Flippers Page'), ), body: Container( // Set background to blue to emphasize that it's a new route. color: Colors.lightBlueAccent, padding: const EdgeInsets.all(16.0), alignment: Alignment.topLeft, child: PhotoHero( photo: 'https://boke.nrnlibrary.com/flutter/website/master/examples/_animation/Hero_animation/images/flippers-alpha.png', width: 100.0, onTap: () { Navigator.of(context).pop(); }, ), ), ); } )); }, ), ), ); } } ... 

Hero的函数原型

 const Hero({ Key key, @required this.tag, this.createRectTween, this.flightShuttleBuilder, this.placeholderBuilder, this.transitionOnUserGestures = false, @required this.child, }) : assert(tag != null), assert(transitionOnUserGestures != null), assert(child != null), super(key: key); 
  • tag:[必须]用于关联两个Hero动画的标识;
  • createRectTween:[可选]定义目标Hero的边界,在从起始位置到目的位置的“飞行”过程中该如何变化;
  • child:[必须]定义动画所呈现的widget;

实现径向Hero动画

Radial-Hero-Animation

... class RadialExpansionDemo extends StatelessWidget { static const double kMinRadius = 32.0; static const double kMaxRadius = 128.0; static const opacityCurve = const Interval(0.0, 0.75, curve: Curves.fastOutSlowIn); static RectTween _createRectTween(Rect begin, Rect end) { return MaterialRectCenterArcTween(begin: begin, end: end); } static Widget _buildPage(BuildContext context, String imageName, String description) { return Container( color: Theme.of(context).canvasColor, child: Center( child: Card( elevation: 8.0, child: Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: kMaxRadius * 2.0, height: kMaxRadius * 2.0, child: Hero( createRectTween: _createRectTween, tag: imageName, child: RadialExpansion( maxRadius: kMaxRadius, child: Photo( photo: imageName, onTap: () { Navigator.of(context).pop(); }, ), ), ), ), Text( description, style: TextStyle(fontWeight: FontWeight.bold), textScaleFactor: 3.0, ), const SizedBox(height: 16.0), ], ), ), ), ); } Widget _buildHero(BuildContext context, String imageName, String description) { return Container( width: kMinRadius * 2.0, height: kMinRadius * 2.0, child: Hero( createRectTween: _createRectTween, tag: imageName, child: RadialExpansion( maxRadius: kMaxRadius, child: Photo( photo: imageName, onTap: () { Navigator.of(context).push( PageRouteBuilder<void>( pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { return AnimatedBuilder( animation: animation, builder: (BuildContext context, Widget child) { return Opacity( opacity: opacityCurve.transform(animation.value), child: _buildPage(context, imageName, description), ); } ); }, ), ); }, ), ), ), ); } @override Widget build(BuildContext context) { timeDilation = 5.0; // 1.0 is normal animation speed. return Scaffold( appBar: AppBar( title: const Text('Radial Transition Demo'), ), body: Container( padding: const EdgeInsets.all(32.0), alignment: FractionalOffset.bottomLeft, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _buildHero(context, 'https://boke.nrnlibrary.com/flutter/website/master/examples/_animation/radial_Hero_animation/images/chair-alpha.png', 'Chair'), _buildHero(context, 'https://boke.nrnlibrary.com/flutter/website/master/examples/_animation/radial_Hero_animation/images/binoculars-alpha.png', 'Binoculars'), _buildHero(context, 'https://boke.nrnlibrary.com/flutter/website/master/examples/_animation/radial_Hero_animation/images/beachball-alpha.png', 'Beach ball'), ], ), ), ); } } ... 

点我查看全部完整代码

  • 本节学习过程中遇到无法解决的问题可以在课程问答区进行提问,课程老师会对你进行辅导和帮助;
  • 欢迎加入课程官方群:795410523 和讲师以及其他师兄弟们一起学习交流;

参考资料

Sun, 21 Apr 2019 00:00:00 +0800 http://localhost:4000/2019/04/21/flutter-animation-tutorial-hero/ http://localhost:4000/2019/04/21/flutter-animation-tutorial-hero/ Flutter Android iOS
带你轻松掌握Flutter 动画开发核心技能

带你轻松掌握Flutter 动画开发核心技能(上)

为大家倾力打造的课程《Flutter从入门到进阶-实战携程网App》上线了,解锁Flutter开发新姿势,一网打尽Flutter核心技术 点我Get!!!

在这篇文章中,将向大家分享Flutter动画开发的一些核心技能,以及一些技巧和经验

  • 在大家Flutter开发环境过程中遇到无法解决的问题可以在课程问答区进行提问,课程老师会对你进行辅导和帮助;

精心设计的动画会让用户界面感觉更直观、流畅,能改善用户体验。 Flutter的动画支持可以轻松实现各种动画类型。许多widget,特别是Material Design widgets, 都带有在其设计规范中定义的标准动画效果,但也可以自定义这些效果,在开始学习之前呢,我们先来快速过一下本篇文章的目录:

目录

在Flutter中有哪些类型的动画?

在Flutter中动画分为两类:基于tween基于物理的。

推荐大家查阅我们上面课程中所讲到的Flutter gallery中的示例代码来学习动画。

  • 补间(Tween)动画:在补间动画中,定义了开始点和结束点、时间线以及定义转换时间和速度的曲线。然后由框架计算如何从开始点过渡到结束点。
  • 基于物理的动画:在基于物理的动画中,运动被模拟为与真实世界的行为相似。例如,当你掷球时,它在何处落地,取决于抛球速度有多快、球有多重、距离地面有多远。 类似地,将连接在弹簧上的球落下(并弹起)与连接到绳子上的球放下的方式也是不同。

如何使用动画库中的基础类给widget添加动画?

在为widget添加动画之前,先让我们认识下动画的几个朋友:

  • Animation:是Flutter动画库中的一个核心类,它生成指导动画的值;
  • CurvedAnimation:Animation 的一个子类,将过程抽象为一个非线性曲线;
  • AnimationController:Animation 的一个子类,用来管理Animation;
  • manbetx客户端 ios-Tween:在正在执行动画的对象所使用的数据范围之间生成值。例如,Tween可生成从红到蓝之间的色值,或者从0到255;

Animation

在Flutter中,Animation对象本身和UI渲染没有任何关系。Animation是一个抽象类,它拥有其当前值和状态(完成或停止)。其中一个比较常用的Animation类是[Animation ]()。

Flutter中的Animation对象是一个在一段时间内依次生成一个区间之间值的类Animation对象的输出可以是线性的、曲线的、一个步进函数或者任何其他可以设计的映射。 根据Animation对象的控制方式,动画可以反向运行,甚至可以在中间切换方向。

  • Animation还可以生成除double之外的其他类型值,如:[Animation ]() 或 [Animation ]();
  • manbetx客户端 ios-Animation对象有状态。可以通过访问其value属性获取动画的当前值;
  • Animation对象本身和UI渲染没有任何关系;

CurvedAnimation

CurvedAnimation将动画过程定义为一个非线性曲线。

final CurvedAnimation curve = new CurvedAnimation(parent: controller, curve: Curves.easeIn); 

注: Curves 类定义了许多常用的曲线,也可以创建自己的,例如:

class ShakeCurve extends Curve { @override double transform(double t) { return math.sin(t * math.PI * 2); } } 

点我查看全部完整代码

AnimationController

AnimationController是一个特殊的Animation对象,在屏幕刷新的每一帧,就会生成一个新的值。默认情况下,AnimationController在给定的时间段内会线性的生成从0.0到1.0的数字。 例如,下面代码创建一个Animation对象:

final AnimationController controller = new AnimationController( duration: const Duration(milliseconds: 2000), vsync: this); 

AnimationController派生自Animation ,因此可以在需要Animation对象的任何地方使用。 但是,AnimationController具有控制动画的其他方法:

当创建一个AnimationController时,需要传递一个vsync参数,存在vsync时会防止屏幕外动画消耗不必要的资源,可以将stateful对象作为vsync的值。

注意: 在某些情况下,值(position,值动画的当前值)可能会超出AnimationController的0.0-1.0的范围。例如,fling()函数允许您提供速度(velocity)、力量(force)、position(通过Force对象)。位置(position)可以是任何东西,因此可以在0.0到1.0范围之外。 CurvedAnimation生成的值也可以超出0.0到1.0的范围。根据选择的曲线,CurvedAnimation的输出可以具有比输入更大的范围。例如,Curves.elasticIn等弹性曲线会生成大于或小于默认范围的值。

Tween

默认情况下,AnimationController对象的范围从0.0到1.0。如果您需要不同的范围或不同的数据类型,则可以使用Tween来配置动画以生成不同的范围或数据类型的值。例如,以下示例,Tween生成从-200.0到0.0的值:

final Tween doubleTween = new Tween
         
          
           
          (begin: -200.0, end: 0.0); 
         
          

Tween是一个无状态(stateless)对象,需要begin和end值。Tween的唯一职责就是定义从输入范围到输出范围的映射。输入范围通常为0.0到1.0,但这不是必须的。

Tween继承自Animatable ,而不是继承自Animation AnimatableAnimation相似,不是必须输出double值。例如,ColorTween指定两种颜色之间的过渡。

final Tween colorTween = new ColorTween(begin: Colors.transparent, end: Colors.black54); 

Tween对象不存储任何状态。相反,它提供了evaluate(Animation animation) 方法将映射函数应用于动画当前值。 Animation对象的当前值可以通过value()方法取到。evaluate函数还执行一些其它处理,例如分别确保在动画值为0.0和1.0时返回开始和结束状态。

Tween.animate

要使用Tween对象,可调用它的animate()方法,传入一个控制器对象。例如,以下代码在500毫秒内生成从0到255的整数值。

final AnimationController controller = new AnimationController( duration: const Duration(milliseconds: 500), vsync: this); Animation
         
          
           
           alpha = new IntTween(begin: 0, end: 255).animate(controller); 
         
          

注意animate()返回的是一个Animation,而不是一个Animatable

以下示例构建了一个控制器、一条曲线和一个Tween

final AnimationController controller = new AnimationController( duration: const Duration(milliseconds: 500), vsync: this); final Animation curve = new CurvedAnimation(parent: controller, curve: Curves.easeOut); Animation
         
          
           
           alpha = new IntTween(begin: 0, end: 255).animate(curve); 
         
          

点我查看全部完整代码

为widget添加动画

在下面的实例中我们为一个logo添加了一个从小放大的动画: zoom.gif

class _LogoAppState extends State
         
          
           
           with SingleTickerProviderStateMixin { Animation
          
           
            
            animation; AnimationController controller; AnimationStatus animationState; double animationValue; @override void initState() { super.initState(); controller = AnimationController(duration: const Duration(seconds: 2), vsync: this); // #docregion addListener animation = Tween
           
            
             
            (begin: 0, end: 300).animate(controller) ..addListener(() { // #enddocregion addListener setState(() { animationValue = animation.value; }); // #docregion addListener }) ..addStatusListener((AnimationStatus state) { setState(() { animationState = state; }); }); // #enddocregion addListener } @override Widget build(BuildContext context) { return Container( margin: EdgeInsets.only(top: 50), child: Column( children: 
            
             
              
             [ GestureDetector( onTap: () { controller.reset(); controller.forward(); }, child: Text('Start', textDirection: TextDirection.ltr), ), Text('State:' + animationState.toString(), textDirection: TextDirection.ltr), Text('Value:' + animationValue.toString(), textDirection: TextDirection.ltr), Container( height: animation.value, width: animation.value, child: FlutterLogo(), ), ], ), ); } @override void dispose() { controller.dispose(); super.dispose(); } } 
            
             
           
            
          
           
         
          

点我查看全部完整代码

注意,在上述代码中要实现这个动画的关键一步是在addListener()的回调中添加setState的调用这样才能触发页面重新渲染,动画才能有效,另外也可以通过AnimatedWidget来实现,在下文中会讲到。

如何为动画添加监听器?

有时我们需要知道动画执行的进度和状态,在Flutter中我们可以通过Animation的addListeneraddStatusListener方法为动画添加监听器:

 @override void initState() { super.initState(); controller = AnimationController(duration: const Duration(seconds: 2), vsync: this); animation = Tween<double>(begin: 0, end: 300).animate(controller) // #enddocregion print-state ..addStatusListener((status) { if (status == AnimationStatus.completed) { controller.reverse(); } else if (status == AnimationStatus.dismissed) { controller.forward(); } }) // #docregion print-state ..addStatusListener((state) => print('$state')); ..addListener(() { // #enddocregion addListener setState(() { // The state that has changed here is the animation object’s value. }); // #docregion addListener }); controller.forward(); } 

可对照学习为widget添加动画的例子;

用AnimatedWidget与AnimatedBuilder简化和重构我们对动画的使用

什么是AnimatedWidget?

我们可以将AnimatedWidget理解为Animation的助手,使用它可以简化我们对动画的使用,在为widget添加动画的学习中我们不难发现,在不使用AnimatedWidget的情况下需要手动调用动画的addListener()并在回调中添加setState才能看到动画效果,AnimatedWidget将为我们简化这一操作。

在下面的重构示例中,LogoApp现在继承自AnimatedWidget而不是StatefulWidgetAnimatedWidget在绘制时使用动画的当前值。LogoApp仍然管理着AnimationControllerTween

class AnimatedLogo extends AnimatedWidget { AnimatedLogo({Key key, Animation<double> animation}) : super(key: key, listenable: animation); Widget build(BuildContext context) { final Animation<double> animation = listenable; return new Center( child: new Container( margin: new EdgeInsets.symmetric(vertical: 10.0), height: animation.value, width: animation.value, child: new FlutterLogo(), ), ); } } class LogoApp extends StatefulWidget { _LogoAppState createState() => new _LogoAppState(); } class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin { AnimationController controller; Animation<double> animation; initState() { super.initState(); controller = new AnimationController( duration: const Duration(milliseconds: 2000), vsync: this); animation = new Tween(begin: 0.0, end: 300.0).animate(controller); controller.forward(); } Widget build(BuildContext context) { return new AnimatedLogo(animation: animation); } dispose() { controller.dispose(); super.dispose(); } } ... 

点我查看全部完整代码

什么是AnimatedBuilder?

AnimatedBuilder是用于构建动画的通用widget,AnimatedBuilder对于希望将动画作为更大构建函数的一部分包含在内的更复杂的widget时非常有用,其实你可以这样理解:AnimatedBuilder是拆分动画的一个工具类,借助它我们可以将动画和widget进行分离:

在上面的实例中我们的代码存在的一个问题: 更改动画需要更改显示logo的widget。更好的解决方案是将职责分离:

  • 显示logo
  • 定义Animation对象
  • 渲染过渡效果

接下来我们就借助AnimatedBuilder]()类来完成此分离。[AnimatedBuilder]()是渲染树中的一个独立的类, 与[AnimatedWidget类似,AnimatedBuilder自动监听来自Animation对象的通知,不需要手动调用addListener()

我们根据下图的 widget 树来创建我们的代码:

AnimatedBuilder-WidgetTree

... // #docregion LogoWidget class LogoWidget extends StatelessWidget { // Leave out the height and width so it fills the animating parent Widget build(BuildContext context) => Container( margin: EdgeInsets.symmetric(vertical: 10), child: FlutterLogo(), ); } // #enddocregion LogoWidget // #docregion GrowTransition class GrowTransition extends StatelessWidget { GrowTransition({this.child, this.animation}); final Widget child; final Animation<double> animation; Widget build(BuildContext context) => Center( child: AnimatedBuilder( animation: animation, builder: (context, child) => Container( height: animation.value, width: animation.value, child: child, ), child: child), ); } // #enddocregion GrowTransition class LogoApp extends StatefulWidget { _LogoAppState createState() => _LogoAppState(); } // #docregion print-state class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin { Animation<double> animation; AnimationController controller; @override void initState() { super.initState(); controller = AnimationController(duration: const Duration(seconds: 2), vsync: this); animation = Tween<double>(begin: 0, end: 300).animate(controller); controller.forward(); } // #enddocregion print-state @override Widget build(BuildContext context) => GrowTransition( child: LogoWidget(), animation: animation, ); @override void dispose() { controller.dispose(); super.dispose(); } // #docregion print-state } 

点我查看全部完整代码

  • 本节学习过程中遇到无法解决的问题可以在课程问答区进行提问,课程老师会对你进行辅导和帮助;
  • 欢迎加入课程官方群:795410523 和讲师以及其他师兄弟们一起学习交流;

参考资料

Sat, 20 Apr 2019 00:00:00 +0800 http://localhost:4000/2019/04/20/flutter-animation-tutorial-base/ http://localhost:4000/2019/04/20/flutter-animation-tutorial-base/ Flutter Android iOS
带你快速掌握Flutter图片开发核心技能

带你快速掌握Flutter图片开发核心技能

为大家倾力打造的课程《Flutter从入门到进阶-实战携程网App》上线了,解锁Flutter开发新姿势,一网打尽Flutter核心技术 点我Get!!!

在这篇文章中,将带着大家一起学习在Flutter中图片开发以及应用场景中的必备技能以及一些经验技巧

  • 本文学习过程中遇到无法解决的问题可以在课程问答区进行manbetx客户端应用-提问,课程老师会对你进行辅导和帮助;
  • 欢迎加入课程官方群:795410523 和讲师以及其他师兄弟们一起学习交流;

目录

  • 什么是Image widget
  • 如何加载网络图片?
  • 如何加载静态图片?
  • 如何加载本地图片?
  • 如何设置Placeholder?
  • 如何配置图片缓存?
  • 如何加载Icon

什么是Image widget?

Flutter中一个用来展示图片的widget。

Image支持如下几种类型的构造函数:

在加载项目中的图片资源时,为了让Image能够根据像素密度自动适配不同分辨率的图片,请使用AssetImage指定图像,并确保在widget树中的“Image” widget上方存在MaterialAppWidgetsAppMediaQuery窗口widget。

Image支持的图片格式

Image 支持以下类型的图片:JPEG, PNG, GIF, Animated GIF, WebP, Animated WebP, BMP, 和 WBMP。

如何加载网络图片?

要加载网络图片,我们需要使用Iboke.nrnlibrary.com构造方法:

Image.network( 'https://boke.nrnlibrary.com/img/avatar.png', ) 

demo:

... class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter bottomNavigationBar', theme: new ThemeData.fallback(), home: Image.network( 'https://boke.nrnlibrary.com/img/avatar.png', ), ); } } 

manbetx客户端应用-点我查看全部完整代码

如何加载静态图片,以及处理不同分辨率的图片?

要加载项目中的静态图片,需要一些两步:

我们在《快速上手Flutter开发》《项目结构、资源、依赖和本地化》一节中详细讲解过如何归档图片资源以及处理不同分辨率的图片,在这里就不重复了。

pubspec.yaml声明图片路径:

assets: - images/my_icon.png 

使用AssetImage访问图片图片:

Image( height: 26, width: 26, image: AssetImage(my_icon.png), ), 

除了我们使用Image的构造方法手动指定AssetImage之外,还可通过Image.asset来加载静态图片:

Image.asset(my_icon.png, width: 26, height: 26, ) 

两者是等效的。

/sdcard/Download/Stack.png

如何加载本地图片?

加载完整路径的本地图片

import 'dart:io'; Image.file(File('/sdcard/Download/Stack.png')), 

加载相对路径的本地图片

第一步:

manbetx客户端应用-pubspec.yaml中添加path_provider插件;

第二步:导入头文件

import 'dart:io'; import 'package:path_provider/path_provider.dart'; //Image.file(File('/sdcard/Download/Stack.png')), FutureBuilder(future: _getLocalFile("Download/Stack.png"), builder: (BuildContext context, AsyncSnapshot<File> snapshot) { return snapshot.data != null ? Image.file(snapshot.data) : Container(); }) ) //获取SDCard的路径: Future<File> _getLocalFile(String filename) async { String dir = (await getExternalStorageDirectory()).path; File f = new File('$dir/$filename'); return f; } 

manbetx客户端应用下载-点我查看全部完整代码

如何设置Placeholder?

为了设置Placeholder我们需要借助FadeInImage,它能够从内存,本地资源中加载placeholder。

从内存中加载Placeholder

第一步:

安装transparent_image插件。

第二步:

... class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { final title = 'Fade in images'; return MaterialApp( title: title, home: Scaffold( appBar: AppBar( title: Text(title), ), body: Stack( children: <Widget>[ Center(child: CircularProgressIndicator()), Center( child: FadeInImage.memoryNetwork( placeholder: kTransparentImage, image: 'https://boke.nrnlibrary.com/img/avatar.png', ), ), ], ), ), ); } } 

点我查看全部完整代码

从本地资源中加载Placeholder

第一步

配置本地资源图片:

 flutter: assets: + - assets/loading.gif 

第二步

加载本地资源图片作为Placeholder

FadeInImage.assetNetwork( placeholder: 'assets/loading.gif', image: 'https://boke.nrnlibrary.com/img/avatar.png', ); 

点我查看全部完整代码

如何配置图片缓存?

在Flutter中我们可以借助cached_network_image插件,来从网络上加载图片,并且将其缓存到本地,以供下次使用。

class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { final title = 'Cached Images'; return MaterialApp( title: title, home: Scaffold( appBar: AppBar( title: Text(title), ), body: Center( child: CachedNetworkImage( placeholder: (context, url) => new CircularProgressIndicator(), imageUrl: 'https://picsum.photos/250?image=9', ), ), ), ); } } 

关于创建图片控件开发详解的更多实战技巧与最佳实践可学习《基于Flutter1.x开发携程网App》

如何加载Icon?

在Flutter中我们可以借助Icon来加载icon:

const Icon(this.icon//IconDate, { Key key, this.size,//大小 this.color,//颜色 this.semanticLabel,//标志位 this.textDirection,//绘制方向,一般使用不到 }) 

点我查看全部完整代码

从Icon的构造方法可以很清楚的看出Icon构造方法需要一个默认的类型为IconData类型的参数,我们可以构造一个自己的IconData,也可以使用Flutter提供的material_fonts

使用Icons

通过如下代码我们可以使用Flutter内置的material_fonts

... class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { // TODO: implement build return new Scaffold( appBar: new AppBar( title: new Text("Icons"), ), body: new Center( child: new Icon(Icons.android,size: 100.0), ), ); } } 

点我查看全部完整代码

使用自定义的Icon

使用自定义的我们需要构造一个:

const IconData( this.codePoint,//必填参数,fonticon对应的16进制Unicode { this.fontFamily,//字体库系列 this.fontPackage,//字体在那个包中,不填仅在自己程序包中查找 this.matchTextDirection: false,图标是否按照图标绘制方向显示 }); 

首先我我们需要向使用字体一样,在pubspec.yaml中配置我们的icon:

fonts: - family: devio fonts: - asset: fonts/devio.ttf 

接下来就可以使用了:

child: new Icon(new IconData(0xf5566,fontFamily: "devio"),size: 100.0,color: Colors.blueAccent,) 
  • 本节学习过程中遇到无法解决的问题可以在manbetx客户端应用-课程问答区进行提问,课程老师会对你进行辅导和帮助;
  • 欢迎加入课程官方群:795410523 和讲师以及其他师兄弟们一起学习交流;

参考资料

Thu, 11 Apr 2019 00:00:00 +0800 http://localhost:4000/2019/04/11/flutter-image-widget/ http://localhost:4000/2019/04/11/flutter-image-widget/ Flutter Android iOS
两分钟带你快速搭建Flutter开发环境(Windows)

两分钟带你快速搭建Flutter开发环境(Windows)

为大家倾力打造的课程《Flutter从入门到进阶-实战携程网App》上线了,解锁Flutter开发新姿势,一网打尽Flutter核心技术 点我Get!!!

在这篇文章中,将带着大家一起在Windows平台上快速搭建Flutter的开发环境同时会将搭建Flutter开发环境中的一些技巧和经验分享给大家。

  • 在大家Flutter开发环境过程中遇到无法解决的问题可以在课程问答区进行提问,课程老师会对你进行辅导和帮助;

目录


  • 系统要求
  • 设置FLutter镜像(非必须)
  • 获取Flutter SDK
  • Android开发环境设置
  • 安装Flutter插件

系统要求

在Windows上要安装并运行Flutter要满足以下最低要求:

  • 操作系统: Windows 7 SP1或更新版本
  • 磁盘空间: 400 MB (Android Studio的磁盘空间).
  • 工具: Flutter 依赖下面这些命令行工具:

设置FLutter镜像(非必须)

由于在国内访问Flutter可能会受到限制,Flutter官方为中国开发者搭建了临时镜像,大家可以将如下环境变量加入到用户环境变量中:

PUB_HOSTED_URL=https://boke.nrnlibrary.com FLUTTER_STORAGE_BASE_URL=https://boke.nrnlibrary.com 

注意:此镜像为临时镜像,并不能保证一直可用,大家可以从 Using Flutter in China 上获得有关镜像服务器的最新动态。

获取Flutter SDK

1.点Flutter官网下载其最新可用的安装包。

2.解压安装包到你想安装的目录,如:C:\flutter

注意,不要将flutter安装到需要一些高权限的路径如C:\Program Files\等。

3.在Flutter安装目录的flutter文件下找到flutter_console.bat,双击运行并启动flutter命令行;

接下来,你就可以在Flutter命令行运行flutter命令了。

设置环境变量

要在终端运行 flutter 命令, 你需要添加以下环境变量到系统PATH

  • 在Windows的Start 的搜索条中搜索env,选择编辑帐户的环境变量
  • 在“用户变量”下检查是否有名为“Path”的条目:
    • 如果该条目存在, 追加 flutter\bin的全路径,使用 ; 作为分隔符.
    • 如果条目不存在, 创建一个新用户变量 Path ,然后将 flutter\bin的全路径作为它的值.

在“用户变量”下检查是否有名为”PUB_HOSTED_URL”和”FLUTTER_STORAGE_BASE_URL”的条目,如果没有,也添加它们。

  • 重启Windows以应用此更改;

运行 flutter doctor

上面path配置完成之后,打开一个新的命令提示符或PowerShell窗口并运行以下命令以查看是否需要安装任何依赖项来完成安装:

$ flutter doctor 

该命令检查你的环境并在终端窗口中显示报告。Dart SDK已经在捆绑在Flutter里了,没有必要单独安装Dart。 仔细检查命令行输出以获取可能需要安装的其他软件或进一步需要执行的任务(以粗体显示):

例如:

[-] Android toolchain - develop for Android devices • Android SDK at /Users/obiwan/Library/Android/sdk ✗ Android SDK is missing command line tools; download from https://goo.gl/XxQghQ • Try re-installing or updating your Android SDK, visit https://flutter.dev/setup/#android-setup for detailed instructions. 

一般的错误会是Android Studio版本太低、或者没有ANDROID_HOME环境变量等

第一次运行一个flutter命令(如flutter doctor)时,它会下载它自己的依赖项并自行编译。以后再运行就会快得多。

Android开发环境设置

安装Android Studio

1.下载并安装 Android Studio

因为Android网站设在国外,如果你的网络无法访问第一个地址,可以选择使用Google为中国开发者提供的中国网址进行访问。

另外,关于Android Studio的安装和配置,Android官方有比较详细的说明文档https://boke.nrnlibrary.com/studio/intro,大家可以根据需要进行查阅;

大家在安装过程中遇到问题无法解决的,可以在我们课程的问答区提问进行提问

2.启动Android Studio,然后执行“Android Studio安装向导”。这将安装最新的Android SDK,Android SDK平台工具和Android SDK构建工具

Flutter插件安装

  • 打开Android Studio
  • 打开Preferences > Plugins (macOS), File > Settings > Plugins (Windows & Linux)
  • 选择 Browse repositories, 搜索 Flutter plugin
  • 然后点击安装,然后安装Dart插件
  • 完成之后选择重启Android Studio

如何在Android模拟器上运行Flutter?

要准备在Android模拟器上运行并测试您的Flutter应用,需要按照以下步骤操作:

  • 在你的机器上启用 VM acceleration
  • 启动 Android Studio>Tools>Android>AVD Manager 并选择 Create Virtual Device
  • 选择一个设备并选择 Next;
  • 为要模拟的Android版本选择一个或多个系统映像,然后选择 Next. 建议使用 x86 或 x86_64 的镜像;
  • 在 Emulated Performance下, 选择 Hardware - GLES 2.0 以启用硬件加速;
  • 验证AVD配置是否正确,然后选择 Finish;

    如果对以上步骤还有不清楚的可以参阅Android官方的 Managing AVDs文档。

    大家在安装过程中遇到问题无法解决的,可以在我们课程的问答区提问进行提问

  • 在 Android Virtual Device Manager中, 点击工具栏的 Run,模拟器启动并显示所选操作系统版本或设备的启动画面;
  • 通过flutter run运行启动项目;

如何在Android真机运行?

要准备在Android设备上运行并测试您的Flutter应用,您需要安装Android 4.1(API level 16)或更高版本的Android设备

  • 在你的设备上启用 开发人员选项USB调试 。详细说明可在Android文档中找到;
  • 使用USB将手机插入电脑,如果有授权提示需要同意授权;
  • 在终端中,运行 flutter devices 命令以验证Flutter是否识别你连接的Android设备;
  • 通过flutter run运行启动项目;

默认情况下,Flutter使用的Android SDK版本是基于你的 adb 工具版本, 如果你想让Flutter使用不同版本的Android SDK,则必须将该 ANDROID_HOME 环境变量修改SDK的目录。

创建和运行一个简单的Flutter项目

1.通过如下命令创建一个Flutter项目

$ flutter create my_app 

2.命令运行完成之后会在当前目录下创建一个名为my_app的Flutter项目,然后通过一下命令可以运行它:

$ cd my_app $ flutter run 

manbetx客户端 ios-FAQ

无法启动模拟器

emulator: ERROR: x86 emulation currently requires hardware acceleration! Please ensure Windows Hypervisor Platform (WHPX) is properly installed and usable. CPU acceleration status: HAXM is not installed on this machine 

解决方案:选择 Tools > SDK Manager > SDK Tools , 安装 HAXM 即可

android-emulator-acceleration

关于开发环境搭建更多实战技巧与最佳实践可学习《基于Flutter1.x开发携程网App-开发环境搭建》部分的课程。

  • 本节学习过程中遇到无法解决的问题可以在课程问答区进行提问,课程老师会对你进行辅导和帮助;
  • 欢迎加入课程官方群:687196170 和讲师以及其他师兄弟们一起学习交流;
Sun, 07 Apr 2019 00:00:00 +0800 http://localhost:4000/2019/04/07/development-environment-windows/ http://localhost:4000/2019/04/07/development-environment-windows/ Flutter Android iOS