-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 363 KB
/
content.json
1
{"meta":{"title":"少停","subtitle":null,"description":"ʕ•̫͡•ོʔ•̫͡•ཻʕ•̫͡•ʔ•͓͡•ʔ","author":null,"url":"https://zhoushaoting.com"},"pages":[{"title":"","date":"2023-12-03T07:50:19.388Z","updated":"2023-12-03T07:50:19.383Z","comments":true,"path":"README.html","permalink":"https://zhoushaoting.com/README.html","excerpt":"","text":"现在的博客 旧博客"},{"title":"关于我","date":"2023-11-23T00:42:59.086Z","updated":"2021-08-28T13:32:45.546Z","comments":true,"path":"about/index.html","permalink":"https://zhoushaoting.com/about/index.html","excerpt":"","text":""},{"title":"categories","date":"2023-11-23T00:42:59.087Z","updated":"2018-03-18T03:30:29.000Z","comments":true,"path":"categories/index.html","permalink":"https://zhoushaoting.com/categories/index.html","excerpt":"","text":""},{"title":"search","date":"2018-04-07T08:31:05.000Z","updated":"2018-04-07T08:31:05.000Z","comments":true,"path":"search/index.html","permalink":"https://zhoushaoting.com/search/index.html","excerpt":"","text":""},{"title":"留言板","date":"2023-11-23T00:42:59.086Z","updated":"2018-07-09T05:40:53.000Z","comments":true,"path":"message/index.html","permalink":"https://zhoushaoting.com/message/index.html","excerpt":"","text":""},{"title":"tags","date":"2023-11-23T00:42:59.086Z","updated":"2018-03-18T03:30:47.000Z","comments":true,"path":"tags/index.html","permalink":"https://zhoushaoting.com/tags/index.html","excerpt":"","text":""}],"posts":[{"title":"Flutter~滑动组件探索","slug":"移动端学习/Flutter~滑动组件探索","date":"2023-12-31T16:00:00.000Z","updated":"2023-12-21T02:16:16.258Z","comments":true,"path":"2024/01/01/移动端学习/Flutter~滑动组件探索/","link":"","permalink":"https://zhoushaoting.com/2024/01/01/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~%E6%BB%91%E5%8A%A8%E7%BB%84%E4%BB%B6%E6%8E%A2%E7%B4%A2/","excerpt":"2024的元旦,从滑动组件开始~","text":"2024的元旦,从滑动组件开始~ 平时开发中,最常用的滑动组件差不多以下几种:ListView组件一族、NestedScrollView组件、SingleChildScrollView组件、PageView组件、ListWheelScrollView组件、GridView、CustomScrollView…… 滑动的构成 在flutter中,其实所有的滑动组件都是基于三个部分:滑动处理组件 Scrollable 和 视口组件 Viewport 和 滑动内容 sliver 列表。 从外至内ListView1、先来看下ListView,首先,ListView继承于BoxScrollView,而BoxScrollView继承于Scrollview,Scrollview继承于StatelessWidget,其中,ListView是一个普通类,BoxScrollView 和Scrollview 和StatelessWidget 都是抽象类。我们知道抽象类的子类都需要实现父类的抽象方法。所以很多的参数都是交给父类去初始化的,各个抽象类都只需要完成各自的工作即可。 12345class ListView extends BoxScrollView {abstract class BoxScrollView extends ScrollView {abstract class ScrollView extends StatelessWidget {... ListView一种有四种构造方法,ListView构造 ListView.builder构造 ListView.separated构造 ListView.custom构造。因为ListView 继承了BoxScrollView 抽象类,所以它需要重写BoxScrollView 的抽象方法buildChildLayout。如下: 123456789101112131415@overrideWidget buildChildLayout(BuildContext context) { if (itemExtent != null) { return SliverFixedExtentList( delegate: childrenDelegate, itemExtent: itemExtent!, ); } else if (prototypeItem != null) { return SliverPrototypeExtentList( delegate: childrenDelegate, prototypeItem: prototypeItem!, ); } return SliverList(delegate: childrenDelegate);} 其实ListView的底层里面,都是使用的Slivers。里面会根据itemExtent和prototypeItem参数来做判断使用哪种Slivers 。我用的最多的是SliverList(delegate: childrenDelegate)从源码中可以看出来,里面的好多参数都是super.xxx的,从这里可以确定好多参数都是给父类们使用。这里从源码可以看出来ListView会从StatelessWidget 继承而来,可以为何它却可以改变呢? GridView和ListView 类似,GridView 的继承关系也是如此。 1234class GridView extends BoxScrollView {abstract class BoxScrollView extends ScrollView {abstract class ScrollView extends StatelessWidget {... GridView的源码体系中,也和ListView一样,需要重新抽象类的抽象方法。其中,不同的只有在GridView 源码中,其余的BoxScrollView 和ScrollView 等一模一样。而在buildChildLayout的重写方法中,只有: 1234567@overrideWidget buildChildLayout(BuildContext context) { return SliverGrid( delegate: childrenDelegate, gridDelegate: gridDelegate, );} ok,接下来,研究下参数childrenDelegate 和gridDelegate 。可以看出GridView 一共有5种构造方法。其中,不管是哪一种,要么 123456789101112131415161718192021222324252627282930GridView({ super.key, super.scrollDirection, super.reverse, super.controller, super.primary, super.physics, super.shrinkWrap, super.padding, required this.gridDelegate, bool addAutomaticKeepAlives = true, bool addRepaintBoundaries = true, bool addSemanticIndexes = true, super.cacheExtent, List<Widget> children = const <Widget>[], int? semanticChildCount, super.dragStartBehavior, super.clipBehavior, super.keyboardDismissBehavior, super.restorationId,}) : assert(gridDelegate != null), childrenDelegate = SliverChildListDelegate( children, addAutomaticKeepAlives: addAutomaticKeepAlives, addRepaintBoundaries: addRepaintBoundaries, addSemanticIndexes: addSemanticIndexes, ), super( semanticChildCount: semanticChildCount ?? children.length, ); 1234567891011121314151617181920212223242526272829303132333435GridView.builder({ super.key, super.scrollDirection, super.reverse, super.controller, super.primary, super.physics, super.shrinkWrap, super.padding, required this.gridDelegate, required IndexedWidgetBuilder itemBuilder, ChildIndexGetter? findChildIndexCallback, int? itemCount, bool addAutomaticKeepAlives = true, bool addRepaintBoundaries = true, bool addSemanticIndexes = true, super.cacheExtent, int? semanticChildCount, super.dragStartBehavior, super.keyboardDismissBehavior, super.restorationId, super.clipBehavior,}) : assert(gridDelegate != null), childrenDelegate = SliverChildBuilderDelegate( itemBuilder, findChildIndexCallback: findChildIndexCallback, childCount: itemCount, addAutomaticKeepAlives: addAutomaticKeepAlives, addRepaintBoundaries: addRepaintBoundaries, addSemanticIndexes: addSemanticIndexes, ), super( semanticChildCount: semanticChildCount ?? itemCount, ); 12345678910111213141516171819const GridView.custom({ super.key, super.scrollDirection, super.reverse, super.controller, super.primary, super.physics, super.shrinkWrap, super.padding, required this.gridDelegate, required this.childrenDelegate, super.cacheExtent, super.semanticChildCount, super.dragStartBehavior, super.keyboardDismissBehavior, super.restorationId, super.clipBehavior,}) : assert(gridDelegate != null), assert(childrenDelegate != null); 123456789101112131415161718192021222324252627282930313233343536373839GridView.count({ super.key, super.scrollDirection, super.reverse, super.controller, super.primary, super.physics, super.shrinkWrap, super.padding, required int crossAxisCount, double mainAxisSpacing = 0.0, double crossAxisSpacing = 0.0, double childAspectRatio = 1.0, bool addAutomaticKeepAlives = true, bool addRepaintBoundaries = true, bool addSemanticIndexes = true, super.cacheExtent, List<Widget> children = const <Widget>[], int? semanticChildCount, super.dragStartBehavior, super.keyboardDismissBehavior, super.restorationId, super.clipBehavior,}) : gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: crossAxisCount, mainAxisSpacing: mainAxisSpacing, crossAxisSpacing: crossAxisSpacing, childAspectRatio: childAspectRatio, ), childrenDelegate = SliverChildListDelegate( children, addAutomaticKeepAlives: addAutomaticKeepAlives, addRepaintBoundaries: addRepaintBoundaries, addSemanticIndexes: addSemanticIndexes, ), super( semanticChildCount: semanticChildCount ?? children.length, ); 1234567891011121314151617181920212223242526272829303132333435363738GridView.extent({ super.key, super.scrollDirection, super.reverse, super.controller, super.primary, super.physics, super.shrinkWrap, super.padding, required double maxCrossAxisExtent, double mainAxisSpacing = 0.0, double crossAxisSpacing = 0.0, double childAspectRatio = 1.0, bool addAutomaticKeepAlives = true, bool addRepaintBoundaries = true, bool addSemanticIndexes = true, super.cacheExtent, List<Widget> children = const <Widget>[], int? semanticChildCount, super.dragStartBehavior, super.keyboardDismissBehavior, super.restorationId, super.clipBehavior,}) : gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: maxCrossAxisExtent, mainAxisSpacing: mainAxisSpacing, crossAxisSpacing: crossAxisSpacing, childAspectRatio: childAspectRatio, ), childrenDelegate = SliverChildListDelegate( children, addAutomaticKeepAlives: addAutomaticKeepAlives, addRepaintBoundaries: addRepaintBoundaries, addSemanticIndexes: addSemanticIndexes, ), super( semanticChildCount: semanticChildCount ?? children.length, ); 我们可以看出来,这几种构造方法中,要么gridDelegate 和childrenDelegate 均为必须,要么自己会在初始化后新建出来。 ok,那我们进入父类的BoxScrollView: BoxScrollView12345678910111213141516171819202122abstract class BoxScrollView extends ScrollView { /// Creates a [ScrollView] uses a single child layout model. /// /// If the [primary] argument is true, the [controller] must be null. const BoxScrollView({ super.key, super.scrollDirection, super.reverse, super.controller, super.primary, super.physics, super.shrinkWrap, this.padding, super.cacheExtent, super.semanticChildCount, super.dragStartBehavior, super.keyboardDismissBehavior, super.restorationId, super.clipBehavior, }); .... 从源码来看,BoxScrollView 只有一种构造方法,其中,只有只做一下padding,padding 参数是由子类传过来的,其余参数均由super.xxx传给其父类使用。因为其继承了抽象类ScrollView,所以,BoxScrollView 需要重写父类的抽象方法buildSlivers。 123456789101112131415161718192021222324252627282930313233343536373839@overrideList<Widget> buildSlivers(BuildContext context) { /// 调用自己的抽象方法(子类去实现)返回sliver Widget sliver = buildChildLayout(context); EdgeInsetsGeometry? effectivePadding = padding; if (padding == null) { final MediaQueryData? mediaQuery = MediaQuery.maybeOf(context); if (mediaQuery != null) { // Automatically pad sliver with padding from MediaQuery. final EdgeInsets mediaQueryHorizontalPadding = mediaQuery.padding.copyWith(top: 0.0, bottom: 0.0); final EdgeInsets mediaQueryVerticalPadding = mediaQuery.padding.copyWith(left: 0.0, right: 0.0); // Consume the main axis padding with SliverPadding. effectivePadding = scrollDirection == Axis.vertical ? mediaQueryVerticalPadding : mediaQueryHorizontalPadding; // Leave behind the cross axis padding. sliver = MediaQuery( data: mediaQuery.copyWith( padding: scrollDirection == Axis.vertical ? mediaQueryHorizontalPadding : mediaQueryVerticalPadding, ), child: sliver, ); } } if (effectivePadding != null) { sliver = SliverPadding(padding: effectivePadding, sliver: sliver); } return <Widget>[ sliver ];} // 抽象方法 @protectedWidget buildChildLayout(BuildContext context); 从源码中,可知:会对padding参数做非空判断,如果是空的话,就会取 final MediaQueryData? mediaQuery = MediaQuery.maybeOf(context);的值,就是mediaQuery,然后返回MediaQuery,其child 值就是sliver,也就是子类重写buildChildLayout而返回的widget。最后返回<Widget>[ sliver ]OK,接下来,我们看最为关键的ScrollView。 ScrollView123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130/// 很多子类传过来的值,将在这里使用到。 const ScrollView({ super.key, this.scrollDirection = Axis.vertical, this.reverse = false, this.controller, this.primary, ScrollPhysics? physics, this.scrollBehavior, this.shrinkWrap = false, this.center, this.anchor = 0.0, this.cacheExtent, this.semanticChildCount, this.dragStartBehavior = DragStartBehavior.start, this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, this.restorationId, this.clipBehavior = Clip.hardEdge, }) : assert(scrollDirection != null), assert(reverse != null), assert(shrinkWrap != null), assert(dragStartBehavior != null), assert(clipBehavior != null), assert( !(controller != null && (primary ?? false)), 'Primary ScrollViews obtain their ScrollController via inheritance ' 'from a PrimaryScrollController widget. You cannot both set primary to ' 'true and pass an explicit controller.', ), assert(!shrinkWrap || center == null), assert(anchor != null), assert(anchor >= 0.0 && anchor <= 1.0), assert(semanticChildCount == null || semanticChildCount >= 0), physics = physics ?? ((primary ?? false) || (primary == null && controller == null && identical(scrollDirection, Axis.vertical)) ? const AlwaysScrollableScrollPhysics() : null);··· @protected Widget buildViewport( BuildContext context, ViewportOffset offset, AxisDirection axisDirection, List<Widget> slivers, ) { assert(() { switch (axisDirection) { case AxisDirection.up: case AxisDirection.down: return debugCheckHasDirectionality( context, why: 'to determine the cross-axis direction of the scroll view', hint: 'Vertical scroll views create Viewport widgets that try to determine their cross axis direction ' 'from the ambient Directionality.', ); case AxisDirection.left: case AxisDirection.right: return true; } }()); /// 根据shrinkWrap参数,返回 Viewport 或者 ShrinkWrappingViewport if (shrinkWrap) { return ShrinkWrappingViewport( axisDirection: axisDirection, offset: offset, slivers: slivers, clipBehavior: clipBehavior, ); } return Viewport( axisDirection: axisDirection, offset: offset, slivers: slivers, cacheExtent: cacheExtent, center: center, anchor: anchor, clipBehavior: clipBehavior, ); }··· @override Widget build(BuildContext context) { final List<Widget> slivers = buildSlivers(context); // 首先,我们需要调用本类ScrollView的抽象方法(交由子类去实现),返回一个 List<Widget> final AxisDirection axisDirection = getDirection(context); final bool effectivePrimary = primary ?? controller == null && PrimaryScrollController.shouldInherit(context, scrollDirection); final ScrollController? scrollController = effectivePrimary ? PrimaryScrollController.of(context) : controller; final Scrollable scrollable = Scrollable( dragStartBehavior: dragStartBehavior, axisDirection: axisDirection, controller: scrollController, physics: physics, scrollBehavior: scrollBehavior, semanticChildCount: semanticChildCount, restorationId: restorationId, viewportBuilder: (BuildContext context, ViewportOffset offset) { return buildViewport(context, offset, axisDirection, slivers); }, clipBehavior: clipBehavior, ); final Widget scrollableResult = effectivePrimary && scrollController != null // Further descendant ScrollViews will not inherit the same PrimaryScrollController ? PrimaryScrollController.none(child: scrollable) : scrollable; if (keyboardDismissBehavior == ScrollViewKeyboardDismissBehavior.onDrag) { return NotificationListener<ScrollUpdateNotification>( child: scrollableResult, onNotification: (ScrollUpdateNotification notification) { final FocusScopeNode focusScope = FocusScope.of(context); if (notification.dragDetails != null && focusScope.hasFocus) { focusScope.unfocus(); } return false; }, ); } else { return scrollableResult; } }... ScrollView 继承自StatelessWidget ,因为StatelessWidget 是抽象类,我们需要重写它的抽象方法build。在子类ScrollView 重写build方法中,首先会调用自身的final List<Widget> slivers = buildSlivers(context);抽象方法,返回一组List<Widget>。然后就到了滑动处理组件 Scrollable, 在它的viewportBuilder 的回调里面,会返回一个buildViewport 方法,在buildViewport方法中,会根据shrinkWrap 的值来确定是返回ShrinkWrappingViewport还是Viewport,而前面的抽象方法buildSlivers 返回的slivers 就是在参数slivers 上返回了。而shrinkWrap 的值是子类返回的,如果子类没返回的话,默认是false ,返回的是Viewport 。至此,我们知道了滑动模块的组成源码大致长这样: 其实所有的滑动组件都是基于三个部分:滑动处理组件 Scrollable 和 视口组件 Viewport 和 滑动内容 sliver 列表 滑动内容 sliver列表sliver可译小滑块。Flutter有两种渲染类型:RenderBox RenderSliver ,它们都是RenderObject 对象之一。 RenderBox开发中,使用到的部分组件都是依赖于RenderBox 实现的。如Text 、Image、Stack…… RenderBox 基于RenderObject ,扩展了两个重要的属性:size 属性:Size 类型,表示该渲染对象的尺寸。constraints 属性:BoxConstraints 类型,表示该渲染对象受到的布局约束。 RenderSliver开发中,使用到的以Sliver 为开头的组件都是依赖于RenderSliver 实现的。如SliverList 、SliverOpacity 、SliverPadding …..RenderSliver 基于RenderObject ,扩展了两个重要的属性:geometry 属性:SliverGeometry 类型,表示该渲染对象的几何信息。constraints 属性:SliverConstraints 类型,表示该渲染对象受到的滑动布局约束。RenderViewport 中限定了其子渲染对象们必须是RenderSliver ,所以很多时候会发现组件的子组件不接受RenderBox 。 视口组件 Viewport 从源码中,可以看出Viewport 继承自MultiChildRenderObjectWidget ,表示它可以接收多个子组件. 12345678910111213141516171819202122232425262728293031323334class Viewport extends MultiChildRenderObjectWidget { Viewport({ super.key, this.axisDirection = AxisDirection.down, this.crossAxisDirection, this.anchor = 0.0, required this.offset, this.center, this.cacheExtent, this.cacheExtentStyle = CacheExtentStyle.pixel, this.clipBehavior = Clip.hardEdge, List<Widget> slivers = const <Widget>[], }) : assert(offset != null), assert(slivers != null), assert(center == null || slivers.where((Widget child) => child.key == center).length == 1), assert(cacheExtentStyle != null), assert(cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null), assert(clipBehavior != null), super(children: slivers); ....... @override RenderViewport createRenderObject(BuildContext context) { return RenderViewport( axisDirection: axisDirection, crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection), anchor: anchor, offset: offset, cacheExtent: cacheExtent, cacheExtentStyle: cacheExtentStyle, clipBehavior: clipBehavior, ); ..... 其中,必须传入一个ViewportOffset类型的offset 参数。在这些参数当中,有两个比较重要,那就是cacheExtent 和cacheExtentStyle 。这两个值共同决定了缓存空间的大小。 1、cacheExtent 该值表示缓存区域的大小,是一个double 。2、cacheExtentStyle 该值表示缓存的单位,是一个枚举:pixel 默认值,表示单位是逻辑像素,viewport 表示单位是视口主轴方向尺寸。 1234enum CacheExtentStyle { pixel, // 默认 viewport, } 前面提到了offset参数,offset参数是外面的滑动处理组件 Scrollable 确定后传给Viewport 使用的。 滑动处理组件 Scrollable在Scrollview的源码中 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394..... @protected Widget buildViewport( BuildContext context, ViewportOffset offset, AxisDirection axisDirection, List<Widget> slivers, ) { assert(() { switch (axisDirection) { case AxisDirection.up: case AxisDirection.down: return debugCheckHasDirectionality( context, why: 'to determine the cross-axis direction of the scroll view', hint: 'Vertical scroll views create Viewport widgets that try to determine their cross axis direction ' 'from the ambient Directionality.', ); case AxisDirection.left: case AxisDirection.right: return true; } }()); if (shrinkWrap) { return ShrinkWrappingViewport( axisDirection: axisDirection, offset: offset, slivers: slivers, clipBehavior: clipBehavior, ); } return Viewport( axisDirection: axisDirection, offset: offset, slivers: slivers, cacheExtent: cacheExtent, center: center, anchor: anchor, clipBehavior: clipBehavior, ); }........ @override Widget build(BuildContext context) { final List<Widget> slivers = buildSlivers(context); final AxisDirection axisDirection = getDirection(context); final bool effectivePrimary = primary ?? controller == null && PrimaryScrollController.shouldInherit(context, scrollDirection); final ScrollController? scrollController = effectivePrimary ? PrimaryScrollController.of(context) : controller; final Scrollable scrollable = Scrollable( // 从这里开始,Scrollable负责监听滑动 dragStartBehavior: dragStartBehavior, axisDirection: axisDirection, controller: scrollController, physics: physics, scrollBehavior: scrollBehavior, semanticChildCount: semanticChildCount, restorationId: restorationId, viewportBuilder: (BuildContext context, ViewportOffset offset) { // tag viewportBuilder的回调函数中,返回一个 return buildViewport(context, offset, axisDirection, slivers); }, clipBehavior: clipBehavior, ); final Widget scrollableResult = effectivePrimary && scrollController != null // Further descendant ScrollViews will not inherit the same PrimaryScrollController ? PrimaryScrollController.none(child: scrollable) : scrollable; if (keyboardDismissBehavior == ScrollViewKeyboardDismissBehavior.onDrag) { return NotificationListener<ScrollUpdateNotification>( child: scrollableResult, onNotification: (ScrollUpdateNotification notification) { final FocusScopeNode focusScope = FocusScope.of(context); if (notification.dragDetails != null && focusScope.hasFocus) { focusScope.unfocus(); } return false; }, ); } else { return scrollableResult; } } 首先,Scrollable 会监听着用户滑动屏幕,然后在 viewportBuilder 的回调方法里面,返回context 和offset ,该offset的返回值,就是Viewport当中使用的值。所以Viewport只是负责偏移窗口的显示,而真正负责滑动相关的其实是Scrollable 。 这里从源码可以看出来ListView会从StatelessWidget 继承而来,可以为何它却可以改变呢?由上面可知,真正负责监听滑动的其实是Scrollable ,其余的都只是负责显示即可。 12class Scrollable extends StatefulWidget {.... 其他滑动widgetCustomScrollView123456789101112131415161718192021222324252627282930class CustomScrollView extends ScrollView { /// Creates a [ScrollView] that creates custom scroll effects using slivers. /// /// See the [ScrollView] constructor for more details on these arguments. const CustomScrollView({ super.key, super.scrollDirection, super.reverse, super.controller, super.primary, super.physics, super.scrollBehavior, super.shrinkWrap, super.center, super.anchor, super.cacheExtent, this.slivers = const <Widget>[], super.semanticChildCount, super.dragStartBehavior, super.keyboardDismissBehavior, super.restorationId, super.clipBehavior, }); /// The slivers to place inside the viewport. final List<Widget> slivers; @override List<Widget> buildSlivers(BuildContext context) => slivers;} CustomScrollView 是直接继承自ScrollView ,重写buildSlivers方法,返回slivers ,而该slivers 就是使用者自己使用的一个数组形式的Widget 即可。 pageView1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556class PageView extends StatefulWidget {.... @override Widget build(BuildContext context) { final AxisDirection axisDirection = _getDirection(context); final ScrollPhysics physics = _ForceImplicitScrollPhysics( allowImplicitScrolling: widget.allowImplicitScrolling, ).applyTo( widget.pageSnapping ? _kPagePhysics.applyTo(widget.physics ?? widget.scrollBehavior?.getScrollPhysics(context)) : widget.physics ?? widget.scrollBehavior?.getScrollPhysics(context), ); return NotificationListener<ScrollNotification>( onNotification: (ScrollNotification notification) { if (notification.depth == 0 && widget.onPageChanged != null && notification is ScrollUpdateNotification) { final PageMetrics metrics = notification.metrics as PageMetrics; final int currentPage = metrics.page!.round(); if (currentPage != _lastReportedPage) { _lastReportedPage = currentPage; widget.onPageChanged!(currentPage); } } return false; }, child: Scrollable( dragStartBehavior: widget.dragStartBehavior, axisDirection: axisDirection, controller: widget.controller, physics: physics, restorationId: widget.restorationId, scrollBehavior: widget.scrollBehavior ?? ScrollConfiguration.of(context).copyWith(scrollbars: false), viewportBuilder: (BuildContext context, ViewportOffset position) { return Viewport( // TODO(dnfield): we should provide a way to set cacheExtent // independent of implicit scrolling: // https://github.com/flutter/flutter/issues/45632 cacheExtent: widget.allowImplicitScrolling ? 1.0 : 0.0, cacheExtentStyle: CacheExtentStyle.viewport, axisDirection: axisDirection, offset: position, clipBehavior: widget.clipBehavior, slivers: <Widget>[ SliverFillViewport( viewportFraction: widget.controller.viewportFraction, delegate: widget.childrenDelegate, padEnds: widget.padEnds, ), ], ); }, ), ); } 它也是由Scrollable 负责监听,Viewport 负责视口组件,Slivers负责滑动内容。 ListWheelScrollView1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950class ListWheelScrollView extends StatefulWidget {.... @override Widget build(BuildContext context) { return NotificationListener<ScrollNotification>( onNotification: _handleScrollNotification, child: _FixedExtentScrollable( controller: scrollController, physics: widget.physics, itemExtent: widget.itemExtent, restorationId: widget.restorationId, scrollBehavior: widget.scrollBehavior ?? ScrollConfiguration.of(context).copyWith(scrollbars: false), viewportBuilder: (BuildContext context, ViewportOffset offset) { return ListWheelViewport( diameterRatio: widget.diameterRatio, perspective: widget.perspective, offAxisFraction: widget.offAxisFraction, useMagnifier: widget.useMagnifier, magnification: widget.magnification, overAndUnderCenterOpacity: widget.overAndUnderCenterOpacity, itemExtent: widget.itemExtent, squeeze: widget.squeeze, renderChildrenOutsideViewport: widget.renderChildrenOutsideViewport, offset: offset, childDelegate: widget.childDelegate, clipBehavior: widget.clipBehavior, ); }, ), ); } ... class _FixedExtentScrollable extends Scrollable { const _FixedExtentScrollable({ super.controller, super.physics, required this.itemExtent, required super.viewportBuilder, super.restorationId, super.scrollBehavior, }); final double itemExtent; @override _FixedExtentScrollableState createState() => _FixedExtentScrollableState();} 从源码可知ListWheelScrollView 也是如此。……","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Flutter~PlatformView的简单使用","slug":"移动端学习/Flutter~PlatformView的简单使用","date":"2023-02-07T16:00:00.000Z","updated":"2023-12-20T05:49:13.235Z","comments":true,"path":"2023/02/08/移动端学习/Flutter~PlatformView的简单使用/","link":"","permalink":"https://zhoushaoting.com/2023/02/08/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~PlatformView%E7%9A%84%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8/","excerpt":"PlatformView 就是AndroidView和UIKitView的总称,允许原生端的view嵌入到flutter项目 中的 Widget。","text":"PlatformView 就是AndroidView和UIKitView的总称,允许原生端的view嵌入到flutter项目 中的 Widget。 接下来简单的演示下它的基本使用规则。 1、新建一个Flutter项目,然后在main.dart中: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253import 'package:flutter/material.dart';import 'dart:io';import 'package:flutter/services.dart';void main() { runApp(const MyApp());}class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); }}class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State<MyHomePage> createState() => _MyHomePageState();}class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Platform.isIOS ? const UiKitView( viewType: "platform_text_view", creationParams: <String, dynamic>{"text": "iOS Label"}, creationParamsCodec: StandardMessageCodec(), ) : const AndroidView( viewType: "platform_text_view", creationParams: <String, dynamic>{"text": "Android Text View"}, creationParamsCodec: StandardMessageCodec(), ), ); }} 其中,iOS对应的是UiKitView ,传递的参数是iOS Label,唯一标识符是:platform_text_view 。android对应的是AndroidView ,传递的参数是Android Text View,唯一标识符是platform_text_view 。iOS一侧:新建PlatformTextView.swift 1234567891011121314151617181920212223import Foundationimport Flutterclass PlatformTextView: NSObject,FlutterPlatformView { let frame: CGRect; let viewId: Int64; var text:String = "" init(_ frame: CGRect,viewID: Int64,args :Any?) { self.frame = frame self.viewId = viewID if(args is NSDictionary){ let dict = args as! NSDictionary self.text = dict.value(forKey: "text") as! String } } func view() -> UIView { let label = UILabel() label.text = self.text label.textColor = UIColor.red label.frame = self.frame return label }} 新建PlatformTextView.swift 1234567891011import Foundationimport Flutterclass PlatformTextViewFactory: NSObject,FlutterPlatformViewFactory { func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView { return PlatformTextView(frame,viewID: viewId,args: args) } func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol { return FlutterStandardMessageCodec.sharedInstance() }} 修改 AppDelegate.swift 1234567891011121314151617import UIKitimport Flutter@UIApplicationMain@objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) let factory = PlatformTextViewFactory() let registrar = self.registrar(forPlugin: "platform_text_view_plugin") registrar!.register(factory, withId: "platform_text_view") return super.application(application, didFinishLaunchingWithOptions: launchOptions) }} 然后在info.plist中添加: 1<key>io.flutter.embedded_views_preview</key> Android一侧:新建AndroidTextView.kt 1234567891011121314package com.example.platformviewimport android.content.Contextimport android.graphics.Colorimport android.view.Viewimport android.widget.TextViewimport io.flutter.plugin.platform.PlatformViewclass AndroidTextView(context: Context) : PlatformView { val contentView: TextView = TextView(context) override fun getView(): View { return contentView } override fun dispose() {}} 新建AndroidTextViewFactory.kt 1234567891011121314151617181920package com.example.platformviewimport android.content.Contextimport io.flutter.plugin.common.StandardMessageCodecimport io.flutter.plugin.platform.PlatformViewimport io.flutter.plugin.platform.PlatformViewFactoryclass AndroidTextViewFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) { override fun create(context: Context, viewId: Int, args: Any?): PlatformView { val androidTextView = AndroidTextView(context) androidTextView.contentView.id = viewId val params = args?.let { args as Map<*, *> } val text = params?.get("text") as CharSequence? text?.let { androidTextView.contentView.text = it } return androidTextView }} 修改MainActivity.kt: 1234567891011121314package com.example.platformviewimport androidx.annotation.NonNullimport io.flutter.embedding.android.FlutterActivityimport io.flutter.embedding.engine.FlutterEngineimport io.flutter.plugins.GeneratedPluginRegistrantclass MainActivity: FlutterActivity() { override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) val registry = flutterEngine.platformViewsController.registry registry.registerViewFactory("platform_text_view", AndroidTextViewFactory()) }} OK,回到flutter工程中,重新运行即可。 本文源码 其他:Android原生工程中添加Flutter模块 iOS原生工程中添加Flutter模块","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Flutter~发布package到pub.dev图文详解","slug":"移动端学习/Flutter~发布package到pub.dev图文详解","date":"2022-09-28T16:00:00.000Z","updated":"2023-12-20T05:26:01.860Z","comments":true,"path":"2022/09/29/移动端学习/Flutter~发布package到pub.dev图文详解/","link":"","permalink":"https://zhoushaoting.com/2022/09/29/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~%E5%8F%91%E5%B8%83package%E5%88%B0pub.dev%E5%9B%BE%E6%96%87%E8%AF%A6%E8%A7%A3/","excerpt":"使用了挺久的Flutter,今天学习下发布package到pub.dev~","text":"使用了挺久的Flutter,今天学习下发布package到pub.dev~ 首先得准备的内容有:1、翻墙工具 2、如果是第一次上传的话需要一个谷歌账号。 1、首先我们先确定下合适的名字,去到pub官网上搜索下,如果没有该库,那就可以使用。 2、 flutter create --template=package qwerbbb,新建一个package,叫qwerbbb,名字随意。 如果没有example文件夹,可以执行flutter create example 3、在lib里面就可以写你的组件内容了。 途中如果出现无故报红现象可以执行下flutter packages get 4、插件内容写完之后,可以回到example中,写一下例子。 5、回到最外层的pubspec.yaml中,书写该包的name、description、version、homepage,在README.md中写一些包的描述,如: 6、接下来到最外面的这个 LICENSE 文件中,写下许可证, 我这里直接复制一个,修改顶部的title和年份、作者,当然你可以选择想用的许可证文件 123456789101112131415 qwerbbbCopyright <2022> <zhoushaoting>Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.THS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.I 7、先在开始上传,接下来需要你终端翻墙了。翻墙后,执行 flutter packages pub publish --dry-run,如果是首次发布的话,终端会告诉你需要访问一个网址,浏览器访问,使用一个谷歌账号登录即可。 8、然后,终端如果输出Package has 0 warnings那就成功. 9、执行flutter packages pub publish --server=https://pub.dartlang.org,如果有警告,根据信息改后再次执行命令即可 最后,看到Successfully uploaded …….即表示成功。我们复制上面的https://pub.dev/packages/qwebbb即可看到该库,同时稍等片刻后即可以搜索到该库。 打完,吐槽一下,为何这个库无法彻底删除,会一直挂在你的账号下,所幸可以设置为DISCONTINUED区分下。","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Flutter~常用组件源码分析概览","slug":"移动端学习/Flutter~常用组件源码分析概览","date":"2022-08-31T16:00:00.000Z","updated":"2023-12-03T05:13:47.075Z","comments":true,"path":"2022/09/01/移动端学习/Flutter~常用组件源码分析概览/","link":"","permalink":"https://zhoushaoting.com/2022/09/01/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~%E5%B8%B8%E7%94%A8%E7%BB%84%E4%BB%B6%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E6%A6%82%E8%A7%88/","excerpt":"接下来这段时间,学习一下Flutter众多widget的其中几个的源码,大概了解下其基本面貌","text":"接下来这段时间,学习一下Flutter众多widget的其中几个的源码,大概了解下其基本面貌 举几个栗子: MaterialApp Scaffold StatelessWidget StatefulWidget AppBar Text Column Row Image ListView Container。其中,他们的继承关系如下所示: MaterialApp12345class MaterialApp extends StatefulWidget abstract class StatefulWidget extends Widget {abstract class Widget extends DiagnosticableTree {abstract class DiagnosticableTree with Diagnosticable {mixin Diagnosticable { Scaffold123456class Scaffold extends StatefulWidget {abstract class StatefulWidget extends Widget {abstract class Widget extends DiagnosticableTree {abstract class DiagnosticableTree with Diagnosticable {mixin Diagnosticable { StatelessWidget1234abstract class StatelessWidget extends Widget abstract class Widget extends DiagnosticableTree abstract class DiagnosticableTree with Diagnosticable mixin Diagnosticable StatefulWidget1234abstract class StatefulWidget extends Widget abstract class Widget extends DiagnosticableTree abstract class DiagnosticableTree with Diagnosticable mixin Diagnosticable AppBar123456class AppBar extends StatefulWidget implements PreferredSizeWidget {abstract class StatefulWidget extends Widget {abstract class Widget extends DiagnosticableTree {abstract class DiagnosticableTree with Diagnosticable {mixin Diagnosticable { Text123456class Text extends StatelessWidget {abstract class StatelessWidget extends Widget {abstract class Widget extends DiagnosticableTree {abstract class DiagnosticableTree with Diagnosticable {mixin Diagnosticable { Column12345678class Column extends Flex {class Flex extends MultiChildRenderObjectWidget {abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {abstract class RenderObjectWidget extends Widget {abstract class Widget extends DiagnosticableTree {abstract class DiagnosticableTree with Diagnosticable {mixin Diagnosticable { Row12345678class Row extends Flex {class Flex extends MultiChildRenderObjectWidget {abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {abstract class RenderObjectWidget extends Widget {abstract class Widget extends DiagnosticableTree {abstract class DiagnosticableTree with Diagnosticable {mixin Diagnosticable { Image123456class Image extends StatefulWidget {abstract class StatefulWidget extends Widget {abstract class Widget extends DiagnosticableTree {abstract class DiagnosticableTree with Diagnosticable {mixin Diagnosticable { ListView12345678class ListView extends BoxScrollView {abstract class BoxScrollView extends ScrollView {abstract class ScrollView extends StatelessWidget {abstract class StatelessWidget extends Widget {abstract class Widget extends DiagnosticableTree {abstract class DiagnosticableTree with Diagnosticable {mixin Diagnosticable { Container12345class Container extends StatelessWidget {abstract class StatelessWidget extends Widget {abstract class Widget extends DiagnosticableTree {abstract class DiagnosticableTree with Diagnosticable {mixin Diagnosticable { 从以上可以看出来大部分widget都有个通用的继承关系。 123456class XXXX extends StatelessWidget/StatefulWidget {abstract class StatelessWidget/StatefulWidget extends Widget {abstract class Widget extends DiagnosticableTree {abstract class DiagnosticableTree with Diagnosticable {mixin Diagnosticable { ok,那我们就从这点学习:在源码中,StatelessWidget 和StatefulWidget 都继承自 Widget ,Widget 又继承自DiagnosticableTree ,而DiagnosticableTree 类会混入Diagnosticable 那我们先看Widget : 1234567891011121314151617181920212223242526272829303132333435363738394041@immutableabstract class Widget extends DiagnosticableTree { const Widget({ this.key }); final Key? key; @protected @factory Element createElement(); @override String toStringShort() { final String type = objectRuntimeType(this, 'Widget'); return key == null ? type : '$type-$key'; } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense; } @override @nonVirtual bool operator ==(Object other) => super == other; @override @nonVirtual int get hashCode => super.hashCode; static bool canUpdate(Widget oldWidget, Widget newWidget) { return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key; } static int _debugConcreteSubtype(Widget widget) { return widget is StatefulWidget ? 1 : widget is StatelessWidget ? 2 : 0; }} 从第一行开始:发现会有几个注解: @nonVirtual @override @immutable @protected @factory 这个注解唯一作用就是静态分析出来代码类型。 同时这个类是被关键字abstract修饰的,代表该类抽象的:1、抽象类不能被实例化,只有继承它的子类可以。2、抽象类中一般我们把没有方法体的方法称为抽象方法。3、子类继承抽象类必须实现它的抽象方法。4、如果把抽象类当做接口实现的话必须得实现抽象类里面定义的所有属性和方法。 里面有个可选参数key,通过这个key ,我们可以获取到该组件(可以获取到该组件的宽高属性、位置属性、方法、state值等),如: currentContext: 可以找到包括renderBox在内的各种element有关的东西 currentWidget: 可以得到widget的属性 currentState: 可以得到state里面的变量 key的相关使用可见该链接 接着,里面有个抽象方法createElement(): 1234@protected@factoryElement createElement(); 其子类StatelessWidget 继承并重写,返回 StatelessElement 123@overrideStatelessElement createElement() => StatelessElement(this); 其子类StatefulWidget继承并重写,返回 StatefulElement 123@overrideStatefulElement createElement() => StatefulElement(this); 接着,里面有个单纯描述的方法toStringShort : 1234567/// A short, textual description of this widget.@overrideString toStringShort() { final String type = objectRuntimeType(this, 'Widget'); return key == null ? type : '$type-$key';} 然后 12345@overridevoid debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;} 主要是设置DiagnosticableTree 的一些特性,设置properties.defaultDiagnosticsTreeStyle 为DiagnosticsTreeStyle.dense,其中DiagnosticsTreeStyle为一个枚举: 123456789101112131415enum DiagnosticsTreeStyle { none, sparse, offstage, dense, transition, error, whitespace, flat, singleLine, errorProperty, shallow, truncateChildren,} 接下来有个canUpdate 很关键: 12345 static bool canUpdate(Widget oldWidget, Widget newWidget) { return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key;} 这个方法表示什么情况下视图可以更新,只有当 oldWidget.runtimeType == newWidget.runtimeType 和 oldWidget.key == newWidget.key的情况下,widget方可更新(一个widget可以更新的标准是runtimeType和key都相同)。同时,当在setState的本质是调用Element类的markNeedsBuild实现的。 Widget会继承自DiagnosticableTree,而DiagnosticableTree会混入Diagnosticable 。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748abstract class DiagnosticableTree with Diagnosticable { const DiagnosticableTree(); String toStringShallow({ String joiner = ', ', DiagnosticLevel minLevel = DiagnosticLevel.debug, }) { String? shallowString; assert(() { final StringBuffer result = StringBuffer(); result.write(toString()); result.write(joiner); final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); debugFillProperties(builder); result.write( builder.properties.where((DiagnosticsNode n) => !n.isFiltered(minLevel)) .join(joiner), ); shallowString = result.toString(); return true; }()); return shallowString ?? toString(); } String toStringDeep({ String prefixLineOne = '', String? prefixOtherLines, DiagnosticLevel minLevel = DiagnosticLevel.debug, }) { return toDiagnosticsNode().toStringDeep(prefixLineOne: prefixLineOne, prefixOtherLines: prefixOtherLines, minLevel: minLevel); } @override String toStringShort() => describeIdentity(this); @override DiagnosticsNode toDiagnosticsNode({ String? name, DiagnosticsTreeStyle? style }) { return DiagnosticableTreeNode( name: name, value: this, style: style, ); } @protected List<DiagnosticsNode> debugDescribeChildren() => const <DiagnosticsNode>[];} 123456789101112131415161718192021222324252627mixin Diagnosticable { String toStringShort() => describeIdentity(this); @override String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { String? fullString; assert(() { fullString = toDiagnosticsNode(style: DiagnosticsTreeStyle.singleLine).toString(minLevel: minLevel); return true; }()); return fullString ?? toStringShort(); } DiagnosticsNode toDiagnosticsNode({ String? name, DiagnosticsTreeStyle? style }) { return DiagnosticableNode<Diagnosticable>( name: name, value: this, style: style, ); } @protected @mustCallSuper void debugFillProperties(DiagnosticPropertiesBuilder properties) { }} Widget是继承于DiagnosticableTree 的,关于DiagnosticableTree 这个类,它主要用于在调试时获取子类的各种属性和children信息,在flutter各个对象中你经常能看到它,目前我们不需要去关心与之相关的内容。我们可以看到,Widget 是一个抽象类;同时它被immutable 注解修饰,说明它的各个属性一定是不可变的,这就是为什么我们写各种Widget 时,所写的各个属性要加 final 的原因,否则编译器就会发出警告。 参考文献: 从源码看flutter(一):Widget篇参考文献: abstract","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Flutter~riverpod库学习","slug":"移动端学习/Flutter~riverpod库学习","date":"2022-07-31T16:00:00.000Z","updated":"2023-12-20T05:53:19.513Z","comments":true,"path":"2022/08/01/移动端学习/Flutter~riverpod库学习/","link":"","permalink":"https://zhoushaoting.com/2022/08/01/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~riverpod%E5%BA%93%E5%AD%A6%E4%B9%A0/","excerpt":"今天学习下Flutter中state管理的第三方riverpod,该库的作者和provider的作者是同一人,可以说是provider的重构版本。","text":"今天学习下Flutter中state管理的第三方riverpod,该库的作者和provider的作者是同一人,可以说是provider的重构版本。 代码结构如图所示: 下面是各文件的源码.main.dart1234567891011121314151617181920212223242526272829import 'package:flutter/material.dart';import 'package:riverpod_demo/tabbar.dart';import 'package:flutter_riverpod/flutter_riverpod.dart';void main() { runApp( const ProviderScope( child: MyApp(), ), );}class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const TabBarWidget(), ); }} tabbar.dart123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384import 'package:flutter/material.dart';import 'package:riverpod_demo/pages/one_page/one_page.dart';import 'package:riverpod_demo/pages/two_page/two_page.dart';class TabBarWidget extends StatefulWidget { const TabBarWidget({Key? key}) : super(key: key); @override State<StatefulWidget> createState() { return MainPageState(); }}class MainPageState extends State<TabBarWidget> { @override void initState() { super.initState(); initData(); } int _tabIndex = 0; List tabImages = []; var appBarTitles = ['One', 'Two']; List _pageList = []; Image getTabIcon(int curIndex) { if (curIndex == _tabIndex) { return tabImages[curIndex][1]; } return tabImages[curIndex][0]; } String getTabTitle(int curIndex) { if (curIndex == _tabIndex) { return appBarTitles[curIndex]; } else { return appBarTitles[curIndex]; } } Image getTabImage(path) { return Image.asset(path, width: 24.0, height: 24.0); } void initData() { /* * 初始化选中和未选中的icon */ tabImages = [ [getTabImage('images/tab/home.png'), getTabImage('images/tab/home_select.png')], [getTabImage('images/tab/show.png'), getTabImage('images/tab/show_select.png')], ]; /* * 子界面 */ _pageList = [ const OnePage(), const TwoPage(), ]; } @override Widget build(BuildContext context) { return Scaffold( body: _pageList[_tabIndex], bottomNavigationBar: BottomNavigationBar( items: <BottomNavigationBarItem>[ BottomNavigationBarItem(icon: getTabIcon(0), label: getTabTitle(0)), BottomNavigationBarItem(icon: getTabIcon(1), label: getTabTitle(1)), ], type: BottomNavigationBarType.fixed, currentIndex: _tabIndex, iconSize: 24.0, onTap: (index) { setState(() { _tabIndex = index; }); }, ), ); }} color.dart1234567891011121314151617181920212223import 'dart:math';import 'package:flutter/material.dart';import 'package:flutter_riverpod/flutter_riverpod.dart';final colorProvider = StateNotifierProvider.autoDispose<ColorNotifier, Color>( (ref) => ColorNotifier(),);class ColorNotifier extends StateNotifier<Color> { ColorNotifier() : super(Colors.red); static const _colors = [ Colors.red, Colors.blue, Colors.yellow, Colors.green, ]; void changeColor() => state = _colors[Random().nextInt(_colors.length)];} counter_provider.dart12345678910111213import 'package:flutter_riverpod/flutter_riverpod.dart';final counterProvider = StateNotifierProvider.autoDispose<CounterNotifier, int>( (ref) => CounterNotifier(),);class CounterNotifier extends StateNotifier<int> { CounterNotifier() : super(0); void addCounter() => state = state + 1; void subCounter() => state = state - 1;} one_page.dart123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657import 'package:flutter/material.dart';import 'package:riverpod_demo/rstate/counter.dart';import 'package:flutter_riverpod/flutter_riverpod.dart';class OnePage extends ConsumerWidget { const OnePage({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { return Scaffold( appBar: AppBar( title: const Text('一'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ InkWell( onTap: () { ref.read(counterProvider.notifier).addCounter(); }, child: const Text( '+', style: TextStyle(fontSize: 40), ), ), const _ColorfulCounterText(), InkWell( onTap: () { ref.read(counterProvider.notifier).subCounter(); }, child: const Text( '-', style: TextStyle(fontSize: 40), ), ), ], ), ), ); }}class _ColorfulCounterText extends ConsumerWidget { const _ColorfulCounterText(); @override Widget build(BuildContext context, WidgetRef ref) { final counter = ref.watch(counterProvider); return Text( '$counter', ); }} two_page.dart1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465import 'package:flutter/material.dart';import 'package:flutter_riverpod/flutter_riverpod.dart';import 'package:riverpod_demo/rstate/counter.dart';import 'package:riverpod_demo/rstate/color.dart';class TwoPage extends ConsumerWidget { const TwoPage({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { return Scaffold( appBar: AppBar( backgroundColor: Colors.pink, title: const Text('二'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ InkWell( onTap: () { ref.read(counterProvider.notifier).addCounter(); ref.read(colorProvider.notifier).changeColor(); }, child: const Text( '+', style: TextStyle(fontSize: 40), ), ), const _ColorfulCounterText(), InkWell( onTap: () { ref.read(counterProvider.notifier).subCounter(); ref.read(colorProvider.notifier).changeColor(); }, child: const Text( '-', style: TextStyle(fontSize: 40), ), ), ], ), ), ); }}class _ColorfulCounterText extends ConsumerWidget { const _ColorfulCounterText(); @override Widget build(BuildContext context, WidgetRef ref) { final counter = ref.watch(counterProvider); final color = ref.watch(colorProvider); return Text( '$counter', style: TextStyle( color: color, ), ); }} 效果图: 文章源码 另外:其他管理库学习 另外:Flutter学习demo","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"React学习:React_Hooks学习","slug":"前端学习/React学习~React-Hooks学习","date":"2021-08-21T16:00:00.000Z","updated":"2023-12-20T05:16:55.959Z","comments":true,"path":"2021/08/22/前端学习/React学习~React-Hooks学习/","link":"","permalink":"https://zhoushaoting.com/2021/08/22/%E5%89%8D%E7%AB%AF%E5%AD%A6%E4%B9%A0/React%E5%AD%A6%E4%B9%A0~React-Hooks%E5%AD%A6%E4%B9%A0/","excerpt":"今天尝试下React Hooks的学习.","text":"今天尝试下React Hooks的学习. 什么是React Hooks?React Hooks和传统的开发模式有何不同?使用React Hooks有哪些好处? 什么是React Hooks? 官方解释:Hook 是 React 16.8 的新增特性,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性,如:保存状态、保存上下文、保存引用等.Hook 本质就是 JavaScript 函数.说白了就是对于函数组件的加强,增加了对于函数组件的一些特性,像class组件那样使用,是一套全新的API或者设计思想. 它和传统开发模式有何异同?     官方推荐使用钩子(函数),而不是类。因为钩子更简洁,代码量少,用起来比较\"轻\",而类比较\"重\"。而且,钩子是函数,更符合 React 函数式的本质。 类(class)是数据和逻辑的封装。 也就是说,组件的状态和操作方法是封装在一起的。如果选择了类的写法,就应该把相关的数据和操作,都写在同一个 class 里面。     Hook 这个单词的意思是\"钩子\"。React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码\"钩\"进来。 React Hooks 就是那些钩子。你需要什么功能,就使用什么钩子。React 默认提供了一些常用钩子,你也可以封装自己的钩子。所有的钩子都是为函数引入外部功能,所以 React 约定,钩子一律使用use前缀命名,便于识别。你要使用 xxx 功能,钩子就命名为 usexxx。     Redux 的作者 Dan Abramov 总结了组件类的几个缺点。 12345大型组件很难拆分和重构,也很难测试。业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。组件类引入了复杂的编程模式,比如 render props 和高阶组件。     吹得都上天了,其实 御姐小萝莉,各有所爱,各有所长.据官方资料来说: 1234567完全可选的。 你无需重写任何已有代码就可以在一些组件中尝试 Hook。但是如果你不想,你不必现在就去学习或使用 Hook。100% 向后兼容的。 Hook 不包含任何破坏性改动。现在可用。 Hook 已发布于 v16.8.0。没有计划从 React 中移除 class。Hook 不会影响你对 React 概念的理解。 恰恰相反,Hook 为已知的 React 概念提供了更直接的 API:props, state,context,refs 以及生命周期。 实操一把 准备工作: 1、react版本需 16.8.0以上. 2、使用 create-react-app或 npm 或yarn等方式新建一个最基本的项目即可,如:npx create-react-app my-app 传统模式-->Example-class.js: 123456789101112131415161718192021222324import React,{Component} from 'react'// class的基本演示class ExampleClass extends Component { constructor(props){ super(props); this.state = {count:0} } // 组件第一次执行 componentDidMount(){ console.log('ExampleClass执行了') } render(){ return ( <div> <p>class简单使用</p> <p>{this.state.count}</p> <button onClick={()=> {this.setState({count:this.state.count+1})}}>+1</button> <button onClick={()=> {this.setState({count:this.state.count-1})}}>-1</button> </div> ) }}export default ExampleClass; useState事例: 12345678910111213141516171819202122import React, { useState,useEffect } from 'react'// useState 简单使用function Example1() { const [count, setCount] = useState(0); // 此处的useEffect相当于componentDidMount 和 componentDidUpdate useEffect(()=>{ console.log('Example1下 useEffect执行了'); }) return <div> <p>useState的简单使用</p> <p> {count}</p> <button onClick={() => { setCount(count + 1) }}>+1</button> <button onClick={() => { setCount(count - 1) }}>-1</button> </div>}export default Example1; useEffect: 123456789101112131415161718192021222324252627282930313233343536import React, { useState ,useEffect} from 'react'function Example2() { const [count, setCount] = useState(0); const [color, setColor] = useState('red'); // 此处的useEffect相当于componentDidMount 和 color的componentDidUpdate useEffect(()=>{ console.log('------切换color--------'); },[color]) // 此处的useEffect相当于componentDidMount 和 count的componentDidUpdate useEffect(()=>{ console.log('点击count'); },[count]) function changeColor(){ if(color === 'yellow'){ setColor('red'); }else{ setColor('yellow'); } } return <div style={{background:color}}> <p>useEffect简单使用</p> <p> {count}</p> <button onClick={() => { setCount(count + 1) }}>+1</button> <button onClick={() => { setCount(count - 1) }}>-1</button> <button onClick={() => changeColor()}>切换颜色</button> </div> }export default Example2; useEffect代替componentWillUnmount使用(需要安装react-router-dom): 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647import React, { useEffect } from 'react'import { BrowserRouter as Router, Route, Link } from 'react-router-dom';function Index1() { useEffect(() => { console.log('组件1componentDidMount '); return () => { console.log('组件1componentWillUnmount'); } }) return <div> <h2>组件1</h2> </div>}function Index2() { useEffect(() => { console.log('组件2componentDidMount '); return () => { console.log('组件2componentWillUnmount'); } }) return <div> <h2>组件2</h2> </div>}function Example3() { return <div> <p>useEffect代替componentWillUnmount使用</p> <Router> <ul> <li><Link to="/">组件1</Link></li> <li><Link to="/two/">组件2</Link></li> </ul> <Route path="/" exact component={Index1} /> <Route path="/two/" component={Index2} /> </Router> </div>}export default Example3; useContext的简单使用:父子之间传递数据: 12345678910111213141516171819202122232425262728import React, { useState,createContext,useContext } from 'react'// useState 简单使用function Example4() { const [count, setCount] = useState(0); const CountContext = createContext(); function Child(){ let count = useContext(CountContext); return ( <h1>我是子组件{count}</h1> ) } return <div> <p>useContext的简单使用:父子之间传递数据</p> <p>我是父组件: {count}</p> <button onClick={() => { setCount(count + 1) }}>+1</button> <button onClick={() => { setCount(count - 1) }}>-1</button> <CountContext.Provider value={count}> <Child /> </CountContext.Provider> </div>}export default Example4; useRef使用: 12345678910111213141516171819import React, { useState,useRef } from 'react'// useRef 简单使用function Example5() { const [inputVal,setInputVal] = useState('') const ref = useRef(); function inputAction (){ console.log(ref.current.value); setInputVal(ref.current.value); } return <div> <p>useRef使用:{inputVal}</p> <input onChange={inputAction} ref={ref} /> </div>}export default Example5; useReducer的简单使用: 12345678910111213141516171819202122232425262728import React, { useReducer } from 'react'; const initialState = 0;const reducer = (state, action) => { switch (action) { case 'increment': return state + 1; case 'decrement': return state - 1; case 'reset': return 0; default: throw new Error('Unexpected action'); }}; const Example6 = () => { const [count, dispatch] = useReducer(reducer, initialState); return ( <div> <p>useReducer的简单使用</p> {count} <button onClick={() => dispatch('increment')}>+1</button> <button onClick={() => dispatch('decrement')}>-1</button> <button onClick={() => dispatch('reset')}>reset</button> </div> );}; export default Example6; useMemo提升性能: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849import React, { useState, useMemo } from 'react'// useState 简单使用function Example7() { const [name, setName] = useState('name'); const [content, setContent] = useState('content'); return ( <div> <div>useMemo提升性能</div> <button onClick={() => setName(new Date().getTime())}>{name}</button> <button onClick={() => setContent(new Date().getTime())}>{content}</button> <Child name={name}>{content}</Child> </div> )}// 不使用useMemo,会造成不必要的性能开销// function Child({name,children}){// function changeName(name){// console.log('child执行了');// return name + '改变';// }// const otherName = changeName(name);// return (// <div>// <div>{otherName}</div>// <div>{children}</div> // </div>// )// } // 使用useMemo function Child({ name, children }) { function changeName(name) { console.log('child执行了'); return name + '改变'; } const otherName = useMemo(() => changeName(name),[name]) return ( <div> <div>{otherName}</div> <div>{children}</div> </div> ) }export default Example7; 自定义Hook函数: 1234567891011121314151617181920212223242526272829303132333435import React, { useState,useEffect,useCallback } from 'react'function useWinSize(){ const [size,setSize] = useState({ width:document.documentElement.clientWidth, height:document.documentElement.clientHeight, }) const onResize = useCallback(()=>{ setSize({ width:document.documentElement.clientWidth, height:document.documentElement.clientHeight, }) },[]) useEffect(()=>{ window.addEventListener('resize',onResize); return ()=>{ window.removeEventListener('resize',onResize); } }) return size;}function Example8() { const size = useWinSize(); return <div> <p>自定义Hook函数</p> <p>页面size:{size.width} + {size.height}</p> </div> } export default Example8; ps:入口文件就只是引入而已.index.js 123456789101112131415161718192021222324252627282930313233343536import React from 'react';import ReactDOM from 'react-dom';import './index.css';import Example1 from './Example1';import Example2 from './Example2';import Example3 from './Example3';import Example4 from './Example4';import Example5 from './Example5';import Example6 from './Example6';import Example7 from './Example7'; import Example8 from './Example8'; import ExampleClass from './Example-class';ReactDOM.render( <div> {/* <ExampleClass /> */} <Example1 /> <hr/> <Example2 /> <hr/> <Example3/> <hr/> <Example4/> <hr/> <Example5/> <hr/> <Example6/> <hr/> <Example7/> <hr/> <Example8/> </div>, document.getElementById('root')); React Hooks 的优点 12345678通过 Hooks 我们可以对 state 逻辑进行良好的封装,轻松做到隔离和复用,优点主要体现在:复用代码更容易:hooks 是普通的 JavaScript 函数,所以开发者可以将内置的 hooks 组合到处理 state 逻辑的自定义 hooks中,这样复杂的问题可以转化一个单一职责的函数,并可以被整个应用或者 React 社区所使用;使用组合方式更优雅:不同于 render props 或高阶组件等的模式,hooks 不会在组件树中引入不必要的嵌套,也不会受到 mixins 的负面影响;更少的代码量:一个 useEffect 执行单一职责,可以干掉生命周期函数中的重复代码。避免将同一职责代码分拆在几个生命周期函数中,更好的复用能力可以帮助优秀的开发者最大限度降低代码量;代码逻辑更清晰:hooks 帮助开发者将组件拆分为功能独立的函数单元,轻松做到“分离关注点”,代码逻辑更加清晰易懂;单元测试:处理 state 逻辑的自定义 hooks 可以被独立进行单元测试,更加可靠; 整理自: React Hooks完全上手指南-蚂蚁 RichLab 前端团队 React Hooks 入门教程-阮一峰 轻松学会 React 钩子:以 useEffect() 为例-阮一峰 bilibili 本文源码","categories":[{"name":"前端学习","slug":"前端学习","permalink":"https://zhoushaoting.com/categories/%E5%89%8D%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"前端学习","slug":"前端学习","permalink":"https://zhoushaoting.com/tags/%E5%89%8D%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Flutter~getx库学习","slug":"移动端学习/Flutter~getx学习","date":"2021-03-12T16:00:00.000Z","updated":"2023-12-20T05:42:04.903Z","comments":true,"path":"2021/03/13/移动端学习/Flutter~getx学习/","link":"","permalink":"https://zhoushaoting.com/2021/03/13/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~getx%E5%AD%A6%E4%B9%A0/","excerpt":"今天学习下Flutter中state管理的第三方getx.其中,get: 3.26.0","text":"今天学习下Flutter中state管理的第三方getx.其中,get: 3.26.0 官方文档 例子展示tabbar、state管理、国际化、跳转.效果如图 各文件如下main.dart12345678910111213141516171819202122232425import 'package:flutter/material.dart';import 'package:get/get.dart';import './languages.dart';import 'routes/app_pages.dart';import 'routes/app_routes.dart';void main() { runApp(MyApp());}class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return GetMaterialApp( translations: Languages(), // 你的翻译 locale: Locale('zh', 'CN'), // 将会按照此处指定的语言翻译 fallbackLocale: Locale('en', 'US'), // 添加一个回调语言选项,以备上面指定的语言翻译不存在 initialRoute: AppRoutes.DASHBOARD, getPages: AppPages.list, debugShowCheckedModeBanner: false, themeMode: ThemeMode.system, ); }} main.dart是入口文件. languages.dart123456789101112131415161718import 'package:get/get.dart';class Languages extends Translations { @override Map<String, Map<String, String>> get keys => { 'zh_CN': { 'hello': '你好 世界', 'one': '首页', 'two': '我的', }, 'en_US': { 'hello': 'Hello World', 'one': 'Home', 'two': 'Mine', } };} languages.dart文件是国际化的配置文件. routes文件夹下: routes -> app_pages.dart12345678910111213141516import 'package:get/get.dart';import 'package:getx_demo/pages/dashboard/dashboard_binding.dart';import 'package:getx_demo/pages/dashboard/dashboard_page.dart';import 'app_routes.dart';class AppPages { static var list = [ GetPage( name: AppRoutes.DASHBOARD, page: () => DashboardPage(), binding: DashboardBinding(), ), ];} routes -> app_routes.dart1234class AppRoutes { static const String DASHBOARD = '/';} pages文件夹如下:先是tabbar dashboard -> dashboard_binding.dart1234567891011121314import 'package:get/get.dart';import 'package:getx_demo/pages/account/account_controller.dart';import 'package:getx_demo/pages/home/home_controller.dart';import 'dashboard_controller.dart';class DashboardBinding extends Bindings { @override void dependencies() { Get.lazyPut<DashboardController>(() => DashboardController()); Get.lazyPut<HomeController>(() => HomeController()); Get.lazyPut<AccountController>(() => AccountController()); }} dashboard -> dashboard_controller.dart12345678910import 'package:get/get.dart';class DashboardController extends GetxController { var tabIndex = 0; void changeTabIndex(int index) { tabIndex = index; update(); }} dashboard -> dashboard_page.dart12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152import 'package:flutter/cupertino.dart';import 'package:flutter/material.dart';import 'package:get/get.dart';import 'package:getx_demo/pages/account/account_page.dart';import 'package:getx_demo/pages/home/home_page.dart';import 'dashboard_controller.dart';class DashboardPage extends StatelessWidget { @override Widget build(BuildContext context) { return GetBuilder<DashboardController>( builder: (controller) { return Scaffold( body: IndexedStack( index: controller.tabIndex, children: [ HomePage(), AccountPage(), ], ), bottomNavigationBar: BottomNavigationBar( unselectedItemColor: Colors.black, selectedItemColor: Colors.redAccent, onTap: controller.changeTabIndex, currentIndex: controller.tabIndex, // showSelectedLabels: false, // showUnselectedLabels: false, // type: BottomNavigationBarType.fixed, backgroundColor: Colors.white, elevation: 0, items: [ _bottomNavigationBarItem( icon: CupertinoIcons.home, title: Text('one'.tr), ), _bottomNavigationBarItem( icon: CupertinoIcons.person, title: Text('two'.tr), ), ], ), ); }, ); } _bottomNavigationBarItem({IconData icon, Widget title}) { return BottomNavigationBarItem(icon: Icon(icon), title: title); }} 第一个Tab:home home -> home_controller.dart12345678910import 'package:get/get.dart';class HomeController extends GetxController { final String title = 'Home Title'; var lgroupValue = '中文'.obs; void changeValue(value) { lgroupValue.value = value; }} home -> home_page.dart123456789101112131415161718192021222324252627282930313233343536373839404142import 'package:flutter/material.dart';import 'package:get/get.dart';import 'home_controller.dart';class HomePage extends GetView<HomeController> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('首页'), ), body: ListView( children: [ Text( 'hello'.tr, style: TextStyle(fontSize: 20), ), RadioListTile( value: '中文', groupValue: controller.lgroupValue.toString(), title: Text('中文'), onChanged: (type) { var locale = Locale('zh', 'CN'); Get.updateLocale(locale); controller.changeValue('中文'); }, ), RadioListTile( value: '英文', groupValue: controller.lgroupValue.toString(), title: Text('英文'), onChanged: (type) { var locale = Locale('en', 'US'); Get.updateLocale(locale); controller.changeValue('英文'); }, ) ], ), ); }} 再是第二个Tab:account account -> account_controller.dart1234567891011import 'package:get/get.dart';class AccountController extends GetxController { var counter = 0.obs; String name = '名字'; int age = 18; void increaseCounter() { counter.value += 1; }} account -> account_page.dart123456789101112131415161718192021222324252627282930313233343536373839404142434445464748import 'package:flutter/material.dart';import 'package:get/get.dart';import 'package:getx_demo/pages/account/second/second_page.dart';import 'account_controller.dart';class AccountPage extends GetView<AccountController> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('个人'), ), body: Container( child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Obx( () => Text("结果 ${controller.counter.value}"), ), InkWell( onTap: () => controller.increaseCounter(), child: Text( '加1', style: TextStyle(color: Colors.red, fontSize: 20), ), ), InkWell( onTap: () { Get.to( () => SecondPage( name: controller.name, age: controller.age, ), ); }, child: Text('跳转二级'), ), ], ), ), ), ); }} 接下来,是account下的二级界面:second account -> second_page.dart123456789101112131415161718192021222324252627282930313233343536373839404142import 'package:flutter/material.dart';import 'package:get/get.dart';import '../account_controller.dart';class SecondPage extends GetView<AccountController> { final String name; final int age; SecondPage({Key key, @required this.name, this.age}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('二级'), ), body: Container( child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( '参数--' + this.name + this.age.toString(), ), Obx( () => Text("结果 ${controller.counter.value}"), ), InkWell( onTap: () => controller.increaseCounter(), child: Text( '加1', style: TextStyle(color: Colors.red, fontSize: 20), ), ), ], ), ), ), ); }} 源码 另外:其他管理库学习 另外:Flutter学习demo","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Flutter~provider学习","slug":"移动端学习/Flutter~provider学习","date":"2021-01-05T16:00:00.000Z","updated":"2023-12-20T05:52:01.941Z","comments":true,"path":"2021/01/06/移动端学习/Flutter~provider学习/","link":"","permalink":"https://zhoushaoting.com/2021/01/06/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~provider%E5%AD%A6%E4%B9%A0/","excerpt":"今天学习下Flutter中state管理的第三方provider的4.x新版本.其中,provider: 4.3.2+2, 3.1.0旧版本在文章末尾。","text":"今天学习下Flutter中state管理的第三方provider的4.x新版本.其中,provider: 4.3.2+2, 3.1.0旧版本在文章末尾。 代码结构如图所示: 下面是各文件的源码.main.dart12345678910111213141516171819202122232425262728293031323334353637383940import 'package:flutter/material.dart';import 'package:provider/provider.dart';import 'package:provider_demo1/tabbar.dart';import 'package:provider_demo1/provider/color_provider.dart';import 'package:provider_demo1/provider/counter_provider.dart';void main() { var colorProvider = ColorProvider(); var counterProvider = CounterProvider(); Provider.debugCheckInvalidValueType = null; runApp(MultiProvider( providers: [ ChangeNotifierProvider.value(value: colorProvider), ChangeNotifierProvider.value(value: counterProvider), ], child: MyApp(), ));}class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return Consumer<CounterProvider>( builder: (context, model, child) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: Tabbar(), ); }, ); }} tabbar.dart123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107import 'package:flutter/material.dart';import 'package:provider_demo1/pages/one_pege.dart';import 'package:provider_demo1/pages/two_page.dart';class Tabbar extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( debugShowCheckedModeBanner: false, home: new MainPageWidget()); }}class MainPageWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return new MainPageState(); }}class MainPageState extends State<MainPageWidget> { int _tabIndex = 0; var tabImages; var appBarTitles = ['One', 'Two']; /* * 存放二个页面,跟fragmentList一样 */ var _pageList; /* * 根据选择获得对应的normal或是press的icon */ Image getTabIcon(int curIndex) { if (curIndex == _tabIndex) { return tabImages[curIndex][1]; } return tabImages[curIndex][0]; } /* * 获取bottomTab的颜色和文字 */ String getTabTitle(int curIndex) { if (curIndex == _tabIndex) { return appBarTitles[curIndex]; } else { return appBarTitles[curIndex]; } } /* * 根据image路径获取图片 */ Image getTabImage(path) { return new Image.asset(path, width: 24.0, height: 24.0); } void initData() { /* * 初始化选中和未选中的icon */ tabImages = [ [ getTabImage('images/tab/home.png'), getTabImage('images/tab/home_select.png') ], [ getTabImage('images/tab/show.png'), getTabImage('images/tab/show_select.png') ], ]; /* * 子界面 */ _pageList = [ new OnePage(), new TwoPage(), ]; } @override Widget build(BuildContext context) { //初始化数据 initData(); return Scaffold( body: _pageList[_tabIndex], bottomNavigationBar: new BottomNavigationBar( items: <BottomNavigationBarItem>[ new BottomNavigationBarItem( icon: getTabIcon(0), label: getTabTitle(0)), new BottomNavigationBarItem( icon: getTabIcon(1), label: getTabTitle(1)), ], type: BottomNavigationBarType.fixed, //默认选中首页 currentIndex: _tabIndex, iconSize: 24.0, //点击事件 onTap: (index) { setState(() { _tabIndex = index; }); }, )); }} color_provider.dart12345678910111213import 'package:flutter/material.dart';class ColorProvider with ChangeNotifier { Color _color = Colors.red; Color get color => _color; changeColor() { _color = _color == Colors.red ? Colors.blue : Colors.red; notifyListeners(); }} counter_provider.dart12345678910111213141516171819import 'package:flutter/material.dart';class CounterProvider with ChangeNotifier { int _count = 0; int get count => _count; addCounter() { _count++; notifyListeners(); } subCounter() { _count--; notifyListeners(); }} one_page.dart123456789101112131415161718192021222324252627282930313233343536373839404142434445import 'package:flutter/material.dart';import 'package:provider/provider.dart';import 'package:provider_demo1/provider/color_provider.dart';import 'package:provider_demo1/provider/counter_provider.dart';class OnePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('One'), ), body: Consumer<CounterProvider>( builder: (context, model, child) { return ListView( children: [ IconButton( icon: Icon(Icons.add), onPressed: () { model.addCounter(); }, ), Chip( backgroundColor: Provider.of<ColorProvider>(context).color, padding: EdgeInsets.all(12.0), label: Text( model.count.toString(), style: TextStyle(fontSize: 30.0), ), ), IconButton( icon: Icon(Icons.remove), onPressed: () { model.subCounter(); }, ), ], ); }, ), ); }} two_page.dart123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354import 'package:flutter/material.dart';import 'package:provider/provider.dart';import 'package:provider_demo1/provider/color_provider.dart';import 'package:provider_demo1/provider/counter_provider.dart';class TwoPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Two'), ), body: Consumer<CounterProvider>( builder: (context, model, child) { return ListView( children: [ IconButton( icon: Icon(Icons.add), onPressed: () { model.addCounter(); }, ), Chip( backgroundColor: Provider.of<ColorProvider>(context).color, padding: EdgeInsets.all(12.0), label: Text( model.count.toString(), style: TextStyle(fontSize: 30.0), ), ), IconButton( icon: Icon(Icons.remove), onPressed: () { model.subCounter(); }, ), ], ); }, ), floatingActionButton: FloatingActionButton( onPressed: () { Provider.of<ColorProvider>(context, listen: false).changeColor(); }, child: Icon( Icons.build, color: Colors.white, ), ), ); }} 效果图: <——————————–2023-11-02,再写一下对provider的封装使用———————————–> 封装成这样: provider_widget.dart 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113// ignore_for_file: prefer_const_constructors_in_immutablesimport 'package:flutter/material.dart';import 'package:provider/provider.dart';/// Provider封装类 方便数据初始化class ProviderWidget<T extends ChangeNotifier> extends StatefulWidget { final ValueWidgetBuilder<T> builder; final T model; final Widget? child; final Function(T model)? onModelReady; final bool autoDispose; ProviderWidget({ Key? key, required this.builder, required this.model, this.child, this.onModelReady, this.autoDispose = true, }) : super(key: key); _ProviderWidgetState<T> createState() => _ProviderWidgetState<T>();}class _ProviderWidgetState<T extends ChangeNotifier> extends State<ProviderWidget<T>> { late T model; @override void initState() { model = widget.model; widget.onModelReady?.call(model); super.initState(); } @override void dispose() { if (widget.autoDispose) model.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return ChangeNotifierProvider<T>.value( value: model, child: Consumer<T>( builder: widget.builder, child: widget.child, ), ); }}class ProviderWidget2<A extends ChangeNotifier, B extends ChangeNotifier> extends StatefulWidget { final Widget Function(BuildContext context, A model1, B model2, Widget? child) builder; final A model1; final B model2; final Widget? child; final Function(A model1, B model2)? onModelReady; final bool autoDispose; ProviderWidget2({ Key? key, required this.builder, required this.model1, required this.model2, this.child, this.onModelReady, this.autoDispose = true, }) : super(key: key); _ProviderWidgetState2<A, B> createState() => _ProviderWidgetState2<A, B>();}class _ProviderWidgetState2<A extends ChangeNotifier, B extends ChangeNotifier> extends State<ProviderWidget2<A, B>> { late A model1; late B model2; @override void initState() { model1 = widget.model1; model2 = widget.model2; widget.onModelReady?.call(model1, model2); super.initState(); } @override void dispose() { if (widget.autoDispose) { model1.dispose(); model2.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider<A>.value(value: model1), ChangeNotifierProvider<B>.value(value: model2), ], child: Consumer2<A, B>( builder: widget.builder, child: widget.child, )); }} 使用:逻辑部分如下: home_vm.dart 123456789101112131415161718192021222324252627/* * @Author: zhoushaoting [email protected] * @Date: 2023-11-02 16:29:16 * @LastEditors: zhoushaoting [email protected] * @LastEditTime: 2023-11-02 16:40:03 * @FilePath: /provider_init/lib/home/home_vm.dart * @Description: 逻辑 */import 'package:flutter/material.dart';class HomeVM extends ChangeNotifier{ int num = 0; void add(){ num++; notifyListeners(); } void minus(){ num--; notifyListeners(); }} UI部分如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768/* * @Author: zhoushaoting [email protected] * @Date: 2023-11-02 16:29:16 * @LastEditors: zhoushaoting [email protected] * @LastEditTime: 2023-11-02 16:50:46 * @FilePath: /provider_init/lib/home/home_page.dart * @Description: UI */import 'package:flutter/material.dart';import 'package:provider_init/home/home_vm.dart';import 'package:provider_init/utils/provider_widget.dart';class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState();}class _HomePageState extends State<HomePage> { late final HomeVM _homeVM = HomeVM(); @override void dispose() { _homeVM.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Home Page'), ), body: ProviderWidget<HomeVM>( autoDispose: false, model: _homeVM, builder: (context, ref, child) { return Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ InkWell( onTap: () => ref.add(), child: Container( height: 50, alignment: Alignment.center, child: const Text('+'), ), ), Text('${ref.num}'), InkWell( onTap: () => ref.minus(), child: Container( height: 50, alignment: Alignment.center, child: const Text('-'), ), ), ], ); }, ), ); }} 功能和之前的一样,但是这样更加清晰,UI和逻辑完全分开,也和之前一样不再使用setSatte直接刷新build。 文章源码 provider的封装使用源码 3.1.0版本源码 另外:其他管理库学习 另外:Flutter学习demo","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Flutter~fish_redux学习","slug":"移动端学习/Flutter~fish_redux学习","date":"2020-04-10T16:00:00.000Z","updated":"2023-12-20T05:39:39.938Z","comments":true,"path":"2020/04/11/移动端学习/Flutter~fish_redux学习/","link":"","permalink":"https://zhoushaoting.com/2020/04/11/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~fish_redux%E5%AD%A6%E4%B9%A0/","excerpt":"今天,学习下阿里出品的数据管理库:fish_redux。","text":"今天,学习下阿里出品的数据管理库:fish_redux。      在成品demo中,使用了fish_redux、网络请求dio、tabbar、数据模型model、基本传值、component、globalStrore、adapter。已经完成了一个基本项目的架子。先看下完成之后的效果图:💡:简介在一开始,需要先行了解下fish_redux为何物?何用?使用场景?何物?   Fish Redux是一个基于 Redux 数据管理的组装式 flutter 应用框架, 特别适用于构建中大型的复杂应用,它最显著的特征是 函数式的编程模型、可预测的状态管理、可插拔的组件体系、最佳的性能表现。何用?   管理项目的数据流、在redux的中心思想前提下,\b\b降低耦合度和提高可扩展性,解决了【集中】和【分治】之间的矛盾,同时对 Reducer 的手动层层 Combine 变成由框架自动完成,大大简化了使用 Redux 的困难。使用场景?   中大型的复杂应用。鲁迅说过大项目越使用越方便,小项目越使用越恶心。参考资料: fish redux pdf 阿里fish redux视频 阿里fish redux初识 阿里fish redux中文介绍 💡:分层架构图fish_redux由redux改良而来:ReduxRedux 是来自前端社区的一个数据管理框架,对 Native开发同学来说可能会有一点陌生,我们做一个简单的介绍。Redux 是做什么的?Redux 是一个用来做[可预测][集中式][易调试][灵活性]的数据管理的框架。所有对数据的增删改查等操作都由 Redux 来集中负责。Redux 是怎么设计和实现的?Redux 是一个函数式的数据管理的框架。传统 OOP 做数据管理,往往是定义一些 Bean,每一个 Bean 对外暴露一些 Public-API 用来操作内部数据(充血模型)。函数式的做法是更上一个抽象的纬度,对数据的定义是一些 Struct(贫血模型),而操作数据的方法都统一到具有相同函数签名 (T, Action) => T 的 Reducer 中。FP:Struct(贫血模型) + Reducer = OOP:Bean(充血模型)同时 Redux 加上了 FP 中常用的 Middleware(AOP) 模式和 Subscribe 机制,给框架带了极高的灵活性和扩展性。贫血模型、充血模型请参考:https://en.wikipedia.org/wiki/Plain_old_Java_objectRedux 的缺点Redux 核心仅仅关心数据管理,不关心具体什么场景来使用它,这是它的优点同时也是它的缺点。在我们实际使用 Redux 中面临两个具体问题:• Redux 的集中和 Component 的分治之间的矛盾;• Redux 的 Reducer 需要一层层手动组装,带来的繁琐性和易错性。Fish Redux 的改良Fish Redux 通过 Redux 做集中化的可观察的数据管理。然不仅于此,对于传统 Redux 在使用层面上的缺点,在面向端侧 flutter 页面纬度开发的场景中,我们通过更好更高的抽象,做了改良。一个组件需要定义一个数据(Struct)和一个 Reducer。同时组件之间存在着父依赖子的关系。通过这层依赖关系,我们解决了【集中】和【分治】之间的矛盾,同时对 Reducer 的手动层层 Combine 变成由框架自动完成,大大简化了使用 Redux 的困难。我们得到了理想的集中的效果和分治的代码。💡:构成  1、数据 核心部分。\b定义了组件需要用到的数据,也是组件的重要组成,其分为两部分:• 参与视图工作的 Redux• 不参与视图工作的 LocalProps  2、视图最基本的,也就是最重要的部分,每一个组件都应该是可视的。所以在组件构建时,我们必须为组件提供一个用于构建视图的函数。  3、依赖 描述了组件与组件之间的关系,也是可插拔的组件式开发的一个重要特性。其分为两部分:\b• 为列表而优化的 Adapter• 组件整体的组成部分 slot\bComponent组件是对局部的展示和功能的封装。 基于 Redux 的原则,我们对功能细分为修改数据的功能(Reducer)和非修改数据的功能(副作用 Effect)。于是我们得到了,View、 Effect、Reducer 三部分,称之为组件的三要素,分别负责了组件的展示、非修改数据的行为、修改数据的行为。这是一种面向当下,也面向未来的拆分。在面向当下的 Redux 看来,是数据管理和其他。在面向未来的 UI-Automation 看来是 UI 表达和其他。UI 的表达对程序员而言即将进入黑盒时代,研发工程师们会把更多的精力放在非修改数据的行为、修改数据的行为上。组件是对视图的分治,也是对数据的分治。通过逐层分治,我们将复杂的页面和数据切分为相互独立的小模块。这将利于团队内的协作开发。关于 ViewView 仅仅是一个函数签名: (T,Dispatch,ViewService) => Widget它主要包含三方面的信息• 视图是完全由数据驱动。• 视图产生的事件/回调,通过 Dispatch 发出“意图”,不做具体的实现。• 需要用到的组件依赖等,通过 ViewService 标准化调用。比如一个典型的符合 View 签名的函数。关于 EffectEffect 是对非修改数据行为的标准定义,它是一个函数签名: (Context, Action) => Object它主要包含四方面的信息• 接收来自 View 的“意图”,也包括对应的生命周期的回调,然后做出具体的执行。• 它的处理可能是一个异步函数,数据可能在过程中被修改,所以我们不崇尚持有数据,而通过上下文来获取最新数据。• 它不修改数据, 如果修要,应该发一个 Action 到 Reducer 里去处理。• 它的返回值仅限于 bool or Future, 对应支持同步函数和协程的处理流程。ReducerReducer 是一个完全符合 Redux 规范的函数签名,即一个纯函数。State一个可变的 State 需要实现 Cloneable 类。其核心在于 clone 函数,它总是返回一个新的实例,Action 在 Redux 中,修改 State 是通过调用 dispatch 函数去触发 Action 来进行的, 但需要注意的是,Action 仅仅是表达了修改 State 的意图。• Action 的构造器接收两个参数:• Object type - 必要参数,Action 实例的类型dyanmic payload - 可选参数,Action 实例携带的参数为了更好的协作开发和减少低级错误,建议声明一个 Action 类型的枚举类,以及定义一个集合返回 Action 的函数的类,这样可以约束到 payload 的类型Adapterhttps://github.com/alibaba/fish-redux/blob/master/doc/concept/adapter-cn.mdhttps://github.com/alibaba/fish-redux/blob/master/doc/concept/what's-adapter.md在基本了解fish_redux的概念之后,照黑猫画白狗,写写代码熟悉下。 在正式使用之前,最好采用下IDE插件,快捷生成基本的文件和代码。 fish_redux模版工具FishReduxTemplateForAS-Android Studio fish_redux模版工具fish-redux-template-VScode 这里,我使用的是Android Studio,首先,新建一个初始项目,然后先加入fish_redux库: 然后Packages get。 然后我们新建若干文件夹分分类: components:存放子界面 model:数据转模型 one_tab: 存放第一个Tab service_api:网络请求等 store:根store tabbar: Tabbar栏 two_tab:存放第二个Tab app.dart: PageRoutes和MaterialApp的开始 main.dart: 起始文件先选中one_tab,然后File->New->FishReduxTemplate,选择page,Select Files选中所有文件,Module Name我们可以写One,然后OK,我们可以得到一些文件。以此类推,我们再在two_tab下新建Two,tabbar新建Tabbar。 基本文件新建完毕,开始写Tabbar:tabbar--state.dart 12345678910111213141516import 'package:fish_redux/fish_redux.dart';class TabbarState implements Cloneable<TabbarState> { var index = 0; @override TabbarState clone() { TabbarState newState = TabbarState()..index = index; return newState; }}TabbarState initState(Map<String, dynamic> args) { return TabbarState()..index = 0;} 如上所示,先定义一个用于控制当前index的值,默认给0位。 tabbar--view.dart 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081import 'package:fish_redux/fish_redux.dart';import 'package:fishreduxdemo/tabbar/action.dart';import 'package:flutter/material.dart';import 'state.dart';import '../one_tab/page.dart';import '../two_tab/page.dart';Widget buildView( TabbarState state, Dispatch dispatch, ViewService viewService) { var tabImages; var appBarTitles = ['首页', '我的']; /* * 根据选择获得对应的normal或是press的icon */ Image getTabIcon(int curIndex) { if (curIndex == state.index) { return tabImages[curIndex][1]; } return tabImages[curIndex][0]; } /* * 获取bottomTab的颜色和文字 */ Text getTabTitle(int curIndex) { if (curIndex == state.index) { return new Text(appBarTitles[curIndex], style: new TextStyle(fontSize: 14.0, color: Colors.red)); } else { return new Text(appBarTitles[curIndex], style: new TextStyle(fontSize: 14.0, color: Colors.black)); } } /* * 根据image路径获取图片 */ Image getTabImage(path) { return new Image.asset(path, width: 24.0, height: 24.0); } /* * 初始化选中和未选中的icon */ tabImages = [ [ getTabImage('images/tab/home.png'), getTabImage('images/tab//home_select.png') ], [ getTabImage('images/tab/mine.png'), getTabImage('images/tab//mine_select.png') ] ]; return Scaffold( body: IndexedStack( children: <Widget>[ OnePage().buildPage(null), TwoPage().buildPage(null), ], index: state.index, ), bottomNavigationBar: new BottomNavigationBar( items: <BottomNavigationBarItem>[ new BottomNavigationBarItem(icon: getTabIcon(0), title: getTabTitle(0)), new BottomNavigationBarItem(icon: getTabIcon(1), title: getTabTitle(1)), ], type: BottomNavigationBarType.fixed, //默认选中首页 currentIndex: state.index, iconSize: 24.0, //点击事件 onTap: (index) { dispatch(TabbarActionCreator.switchIndex(index)); }, ), );} 如上所示,先引入必须的文件,切换方法改为fish_redux的触发action->reducer修改state->界面修改tabbar--action.dart 12345678910111213141516import 'package:fish_redux/fish_redux.dart';//TODO replace with your own actionenum TabbarAction { action, switchIndex }class TabbarActionCreator { static Action onAction() { return Action(TabbarAction.action); } // 切换tab static Action switchIndex(int index) { return Action(TabbarAction.switchIndex, payload: index); }} 定义一个切换index的action ** switchIndex **tabbar-reducer.dart 1234567891011121314151617181920212223242526272829import 'package:fish_redux/fish_redux.dart';import 'action.dart';import 'state.dart';Reducer<TabbarState> buildReducer() { return asReducer( <Object, Reducer<TabbarState>>{ TabbarAction.action: _onAction, TabbarAction.switchIndex: _switchIndex, }, );}TabbarState _onAction(TabbarState state, Action action) { final TabbarState newState = state.clone(); return newState;}/** 切换tab点击* */TabbarState _switchIndex(TabbarState state, Action action) { var index = action.payload; final TabbarState newState = state.clone()..index = index; return newState;} reducer里面处理切换方法。 effect 不变tabbar-effect.dart 123456789101112import 'package:fish_redux/fish_redux.dart';import 'action.dart';import 'state.dart';Effect<TabbarState> buildEffect() { return combineEffects(<Object, Effect<TabbarState>>{ TabbarAction.action: _onAction, });}void _onAction(Action action, Context<TabbarState> ctx) {} tabbar-page.dart不变 1234567891011121314151617181920212223import 'package:fish_redux/fish_redux.dart';import 'effect.dart';import 'reducer.dart';import 'state.dart';import 'view.dart';class TabbarPage extends Page<TabbarState, Map<String, dynamic>> { TabbarPage() : super( initState: initState, effect: buildEffect(), reducer: buildReducer(), view: buildView, dependencies: Dependencies<TabbarState>( adapter: null, slots: <String, Dependent<TabbarState>>{ }), middleware: <Middleware<TabbarState>>[ ],);} one_tab和two_tab暂时不变。main.dart 123import 'package:flutter/material.dart';import './app.dart';void main() => runApp(createApp()); app.dart 123456789101112131415161718192021222324252627282930import 'package:fish_redux/fish_redux.dart';import 'package:flutter/material.dart' hide Action;import 'package:flutter/widgets.dart' hide Action;import './tabbar/page.dart';import './one_tab/page.dart';import './two_tab/page.dart';Widget createApp() { final AbstractRoutes routes = PageRoutes(pages: <String, Page<Object, dynamic>>{ 'tabbar': TabbarPage(), 'one': OnePage(), //在这里添加页面 'two': TwoPage(), }); return MaterialApp( title: 'FishDemo', theme: ThemeData( primarySwatch: Colors.blue, ), home: routes.buildPage('tabbar', null), //把他作为默认页面 onGenerateRoute: (RouteSettings settings) { return MaterialPageRoute<Object>(builder: (BuildContext context) { return routes.buildPage(settings.name, settings.arguments); }); }, );} 这里需要配置路由。OK。基本界面已经完成。 然后我们试试使用globalStore配置全局state。修改全局的颜色在store文件夹下新建文件action.dart reducer.dart state.dart store.dart update.dart store--action.dart 123456789import 'package:fish_redux/fish_redux.dart';enum GlobalAction { changeThemeColor }class GlobalActionCreator{ static Action onChangeThemeColor(){ return const Action(GlobalAction.changeThemeColor); }} 定义一个action:changeThemeColorstore--reducer 1234567891011121314151617181920import 'package:fish_redux/fish_redux.dart';import 'dart:ui';import 'package:flutter/material.dart' hide Action;import 'action.dart';import 'state.dart';Reducer<GlobalState> buildReducer() { return asReducer( <Object, Reducer<GlobalState>>{ GlobalAction.changeThemeColor: _onChangeThemeColor, }, );}GlobalState _onChangeThemeColor(GlobalState state, Action action) { final Color color = state.themeColor == Colors.green ? Colors.blue : Colors.green; return state.clone()..themeColor = color;} 切换方法为 state.themeColor == Colors.green ? Colors.blue : Colors.green;state.dart 1234567891011121314151617import 'dart:ui';import 'package:fish_redux/fish_redux.dart';abstract class GlobalBaseState{ Color get themeColor; set themeColor(Color color);}class GlobalState implements GlobalBaseState, Cloneable<GlobalState>{ @override Color themeColor; @override GlobalState clone() { return GlobalState(); }} implements Cloneable 然后重写themeColorstore-store.dart 12345678910import 'package:fish_redux/fish_redux.dart';import 'state.dart';import 'reducer.dart';class GlobalStore{ static Store<GlobalState> _globalStore; static Store<GlobalState> get store => _globalStore ??= createStore<GlobalState>(GlobalState(), buildReducer());} 定义一个Storestore--update.dart 12345678910111213141516171819202122232425import 'package:fish_redux/fish_redux.dart';import 'state.dart';/// 全局刷新状态/// 页面状态刷新,在 [createApp()] visitor 中注册/// 如果是 `PageView` 中的页面做刷新,在 `Page` 构造函数中注册/// 参考 [HomeArticlePage]globalUpdate() => (Object pageState, GlobalState appState) { final GlobalBaseState p = pageState; if (pageState is Cloneable) { final Object copy = pageState.clone(); final GlobalBaseState newState = copy; if (p.themeColor != appState.themeColor) { newState.themeColor = appState.themeColor; } return newState; } return pageState; }; store书写完毕。接下里修改下app.dart 123456789101112131415161718192021222324252627282930···import './store/state.dart';import './store/store.dart';··· final AbstractRoutes routes = PageRoutes( pages: <String, Page<Object, dynamic>>{ 'tabbar': TabbarPage(), 'one': OnePage(), //在这里添加页面 'adapter_page': AdapterTestPage(), 'two': TwoPage(), }, visitor: (String path, Page<Object, dynamic> page) { if (page.isTypeof<GlobalBaseState>()) { page.connectExtraStore<GlobalState>(GlobalStore.store, (Object pageState, GlobalState appState) { final GlobalBaseState p = pageState; if (p.themeColor != appState.themeColor) { if (pageState is Cloneable) { final Object copy = pageState.clone(); final GlobalBaseState newState = copy; newState.themeColor = appState.themeColor; return newState; } } return pageState; }); } }, );··· 往其中加入visitor 即可。然后在界面中写入该themeColor: 1234567891011121314151617181920212223···import 'package:fish_redux/fish_redux.dart';import 'dart:ui';import '../components/child_view/state.dart';import '../components/achild_view/state.dart';import '../store/state.dart';class OneState implements Cloneable<OneState>, GlobalBaseState { ChildViewState childState; AchildViewState aChildState; @override Color themeColor; @override OneState clone() { return OneState() ..childState = childState ..aChildState = aChildState ..themeColor = themeColor; }}··· 1234567···return Scaffold( appBar: AppBar( backgroundColor: state.themeColor, title: Text('one'), ),··· 修改: 123456··· GlobalStore.store.dispatch(GlobalActionCreator.onChangeThemeColor());··· 其中别忘了连接 connectExtraStore(GlobalStore.store, globalUpdate()); 详细的可以看文末GitHub链接。 OK,我们再尝试下component。在components文件夹下新建一个child_view文件夹,然后在其文件夹下新建component,选择File->New->FishReduxTemplate,选择Component,输入一个Module Name。点击OK这个时候,会生成action.dart component.dart effect.dart reducer.dart state.dart view.dart:然后我们可以在主page里面使用这个component:one_tab--state.dart 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253import 'package:fish_redux/fish_redux.dart';import 'dart:ui';import '../components/child_view/state.dart';import '../components/achild_view/state.dart';import '../store/state.dart';class OneState implements Cloneable<OneState>, GlobalBaseState { ChildViewState childState; AchildViewState aChildState; @override Color themeColor; @override OneState clone() { return OneState() ..childState = childState ..aChildState = aChildState ..themeColor = themeColor; }}OneState initState(Map<String, dynamic> args) { OneState state = OneState(); state.childState = ChildViewState(); state.aChildState = AchildViewState(); return state;}class ChildViewConnector extends ConnOp<OneState, ChildViewState> { @override ChildViewState get(OneState state) { return state.childState; } @override void set(OneState state, ChildViewState subState) { state.childState = subState; }}class AchildViewConnector extends ConnOp<OneState, AchildViewState> { @override AchildViewState get(OneState state) { return state.aChildState; } @override void set(OneState state, AchildViewState subState) { state.aChildState = subState; }} 如上所示:先引入 child_view下的state文件。初始化、新建ChildViewConnector。然后在one_tab-page.dart下修改:one_tab-page.dart 12345678910111213141516171819202122232425262728293031323334import 'package:fish_redux/fish_redux.dart';import 'effect.dart';import 'reducer.dart';import 'state.dart';import 'view.dart';import '../components/child_view/component.dart';import '../components/achild_view/component.dart';import '../store/store.dart';import '../store/update.dart';class OnePage extends Page<OneState, Map<String, dynamic>> { OnePage() : super( initState: initState, effect: buildEffect(), reducer: buildReducer(), view: buildView, dependencies: Dependencies<OneState>( adapter: null, slots: <String, Dependent<OneState>>{ 'ChildViewComponent': ChildViewConnector() + ChildViewComponent(), 'AchildViewComponent': AchildViewConnector() + AchildViewComponent(), }), middleware: <Middleware<OneState>>[], ) { connectExtraStore(GlobalStore.store, globalUpdate()); }} slots下引入该component。然后就可以在view中显示了:one_tab--view.dart 12345678910111213141516171819202122232425262728import 'package:fish_redux/fish_redux.dart';import 'package:flutter/material.dart';import 'state.dart';Widget buildView(OneState state, Dispatch dispatch, ViewService viewService) { return Scaffold( appBar: AppBar( backgroundColor: state.themeColor, title: Text('one'), ), body: ListView( children: <Widget>[ _childView(state, viewService), _achildView(state, viewService), ], ), );}Align _childView(OneState state, ViewService viewService) { return Align(child: viewService.buildComponent('ChildViewComponent'));}Align _achildView(OneState state, ViewService viewService) { return Align(child: viewService.buildComponent('AchildViewComponent'));} 其他的,如网络请求只需要在 Lifecycle.initState: _init,执行即可。如:two_bar-effect 123456789101112131415161718192021222324import 'dart:convert';import 'package:fish_redux/fish_redux.dart';import 'action.dart';import 'state.dart';import '../service_api/ServiceApi.dart';import '../model/twoModel.dart';Effect<TwoState> buildEffect() { return combineEffects(<Object, Effect<TwoState>>{ Lifecycle.initState: _init, TwoAction.action: _onAction, });}void _onAction(Action action, Context<TwoState> ctx) {}void _init(Action action, Context<TwoState> ctx) { ServiceApi().twoGetData().then((value) { ctx.dispatch(TwoActionCreator.onloadData(value)); });} ServiceApi.dart 1234567891011121314151617181920import 'package:dio/dio.dart';import 'dart:async';import '../model/twoModel.dart';class ServiceApi { Future twoGetData() async { try { Response response; Dio dio = new Dio(); response = await dio.get('https://gank.io/api/v2/banners'); TwoModel model = TwoModel.fromJson(response.data); return model; } catch (e) { print('发送错误'); print(e); } }} 其中model转换采用最为直接的https://javiercbk.github.io/json_to_dart/其model需要在state中进行赋值。 其他的如跳转、传参、adapter可以在下面的GitHub链接中找到。 以上就是简单版的fish_redux学习了.如果不明白的下面有源码地址,可以去那里看看. 本文源码地址 最简单的demo地址 flutter学习历程 ps:前几天面试,面试官有个需求,就是不同角色登录需要展示不同的tabbar,不同的界面,需要各自一个store,但只能是在一个app中实现。后来的路上想了下,简单搭了个架子。 架子源码地址","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Flutter~自定义tabbar模块","slug":"移动端学习/Flutter~自定义tabbar模块","date":"2020-01-22T16:00:00.000Z","updated":"2023-12-20T05:32:43.941Z","comments":true,"path":"2020/01/23/移动端学习/Flutter~自定义tabbar模块/","link":"","permalink":"https://zhoushaoting.com/2020/01/23/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~%E8%87%AA%E5%AE%9A%E4%B9%89tabbar%E6%A8%A1%E5%9D%97/","excerpt":"项目中,遇到一个需求,需要自定义tabbar的高度,但是自带的tabbar组件都不支持修改高度.","text":"项目中,遇到一个需求,需要自定义tabbar的高度,但是自带的tabbar组件都不支持修改高度. 所幸flutter的万物皆组件的概念,所以我们自己写一个tabbar即可.需求:需要一个可以自行控制高度的tabbar. 大致效果如下: 其中,自定义Tabbar组件如下 bottom_bar.dart123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129/** 自定义tabbar* */import 'package:flutter/material.dart';class BottomBarItem { BottomBarItem({this.imageIcon, this.imageSelectedIcon, this.text}); Image imageIcon; Image imageSelectedIcon; String text;}class BottomBar extends StatefulWidget { BottomBar({ this.items, this.centerItemText, this.height: 60.0, this.iconSize: 24.0, this.backgroundColor, this.color, this.selectedColor, this.notchedShape, this.onTabSelected, }) { assert(this.items.length == 2 || this.items.length == 4); } final List<BottomBarItem> items; final String centerItemText; final double height; final double iconSize; final Color backgroundColor; final Color color; final Color selectedColor; final NotchedShape notchedShape; final ValueChanged<int> onTabSelected; @override State<StatefulWidget> createState() => BottomBarState();}class BottomBarState extends State<BottomBar> { int _selectedIndex = 0; _updateIndex(int index) { widget.onTabSelected(index); setState(() { _selectedIndex = index; }); } @override Widget build(BuildContext context) { List<Widget> items = List.generate(widget.items.length, (int index) { return _buildTabItem( item: widget.items[index], index: index, onPressed: _updateIndex, ); }); items.insert(items.length >> 1, _buildMiddleTabItem()); return BottomAppBar( shape: widget.notchedShape, child: Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: items, ), color: widget.backgroundColor, ); } Widget _buildMiddleTabItem() { return Expanded( child: SizedBox( height: widget.height, child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ SizedBox(height: widget.iconSize), Text( widget.centerItemText ?? '', style: TextStyle(color: widget.color), ), ], ), ), ); } Widget _buildTabItem({ BottomBarItem item, int index, ValueChanged<int> onPressed, }) { Image icon = _selectedIndex == index ? item.imageSelectedIcon : item.imageIcon; Color resultColor = _selectedIndex == index ? widget.selectedColor : widget.color; return Expanded( child: SizedBox( height: widget.height, child: Material( type: MaterialType.transparency, child: InkWell( onTap: () => onPressed(index), child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ icon, Text( item.text, style: TextStyle(color: resultColor), ) ], ), ), ), ), ); }} 以上就是自定义的tabbar组件.其中字段解释如下:. items tabs数组,BottomBarItem类型. centerItemText 中间Tab按钮文字. height tab的高度. backgroundColor 整体背景色. color icon默认色. selectedColor 选中icon色. notchedShape icon的大小. onTabSelected 选中tab事件,做的主要就是切换当前page. notchedShape 设置notchedShape,和系统的NotchedShape一致 其他需要的属性,可以自行添加. 创建好自定义tabbar之后,使用即可. main.dart123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104import 'package:flutter/material.dart';import './bottom_bar.dart';import './pages/one.dart';import './pages/two.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: STTabBar(), ); }}class STTabBar extends StatefulWidget { @override _STTabBarState createState() => _STTabBarState();}class _STTabBarState extends State<STTabBar> { int _tabIndex = 0; double _tab_H = 70.0; // tag 高度 @override Widget build(BuildContext context) { return Scaffold( body: IndexedStack( index: _tabIndex, children: <Widget>[ One( // 滑块 changeTabOne: (e) { setState(() { _tab_H = e; }); }, // 高度为0 changeTabTwo: () { setState(() { _tab_H = 0; }); }, // 高度为70 changeTabThree: () { setState(() { _tab_H = 70; }); }, ), Two(), ], ), bottomNavigationBar: BottomBar( height: _tab_H, color: Color(0xFF999999), selectedColor: Color.fromRGBO(255, 175, 76, 1),// backgroundColor: Colors.red, items: [ BottomBarItem( imageIcon: Image.asset( 'assets/one.png', width: 40, height: 40, ), imageSelectedIcon: Image.asset( 'assets/one_select.png', width: 40, height: 40, ), text: '首页', ), BottomBarItem( imageIcon: Image.asset( 'assets/two.png', width: 40, height: 40, ), imageSelectedIcon: Image.asset( 'assets/two_select.png', width: 40, height: 40, ), text: '我的', ) ], onTabSelected: (index) { setState(() { _tabIndex = index; }); }, ), ); }} 上面的BottomBar()这个就是之前自定义的tabbar,配置好参数即可. 完整源码 另:以上功能也可以采用PreferredSize 制作,如 PreferredSize 示例 ps:新年前最后一天,自己一个人在出租屋里写着博客,外面下着雨,路上没什么人,好好的年被着该该死的肺炎给搅和了,也不知道自己能不能度过这个灾难…..","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Flutter~自定义Dialog(基本的组件封装)","slug":"移动端学习/Flutter~自定义Dialog(基本的组件封装)","date":"2019-11-10T16:00:00.000Z","updated":"2023-12-20T05:32:21.777Z","comments":true,"path":"2019/11/11/移动端学习/Flutter~自定义Dialog(基本的组件封装)/","link":"","permalink":"https://zhoushaoting.com/2019/11/11/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~%E8%87%AA%E5%AE%9A%E4%B9%89Dialog(%E5%9F%BA%E6%9C%AC%E7%9A%84%E7%BB%84%E4%BB%B6%E5%B0%81%E8%A3%85)/","excerpt":"光棍节就得写个bug庆祝下!!!","text":"光棍节就得写个bug庆祝下!!! 写个自定义的Dialog,可以随意控制UI,同时封装一下,传值控制UI,开个方法回调,控制交互. 先上效果图!!! 再上代码CustomDialog.dart123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160/** 输入框* */import 'package:flutter/material.dart';class CustomDialog extends StatefulWidget { Function dismissCallback; // 取消回调 Function okCallback; // 确认回调 bool isNeedTag; // 外部传入的参数,控制显示slide还是input CustomDialog({ Key key, this.dismissCallback, this.okCallback, this.isNeedTag, }) : super(key: key); @override _CustomDialogState createState() => _CustomDialogState();}class _CustomDialogState extends State<CustomDialog> { String commitStr; double slideValue = 0.0; /* * 取消按钮点击事件 * */ _dismissDialog() { if (widget.dismissCallback != null) { widget.dismissCallback(); } Navigator.of(context).pop(); } /* * 确定按钮点击事件 * */ _okCallback(String txt) { if (widget.okCallback != null) { widget.okCallback(txt); } Navigator.of(context).pop(); } @override Widget build(BuildContext context) { return Material( type: MaterialType.transparency, child: Center( child: SizedBox( width: 240, height: 240, child: Container( decoration: BoxDecoration( color: Colors.cyanAccent, borderRadius: BorderRadius.vertical( top: Radius.circular(120.0), ), ), child: Column( children: <Widget>[ widget.isNeedTag == true ? _topWidget() : _topWidget1(), Spacer(), _bottomWidget(), ], ), ), ), ), ); } /* * 上方输入UI * */ Widget _topWidget() { return Container( margin: EdgeInsets.only(top: 100), child: Container( child: TextField( onSubmitted: (e) { setState(() { commitStr = e; }); }, decoration: InputDecoration( border: InputBorder.none, hintText: "请输入字符", ), ), ), ); } /* * 上方输入UI-1 * */ Widget _topWidget1() { return Container( margin: EdgeInsets.only(top: 100), child: Slider( value: slideValue, max: 100.0, min: 0.0, activeColor: Colors.blue, onChanged: (double val) { setState(() { slideValue = val; commitStr = val.toString(); }); }, ), ); } /* * 底部选择widget * */ Widget _bottomWidget() { return Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: <Widget>[ InkWell( onTap: () { _okCallback(commitStr); }, child: Container( height: 50, width: 120, color: Colors.yellow[800], child: Center( child: Text( '确定', style: TextStyle(color: Colors.white), ), ), ), ), InkWell( onTap: () { _dismissDialog(); }, child: Container( height: 50, width: 120, color: Colors.red[800], child: Center( child: Text( '取消', style: TextStyle(color: Colors.white), ), ), ), ), ], ); }} 使用如下 main.dart1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677import 'package:flutter/material.dart';import './custom_dialog.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(), ); }}class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => _MyHomePageState();}class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('自定义dialog'), ), body: ListView( children: <Widget>[ InkWell( onTap: () { showDialog( context: context, builder: (BuildContext context) { return CustomDialog( isNeedTag: true, okCallback: (text) { print(text); }, dismissCallback: () { print("input 取消了"); }, ); }, ); }, child: Text('第一个'), ), SizedBox(height: 20), InkWell( onTap: () { showDialog( context: context, builder: (BuildContext context) { return CustomDialog( isNeedTag: false, okCallback: (text) { print(text); }, dismissCallback: () { print("slide 取消了"); }, ); }, ); }, child: Text('第二个'), ) ], ), ); }} 最后再讲点废话:isNeedTag 就是用来给外部用来控制内部UI的值,这个简单控制下内部是显示输入框还是滑块 Function dismissCallback 和 Function okCallback 是Dialog内部行为传给外部的方法,控制交互. ok.光棍节庆祝完毕.下班~~~ 源码","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Flutter~Flare动画学习","slug":"移动端学习/Flutter~Flare动画学习","date":"2019-09-06T16:00:00.000Z","updated":"2023-12-20T05:40:22.225Z","comments":true,"path":"2019/09/07/移动端学习/Flutter~Flare动画学习/","link":"","permalink":"https://zhoushaoting.com/2019/09/07/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~Flare%E5%8A%A8%E7%94%BB%E5%AD%A6%E4%B9%A0/","excerpt":"今天学习下Flutter动画之 Flare.","text":"今天学习下Flutter动画之 Flare. Flare的官网.Flare 可以为 App/游戏/网页等制作酷炫的矢量动画模型;Flare 动画的优势是有效减少文件体积且获取极好的动画效果,适用于与场景交互不大的场景.在Flare官网上,可以访问两种文件,Nima文件和Flare文件: Nima 为较旧格式,仅支持光栅图;主要是为游戏引擎和应用构建 2D 动画 – XXX.nima Flare 为较新格式,支持矢量图与光栅图;主要为 App 和 Web 构建高效动画,也可用于游戏设计 – XXX.flr这两种格式文件使用各自的库. nima flare_flutter. 至于动画制作不用采用代码编写,而是采用类似PS的方式设计.我这里就不介绍如何设计动画,只介绍一下如何使用即可.设计方法可以参考网友的教程:网友1.网友2.网友3. 引入库12345678nima: ^1.0.5flare_flutter: ^1.5.4和引入文件: assets: - assets/Hop.nima - assets/Hop.png - assets/Teddy.flr one.dart123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778import 'package:flutter/material.dart';import 'package:nima/nima_actor.dart';class One extends StatefulWidget { @override _OneState createState() => _OneState();}class _OneState extends State<One> { String _animationName = "idle"; @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.grey, body: Stack( children: <Widget>[ Positioned.fill( child: NimaActor( "assets/Hop.nima", alignment: Alignment.center, fit: BoxFit.contain, animation: _animationName, mixSeconds: 0.5, completed: (String animationName) { setState( () { // Return to idle. _animationName = "idle"; }, ); }, ), ), Positioned.fill( child: Row( crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Container( margin: const EdgeInsets.all(5.0), child: FlatButton( child: Text("Jump"), textColor: Colors.white, color: Colors.blue, onPressed: () { setState( () { _animationName = "jump"; }, ); }, ), ), Container( margin: const EdgeInsets.all(5.0), child: FlatButton( child: Text("Attack"), textColor: Colors.white, color: Colors.blue, onPressed: () { setState( () { _animationName = "attack"; }, ); }, ), ), ], ), ) ], ), ); }} 效果图: two.dart12345678910111213141516171819202122import 'package:flutter/material.dart';import 'package:flare_flutter/flare_actor.dart';class Two extends StatefulWidget { @override _TwoState createState() => _TwoState();}class _TwoState extends State<Two> { @override Widget build(BuildContext context) { return Scaffold( body: FlareActor( "assets/Teddy.flr", alignment: Alignment.center, fit: BoxFit.contain, animation: "idle", ), ); }} 效果图: 源码 开发中,你也可以选择其他的方案,如 lottie 或者 svgaplayer_flutter , 我大致试了下,在 Flare 和 lottie 和 svgaplayer_flutter来看,我觉得lottie更加简单。 lottie和svgaplayer_flutter的demo","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Flutter~mobx库学习","slug":"移动端学习/Flutter~mobx库学习","date":"2019-08-07T16:00:00.000Z","updated":"2023-12-20T05:49:40.857Z","comments":true,"path":"2019/08/08/移动端学习/Flutter~mobx库学习/","link":"","permalink":"https://zhoushaoting.com/2019/08/08/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~mobx%E5%BA%93%E5%AD%A6%E4%B9%A0/","excerpt":"今天学习下Flutter中state管理的第三方mobx .其中,mobx: ^0.1.4 flutter_mobx: ^0.1.3,当中,还需要其他的两个库的支持,mobx_codegen: ^0.1.3,build_runner: ^1.4.0","text":"今天学习下Flutter中state管理的第三方mobx .其中,mobx: ^0.1.4 flutter_mobx: ^0.1.3,当中,还需要其他的两个库的支持,mobx_codegen: ^0.1.3,build_runner: ^1.4.0 代码结构如图所示: 首先,先在pubspec.yaml里添加123456789101112131415161718192021version: 1.0.0+1environment: sdk: ">=2.1.0 <3.0.0"dependencies: flutter: sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.2 mobx: ^0.1.4 flutter_mobx: ^0.1.3dev_dependencies: flutter_test: sdk: flutter mobx_codegen: ^0.1.3 build_runner: ^1.4.0 flutter packages get 之后,我们先创建一个基本的结构.其中包含两个Tab页面:one_page.dart和two_page.dart,tabBar的tabbar.dart,mobx的counter.dart ,counter.g.dart.首先,先在counter.dart中写基本代码,其中count是个状态化的数字.然后,终端cd 到更目录之下.执行 flutter packages pub run build_runner build,如果正常的话,会自动生成 count.g.dart文件. counter.dart1234567891011121314151617181920212223242526272829import 'package:mobx/mobx.dart';part 'counter.g.dart';class Counter = CounterBase with _$Counter;final Counter counter = Counter();abstract class CounterBase implements Store { @observable int value = 0; @action void increment() { value++; } @action void decrement() { value--; } @action void set(int value) { this.value = value; }} counter.g.dart123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960// GENERATED CODE - DO NOT MODIFY BY HANDpart of 'counter.dart';// **************************************************************************// StoreGenerator// **************************************************************************// ignore_for_file: non_constant_identifier_names, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_charsmixin _$Counter on CounterBase, Store { final _$valueAtom = Atom(name: 'CounterBase.value'); @override int get value { _$valueAtom.reportObserved(); return super.value; } @override set value(int value) { _$valueAtom.context.checkIfStateModificationsAreAllowed(_$valueAtom); super.value = value; _$valueAtom.reportChanged(); } final _$CounterBaseActionController = ActionController(name: 'CounterBase'); @override void increment() { final _$actionInfo = _$CounterBaseActionController.startAction(); try { return super.increment(); } finally { _$CounterBaseActionController.endAction(_$actionInfo); } } @override void decrement() { final _$actionInfo = _$CounterBaseActionController.startAction(); try { return super.decrement(); } finally { _$CounterBaseActionController.endAction(_$actionInfo); } } @override void set(int value) { final _$actionInfo = _$CounterBaseActionController.startAction(); try { return super.set(value); } finally { _$CounterBaseActionController.endAction(_$actionInfo); } }} ok,在使用的时候,使用counter.value即可使用mobx中的值,使用counter.increment即可改变值.如: one_page.dart1234567891011121314151617181920212223242526272829303132333435363738import 'package:flutter/material.dart';import 'package:flutter_mobx/flutter_mobx.dart';import '../mobx/counter.dart';class OnePage extends StatefulWidget { @override _OnePageState createState() => _OnePageState();}class _OnePageState extends State<OnePage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('111111'), ), body: ListView( children: <Widget>[ Observer( builder: (_) => Text( '${counter.value}', style: Theme.of(context).textTheme.display1, ), ), RaisedButton( child: Text('加'), onPressed: counter.increment, ), RaisedButton( child: Text('减'), onPressed: counter.decrement, ), ], ), ); }} two_page.dart12345678910111213141516171819202122232425262728293031323334353637import 'package:flutter/material.dart';import 'package:flutter_mobx/flutter_mobx.dart';import '../mobx/counter.dart';class TwoPage extends StatefulWidget { @override _TwoPageState createState() => _TwoPageState();}class _TwoPageState extends State<TwoPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('222222'), ), body: ListView( children: <Widget>[ Observer( builder: (_) => Text( '${counter.value}', style: Theme.of(context).textTheme.display1, ), ), RaisedButton( child: Text('加'), onPressed: counter.increment, ), RaisedButton( child: Text('减'), onPressed: counter.decrement, ), ], ), ); }} 其他文件: main.dart12345678910111213141516171819202122232425262728import 'package:flutter/material.dart';import './tabbar.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( // This is the theme of your application. // // Try running your application with "flutter run". You'll see the // application has a blue toolbar. Then, without quitting the app, try // changing the primarySwatch below to Colors.green and then invoke // "hot reload" (press "r" in the console where you ran "flutter run", // or simply save your changes to "hot reload" in a Flutter IDE). // Notice that the counter didn't reset back to zero; the application // is not restarted. primarySwatch: Colors.blue, ), home: Tabbar(), ); }} tabbar.dart123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108import 'package:flutter/material.dart';import './pages/one_page.dart';import './pages/two_page.dart';class Tabbar extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( debugShowCheckedModeBanner: false, home: new MainPageWidget()); }}class MainPageWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return new MainPageState(); }}class MainPageState extends State<MainPageWidget> { int _tabIndex = 0; var tabImages; var appBarTitles = ['一', '二']; /* * 存放两个页面,跟fragmentList一样 */ var _pageList; /* * 根据选择获得对应的normal或是press的icon */ Image getTabIcon(int curIndex) { if (curIndex == _tabIndex) { return tabImages[curIndex][1]; } return tabImages[curIndex][0]; } /* * 获取bottomTab的颜色和文字 */ Text getTabTitle(int curIndex) { if (curIndex == _tabIndex) { return new Text(appBarTitles[curIndex], style: new TextStyle(fontSize: 14.0, color: const Color(0xff1296db))); } else { return new Text(appBarTitles[curIndex], style: new TextStyle(fontSize: 14.0, color: const Color(0xff515151))); } } /* * 根据image路径获取图片 */ Image getTabImage(path) { return new Image.asset(path, width: 24.0, height: 24.0); } void initData() { /* * 初始化选中和未选中的icon */ tabImages = [ [ getTabImage('images/tab/home.png'), getTabImage('images/tab//home_select.png') ], [ getTabImage('images/tab/show.png'), getTabImage('images/tab//show_select.png') ] ]; /* * 三个子界面 */ _pageList = [ new OnePage(), new TwoPage(), ]; } @override Widget build(BuildContext context) { //初始化数据 initData(); return Scaffold( body: _pageList[_tabIndex], bottomNavigationBar: new BottomNavigationBar( items: <BottomNavigationBarItem>[ new BottomNavigationBarItem( icon: getTabIcon(0), title: getTabTitle(0)), new BottomNavigationBarItem( icon: getTabIcon(1), title: getTabTitle(1)), ], type: BottomNavigationBarType.fixed, //默认选中首页 currentIndex: _tabIndex, iconSize: 24.0, //点击事件 onTap: (index) { setState(() { _tabIndex = index; }); }, )); }} 效果图: 源码 另外:其他管理库学习 另外:Flutter学习demo","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"MAC下的cocos2d-x环境搭建","slug":"其他学习/MAC下的cocos2d-x环境搭建","date":"2019-06-22T16:00:00.000Z","updated":"2023-12-20T03:20:27.003Z","comments":true,"path":"2019/06/23/其他学习/MAC下的cocos2d-x环境搭建/","link":"","permalink":"https://zhoushaoting.com/2019/06/23/%E5%85%B6%E4%BB%96%E5%AD%A6%E4%B9%A0/MAC%E4%B8%8B%E7%9A%84cocos2d-x%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/","excerpt":"很久以前,朋友建议培养下业余爱好,思来想去,决定学习下游戏开发.猿的下班生活如果还是和代码相关的话,那么它真的无药可救了.哎….","text":"很久以前,朋友建议培养下业余爱好,思来想去,决定学习下游戏开发.猿的下班生活如果还是和代码相关的话,那么它真的无药可救了.哎…. 今天,学习下Mac系统下的cocos2d-x的环境搭建. 先进入官网: https://cocos2d-x.org/下载最新的SDK.如图所示 下载之后,解压找个地方放置下,如: 打开终端,cd到SDK目录.然后执行 python setup.py .终端运行之后,会显示 1->Please enter the path of NDK_ROOT (or press Enter to skip): 直接回车即可.显示: 1Please execute command: "source /Users/shaotingzhou/.bash_profile" to make added system variables take effect 复制其中””的执行一下即可. 环境安装好了.可以执行 cocos -v 看下版本信息. 然后我们新建一个工程,使用Xcode运行一下看看.终端执行 12cocos new -p com.duan.test -l cpp -d /Users/shaotingzhou/Desktop//workspace helloworld 然后就会在桌面上看到一个workspace文件夹,里面就有helloworld工程,在 proj.ios_mac 下使用Xcode打开编译运行即可.如图:","categories":[{"name":"其他学习","slug":"其他学习","permalink":"https://zhoushaoting.com/categories/%E5%85%B6%E4%BB%96%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"其他学习","slug":"其他学习","permalink":"https://zhoushaoting.com/tags/%E5%85%B6%E4%BB%96%E5%AD%A6%E4%B9%A0/"}]},{"title":"Flutter~国际化教程方案","slug":"移动端学习/Flutter~国际化教程方案","date":"2019-06-10T16:00:00.000Z","updated":"2023-12-20T05:26:59.118Z","comments":true,"path":"2019/06/11/移动端学习/Flutter~国际化教程方案/","link":"","permalink":"https://zhoushaoting.com/2019/06/11/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~%E5%9B%BD%E9%99%85%E5%8C%96%E6%95%99%E7%A8%8B%E6%96%B9%E6%A1%88/","excerpt":"今天,学习下Flutter中的国际化多语言配置方案.","text":"今天,学习下Flutter中的国际化多语言配置方案. 原教程地址,本猿按照该大神的方案成功实现了需求,在此记录一下.本来在网上搜索了很多教程博客,要么一堆截图下来,最后两个字成功,要么一片片的代码片段,最后两个字成功,我均试了下,全是取值为空的错误.可能本猿少了哪几步操作吧.所幸在朋友的帮助下,实现了该方案,也找到了该方案的原大佬.先行上图: 效果图: 此方案使用到了下面的两个库,一个国际化库,一个本地存储库(做持久化语言操作). Flutter的使用版本和代码结构如图: 下面是各文件的详细代码: 首先我们新建两个语言json文件,里面配置各自平台需要的文字,如下所示. i18n_en.json 1234567 { "register": "register", "mine": "mine", "home": "home", "zh": "zh", "en": "en"} i18n_zh.json 123456 { "mine":"我的", "home":"首页", "zh":"中文", "en": "英文"} 然后新建最重要的配置文件trahslations.dart trahslations.dart 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105 import 'dart:async';import 'dart:convert';import 'package:flutter/material.dart';import 'package:flutter/services.dart' show rootBundle;import 'package:shared_preferences/shared_preferences.dart';/// 自定义的Translations类class Translations { Translations(Locale locale) { this.locale = locale; _localizedValues = null; } Locale locale; static Map<dynamic, dynamic> _localizedValues; static Translations of(BuildContext context){ return Localizations.of<Translations>(context, Translations); } String text(String key) { if(_localizedValues==null) { return "no locale"; } return _localizedValues[key] ?? '** $key not found'; } static Future<Translations> load(Locale locale) async { SharedPreferences sp = await SharedPreferences.getInstance(); String lang = sp.get("lang"); if(lang==null) { lang = "zh"; } print('zh:$lang'); Translations translations = new Translations(locale); String jsonContent = await rootBundle.loadString("locale/i18n_$lang.json"); _localizedValues = json.decode(jsonContent); applic.shouldReload = false; return translations; } get currentLanguage => locale.languageCode;}/// 自定义的localization代表,它的作用是在验证支持的语言前,初始化我们的自定义Translations类class TranslationsDelegate extends LocalizationsDelegate<Translations> { const TranslationsDelegate(); /// 改这里是为了不硬编码支持的语言 @override bool isSupported(Locale locale) => applic.supportedLanguages.contains(locale.languageCode); @override Future<Translations> load(Locale locale)=> Translations.load(locale); @override bool shouldReload(TranslationsDelegate old) => false;}/// Delegate类的实现,每次选择一种新的语言时,强制初始化一个新的Translations类class SpecificLocalizationDelegate extends LocalizationsDelegate<Translations> { final Locale overriddenLocale; const SpecificLocalizationDelegate(this.overriddenLocale); @override bool isSupported(Locale locale) => overriddenLocale != null; @override Future<Translations> load(Locale locale) => Translations.load(overriddenLocale); @override bool shouldReload(LocalizationsDelegate<Translations> old) { return applic.shouldReload??false; }}typedef void LocaleChangeCallback(Locale locale);class APPLIC { // 支持的语言列表 final List<String> supportedLanguages = ['en','zh']; // 支持的Locales列表 Iterable<Locale> supportedLocales() => supportedLanguages.map<Locale>((lang) => new Locale(lang, '')); // 当语言改变时调用的方法 LocaleChangeCallback onLocaleChanged; bool shouldReload; /// /// Internals /// static final APPLIC _applic = new APPLIC._internal(); factory APPLIC(){ return _applic; } APPLIC._internal();}APPLIC applic = new APPLIC(); 在里面,我们自定义一个Translations类,里面使用 Locale初始化语言配置,且默认给的汉语zh,可以添加自己支持的Locales列表,新建对应的json文件即可. 接下来需要在入口配置Delegate的相关代码: main.dart 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162import 'package:flutter/material.dart';import 'package:shared_preferences/shared_preferences.dart';import 'package:flutter_localizations/flutter_localizations.dart';import './utils/trahslations.dart';import './tabbar.dart';void main() => runApp(MyApp());class MyApp extends StatefulWidget { MyApp({Key key}) : super(key: key); _MyAppState createState() => _MyAppState();}class _MyAppState extends State<MyApp> { SpecificLocalizationDelegate _localeOverrideDelegate; onLocaleChange(Locale locale) async { print('onLocaleChange--'); SharedPreferences sp = await SharedPreferences.getInstance(); await sp.setString("lang", locale.languageCode); setState(() { _localeOverrideDelegate = new SpecificLocalizationDelegate(locale); }); } @override void initState() { super.initState(); _localeOverrideDelegate = new SpecificLocalizationDelegate(null); applic.onLocaleChanged = onLocaleChange; } @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'box chain', theme: new ThemeData( backgroundColor: Color.fromRGBO(240, 240, 240, 1.0), ), home: MainPageWidget(), localizationsDelegates: [ _localeOverrideDelegate, const TranslationsDelegate(), GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], supportedLocales: applic.supportedLocales(), localeResolutionCallback: (deviceLocale, supportedLocales) { if(deviceLocale.toString() == 'en_CN'||deviceLocale.toString() == 'zh_CN'){ applic.shouldReload = true; applic.onLocaleChanged(new Locale('zh','')); } else { applic.shouldReload = true; applic.onLocaleChanged(new Locale('en','')); } } ); }} 这里,我们先引入本地化库,国际化库和刚才新建的trahslations.dart,然后在初始化方法里面初始化配置,在MaterialApp里面设置localizationsDelegates和supportedLocales,然后新建一个方法用于修改本地的Locale. 如上面代码中的onLocaleChange方法. 下面就是使用方法了: 使用语言 123456String _lang(String key) { return Translations.of(context).text(key);}_lang("zh") 修改语言 123applic.shouldReload = true; applic.onLocaleChanged(new Locale('zh', '')); 完整代码: one_page.dart 1234567891011121314151617181920212223242526272829303132333435363738394041 import 'package:flutter/material.dart';import '../../utils/trahslations.dart';class OnePage extends StatefulWidget { @override _OnePageState createState() => _OnePageState();}class _OnePageState extends State<OnePage> { String _lang(String key) { return Translations.of(context).text(key); } Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(_lang("home")), ), body: Center( child: Column( children: <Widget>[ RaisedButton( child: Text(_lang("zh")), onPressed: () { applic.shouldReload = true; applic.onLocaleChanged(new Locale('zh', '')); }, ), RaisedButton( child: Text(_lang("en")), onPressed: () { applic.shouldReload = true; applic.onLocaleChanged(new Locale('en', '')); }, ), ], ), ), ); }} two_page.dart 123456789101112131415161718192021222324 import 'package:flutter/material.dart';import '../../utils/trahslations.dart';class TwoPage extends StatefulWidget { @override _TwoPageState createState() => _TwoPageState();}class _TwoPageState extends State<TwoPage> { String _lang(String key) { return Translations.of(context).text(key); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(_lang("mine"))), body: Center( child: Text('........'), ), ); }} 其中tabbar的代码中,引入类trahslations.dart,使用如下: 1234567891011import './utils/trahslations.dart'; Text getTabTitle(int curIndex) { if (curIndex == _tabIndex) { return new Text(Translations.of(context).text(appBarTitles[curIndex]), style: new TextStyle(fontSize: 14.0, color: const Color(0xff1296db))); } else { return new Text( Translations.of(context).text( appBarTitles[curIndex]), style: new TextStyle(fontSize: 14.0, color: const Color(0xff515151))); } } 完整代码: tabbar.dart 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102 import 'package:flutter/material.dart';import './utils/trahslations.dart';import './pages/one/one_page.dart';import './pages/two/two_page.dart';class MainPageWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return new MainPageState(); }}class MainPageState extends State<MainPageWidget> { int _tabIndex = 0; var tabImages; var appBarTitles = ['home', 'mine']; /* * 存放二个页面,跟fragmentList一样 */ var _pageList; /* * 根据选择获得对应的normal或是press的icon */ Image getTabIcon(int curIndex) { if (curIndex == _tabIndex) { return tabImages[curIndex][1]; } return tabImages[curIndex][0]; } /* * 获取bottomTab的颜色和文字 */ Text getTabTitle(int curIndex) { if (curIndex == _tabIndex) { return new Text(Translations.of(context).text(appBarTitles[curIndex]), style: new TextStyle(fontSize: 14.0, color: const Color(0xff1296db))); } else { return new Text( Translations.of(context).text( appBarTitles[curIndex]), style: new TextStyle(fontSize: 14.0, color: const Color(0xff515151))); } } /* * 根据image路径获取图片 */ Image getTabImage(path) { return new Image.asset(path, width: 24.0, height: 24.0); } void initData() { /* * 初始化选中和未选中的icon */ tabImages = [ [ getTabImage('images/tab/home.png'), getTabImage('images/tab/home_active.png') ], [ getTabImage('images/tab/mine.png'), getTabImage('images/tab/mine_active.png') ], ]; /* * 子界面 */ _pageList = [ new OnePage(), new TwoPage(), ]; } @override Widget build(BuildContext context) { //初始化数据 initData(); return Scaffold( body: _pageList[_tabIndex], bottomNavigationBar: new BottomNavigationBar( items: <BottomNavigationBarItem>[ new BottomNavigationBarItem( icon: getTabIcon(0), title: getTabTitle(0)), new BottomNavigationBarItem( icon: getTabIcon(1), title: getTabTitle(1)), ], type: BottomNavigationBarType.fixed, //默认选中首页 currentIndex: _tabIndex, iconSize: 24.0, //点击事件 onTap: (index) { setState(() { _tabIndex = index; }); }, ), ); }} 其中,Xcode的多语言也最好跟着配置下文件,如图: 以上就是全部代码了.如果不明白的下面有源码地址,可以去那里看看. 源码地址","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Flutter~Android原生工程中添加Flutter模块","slug":"移动端学习/Flutter~Android原生工程中添加Flutter模块","date":"2019-05-30T16:00:00.000Z","updated":"2023-12-20T05:34:28.864Z","comments":true,"path":"2019/05/31/移动端学习/Flutter~Android原生工程中添加Flutter模块/","link":"","permalink":"https://zhoushaoting.com/2019/05/31/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~Android%E5%8E%9F%E7%94%9F%E5%B7%A5%E7%A8%8B%E4%B8%AD%E6%B7%BB%E5%8A%A0Flutter%E6%A8%A1%E5%9D%97/","excerpt":"今天,学习下,如何在Android原生工程中集成Flutter模块","text":"今天,学习下,如何在Android原生工程中集成Flutter模块 ok,首先我们使用Android Studio新建一个Android原生工程,先放着.然后通过flutter create -t module flutter_module新建一个flutter的模块,这里名字随意.以flutter_module为例. 使用Android Studio打开Android原生工程,在settings.gradle中添加如下代码: 123456 setBinding(new Binding([gradle: this])) // newevaluate(new File( // new settingsDir.parentFile, // new 'flutter_module/.android/include_flutter.groovy' // new)) 注意路径的正确性.其中这个Binding会报红,可以无视,不影响运行.如图所示: 在项目下的build.grade中的dependencies中添加flutter模块的引入: 12implementation project(':flutter') 如图所示: build下工程,如果出现报错信息:Error:Invoke-customs are only supported starting with Android 0 (--min-api 26) 的话,需要添加: 12345compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } 如图所示: OK.Android原生工程集成flutter完毕.接下来就是演示代码了. 首先在activity_mian.xml中添加 1234567<Button android:id="@+id/jump" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Go to" /> 然后在MainActivity.java中添加调用代码.文件代码如下: 123456789101112131415161718192021222324252627282930313233 package com.example.androidaddflutter;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.view.View;import android.widget.FrameLayout;import io.flutter.facade.Flutter;public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.jump).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { View flutterView = Flutter.createView( MainActivity.this, getLifecycle(), "android 传给Flutter的值" ); FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); layout.leftMargin = 0; layout.topMargin = 200; addContentView(flutterView, layout); } }); }} 其中,可以向flutter传一个参数过去.在flutter_module模块中,通过window.defaultRouteName的方式即可获取到Android原生传过去的值.其中需要引入 import 'dart:ui'; 源码 效果图:","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Flutter~iOS原生工程中添加Flutter模块","slug":"移动端学习/Flutter~iOS原生工程中添加Flutter模块","date":"2019-05-28T16:00:00.000Z","updated":"2023-12-20T05:43:31.430Z","comments":true,"path":"2019/05/29/移动端学习/Flutter~iOS原生工程中添加Flutter模块/","link":"","permalink":"https://zhoushaoting.com/2019/05/29/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~iOS%E5%8E%9F%E7%94%9F%E5%B7%A5%E7%A8%8B%E4%B8%AD%E6%B7%BB%E5%8A%A0Flutter%E6%A8%A1%E5%9D%97/","excerpt":"今天,学习下,如何在iOS原生工程中集成Flutter模块","text":"今天,学习下,如何在iOS原生工程中集成Flutter模块 ok,首先我们使用Xcode新建一个iOS原生工程,先放着.然后通过flutter create -t module flutter_module新建一个flutter的模块,这里名字随意.以flutter_module为例. 然后在iOS原生工程中,使用pod导入flutter:首先,终端上先通过pod init为原生工程创建Podfile文件. 然后在该Podfile文件中添加以下: 123 flutter_application_path = '../flutter_module/'eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding) 注意路径正确. 终端 cd到iOS原生项目根目录下. 执行pod install 通过点击XXXX.xcworkspace文件打开Xcode.找到”TAGGETS”->”Build Settings”->”Enable Bitcode”首先把项目bitcode关闭. 找到”TAGGETS”->”Build Settings”->”Enable Phases”.点击+号,新建一个”New Run Script Phase”.其中加入: 123 "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed 然后,把它移动Target Dependencied下面. ok.Xcode build下,正确情况下是工程好的.集成步骤完毕. 然后我们在 ViewController.m中通过presentViewController的方式跳至flutter模块即可.首先,引入 123#import <Flutter/Flutter.h>#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h> 其中,可以为flutter模块传个参数,通过setInitialRoute.该文件完整代码如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344//// ViewController.m// iOSAddFlutter//// Created by Shaoting Zhou on 2019/5/29.// Copyright © 2019 Shaoting Zhou. All rights reserved.//#import "ViewController.h"#import <Flutter/Flutter.h>#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; UIButton *button = [[UIButton alloc]init]; [button setTitle:@"跳至Flutter模块" forState:UIControlStateNormal]; button.backgroundColor=[UIColor redColor]; button.frame = CGRectMake(50, 50, 200, 100); [button setTitleColor:[UIColor redColor] forState:UIControlStateHighlighted]; [button addTarget:self action:@selector(buttonPrint) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:button]; // Do any additional setup after loading the view.}- (void)buttonPrint{ FlutterViewController * flutterVC = [[FlutterViewController alloc]init]; [flutterVC setInitialRoute:@"我是Native传给FLutter模块的"]; [self presentViewController:flutterVC animated:true completion:nil];}@end 在flutter_module模块中,通过window.defaultRouteName的方式即可获取到iOS原生传过去的值.其中需要引入 import 'dart:ui'; 源码 效果图:","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"React-Native使用自定义字体","slug":"移动端学习/RN~React-Native使用自定义字体","date":"2019-05-13T16:00:00.000Z","updated":"2023-12-20T06:11:00.055Z","comments":true,"path":"2019/05/14/移动端学习/RN~React-Native使用自定义字体/","link":"","permalink":"https://zhoushaoting.com/2019/05/14/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/RN~React-Native%E4%BD%BF%E7%94%A8%E8%87%AA%E5%AE%9A%E4%B9%89%E5%AD%97%E4%BD%93/","excerpt":"今天,学习一下在React-Native中使用自定义字体.使用版本是 "react-native": "0.59.8"","text":"今天,学习一下在React-Native中使用自定义字体.使用版本是 "react-native": "0.59.8" 首先先在项目根目录新建一个文件夹用来存放XXXX.ttf字体文件,我一般喜欢在最外面加一个assets文件夹,里面再加一个fonts文件夹,之后再放置字体文件XXX.ttf.然后,在package.json中加入该文件路径.如 12345678910{ .... "rnpm": { "assets": [ "./assets/fonts" ] } } 如图:之后,终端cd 到项目根目录,然后运行react-native link.完成之后打开Xcode可以看到生成了Resources文件夹下的xxx.ttf字体文件,同时在项目的info.plist中多了Fonts provided by application这个数组,其中就有前面的xxx.ttf文件.如图: OK.字体文件已经配置完毕,接下来使用即可.在iOS平台上不能直接使用字体文件名,需要使用familyNames 才可以.我们先获取一下该字体文件的的familyNames .在AppDelegate.m中,使用 123456789for(NSString *fontfamilyname in [UIFont familyNames]) { NSLog(@"family:'%@'",fontfamilyname); for(NSString *fontName in [UIFont fontNamesForFamilyName:fontfamilyname]) { NSLog(@"\\tfont:'%@'",fontName); } NSLog(@"-------------");} 其中 1NSLog(@"family:'%@'",fontfamilyname); 打印的就是familyNames .android直接使用字体文件名xxx即可.如图:效果如图(部分字体只能使用真机才能看出来): 源码","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"react-native-router-flux初步认识","slug":"移动端学习/RN~react-native-router-flux初步认识","date":"2019-04-22T07:56:58.000Z","updated":"2023-12-20T06:08:03.842Z","comments":true,"path":"2019/04/22/移动端学习/RN~react-native-router-flux初步认识/","link":"","permalink":"https://zhoushaoting.com/2019/04/22/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/RN~react-native-router-flux%E5%88%9D%E6%AD%A5%E8%AE%A4%E8%AF%86/","excerpt":"今天初步了解下 react-native-router-flux 这个三方导航库,基于react-native0.55.4,react-native-router-flux^4.0.0-beta.31","text":"今天初步了解下 react-native-router-flux 这个三方导航库,基于react-native0.55.4,react-native-router-flux^4.0.0-beta.31 效果图:使用方法请参考:参考1参考2我这里不详细说明了,贴出关键代码一看便知.代码基本结构如下: App.js: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394/** * Sample React Native App * https://github.com/facebook/react-native * @flow */import React, {Component} from 'react';import { Platform, StyleSheet, Text, View, Image} from 'react-native';import { Navigation, Scene, Router, Modal} from 'react-native-router-flux';import IOS from './src/ios';import Android from './src/android'import Web from './src/web'import TabIcon from './src/TabIcon'type Props = {};export default class App extends Component<Props> { render() { return ( <Router> <Modal> <Scene key="root" tabBarPosition="bottom" tabs> <Scene hideBackImage key="IOS" title="苹果" component={IOS} icon={TabIcon} Image={require('./src/image/ios.png')} selectedImage={require('./src/image/ios_active.png')} /> <Scene hideBackImage key="Web" component={Web} title="web" icon={TabIcon} Image={require('./src/image/web.png')} showLabel = {false} selectedImage={require('./src/image/web_active.png')} /> <Scene hideBackImage key="Android" component={Android} title="安卓" icon={TabIcon} Image={require('./src/image/android.png')} hideNavBar={true} //隐藏导航栏 selectedImage={require('./src/image/android_active.png')} /> </Scene> </Modal> </Router> ); }}const styles = StyleSheet.create({ tabbarContainer: { flex: 1, backgroundColor: "#f6f6f6", overflow: 'visible' }, tabIconItem: { flex: 1, height: 56, flexDirection: 'column', alignItems: 'center', justifyContent: 'center', paddingLeft: 8, paddingRight: 8, backgroundColor: 'transparent', overflow: 'visible' }, tabIconImage: { width: 60, height: 60, overflow: 'visible' },}); TabIcon.js 1234567891011121314151617181920212223242526272829303132333435363738394041424344import React, {PropTypes, Component} from 'react'import {Text, View, Image, StyleSheet} from 'react-native'const tabIconStyles = StyleSheet.create({ tabIconItem: { flex: 1, height: 56, flexDirection: 'column', alignItems: 'center', justifyContent: 'center', paddingLeft: 8, paddingRight: 8, backgroundColor: 'transparent', }, tabIconImage: { width: 24, height: 24 }, titleText: { marginTop: 5, textAlign: 'center', fontSize: 11 }, titleTextDefaultColor: { color: 'black' }, titleTextSelectColor: { color: 'red' }, tabItemRow: { flexDirection: 'row' },});export default TabIcon=(props)=>{ return ( <View style={tabIconStyles.tabIconItem}> <Image style={tabIconStyles.tabIconImage} source={props.focused ? props.selectedImage : props.Image}/> <Text>{props.tabTitle}</Text> </View> );}; 而至于ios.js,web.js,android.js只是基本的模板界面 123456789101112131415161718192021222324252627282930313233343536373839404142434445/** * Sample React Native App * https://github.com/facebook/react-native * @flow */import React, { Component } from 'react';import { Platform, StyleSheet, Text, View} from 'react-native';type Props = {};export default class IOS extends Component<Props> { render() { return ( <View style={styles.container}> <Text style={styles.welcome}> 首页 </Text> </View> ); }}const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, welcome: { fontSize: 20, textAlign: 'center', margin: 10, }, instructions: { textAlign: 'center', color: '#333333', marginBottom: 5, },}); 源码","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Android和React-Native的简单交互","slug":"移动端学习/RN~Android和React-Native的简单交互","date":"2019-04-22T07:53:06.000Z","updated":"2023-12-20T06:01:09.824Z","comments":true,"path":"2019/04/22/移动端学习/RN~Android和React-Native的简单交互/","link":"","permalink":"https://zhoushaoting.com/2019/04/22/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/RN~Android%E5%92%8CReact-Native%E7%9A%84%E7%AE%80%E5%8D%95%E4%BA%A4%E4%BA%92/","excerpt":"首先在Android原生中.新建class文件TransMissonMoudle:","text":"首先在Android原生中.新建class文件TransMissonMoudle: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109/** * Created by shaotingzhou on 2018/3/7. */package com.androidrn.RN;import com.facebook.react.bridge.Callback;import com.facebook.react.bridge.Promise;import com.facebook.react.bridge.ReactApplicationContext;import com.facebook.react.bridge.ReactContext;import com.facebook.react.bridge.ReactContextBaseJavaModule;import com.facebook.react.bridge.ReactMethod;import com.facebook.react.bridge.WritableMap;import com.facebook.react.bridge.WritableNativeMap;import com.facebook.react.modules.core.DeviceEventManagerModule;import java.text.SimpleDateFormat;import java.util.Date;import javax.annotation.Nullable;public class TransMissonMoudle extends ReactContextBaseJavaModule { private static final String REACT_CLASS = "TransMissonMoudle"; private ReactContext mReactContext; public TransMissonMoudle(ReactApplicationContext reactContext) { super(reactContext); this.mReactContext = reactContext; } @Override public String getName() { return REACT_CLASS; } //延迟0.1秒获取时间。 @ReactMethod public void getTime() { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } String time = getTimeMillis(); WritableMap writableMap = new WritableNativeMap(); writableMap.putString("key", time); sendTransMisson(mReactContext, "EventName", writableMap); } }).start(); } private String getTimeMillis() { SimpleDateFormat formatDate = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); Date date = new Date(System.currentTimeMillis()); String time = formatDate.format(date); return time; } /** * RCTDeviceEventEmitter方式 * * @param reactContext * @param eventName 事件名 * @param params 传惨 */ public void sendTransMisson(ReactContext reactContext, String eventName, @Nullable WritableMap params) { reactContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit(eventName, params); } /** * CallBack方式 * * @param name * @param callback */ @ReactMethod public void callBackTime(String name, Callback callback) { callback.invoke(getTimeMillis()); } /** * Promise方式 * @param name * @param promise */ @ReactMethod public void sendPromiseTime(String name, Promise promise) { WritableMap writableMap=new WritableNativeMap(); writableMap.putString("age","20"); writableMap.putString("time",getTimeMillis()); promise.resolve(writableMap); }} 再建class文件TransMissonPackage: 123456789101112131415161718192021222324252627282930313233343536373839package com.androidrn.RN;import com.facebook.react.ReactPackage;import com.facebook.react.bridge.JavaScriptModule;import com.facebook.react.bridge.NativeModule;import com.facebook.react.bridge.ReactApplicationContext;import com.facebook.react.uimanager.ViewManager;import java.util.ArrayList;import java.util.Collections;import java.util.List;/** * Created by shaotingzhou on 2018/3/7. */public class TransMissonPackage implements ReactPackage { @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { List<NativeModule> modules = new ArrayList<>(); modules.add(new TransMissonMoudle(reactContext));//摇一摇 return modules; }// @Override// public List<Class<? extends JavaScriptModule>> createJSModules() {// return Collections.emptyList();// } @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { List<ViewManager> viewManagerList=new ArrayList<>();// viewManagerList.add(new PTRRefreshManager()); return viewManagerList; }} 接着在MainApplication里引入TransMissonPackage,加入包.如 import com.androidrn.RN.TransMissonPackage; new TransMissonPackage() 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849package com.androidrn;import android.app.Application;import com.androidrn.RN.TransMissonPackage;import com.facebook.react.ReactApplication;import com.facebook.react.ReactNativeHost;import com.facebook.react.ReactPackage;import com.facebook.react.shell.MainReactPackage;import com.facebook.soloader.SoLoader;import java.util.Arrays;import java.util.List;public class MainApplication extends Application implements ReactApplication { private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { @Override public boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; } @Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(), new TransMissonPackage() ); } @Override protected String getJSMainModuleName() { return "index"; } }; @Override public ReactNativeHost getReactNativeHost() { return mReactNativeHost; } @Override public void onCreate() { super.onCreate(); SoLoader.init(this, /* native exopackage */ false); }} 我的文件放置情况如图: 然后只需要在RN这边: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111/** * Sample React Native App * https://github.com/facebook/react-native * @flow */import React, {Component} from 'react';import { AppRegistry, StyleSheet, Text, View, DeviceEventEmitter, NativeModules, ToastAndroid, Platform} from 'react-native';const instructions = Platform.select({ ios: 'Press Cmd+R to reload,\\n' + 'Cmd+D or shake for dev menu', android: 'Double tap R on your keyboard to reload,\\n' + 'Shake or press menu button for dev menu',});type Props = {};export default class App extends Component<Props> { componentWillMount() { DeviceEventEmitter.addListener('EventName', function (msg) { console.log('DeviceEventEmitter收到消息'+msg); alert('DeviceEventEmitter收到消息'+ msg.key) }); } render() { return ( <View style={styles.container}> <Text style={styles.welcome} onPress={this.getDeviceEventEmitterTime.bind(this)} > RCTDeviceEventEmitter获取时间 </Text> <Text style={styles.welcome} onPress={this.getCallBackTime.bind(this)} > CallBack获取时间 </Text> <Text style={styles.welcome} onPress={this.getPromiseTime.bind(this)} > Promise获取时间 </Text> </View> ); } getDeviceEventEmitterTime() { NativeModules.TransMissonMoudle.getTime(); } getCallBackTime() { NativeModules.TransMissonMoudle.callBackTime("Allure", (msg) => { console.log('callBack:---' + msg); alert('callBack:---'+msg) } ); } getPromiseTime() { NativeModules.TransMissonMoudle.sendPromiseTime("Allure").then(msg=> { console.log("年龄:" + msg.age + "/n" + "时间:" + msg.time); alert("年龄" + msg.age + "时间" + msg.time) this.setState({ age: msg.age, time: msg.time, }) }).catch(error=> { console.log('错误' + error); }); }}const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, welcome: { fontSize: 20, textAlign: 'center', margin: 10, }, instructions: { textAlign: 'center', color: '#333333', marginBottom: 5, },}); 源码 效果图:","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"React-Native与Web的基本交互","slug":"移动端学习/RN~React-Native与Web的基本交互","date":"2019-04-21T16:00:00.000Z","updated":"2023-12-20T06:12:14.809Z","comments":true,"path":"2019/04/22/移动端学习/RN~React-Native与Web的基本交互/","link":"","permalink":"https://zhoushaoting.com/2019/04/22/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/RN~React-Native%E4%B8%8EWeb%E7%9A%84%E5%9F%BA%E6%9C%AC%E4%BA%A4%E4%BA%92/","excerpt":"今天学习下React Native与Web端的基本交互,暂时涉及到RN向Web端传值,Web向RN端传值,因为现在转为Flutter开发,所以这一块暂时只能涉及到这里.其中的Web端采用本地html页面.","text":"今天学习下React Native与Web端的基本交互,暂时涉及到RN向Web端传值,Web向RN端传值,因为现在转为Flutter开发,所以这一块暂时只能涉及到这里.其中的Web端采用本地html页面. 本文设计的交互主要靠webView的onMessage和postMessage.在RN端发送数据给Web,主要依靠调用webView的potsMessage方法实现,如: 1this.refs.webview.postMessage(this.data); 接收Web端的数据靠的是WebView的onMessage方法 123onMessage={(e) => { this.handleMessage(e) }} 下面是完整代码: App.js12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273import React from 'react';import { View, Text, StyleSheet, WebView} from 'react-native';export default class Web extends React.Component { constructor(props) { super(props); this.state = { webViewData: '' } this.data = '我是RN传给web端的参数'; } sendMessage() { this.refs.webview.postMessage(this.data); } handleMessage(e) { this.setState({webViewData: e.nativeEvent.data}); } render() { return ( <View style={styles.container}> {/*web*/} <View style={{width: 375, height: 220}}> <WebView ref={'webview'} source={require('./test.html')} style={{width: 375, height: 220}} onMessage={(e) => { this.handleMessage(e) }} /> </View> {/*RN*/} <Text>来自webview的数据 : {this.state.webViewData}</Text> <Text onPress={() => { this.sendMessage() }}> 发送数据到WebView </Text> </View> ) }}const styles = StyleSheet.create({ container: { flex: 1, marginTop: 22, backgroundColor: '#F5FCFF', },}); 而在html里面,需要监听数据,通过: 123document.addEventListener('message', function (e) { document.getElementById('data').textContent = e.data; }); 发送数据通过: 12window.postMessage(data); 完整代码如下: test.html1234567891011121314151617181920212223242526272829303132333435363738<!DOCTYPE html><html lang="en"><head> <title></title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"></head><body><p style="text-align: center"> <button id="button">发送数据到react native</button><p style="text-align: center">收到react native发送的数据: <span id="data"></span></p></p><script> var data = 0; function sendData(data) { if (window.originalPostMessage) { window.postMessage(data); } else { throw Error('postMessage接口还未注入'); } } window.onload = function () { document.addEventListener('message', function (e) { document.getElementById('data').textContent = e.data; }); document.getElementById('button').onclick = function () { data += 11; sendData(data); } }</script></body></html> 效果图: 源码 另外:在未来的版本中,可能会移除自带的webView,改用在外部库里引用的方式.同时,在三方库中,也有webView-bridge库使用,cookie库使用.react-native-webviewwebView-bridgecookie","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Flutter~路由管理库fluro学习","slug":"移动端学习/Flutter~路由管理库fluro学习","date":"2019-03-29T16:00:00.000Z","updated":"2023-12-20T05:31:36.389Z","comments":true,"path":"2019/03/30/移动端学习/Flutter~路由管理库fluro学习/","link":"","permalink":"https://zhoushaoting.com/2019/03/30/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~%E8%B7%AF%E7%94%B1%E7%AE%A1%E7%90%86%E5%BA%93fluro%E5%AD%A6%E4%B9%A0/","excerpt":"今天学习下Flutter中路由管理库 fluro使用版本是: fluro: "^1.4.0".","text":"今天学习下Flutter中路由管理库 fluro使用版本是: fluro: "^1.4.0". fluro 代码结构:首先,新建文件夹存放fluro的关键代码: application.dartapplication.dart,该文件主要是生成一个静态文件,方便后面直接调用初始化的Router对象 12345import 'package:fluro/fluro.dart';class Application { static Router router;} router_handler.dartrouter_handler.dart文件,主要负责路由跳转,从代码可以看出,有两个handler,其中一个需要带一个参数paramsId 12345678910111213141516import 'package:flutter/material.dart';import 'package:fluro/fluro.dart';import '../pages/detailspage.dart';import '../pages/otherpaage.dart';Handler detailsHandle = Handler( handlerFunc: (BuildContext context, Map<String, List<String>> params) { String paramsId = params['id'].first; return DetailsPage(paramsId);});Handler otherHandle = Handler( handlerFunc: (BuildContext context, Map<String, List<String>> params) { return OtherPage();}); routers.dartrouters.dart负责将上面创建好的handle注册进fluro. 12345678910111213141516171819import 'package:flutter/material.dart';import 'package:fluro/fluro.dart';import './router_handler.dart';class Routers{ static String root = '/'; static String detailsPage = './detail'; static String otherPage = './other'; static void configureRouters(Router router){ router.notFoundHandler = new Handler( handlerFunc: (BuildContext context,Map<String,List<String>> params){ print('错误路由'); } ); router.define(detailsPage,handler: detailsHandle); router.define(otherPage,handler: otherHandle); }} 然后需要在项目入口处,配置自定义路由onGenerateRoute main.dart1234567891011121314151617181920212223242526import 'package:flutter/material.dart';import 'tabbar.dart';import 'package:fluro/fluro.dart';import './routers/application.dart';import './routers/routers.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { final router =Router(); Routers.configureRouters(router); Application.router = router; return MaterialApp( title: 'Flutter Demo', onGenerateRoute: Application.router.generator, theme: ThemeData( primarySwatch: Colors.blue, ), home: Tabbar(), ); }} 配置好后可以在需要跳转的方法跳转,同时可以配置push方向等.如: 12Application.router.navigateTo(context, "./detail?id=${dataList[index]}", transition: TransitionType.inFromLeft); 下面是页面中的所有代码: homepage.dart12345678910111213141516171819202122232425262728293031323334353637383940414243444546import 'package:flutter/material.dart';import 'package:fluro/fluro.dart';import '../routers/application.dart';class Homepage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('HomePage')), body: HomeContainer(), ); }}class HomeContainer extends StatefulWidget { @override _HomeContainerState createState() => _HomeContainerState();}class _HomeContainerState extends State<HomeContainer> { List<String> dataList = []; @override void initState() { super.initState(); for (var i = 0; i < 100; i++) { dataList.add(i.toString()); } } @override Widget build(BuildContext context) { return ListView.builder( itemCount: dataList.length, itemBuilder: (context, index) { return ListTile( onTap: (){ Application.router.navigateTo(context, "./detail?id=${dataList[index]}", transition: TransitionType.inFromLeft); }, title: Text(dataList[index] + '👌'), ); }, ); }} Showpage.dart12345678910111213141516171819202122import 'package:flutter/material.dart';import 'package:fluro/fluro.dart';import '../routers/application.dart';class Showpage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('SHOW')), body: Center( child: RaisedButton( onPressed: (){ Application.router.navigateTo(context, "./other", transition: TransitionType.nativeModal); }, child: Text('跳转'), ), ), ); }} minepage.dart1234567891011121314import 'package:flutter/material.dart';class Minepage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Minepage')), body: Center( child: Text('Minepage'), ), ); }} 效果如图: 源码","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Flutter~flutter_bloc库学习","slug":"移动端学习/Flutter~flutter_bloc库学习","date":"2019-03-06T16:00:00.000Z","updated":"2023-12-20T05:41:20.961Z","comments":true,"path":"2019/03/07/移动端学习/Flutter~flutter_bloc库学习/","link":"","permalink":"https://zhoushaoting.com/2019/03/07/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~flutter_bloc%E5%BA%93%E5%AD%A6%E4%B9%A0/","excerpt":"今天学习下Flutter中state管理库 bloc下的flutter_bloc使用版本是:flutter_bloc 0.7.0.","text":"今天学习下Flutter中state管理库 bloc下的flutter_bloc使用版本是:flutter_bloc 0.7.0. blocflutter_bloc 代码结构: main.dart123456789101112131415161718192021222324252627import 'package:flutter/material.dart';import 'package:flutter_bloc_demo/top_screen.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( // This is the theme of your application. // // Try running your application with "flutter run". You'll see the // application has a blue toolbar. Then, without quitting the app, try // changing the primarySwatch below to Colors.green and then invoke // "hot reload" (press "r" in the console where you ran "flutter run", // or simply save your changes to "hot reload" in a Flutter IDE). // Notice that the counter didn't reset back to zero; the application // is not restarted. primarySwatch: Colors.blue, ), home: TopScreen() ); }} top_screen.dart12345678910111213141516171819202122232425import 'package:flutter/material.dart';import 'package:flutter_bloc_demo/bloc/counter_bloc.dart';import 'package:flutter_bloc/flutter_bloc.dart';import 'tabbar.dart';class TopScreen extends StatefulWidget { @override _TopScreenState createState() => _TopScreenState();}class _TopScreenState extends State<TopScreen> { final CounterBloc _counterBloc = CounterBloc(); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', home: BlocProvider<CounterBloc>( bloc: _counterBloc, child: Tabbar(), ), ); }} tabbar.dart123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102import 'package:flutter/material.dart';import 'package:flutter_bloc_demo/pages/one.dart';import 'package:flutter_bloc_demo/pages/Two.dart'; class Tabbar extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( debugShowCheckedModeBanner: false, home: new MainPageWidget()); }} class MainPageWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return new MainPageState(); }} class MainPageState extends State<MainPageWidget> { int _tabIndex = 0; var tabImages; var appBarTitles = ['壹', '贰',]; /* * 存放而个页面,跟fragmentList一样 */ var _pageList; /* * 根据选择获得对应的normal或是press的icon */ Image getTabIcon(int curIndex) { if (curIndex == _tabIndex) { return tabImages[curIndex][1]; } return tabImages[curIndex][0]; } /* * 获取bottomTab的颜色和文字 */ Text getTabTitle(int curIndex) { if (curIndex == _tabIndex) { return new Text(appBarTitles[curIndex], style: new TextStyle(fontSize: 14.0, color: const Color(0xff1296db))); } else { return new Text(appBarTitles[curIndex], style: new TextStyle(fontSize: 14.0, color: const Color(0xff515151))); } } /* * 根据image路径获取图片 */ Image getTabImage(path) { return new Image.asset(path, width: 24.0, height: 24.0); } void initData() { /* * 初始化选中和未选中的icon */ tabImages = [ [getTabImage('images/tab/one.png'), getTabImage('images/tab/one_active.png')], [getTabImage('images/tab/two.png'), getTabImage('images/tab/two_active.png')], ]; /* * 2个子界面 */ _pageList = [ new One(), new Two(), ]; } @override Widget build(BuildContext context) { //初始化数据 initData(); return Scaffold( body: _pageList[_tabIndex], bottomNavigationBar: new BottomNavigationBar( items: <BottomNavigationBarItem>[ new BottomNavigationBarItem( icon: getTabIcon(0), title: getTabTitle(0)), new BottomNavigationBarItem( icon: getTabIcon(1), title: getTabTitle(1)), ], type: BottomNavigationBarType.fixed, //默认选中首页 currentIndex: _tabIndex, iconSize: 24.0, //点击事件 onTap: (index) { setState(() { _tabIndex = index; }); }, )); }} one.dart12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849import 'package:flutter/material.dart';import 'package:flutter_bloc_demo/bloc/counter_bloc.dart';import 'package:flutter_bloc/flutter_bloc.dart';class One extends StatelessWidget { @override Widget build(BuildContext context) { final CounterBloc _counterBloc = BlocProvider.of<CounterBloc>(context); return Scaffold( appBar: AppBar(title: Text('One')), body: BlocBuilder<CounterEvent, int>( bloc: _counterBloc, builder: (BuildContext context, int count) { return Center( child: Text( '$count', style: TextStyle(fontSize: 24.0), ), ); }, ), floatingActionButton: Column( crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ Padding( padding: EdgeInsets.symmetric(vertical: 5.0), child: FloatingActionButton( child: Icon(Icons.add), onPressed: () { _counterBloc.dispatch(CounterEvent.increment); }, ), ), Padding( padding: EdgeInsets.symmetric(vertical: 5.0), child: FloatingActionButton( child: Icon(Icons.remove), onPressed: () { _counterBloc.dispatch(CounterEvent.decrement); }, ), ), ], ), ); }} two.dart12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849import 'package:flutter/material.dart';import 'package:flutter_bloc_demo/bloc/counter_bloc.dart';import 'package:flutter_bloc/flutter_bloc.dart';class Two extends StatelessWidget { @override Widget build(BuildContext context) { final CounterBloc _counterBloc = BlocProvider.of<CounterBloc>(context); return Scaffold( appBar: AppBar(title: Text('Two')), body: BlocBuilder<CounterEvent, int>( bloc: _counterBloc, builder: (BuildContext context, int count) { return Center( child: Text( '$count', style: TextStyle(fontSize: 24.0), ), ); }, ), floatingActionButton: Column( crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ Padding( padding: EdgeInsets.symmetric(vertical: 5.0), child: FloatingActionButton( child: Icon(Icons.remove), onPressed: () { _counterBloc.dispatch(CounterEvent.decrement); }, ), ), Padding( padding: EdgeInsets.symmetric(vertical: 5.0), child: FloatingActionButton( child: Icon(Icons.add), onPressed: () { _counterBloc.dispatch(CounterEvent.increment); }, ), ), ], ), ); }} counter_bloc.dart123456789101112131415161718192021import 'package:bloc/bloc.dart';enum CounterEvent { increment, decrement }class CounterBloc extends Bloc<CounterEvent, int> { @override int get initialState => 0; @override Stream<int> mapEventToState(int currentState, CounterEvent event) async* { switch (event) { case CounterEvent.decrement: yield currentState - 1; break; case CounterEvent.increment: yield currentState + 1; break; } }}shaoting0730 效果如图: 源码 另外:其他管理库学习 另外:Flutter学习demo","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Flutter~json_serializable库学习","slug":"移动端学习/Flutter~json_serializable库学习","date":"2019-03-05T16:00:00.000Z","updated":"2023-12-20T05:45:22.765Z","comments":true,"path":"2019/03/06/移动端学习/Flutter~json_serializable库学习/","link":"","permalink":"https://zhoushaoting.com/2019/03/06/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~json_serializable%E5%BA%93%E5%AD%A6%E4%B9%A0/","excerpt":"今天学习下Flutter中实体类三方库json_serializable.其中,json_serializable: ^2.0.0,json_annotation: ^2.0.0,build_runner: ^1.0.0","text":"今天学习下Flutter中实体类三方库json_serializable.其中,json_serializable: ^2.0.0,json_annotation: ^2.0.0,build_runner: ^1.0.0 1、首先,新建一个Flutter项目,引入dio网络请求库,按照json_serializable的说明,分别在dependencies下引入json_annotation,在dev_dependencies下引入json_serializable和build_runner(build_runner是dart团队提供的一个生成dart代码文件的外部包).2、在main.dart里面写一个网络请求. main.dart1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677import 'package:flutter/material.dart';import 'package:dio/dio.dart';import 'dart:async';import 'package:json_demo/Data.dart';// flutter packages pub run build_runner buildvoid main() => runApp(MyApp());class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: DataToModel(), ); }}class DataToModel extends StatefulWidget { final Widget child; DataToModel({Key key, this.child}) : super(key: key); _DataToModelState createState() => _DataToModelState();}class _DataToModelState extends State<DataToModel> { Future getHomePageContent() async { try { print('开始获取首页数据'); Response response; Dio dio = new Dio(); response = await dio.get('http://gank.io/api/data/%E7%A6%8F%E5%88%A9/10/1'); if (response.statusCode == 200) { return response.data; } else { throw Exception('后端接口出现异常'); } } catch (e) { print('ERROR 发生错误👇'); return print(e); }} @override void initState() { super.initState(); getHomePageContent().then((val) { Data dd = Data.fromJson(val); print(dd.error); // 数组 print(dd.results); // 数组第一个元素:map print(dd.results[0].createdAt); print(dd.results[0].desc); print(dd.results[0].publishedAt); print(dd.results[0].source); print(dd.results[0].type); print(dd.results[0].url); print(dd.results[0].used); print(dd.results[0].who); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('json_serializable')), body: Text('json_serializable'), ); }} 这里的接口,网上随意找一个: http://gank.io/api/data/%E7%A6%8F%E5%88%A9/10/1大致结构如图:3、然后根据返回数据,书写两个dart文件,Data.dart是外部map,Dic.dart是内部的List. Data.dart123456789101112131415import 'package:json_annotation/json_annotation.dart';import 'package:json_demo/Dic.dart';part 'Data.g.dart';@JsonSerializable()class Data{ bool error; List<Dic> results; Data(this.error,this.results); //反序列化 factory Data.fromJson(Map<String, dynamic> json) => _$DataFromJson(json); //序列化 Map<String, dynamic> toJson() => _$DataToJson(this); } Dic.dart12345678910111213141516171819202122import 'package:json_annotation/json_annotation.dart';part 'Dic.g.dart';@JsonSerializable()class Dic{ final String createdAt; final String desc; final String publishedAt; final String source; final String type; final String url; final bool used; final String who; Dic({this.createdAt, this.desc, this.publishedAt, this.source, this.type, this.url, this.used, this.who}); //反序列化 factory Dic.fromJson(Map<String, dynamic> json) => _$DicFromJson(json); //序列化 Map<String, dynamic> toJson() => _$DicToJson(this); } 然后,ide会报几个错误,这个不用管,那是尚未生成解析文件的原因. 4、然后,终端 cd 到项目根目录,回车,执行命令flutter packages pub run build_runner build,会生成对应的解析文件. 5、ok,撸好之后,输出看一下 1234567891011121314Data dd = Data.fromJson(val); print(dd.error); // 数组 print(dd.results); // 数组第一个元素:map print(dd.results[0].createdAt); print(dd.results[0].desc); print(dd.results[0].publishedAt); print(dd.results[0].source); print(dd.results[0].type); print(dd.results[0].url); print(dd.results[0].used); print(dd.results[0].who); 总结:以上就是对flutter中实体类库json_serializable的简单学习,身边大佬有的说很有必要,因为规范点,无规矩不成三角形,有了实体类方便维护,本来原生就是该有model的,这样不容易出现取值错误和其他问题;有的大佬说没啥必要,有点繁琐了.我也不知道谁缩的对,但是学下总没错,就像鲁迅说的那样”人活着什么最重要,肯定是开心辣”. 网上还有其他的方案:a. android studio 安装插件 dart_json_formatb. https://github.com/debuggerx01/JSONFormat4Flutterc. 一个在线转换工具: https://javiercbk.github.io/json_to_dart/ 源码","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Flutter~sqflite库学习","slug":"移动端学习/Flutter~sqflite库学习","date":"2019-03-03T16:00:00.000Z","updated":"2023-12-20T05:54:51.942Z","comments":true,"path":"2019/03/04/移动端学习/Flutter~sqflite库学习/","link":"","permalink":"https://zhoushaoting.com/2019/03/04/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~sqflite%E5%BA%93%E5%AD%A6%E4%B9%A0/","excerpt":"今天学习下Flutter中SQLite数据库第三方sqflite.其中, sqflite: ^1.1.0","text":"今天学习下Flutter中SQLite数据库第三方sqflite.其中, sqflite: ^1.1.0 涉及 数据库的创建 表的创建 增删改查操作 关闭数据库 删除数据库 main.dart123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119import 'package:flutter/material.dart';import 'package:sqflite/sqflite.dart';import 'package:path/path.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( // This is the theme of your application. // // Try running your application with "flutter run". You'll see the // application has a blue toolbar. Then, without quitting the app, try // changing the primarySwatch below to Colors.green and then invoke // "hot reload" (press "r" in the console where you ran "flutter run", // or simply save your changes to "hot reload" in a Flutter IDE). // Notice that the counter didn't reset back to zero; the application // is not restarted. primarySwatch: Colors.blue, ), home: CRUD(), ); }}class CRUD extends StatefulWidget { final Widget child; CRUD({Key key, this.child}) : super(key: key); _CRUDState createState() => _CRUDState();}class _CRUDState extends State<CRUD> { Database db; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('数组库-增删改查')), body: SingleChildScrollView( child: Column( children: <Widget>[ OutlineButton(child: Text('创建数据库和一张表'), onPressed: _createTable), OutlineButton(child: Text('查询数据'), onPressed: _queryData), OutlineButton(child: Text('插入数据'), onPressed: _insertData), OutlineButton(child: Text('更新数据'), onPressed: _updateData), OutlineButton(child: Text('删除数据'), onPressed: _deleteData), OutlineButton(child: Text('关闭数据库'), onPressed: _closeData), OutlineButton(child: Text('删除数据库'), onPressed: _deleteTable), ], ), ), ); } // 创建数据库 和 表 void _createTable() async { // 获取数据库文件的存储路径 var databasesPath = await getDatabasesPath(); String path = join(databasesPath, 'demo.db'); //根据数据库文件路径和数据库版本号创建数据库表 db = await openDatabase(path, version: 1, onCreate: (Database db, int version) async { await db.execute( 'CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT, value INTEGER, num REAL)'); }); print('创建数据库 和 表'); } // 增 void _insertData() async { await db.transaction((txn) async { await txn.rawInsert('INSERT INTO Test(name, value, num) VALUES(?, ?, ?)', ['flutter==>', 0101010101001, 1111111111111111111]); print('插入数据'); }); } // 删 void _deleteData() async { await db.rawDelete('DELETE FROM Test WHERE name = ?', ['updated name']); print('删除'); } // 改 void _updateData() async { await db.rawUpdate( 'UPDATE Test SET name = ?, VALUE = ?,num = ? ', ['updated name', 0,1]); print('修改'); } // 查 void _queryData() async { List<Map> maps = await db.query('Test'); print(maps); } // 关闭数据库 void _closeData() async { await db.close(); print('关闭数据库'); } // 删除数据库 void _deleteTable() async { var databasesPath = await getDatabasesPath(); String path = join(databasesPath, 'demo.db'); await deleteDatabase(path); print('删除数据库'); }} 效果图: 源码","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"React学习:简单个人消费记账系统","slug":"前端学习/React学习~简单个人消费记账系统","date":"2019-03-03T12:33:42.000Z","updated":"2023-12-20T05:16:05.559Z","comments":true,"path":"2019/03/03/前端学习/React学习~简单个人消费记账系统/","link":"","permalink":"https://zhoushaoting.com/2019/03/03/%E5%89%8D%E7%AB%AF%E5%AD%A6%E4%B9%A0/React%E5%AD%A6%E4%B9%A0~%E7%AE%80%E5%8D%95%E4%B8%AA%E4%BA%BA%E6%B6%88%E8%B4%B9%E8%AE%B0%E8%B4%A6%E7%B3%BB%E7%BB%9F/","excerpt":"今天学习React,写一个小demo记录一下,弄一个简单的个人消费记账系统.","text":"今天学习React,写一个小demo记录一下,弄一个简单的个人消费记账系统. 使用脚手架 create-react-app ,后台使用mockAPI模拟,网络请求使用axios,css效果使用bootstrap/4.0.0 .先来个效果图:其主要代码均在../src/components中,主文件是Records.js,具体的消费记录是Record.js,总消费记录是Box.js,消费记录的创建是RecordForm.js ,下面是其源码:Records.js 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175import React, { Component } from 'react';import Record from './Record'import RecordForm from './RecordForm'import Box from './Box'import axios from 'axios'export default class Records extends Component { // 构造 constructor(props) { super(props); // 初始状态 this.state = { error:null, //网络错误信息 isLoader:true, //加载标示 records:[] //数据 }; } render() { //根据 error isLoader 显示不同UI //如果error有值,显示errorUI //如果isLoader 为true,显示加载UI //如果isLoader 为false,显示正确数据UI const {error,isLoader,records} = this.state let renderRecords; if(error){ renderRecords = <div>出错了:{error}</div> }else if(isLoader){ renderRecords = <div><img src={require('./loading.gif')} /></div> }else { renderRecords = ( <div> <table className="table table-bordered"> <thead> <tr> <th>日期</th> <th>标题</th> <th>金额</th> <th>事件</th> </tr> </thead> <tbody> {records.map((item,index)=> <Record key ={item.id} record={item} updateData={this.updateRecord.bind(this)} deleteData={this.deleteRecord.bind(this)} /> )} </tbody> </table> </div> ); } return( <div> <h2>消费记录</h2> <div className="row mb-3"> <Box text="收入" type="success" amount={this.credits()} /> <Box text="支出" type="danger" amount={this.debits()} /> <Box text="余额" type="info" amount={this.balance()} /> </div> <RecordForm handleNewRecord = {this.addRecord.bind(this)} /> {renderRecords} </div> ) } /** * 收入计算 * */ credits() { let credits = this.state.records.filter((record) => { return record.account >= 0; }) return credits.reduce((prev, curr) => { return prev + Number.parseInt(curr.account, 0) }, 0) } /** * 支出计算 * */ debits() { let credits = this.state.records.filter((record) => { return record.account < 0; }) return credits.reduce((prev, curr) => { return prev + Number.parseInt(curr.account, 0) }, 0) } /** * 余额计算 * */ balance() { return this.credits() + this.debits(); } /** * 更新账单 * */ updateRecord(record, data) { const recordIndex = this.state.records.indexOf(record); const newRecords = this.state.records.map( (item, index) => { if(index !== recordIndex) { // This isn't the item we care about - keep it as-is return item; } // Otherwise, this is the one we want - return an updated value return { ...item, ...data }; }); this.setState({ records: newRecords }); } /** * 删除账单 * */ deleteRecord(record){ // console.log(record) const recordIndex = this.state.records.indexOf(record); const newRecords = this.state.records.filter( (item, index) => index !== recordIndex); this.setState({ records: newRecords }); } /** * 把最新数据赋值state * */ addRecord (record) { console.log(record) this.setState({ error:null, isLoader:false, //加载标示 records:[ ...this.state.records, record ] }) } //请求数据 componentDidMount() { var that = this axios.get('http://5b3450a9d167760014c265b5.mockapi.io/accounts/v1/accounts') .then(response => that.setState({ isLoader:false, records:response.data }) ) .catch(err => that.setState({ error:err.message, }) ) }} Record.js 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124import React, { Component } from 'react';import moment from "moment"; //日期格式import axios from 'axios'export default class Record extends Component { // 构造 constructor(props) { super(props); // 初始状态 this.state = { edit:false //编辑按钮状态 }; } render() { //如果编辑状态为true,显示编辑状态,否则显示默认状态 if(this.state.edit){ return this.renderEditRow() }else { return this.renderRow() } } renderEditRow () { var date = this.props.record.date var newDate = moment(date).format('YYYY-MM-DD'); return ( <tr> <td><input type="text" className="form-cantrol" defaultValue={newDate} ref="date" /></td> <td><input type="text" className="form-cantrol" defaultValue={this.props.record.title} ref="title" /></td> <td><input type="text" className="form-cantrol" defaultValue={this.props.record.account} ref="account" /></td> <td> <button className="btn btn-info mr-1" onClick={this.handleUpdate.bind(this)}>更新</button> <button className="btn btn-danger " onClick={this.handleToggle.bind(this)}>取消</button> </td> </tr> ); } renderRow () { var date = this.props.record.date var newDate = moment(date).format('YYYY-MM-DD'); return ( <tr> <td>{newDate}</td> <td>{this.props.record.title}</td> <td>{this.props.record.account}</td> <td> <button className="btn btn-info mr-1" onClick={this.handleToggle.bind(this)}>编辑</button> <button className="btn btn-danger" onClick={this.handleDelete.bind(this)}>删除</button> </td> </tr> ); } /** * 编辑按钮点击事件 * 对state edit做取反操作 * */ handleToggle () { this.setState({ edit:!this.state.edit }) } /** * 删除 按钮点击事件 * */ handleDelete (event) { event.preventDefault() var that = this // alert(that.props.record.id) axios.delete('http://5b3450a9d167760014c265b5.mockapi.io/accounts/v1/accounts/' + that.props.record.id) .then(response =>{ console.log(response) this.props.deleteData(that.props.record) }) .catch(err =>{ }) } /** * 更新记录事件 * */ handleUpdate (event) { event.preventDefault() // 方法阻止元素发生默认的行为 const record ={ date:this.refs.date.value, title:this.refs.title.value, account:this.refs.account.value } var that = this // alert(that.props.record.id) axios.put('http://5b3450a9d167760014c265b5.mockapi.io/accounts/v1/accounts/' + that.props.record.id,{ date:record.date, title:record.title, account:record.account }) .then(response =>{ this.props.updateData(this.props.record,response.data) this.setState({ edit:false }) }) .catch(err => that.setState({ error:err.message, }) ) }} Box.js 12345678910111213141516171819202122/** * 上部的总消费记录 * */import React from 'react';const Box = ({ text, type, amount }) => { return ( <div className="col"> <div className="card"> <div className={`card-header bg-${type} text-white`}> {text} </div> <div className="card-body"> {amount} </div> </div> </div> );}export default Box RecordForm.js 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081import React, { Component } from 'react';import axios from 'axios'export default class RecordForm extends Component { // 构造 constructor(props) { super(props); // 初始状态 this.state = { date:"", title:"", account:"" }; } /** * 判断按钮是否可用 * */ valid(){ return this.state.date && this.state.title && this.state.account; } /** * 输入改变触发 * */ handleChange (event){ let name,obj; name = event.target.name; this.setState(( obj = {}, obj["" + name] = event.target.value, obj )) } /** * 提交按钮触发 * */ handleSubmit (event){ event.preventDefault() var that = this axios.post('http://5b3450a9d167760014c265b5.mockapi.io/accounts/v1/accounts',{ date:that.state.date, title:that.state.title, account:that.state.account }) .then(response =>{ console.log(response); this.props.handleNewRecord(response.data) that.setState({ date:"", title:"", account:"" }) }) .catch(err => that.setState({ error:err.message, }) ) } render() { return ( <form className="form-inline mb-2" onSubmit={this.handleSubmit.bind(this)} > <div className="form-group mr-1"> <input type="text" onChange={this.handleChange.bind(this)} value={this.state.date} className="form-control" placeholder="时间" name="date" /> </div> <div className="form-group mr-1"> <input type="text" onChange={this.handleChange.bind(this)} value={this.state.title} className="form-control" placeholder="标题" name="title" /> </div> <div className="form-group mr-1"> <input type="text" onChange={this.handleChange.bind(this)} value={this.state.account} className="form-control" placeholder="账目" name="account" /> </div> <button type="submit" className="btn btn-primary" disabled={!this.valid()} >创建</button> </form> ); }} ok,下面是源码:源码地址学习前端","categories":[{"name":"前端学习","slug":"前端学习","permalink":"https://zhoushaoting.com/categories/%E5%89%8D%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"前端学习","slug":"前端学习","permalink":"https://zhoushaoting.com/tags/%E5%89%8D%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Flutter~provide学习","slug":"移动端学习/Flutter~provide学习","date":"2019-03-02T16:00:00.000Z","updated":"2023-12-20T05:51:26.475Z","comments":true,"path":"2019/03/03/移动端学习/Flutter~provide学习/","link":"","permalink":"https://zhoushaoting.com/2019/03/03/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~provide%E5%AD%A6%E4%B9%A0/","excerpt":"今天学习下Flutter中state管理的第三方provide.其中,provide: ^1.0.2","text":"今天学习下Flutter中state管理的第三方provide.其中,provide: ^1.0.2 代码结构如图所示: 下面是各文件的源码.main.dart1234567891011121314151617181920212223242526272829303132333435363738import 'package:flutter/material.dart';import 'package:provide_demo/tabbar.dart';import 'package:provide/provide.dart';import 'package:provide_demo/model/counter.dart';void main() { var counter = Counter(); var providers = Providers(); providers ..provide(Provider<Counter>.value(counter)); runApp(ProviderNode(child: MyApp(), providers: providers));}class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( // This is the theme of your application. // // Try running your application with "flutter run". You'll see the // application has a blue toolbar. Then, without quitting the app, try // changing the primarySwatch below to Colors.green and then invoke // "hot reload" (press "r" in the console where you ran "flutter run", // or simply save your changes to "hot reload" in a Flutter IDE). // Notice that the counter didn't reset back to zero; the application // is not restarted. primarySwatch: Colors.blue, ), home: Tabbar(), ); }} tabbar.dart123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102import 'package:flutter/material.dart';import 'One/one.dart';import 'Two/two.dart'; class Tabbar extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( debugShowCheckedModeBanner: false, home: new MainPageWidget()); }} class MainPageWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return new MainPageState(); }} class MainPageState extends State<MainPageWidget> { int _tabIndex = 0; var tabImages; var appBarTitles = ['One', 'Two']; /* * 存放二个页面,跟fragmentList一样 */ var _pageList; /* * 根据选择获得对应的normal或是press的icon */ Image getTabIcon(int curIndex) { if (curIndex == _tabIndex) { return tabImages[curIndex][1]; } return tabImages[curIndex][0]; } /* * 获取bottomTab的颜色和文字 */ Text getTabTitle(int curIndex) { if (curIndex == _tabIndex) { return new Text(appBarTitles[curIndex], style: new TextStyle(fontSize: 14.0, color: const Color(0xff1296db))); } else { return new Text(appBarTitles[curIndex], style: new TextStyle(fontSize: 14.0, color: const Color(0xff515151))); } } /* * 根据image路径获取图片 */ Image getTabImage(path) { return new Image.asset(path, width: 24.0, height: 24.0); } void initData() { /* * 初始化选中和未选中的icon */ tabImages = [ [getTabImage('images/tab/one.png'), getTabImage('images/tab/one_active.png')], [getTabImage('images/tab/two.png'), getTabImage('images/tab/two_active.png')], ]; /* * 子界面 */ _pageList = [ new One(), new Two(), ]; } @override Widget build(BuildContext context) { //初始化数据 initData(); return Scaffold( body: _pageList[_tabIndex], bottomNavigationBar: new BottomNavigationBar( items: <BottomNavigationBarItem>[ new BottomNavigationBarItem( icon: getTabIcon(0), title: getTabTitle(0)), new BottomNavigationBarItem( icon: getTabIcon(1), title: getTabTitle(1)), ], type: BottomNavigationBarType.fixed, //默认选中首页 currentIndex: _tabIndex, iconSize: 24.0, //点击事件 onTap: (index) { setState(() { _tabIndex = index; }); }, )); }} counter.dart12345678910import 'package:flutter/material.dart';class Counter with ChangeNotifier{ int value = 0; increment(){ value++; notifyListeners(); }} one.dart123456789101112131415161718192021222324252627282930313233import 'package:flutter/material.dart';import 'package:provide/provide.dart';import 'package:provide_demo/model/counter.dart';class One extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('One')), body: Padding( padding: EdgeInsets.only(top: 400.0,left: 200.0), child: Column( children: <Widget>[ Provide<Counter>( builder: (context, child, counter) { return Text( '${counter.value}', ); }, ), IconButton( icon: Icon(Icons.add), onPressed: () { Provide.value<Counter>(context).increment(); }, ), ], ), ), ); }} two.dart12345678910111213141516171819202122import 'package:flutter/material.dart';import 'package:provide/provide.dart';import 'package:provide_demo/model/counter.dart';class Two extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Two')), body: Center( child: Provide<Counter>( builder: (context, child, counter) { return Text( '${counter.value}', ); }, ), ), ); }} 效果图: 源码 另外:其他管理库学习 另外:Flutter学习demo","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Flutter~redux学习","slug":"移动端学习/Flutter~redux学习","date":"2019-02-16T16:00:00.000Z","updated":"2023-12-20T05:52:45.966Z","comments":true,"path":"2019/02/17/移动端学习/Flutter~redux学习/","link":"","permalink":"https://zhoushaoting.com/2019/02/17/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~redux%E5%AD%A6%E4%B9%A0/","excerpt":"今天学习下Flutter中state管理的第三方redux.其中,redux: 3.0.0 flutter_redux: 0.5.3","text":"今天学习下Flutter中state管理的第三方redux.其中,redux: 3.0.0 flutter_redux: 0.5.3 代码结构如图所示: 下面是各文件的源码.main.dart12345678910111213141516171819202122232425262728293031import 'package:flutter/material.dart';import 'package:flutter_redux/flutter_redux.dart';import 'package:redux/redux.dart';import 'package:redux_demo/model/count_state.dart';import 'package:redux_demo/top_screen.dart';void main() { final store = Store<CountState>(reducer, initialState: CountState.initState()); runApp(new MyApp(store));}class MyApp extends StatelessWidget { final Store<CountState> store; MyApp(this.store); @override Widget build(BuildContext context) { return StoreProvider<CountState>( store: store, child: new MaterialApp( title: 'Flutter Demo', theme: new ThemeData( primarySwatch: Colors.blue, ), home: TopScreen(), ), ); }} tabbar.dart123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111import 'package:flutter/material.dart';import 'package:redux_demo/one/one.dart';import 'package:redux_demo/two/two.dart';class Tabbar extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( debugShowCheckedModeBanner: false, home: new MainPageWidget()); }}class MainPageWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return new MainPageState(); }}class MainPageState extends State<MainPageWidget> { int _tabIndex = 0; var tabImages; var appBarTitles = ['One', 'Two']; /* * 存放二个页面,跟fragmentList一样 */ var _pageList; /* * 根据选择获得对应的normal或是press的icon */ Image getTabIcon(int curIndex) { if (curIndex == _tabIndex) { return tabImages[curIndex][1]; } return tabImages[curIndex][0]; } /* * 获取bottomTab的颜色和文字 */ Text getTabTitle(int curIndex) { if (curIndex == _tabIndex) { return new Text(appBarTitles[curIndex], style: new TextStyle(fontSize: 14.0, color: const Color(0xff1296db))); } else { return new Text(appBarTitles[curIndex], style: new TextStyle(fontSize: 14.0, color: const Color(0xff515151))); } } /* * 根据image路径获取图片 */ Image getTabImage(path) { return new Image.asset(path, width: 24.0, height: 24.0); } void initData() { /* * 初始化选中和未选中的icon */ tabImages = [ [ getTabImage('images/tab/one.png'), getTabImage('images/tab/one_active.png') ], [ getTabImage('images/tab/two.png'), getTabImage('images/tab/two_active.png') ], ]; /* * 子界面 */ _pageList = [ new One(), new Two(), ]; } @override Widget build(BuildContext context) { //初始化数据 initData(); return Scaffold( body: _pageList[_tabIndex], bottomNavigationBar: new BottomNavigationBar( items: <BottomNavigationBarItem>[ new BottomNavigationBarItem( icon: getTabIcon(0), title: getTabTitle(0)), new BottomNavigationBarItem( icon: getTabIcon(1), title: getTabTitle(1)), ], type: BottomNavigationBarType.fixed, //默认选中首页 currentIndex: _tabIndex, iconSize: 24.0, //点击事件 onTap: (index) { setState( () { _tabIndex = index; }, ); }, ), ); }} top_screen.dart123456789101112131415import 'package:flutter/material.dart';import 'package:redux_demo/tabbar.dart';class TopScreen extends StatefulWidget { @override _TopScreenState createState() => _TopScreenState();}class _TopScreenState extends State<TopScreen> { @override Widget build(BuildContext context) { return Tabbar(); }} count_state.dart1234567891011121314151617181920212223242526272829303132import 'package:meta/meta.dart';/* * State中所有属性都应该是只读的 */@immutableclass CountState { final int _count; get count => _count; CountState(this._count); CountState.initState() : _count = 0;}/* * 定义操作该State的全部Action * 这里只有增加count一个动作 */enum Action { increment }/* * reducer会根据传进来的action生成新的CountState */CountState reducer(CountState state, action) { //匹配Action if (action == Action.increment) { return CountState(state.count + 1); } return state;} one.dart12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849import 'package:flutter/material.dart';import 'package:flutter_redux/flutter_redux.dart';import 'package:redux_demo/model/count_state.dart';class One extends StatefulWidget { @override _OneState createState() => _OneState();}class _OneState extends State<One> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('one Screen'), ), body: Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text( 'You have pushed the button this many times:', ), StoreConnector<CountState,int>( converter: (store) => store.state.count, builder: (context, count) { return Text( count.toString(), style: Theme.of(context).textTheme.display1, ); }, ), ], ), ), floatingActionButton: StoreConnector<CountState,VoidCallback>( converter: (store) { return () => store.dispatch(Action.increment); }, builder: (context, callback) { return FloatingActionButton( onPressed: callback, child: Icon(Icons.add), ); }, ), ); }} two.dart12345678910111213141516171819202122232425262728293031import 'package:flutter/material.dart';import 'package:flutter_redux/flutter_redux.dart';import 'package:redux_demo/model/count_state.dart';class Two extends StatefulWidget { @override _TwoState createState() => _TwoState();}class _TwoState extends State<Two> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('two Screen'), ), body: Center( child: StoreConnector<CountState, int>( converter: (store) => store.state.count, builder: (context, count) { return Text( count.toString(), style: Theme.of(context).textTheme.display1, ); }, ), ), ); }} 效果图: 源码 另外:其他管理库学习 另外:Flutter学习demo","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Flutter~scoped_model学习","slug":"移动端学习/Flutter~scoped_model学习","date":"2019-02-15T16:00:00.000Z","updated":"2023-12-20T05:53:59.642Z","comments":true,"path":"2019/02/16/移动端学习/Flutter~scoped_model学习/","link":"","permalink":"https://zhoushaoting.com/2019/02/16/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/Flutter~scoped_model%E5%AD%A6%E4%B9%A0/","excerpt":"今天学习下Flutter中state管理的第三方scoped_model.其中,scoped_model: 1.0.1","text":"今天学习下Flutter中state管理的第三方scoped_model.其中,scoped_model: 1.0.1 代码结构如图所示: 下面是各文件的源码.main.dart123456789101112131415161718192021222324252627import 'package:flutter/material.dart';import 'package:scoped_model_demo/model/main_model.dart';import 'package:scoped_model/scoped_model.dart';import 'top_screen.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget { //创建顶层状态 MainModel countModel = MainModel(); // This widget is the root of your application. @override Widget build(BuildContext context) { return ScopedModel<MainModel>( model: countModel, child: new MaterialApp( title: 'Flutter Demo', theme: new ThemeData( primarySwatch: Colors.blue, ), home: TopScreen(), ), ); }} tabbar.dart123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100import 'package:flutter/material.dart';import 'One/one.dart';import 'Two/two.dart'; class Tabbar extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( debugShowCheckedModeBanner: false, home: new MainPageWidget()); }} class MainPageWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return new MainPageState(); }} class MainPageState extends State<MainPageWidget> { int _tabIndex = 0; var tabImages; var appBarTitles = ['One', 'Two']; /* * 存放二个页面,跟fragmentList一样 */ var _pageList; /* * 根据选择获得对应的normal或是press的icon */ Image getTabIcon(int curIndex) { if (curIndex == _tabIndex) { return tabImages[curIndex][1]; } return tabImages[curIndex][0]; } /* * 获取bottomTab的颜色和文字 */ Text getTabTitle(int curIndex) { if (curIndex == _tabIndex) { return new Text(appBarTitles[curIndex], style: new TextStyle(fontSize: 14.0, color: const Color(0xff1296db))); } else { return new Text(appBarTitles[curIndex], style: new TextStyle(fontSize: 14.0, color: const Color(0xff515151))); } } /* * 根据image路径获取图片 */ Image getTabImage(path) { return new Image.asset(path, width: 24.0, height: 24.0); } void initData() { /* * 初始化选中和未选中的icon */ tabImages = [ [getTabImage('images/tab/one.png'), getTabImage('images/tab/one_active.png')], [getTabImage('images/tab/two.png'), getTabImage('images/tab/two_active.png')], ]; /* * 子界面 */ _pageList = [ new One(), new Two(), ]; } @override Widget build(BuildContext context) { //初始化数据 initData(); return Scaffold( body: _pageList[_tabIndex], bottomNavigationBar: new BottomNavigationBar( items: <BottomNavigationBarItem>[ new BottomNavigationBarItem( icon: getTabIcon(0), title: getTabTitle(0)), new BottomNavigationBarItem( icon: getTabIcon(1), title: getTabTitle(1)), ], type: BottomNavigationBarType.fixed, //默认选中首页 currentIndex: _tabIndex, iconSize: 24.0, //点击事件 onTap: (index) { setState(() { _tabIndex = index; }); }, )); }} top_screen.dart12345678910111213141516171819202122232425262728293031323334import 'package:flutter/material.dart';import 'package:scoped_model_demo/model/main_model.dart';import 'package:scoped_model/scoped_model.dart';import 'tabbar.dart';class TopScreen extends StatefulWidget { @override _TopScreenState createState() => _TopScreenState();}class _TopScreenState extends State<TopScreen> { //静态获取model用法实例 Model getModel(BuildContext context) { //直接使用of final countModel = ScopedModel.of<MainModel>(context); //使用CountModel中重写的of final countModel2 = MainModel().of(context); countModel.increment(); countModel2.increment(); return countModel; // return countMode2; } @override Widget build(BuildContext context) { return ScopedModelDescendant<MainModel>( builder: (context,child,model){ return Tabbar(); }, ); }} main_model.dart123456789101112131415import 'package:scoped_model/scoped_model.dart';class MainModel extends Model{ int _count = 0; get count => _count; void increment(){ _count++; notifyListeners(); } MainModel of(context) => ScopedModel.of<MainModel>(context);} one.dart123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051import 'package:flutter/material.dart';import 'package:scoped_model_demo/model/main_model.dart';import 'package:scoped_model/scoped_model.dart';class One extends StatefulWidget { @override _OneState createState() => _OneState();}class _OneState extends State<One> { //静态获取model用法实例 Model getModel(BuildContext context) { //直接使用of final countModel = ScopedModel.of<MainModel>(context); //使用CountModel中重写的of final countModel2 = MainModel().of(context); countModel.increment(); countModel2.increment(); return countModel; // return countMode2; } @override Widget build(BuildContext context) { return ScopedModelDescendant<MainModel>( builder: (context, child, model) { return Scaffold( appBar: AppBar( title: Text('One One'), ), body: Center( child: Column( children: <Widget>[ Text( model.count.toString(), style: TextStyle(fontSize: 22.0), ), RaisedButton( child: Text('加1'), onPressed: () => model.increment(), ), ], ), ), ); }, ); }} two.dart123456789101112131415161718192021222324252627282930313233343536373839404142import 'package:flutter/material.dart';import 'package:scoped_model_demo/model/main_model.dart';import 'package:scoped_model/scoped_model.dart';class Two extends StatefulWidget { @override _TwoState createState() => _TwoState();}class _TwoState extends State<Two> { //静态获取model用法实例 Model getModel(BuildContext context){ //直接使用of final countModel = ScopedModel.of<MainModel>(context); //使用CountModel中重写的of final countModel2 = MainModel().of(context); countModel.increment(); countModel2.increment(); return countModel; // return countMode2; } @override Widget build(BuildContext context) { return ScopedModelDescendant<MainModel>( builder: (context,child,model){ return Scaffold( appBar: AppBar( title: Text('Two Screen'), ), body: Center( child: Text( model.count.toString(), style: TextStyle(fontSize: 48.0), ), ) ); }, ); }} 效果图: 源码 另外:其他管理库学习 另外:Flutter学习demo","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"React-Native推送通知至RN且显示推送内容","slug":"移动端学习/RN~React-Native推送通知至RN且显示推送内容","date":"2018-09-07T16:00:00.000Z","updated":"2023-12-20T06:11:29.308Z","comments":true,"path":"2018/09/08/移动端学习/RN~React-Native推送通知至RN且显示推送内容/","link":"","permalink":"https://zhoushaoting.com/2018/09/08/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/RN~React-Native%E6%8E%A8%E9%80%81%E9%80%9A%E7%9F%A5%E8%87%B3RN%E4%B8%94%E6%98%BE%E7%A4%BA%E6%8E%A8%E9%80%81%E5%86%85%E5%AE%B9/","excerpt":"APP推送是基本的要求,今天记录一下,大致是iOS原生收到推送后直接发通知给RN,RN这边监听着iOS原生的这个通知,以此获取到推送的消息.","text":"APP推送是基本的要求,今天记录一下,大致是iOS原生收到推送后直接发通知给RN,RN这边监听着iOS原生的这个通知,以此获取到推送的消息. 效果图: 实现思路 ==>这次采用的友盟推送,至于采用什么推送都是一样的.准备工作:iOS 集成推送,编写中间类,用于传值给RN. RN 界面实现监听iOS原生的通知,通过以上获取到推送的内容. iOS原生准备集成推送: …iOS写一个中间类传输通知给RN:CheckInvoice.h 123456789101112131415161718//// CheckInvoice.h// aitepiao//// Created by tc on 2018/8/28.// Copyright © 2018年 Facebook. All rights reserved.//#import <Foundation/Foundation.h>#import <React/RCTBridgeModule.h>#import <React/RCTEventEmitter.h>@interface CheckInvoice : RCTEventEmitter<RCTBridgeModule>@end CheckInvoice.m 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657//// CheckInvoice.m// aitepiao//// Created by tc on 2018/8/28.// Copyright © 2018年 Facebook. All rights reserved.//#import "CheckInvoice.h"#import <React/RCTEventDispatcher.h>#import <React/RCTBridge.h>static CheckInvoice *_manager = nil;@implementation CheckInvoiceRCT_EXPORT_MODULE()-(instancetype)init{ if (self = [super init]) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(messageDidReceived:) name:@"didReceiveNotification" object:nil]; } return self;}- (NSArray<NSString *> *)supportedEvents{ return @[@"didReceiveNotification", ];//有几个就写几个}-(void)messageDidReceived:(NSNotification *)notification{ NSDictionary *body = notification.object; [self sendEventWithName:@"didReceiveNotification" body:body];}+ (BOOL)requiresMainQueueSetup { return YES;}- (dispatch_queue_t)methodQueue{ return dispatch_get_main_queue();}@end ok.传输类写完.然后在AppDelegate里面直接通过通知传即可:如: 1234567891011121314151617181920212223242526272829303132333435363738394041// iOS 10以上-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler{ NSDictionary * userInfo = notification.request.content.userInfo; NSLog(@"iOS 10以上%@",userInfo); if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) { //应用处于前台时的远程推送接受 //关闭U-Push自带的弹出框 [UMessage setAutoAlert:NO]; //必须加这句代码 [UMessage didReceiveRemoteNotification:userInfo]; }else{ //应用处于前台时的本地推送接受 } //当应用处于前台时提示设置,需要哪个可以设置哪一个 if (@available(iOS 10.0, *)) { completionHandler(UNNotificationPresentationOptionSound|UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionAlert); } else { // Fallback on earlier versions } [[NSNotificationCenter defaultCenter] postNotificationName:@"didReceiveNotification" object:userInfo];}// 收到通知-(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler{ NSDictionary * userInfo = response.notification.request.content.userInfo; NSLog(@"iOS外部 %@",userInfo); [[NSNotificationCenter defaultCenter] postNotificationName:@"didReceiveNotification" object:userInfo];}- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{ NSLog(@"iOS 10以下%@",userInfo); [[NSNotificationCenter defaultCenter] postNotificationName:@"didReceiveNotification" object:userInfo];}- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{ NSLog(@"deviceToken==%@",[[[[deviceToken description] stringByReplacingOccurrencesOfString: @"<" withString: @""] stringByReplacingOccurrencesOfString: @">" withString: @""] stringByReplacingOccurrencesOfString: @" " withString: @""]); [UMessage registerDeviceToken:deviceToken];} ok,iOS原生这边基本完成.剩下RN这边:引入NativeModules和NativeEventEmitter组件引入原生中间类 123const { CheckInvoice } = NativeModules;const checkInvoiceEmitter = new NativeEventEmitter(CheckInvoice); 然后直接在生命周期方法里面监听iOS通知即可. 1234567891011componentDidMount() { // 监听iOS原生 UM push 监听外部通知 checkInvoiceEmitter.addListener( 'didReceiveNotification', (info) => { alert(info.aps.alert.body) this.setState({ info:info.aps.alert.body }) } )} 源码:只有iOS推送 Android也如此,集成推送.书写中间类传通知给RN. 这里就贴一下桥接代码了: CommModule.java 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152package com.eazytec.zqtong.gov.baseapp.RN;import android.content.Intent;import android.net.Uri;import android.util.Log;import com.facebook.react.bridge.Callback;import com.facebook.react.bridge.Promise;import com.facebook.react.bridge.ReactApplicationContext;import com.facebook.react.bridge.ReactContextBaseJavaModule;import com.facebook.react.bridge.ReactMethod;import com.facebook.react.modules.core.DeviceEventManagerModule;/** * 通信Module类 * Created by Song on 2017/2/17. */public class CommModule extends ReactContextBaseJavaModule { private ReactApplicationContext mContext; public static final String MODULE_NAME = "commModule"; public static final String EVENT_NAME = "EventName"; /** * 构造方法必须实现 * @param reactContext */ public CommModule(ReactApplicationContext reactContext) { super(reactContext); this.mContext = reactContext; } /** * 在rn代码里面是需要这个名字来调用该类的方法 * @return */ @Override public String getName() { return MODULE_NAME; } /** * Native调用RN * @param msg */ public void nativeCallRn(String msg) { mContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit(EVENT_NAME,msg); }} CommPackage.java 123456789101112131415161718192021222324252627282930313233343536373839404142package com.eazytec.zqtong.gov.baseapp.RN;import com.facebook.react.ReactPackage;import com.facebook.react.bridge.JavaScriptModule;import com.facebook.react.bridge.NativeModule;import com.facebook.react.bridge.ReactApplicationContext;import com.facebook.react.uimanager.ViewManager;import java.util.ArrayList;import java.util.Collections;import java.util.List;/** * 通信Module类 * Created by Song on 2017/2/17. */public class CommPackage implements ReactPackage { private static final CommPackage mCommPackage = new CommPackage(); public CommModule mModule; /** * 创建Native Module * @param reactContext * @return */ @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { List<NativeModule> modules = new ArrayList<>(); mModule = new CommModule(reactContext); modules.add(mModule); return modules; } @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); }} ok.上面就是桥接文件.然后只需要在返回通知类中发送通知给RN即可.如: MainActivity.java 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186package com.eazytec.zqtong.gov.baseapp;import android.app.Application;import android.widget.Toast;import org.json.JSONObject;import com.eazytec.zqtong.gov.baseapp.RN.CommModule;import com.facebook.react.bridge.ReactContextBaseJavaModule;import com.google.gson.Gson;import android.content.Context;import android.util.Log;import com.eazytec.zqtong.gov.baseapp.BuildConfig;import com.learnium.RNDeviceInfo.RNDeviceInfo;import com.facebook.react.ReactApplication;import cn.reactnative.httpcache.HttpCachePackage;import com.facebook.react.ReactNativeHost;import com.facebook.react.ReactPackage;import com.facebook.react.shell.MainReactPackage;import com.facebook.soloader.SoLoader;import com.react.rnspinkit.RNSpinkitPackage;import java.util.Arrays;import java.util.List;import com.imagepicker.ImagePickerPackage;import com.umeng.message.PushAgent;import com.umeng.commonsdk.UMConfigure;import com.umeng.message.IUmengRegisterCallback;import com.umeng.message.MsgConstant;import com.umeng.message.PushAgent;import com.umeng.message.UTrack;import com.umeng.message.UmengMessageHandler;import com.umeng.message.UmengNotificationClickHandler;import com.umeng.message.entity.UMessage;import com.facebook.react.bridge.ReactApplicationContext;import android.provider.Settings;import android.support.annotation.Nullable;import com.facebook.react.bridge.Arguments;import com.facebook.react.bridge.ReactContext;import com.facebook.react.bridge.WritableMap;import com.facebook.react.modules.core.DeviceEventManagerModule;import com.eazytec.zqtong.gov.baseapp.RN.CommPackage;import java.util.List;import android.app.Activity;import android.app.ActivityManager;import android.app.ActivityManager.RunningAppProcessInfo;import android.content.Context;public class MainApplication extends Application implements ReactApplication { //定义上下文对象 public static ReactContext myContext; private static final CommPackage mCommPackage = new CommPackage(); private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { @Override public boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; } private ReactApplicationContext mContext; @Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(), new HttpCachePackage(), new RNSpinkitPackage(), new RNDeviceInfo(), new ImagePickerPackage(), mCommPackage ); } @Override protected String getJSMainModuleName() { return "index"; } }; @Override public ReactNativeHost getReactNativeHost() { return mReactNativeHost; } //定义发送事件的函数 public void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) { System.out.println("reactContext="+reactContext); reactContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit(eventName,params); } @Override public void onCreate() { super.onCreate(); UMConfigure.setLogEnabled(true); SoLoader.init(this, /* native exopackage */ false); //友盟 push PushAgent mPushAgent = PushAgent.getInstance(this); UmengMessageHandler messageHandler = new UmengMessageHandler() { /** * 通知的回调方法(通知送达时会回调) */ @Override public void dealWithNotificationMessage(Context context, UMessage msg) { //调用super,会展示通知,不调用super,则不展示通知。 super.dealWithNotificationMessage(context, msg); Gson gson = new Gson(); // 推送的内容 Log.i("msg来了内容", msg.text); Log.i("msg来了头", msg.title); String jsonStr = gson.toJson(msg); Log.i("完整输出", jsonStr); mCommPackage.mModule.nativeCallRn(jsonStr); } }; mPushAgent.setMessageHandler(messageHandler); //注册推送服务,每次调用register方法都会回调该接口 mPushAgent.register(new IUmengRegisterCallback() { @Override public void onSuccess(String deviceToken) { //注册成功会返回device token Log.i(deviceToken,"deviceToken来了"); Log.i(deviceToken,deviceToken); } @Override public void onFailure(String s, String s1) { Log.i(s,s); Log.i(s1,s1); } /** * 程序是否在前台运行 * * @return */ public boolean isAppOnForeground() { // Returns a list of application processes that are running on the // device ActivityManager activityManager = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE); String packageName = getApplicationContext().getPackageName(); List<RunningAppProcessInfo> appProcesses = activityManager .getRunningAppProcesses(); if (appProcesses == null) return false; for (RunningAppProcessInfo appProcess : appProcesses) { // The name of the process that this object is associated with. if (appProcess.processName.equals(packageName) && appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { return true; } } return false; } }); }}","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"React-Native类似支付宝的应用增删模块的简单实现","slug":"移动端学习/RN~React-Native类似支付宝的应用增删模块的简单实现","date":"2018-08-16T16:00:00.000Z","updated":"2023-12-20T06:08:53.738Z","comments":true,"path":"2018/08/17/移动端学习/RN~React-Native类似支付宝的应用增删模块的简单实现/","link":"","permalink":"https://zhoushaoting.com/2018/08/17/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/RN~React-Native%E7%B1%BB%E4%BC%BC%E6%94%AF%E4%BB%98%E5%AE%9D%E7%9A%84%E5%BA%94%E7%94%A8%E5%A2%9E%E5%88%A0%E6%A8%A1%E5%9D%97%E7%9A%84%E7%AE%80%E5%8D%95%E5%AE%9E%E7%8E%B0/","excerpt":"项目中,涉及到类似支付宝的应用增删模块,这几天简单的实现了下.基本雏形实现了.后面的细节稍后再做处理.写篇博客记录下.","text":"项目中,涉及到类似支付宝的应用增删模块,这几天简单的实现了下.基本雏形实现了.后面的细节稍后再做处理.写篇博客记录下. 效果图: 实现思路 ==>UI: 一开始采用外部ScrollView内部FlatList和ScrollView实现,后来发现有点臃肿,于是采用外部FlatList,内部ScrollView实现.然后发现其实代码差不太多…….浪费表情,其中依赖几个三方库:react-native-scrollable-tab-view,react-native-underline-tabbar.本来想使用SectionList实现的,但是感觉使用SectionList会更麻烦.逻辑: 通过数据中 id 和 tag实现 废话不多缩,首先,新建一个项目,添加相应库:第一种实现: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578/** * UI 通过 外部ScrollView 内部 FlatList或者 ScrollView 实现 (使用三方库react-native-scrollable-tab-view&&react-native-underline-tabbar) * 逻辑 通过 数据中 id 和 tag实现 * */import React, { Component } from 'react'import { StyleSheet, View, Image, Text, Dimensions, TouchableOpacity, FlatList, Linking, ScrollView} from 'react-native'const { width } = Dimensions.get('window')import ScrollableTabView from 'react-native-scrollable-tab-view'import TabBar from 'react-native-underline-tabbar'export default class One extends Component { // 构造 constructor(props) { super(props) // 初始状态 this.state = { startEdit: false, // 是否点击了编辑 默认未点击false myApplyData: [], otherApplyData: [], applyData: [ { title: '我的应用', sub: [ { id: '10086', icon: 'https://ww1.sinaimg.cn/large/0065oQSqgy1fu39hosiwoj30j60qyq96.jpg', name: '价费通10086', }, { id: '10010', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ftzsj15hgvj30sg15hkbw.jpg', name: '展示中心10010', } ], }, { title: '政务服务', sub: [ { subTitle: '政企', sub: [ { id: '10086', icon: 'https://ww1.sinaimg.cn/large/0065oQSqgy1fu39hosiwoj30j60qyq96.jpg', name: '价费通10086', tag: true, }, { id: '10010', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ftzsj15hgvj30sg15hkbw.jpg', name: '展示中心10010', tag: true, }, { id: '9', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ftzsj15hgvj30sg15hkbw.jpg', name: '办事平台', tag: false, }, { id: '10', icon: 'https://ww1.sinaimg.cn/large/0065oQSqgy1ftwcw4f4a5j30sg10j1g9.jpg', name: '少儿图书馆', tag: false, }, ], }, { subTitle: '第三方服务', sub: [ { id: '11', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ftzsj15hgvj30sg15hkbw.jpg', name: '价费通', tag: false, }, { id: '12', icon: 'https://ww1.sinaimg.cn/large/0065oQSqgy1ftwcw4f4a5j30sg10j1g9.jpg', name: '价费通', tag: false, }, { id: '13', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ftzsj15hgvj30sg15hkbw.jpg', name: '价费通', tag: false, }, { id: '14', icon: 'https://ww1.sinaimg.cn/large/0065oQSqgy1ftwcw4f4a5j30sg10j1g9.jpg', name: '价费通', tag: false, }, { id: '15', icon: 'https://ww1.sinaimg.cn/large/0065oQSqgy1ftwcw4f4a5j30sg10j1g9.jpg', name: '价费通', tag: false, }, { id: '16', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ftu6gl83ewj30k80tites.jpg', name: '价费通', tag: false, }, { id: '17', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ftu6gl83ewj30k80tites.jpg', name: '价费通', tag: false, }, { id: '18', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ftu6gl83ewj30k80tites.jpg', name: '价费通', tag: false, }, ], }, ], }, { title: '政企应用', sub: [ { subTitle: '按主题', sub: [ { id: '19', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ftdtot8zd3j30ju0pt137.jpg', name: '价费通', tag: false, }, { id: '20', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ft5q7ys128j30sg10gnk5.jpg', name: '价费通', tag: false, }, ], }, { subTitle: '按部门', sub: [ { id: '21', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ftdtot8zd3j30ju0pt137.jpg', name: '价费通', tag: false, }, ], }, { subTitle: '按证件', sub: [ { id: '22', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ft5q7ys128j30sg10gnk5.jpg', name: '价费通', tag: false, }, ], }, { subTitle: '主体周期', sub: [ { id: '23', icon: 'http://ww1.sinaimg.cn/large/0065oQSqly1fsoe3k2gkkj30g50niwla.jpg', name: '价费通', tag: false, }, { id: '24', icon: 'http://ww1.sinaimg.cn/large/0065oQSqly1fsoe3k2gkkj30g50niwla.jpg', name: '价费通', tag: false, }, ], }, ], }, ], } } componentDidMount() { var data = this.state.applyData //赋值 我的应用数据 this.setState({ myApplyData: data[0].sub, }) //赋值 其他应用数据 this.setState({ otherApplyData: data.slice(1), }) } /** * 点击编辑 * */ editAction = () => { this.setState({ startEdit:!this.state.startEdit }) } /** * 去除我的应用 * */ subAction = item => { var data = this.state.myApplyData // 我的应用数据 var otherData = this.state.otherApplyData //其他应用数据 var selectId = item.item.id // 选中的id //修改我的应用数据源 data.map((item,index) =>{ if(item.id == selectId){ data.splice(index,1) } }) //修改其他应用数据源 otherData.map((item,index) =>{ item.sub.map((item,index) =>{ item.sub.map((item,index) =>{ if(item.id == selectId){ item.tag = false } }) }) }) // 重新赋值 this.setState({ myApplyData:data, otherApplyData:otherData }) } /** * 点击其他应用 * */ otherAction =(item) =>{ var data = this.state.myApplyData // 我的应用数据 var otherData = this.state.otherApplyData //其他应用数据 var selectId = item.id //id console.log(item) //根据数据中tag值判断 if(item.tag == true){ //在我的应用当中,需要去除 data.map((item,index) =>{ if(item.id == selectId){ data.splice(index,1) } }) otherData.map((item,index) =>{ item.sub.map((item,index) =>{ item.sub.map((item,index) =>{ if(item.id == selectId){ item.tag = false } }) }) }) }else { //不在我的应用当中,需要添加 data.push(item) otherData.map((item,index) =>{ item.sub.map((item,index) =>{ item.sub.map((item,index) =>{ if(item.id == selectId){ item.tag = true } }) }) }) } // 重新赋值 this.setState({ myApplyData:data, otherApplyData:otherData }) } /** * 去详情页面 * */ goDetailsAction = item => { alert('去详情') } render() { return ( <View style={styles.container}> {/*我的应用条-编辑*/} <MyApplyEdit applyEditData={this.state.myApplyData} editAction={() => { this.editAction() }} /> <ScrollView> {/*我的应用*/} <View style={{ marginTop: 10, backgroundColor: '#ffffff' }}> <View style={{ flexDirection: 'row', alignItems: 'center', marginTop: 10, }} > <View style={{ marginLeft: 10, width: 8, height: 20, backgroundColor: 'blue', }} /> <Text style={{ marginLeft: 10, fontWeight: '700' }}> 我的应用 </Text> </View> <FlatList data={this.state.myApplyData} style={{ marginBottom: 20 }} renderItem={item => this.renderMyApplyRow(item)} keyExtractor={this.keyMyApplyExtractor} numColumns={4} /> </View> {/*其他应用*/} {this.state.otherApplyData.map((item, index) => ( <View key={index}> {/*标题*/} <View style={{ paddingTop: 10, alignItems: 'center', backgroundColor: 'white', flexDirection: 'row', alignItems: 'center', marginTop: 10, }} > <View style={{ marginLeft: 10, width: 8, height: 20, backgroundColor: 'blue', }} /> <Text style={{ marginLeft: 10, fontWeight: '700' }}> {item.title} </Text> </View> {/*主体内容*/} <ScrollableTabView style={{ backgroundColor: 'white' }} tabBarActiveTextColor="#118EE9" renderTabBar={() => <TabBar underlineColor="#118EE9" />} > {item.sub.map((item, index) => ( <View key={index} tabLabel={{ label: item.subTitle }}> <ScrollView> <View style={{ flexDirection: 'row', height: 250, flexWrap: 'wrap', }} > {item.sub.map((item, index) => ( <TouchableOpacity key={index} onPress={() => this.goDetailsAction(item)} > <View style={{ marginTop: 25 }}> <Image style={{ width: 55, height: 55, marginLeft: (width - 55 * 4) / 8, marginRight: (width - 55 * 4) / 8, }} source={{ uri: item.icon }} /> <Text style={{ alignSelf: 'center', marginTop: 15, marginBottom: 10, }} > {item.name} </Text> <TouchableOpacity style={{ position: 'absolute', top: -10, right: 10, }} onPress={() => this.otherAction(item)} > <Image source={ item.tag ? require('./image/sub.png') : require('./image/add.png') } style={{ opacity: this.state.startEdit ? 1 : 0, width: 15, height: 15, }} /> </TouchableOpacity> </View> </TouchableOpacity> ))} </View> </ScrollView> </View> ))} </ScrollableTabView> </View> ))} </ScrollView> </View> ) } //-------------------------------我的应用start -------------------------------- /** * 我的应用render * */ renderMyApplyRow = item => ( <TouchableOpacity onPress={() => this.goDetailsAction(item)}> <View style={{ marginTop: 25 }}> <Image style={{ width: 55, height: 55, marginLeft: (width - 55 * 4) / 8, marginRight: (width - 55 * 4) / 8, }} source={{ uri: item.item.icon }} /> <Text style={{ alignSelf: 'center', marginTop: 15, marginBottom: 10 }}> {item.item.name} </Text> <TouchableOpacity style={{ position: 'absolute', top: -10, right: 10 }} onPress={() => this.subAction(item)} > <Image source={require('./image/sub.png')} style={{ opacity: this.state.startEdit ? 1 : 0, width: 15, height: 15, }} /> </TouchableOpacity> </View> </TouchableOpacity> ) // 使用json中的key动态绑定key keyMyApplyExtractor = item => item.id //-------------------------------我的应用end --------------------------------}// 上方的编辑 我的应用框 UIclass MyApplyEdit extends React.Component { render() { const { applyEditData } = this.props return ( <View style={{ paddingTop: 5, paddingBottom: 5, alignItems: 'center', flexDirection: 'row', backgroundColor: '#ffffff', }} > <Text style={{ marginLeft: 10, fontWeight: '700' }}>我的应用</Text> <ScrollView showsHorizontalScrollIndicator={false} showsVerticalScrollIndicator={false} horizontal={true} style={{ marginRight: 15 }} > {applyEditData.map((item, index) => ( <Image key={index} style={{ width: 30, height: 30, marginLeft: 10 }} source={{ uri: item.icon }} /> ))} </ScrollView> <TouchableOpacity onPress={() => this.props.editAction()}> <View style={{ borderRadius: 4, marginRight: 10, paddingTop: 5, paddingBottom: 5, paddingLeft: 10, paddingRight: 10, borderWidth: 1, borderColor: 'blue', }} > <Text style={{ color: 'blue' }}>编辑</Text> </View> </TouchableOpacity> </View> ) }}const styles = StyleSheet.create({ container: { flex: 1, }, icon: { width: 22, height: 22, },}) 第二种实现 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571/** * UI 通过 外部FlatList 内部 ScrollView + view 实现 (使用三方库react-native-scrollable-tab-view&&react-native-underline-tabbar) * 逻辑 通过 数据中 id 和 tag实现 * */import React, { Component } from 'react'import { StyleSheet, View, Image, Text, Dimensions, TouchableOpacity, FlatList, Linking, ScrollView} from 'react-native'const { width } = Dimensions.get('window')import ScrollableTabView from 'react-native-scrollable-tab-view'import TabBar from 'react-native-underline-tabbar'export default class Two extends Component { // 构造 constructor(props) { super(props) // 初始状态 this.state = { startEdit: false, // 是否点击了编辑 默认未点击false myApplyData: [], otherApplyData: [], applyData: [ { title: '我的应用', sub: [ { id: '10086', icon: 'https://ww1.sinaimg.cn/large/0065oQSqgy1fu39hosiwoj30j60qyq96.jpg', name: '价费通10086', }, { id: '10010', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ftzsj15hgvj30sg15hkbw.jpg', name: '展示中心10010', } ], }, { title: '政务服务', sub: [ { subTitle: '政企', sub: [ { id: '10086', icon: 'https://ww1.sinaimg.cn/large/0065oQSqgy1fu39hosiwoj30j60qyq96.jpg', name: '价费通10086', tag: true, }, { id: '10010', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ftzsj15hgvj30sg15hkbw.jpg', name: '展示中心10010', tag: true, }, { id: '9', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ftzsj15hgvj30sg15hkbw.jpg', name: '办事平台', tag: false, }, { id: '10', icon: 'https://ww1.sinaimg.cn/large/0065oQSqgy1ftwcw4f4a5j30sg10j1g9.jpg', name: '少儿图书馆', tag: false, }, ], }, { subTitle: '第三方服务', sub: [ { id: '11', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ftzsj15hgvj30sg15hkbw.jpg', name: '价费通', tag: false, }, { id: '12', icon: 'https://ww1.sinaimg.cn/large/0065oQSqgy1ftwcw4f4a5j30sg10j1g9.jpg', name: '价费通', tag: false, }, { id: '13', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ftzsj15hgvj30sg15hkbw.jpg', name: '价费通', tag: false, }, { id: '14', icon: 'https://ww1.sinaimg.cn/large/0065oQSqgy1ftwcw4f4a5j30sg10j1g9.jpg', name: '价费通', tag: false, }, { id: '15', icon: 'https://ww1.sinaimg.cn/large/0065oQSqgy1ftwcw4f4a5j30sg10j1g9.jpg', name: '价费通', tag: false, }, { id: '16', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ftu6gl83ewj30k80tites.jpg', name: '价费通', tag: false, }, { id: '17', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ftu6gl83ewj30k80tites.jpg', name: '价费通', tag: false, }, { id: '18', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ftu6gl83ewj30k80tites.jpg', name: '价费通', tag: false, }, ], }, ], }, { title: '政企应用', sub: [ { subTitle: '按主题', sub: [ { id: '19', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ftdtot8zd3j30ju0pt137.jpg', name: '价费通', tag: false, }, { id: '20', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ft5q7ys128j30sg10gnk5.jpg', name: '价费通', tag: false, }, ], }, { subTitle: '按部门', sub: [ { id: '21', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ftdtot8zd3j30ju0pt137.jpg', name: '价费通', tag: false, }, ], }, { subTitle: '按证件', sub: [ { id: '22', icon: 'https://ww1.sinaimg.cn/large/0065oQSqly1ft5q7ys128j30sg10gnk5.jpg', name: '价费通', tag: false, }, ], }, { subTitle: '主体周期', sub: [ { id: '23', icon: 'http://ww1.sinaimg.cn/large/0065oQSqly1fsoe3k2gkkj30g50niwla.jpg', name: '价费通', tag: false, }, { id: '24', icon: 'http://ww1.sinaimg.cn/large/0065oQSqly1fsoe3k2gkkj30g50niwla.jpg', name: '价费通', tag: false, }, ], }, ], }, ], } } componentDidMount() { var data = this.state.applyData //赋值 我的应用数据 this.setState({ myApplyData: data[0].sub, }) //赋值 其他应用数据 this.setState({ otherApplyData: data.slice(1), }) } render() { return ( <View> {/*我的应用条-编辑*/} <MyApplyEdit applyEditData={this.state.myApplyData} editAction={() => { this.editAction() }} /> {/*应用列表*/} <FlatList data={this.state.otherApplyData} style={{ marginBottom: 20 }} ListHeaderComponent={this.ListHeaderComponent} renderItem={item => this.renderItem(item)} keyExtractor={this.keyExtractor} /> </View> ) } /** * 去详情 * */ goDetailsAction =(item) =>{ alert('去详情') } /** * 去除我的应用 * */ subAction = item => { var data = this.state.myApplyData // 我的应用数据 var otherData = this.state.otherApplyData //其他应用数据 var selectId = item.id // 选中的id //修改我的应用数据源 data.map((item,index) =>{ if(item.id == selectId){ data.splice(index,1) } }) //修改其他应用数据源 otherData.map((item,index) =>{ item.sub.map((item,index) =>{ item.sub.map((item,index) =>{ if(item.id == selectId){ item.tag = false } }) }) }) // 重新赋值 this.setState({ myApplyData:data, otherApplyData:otherData }) } /** * 点击其他应用 * */ otherAction =(item) =>{ var data = this.state.myApplyData // 我的应用数据 var otherData = this.state.otherApplyData //其他应用数据 var selectId = item.id //id console.log(item) //根据数据中tag值判断 if(item.tag == true){ //在我的应用当中,需要去除 data.map((item,index) =>{ if(item.id == selectId){ data.splice(index,1) } }) otherData.map((item,index) =>{ item.sub.map((item,index) =>{ item.sub.map((item,index) =>{ if(item.id == selectId){ item.tag = false } }) }) }) }else { //不在我的应用当中,需要添加 data.push(item) otherData.map((item,index) =>{ item.sub.map((item,index) =>{ item.sub.map((item,index) =>{ if(item.id == selectId){ item.tag = true } }) }) }) } // 重新赋值 this.setState({ myApplyData:data, otherApplyData:otherData }) } ListHeaderComponent =()=>{ var data = this.state.myApplyData return( <View style={{marginTop:10}}> <HeaderView title='我的应用'/> <View style={{flexDirection:'row',backgroundColor:'#ffffff'}}> { data.map((item,index) =>( <TouchableOpacity key={index} onPress={() => this.goDetailsAction(item)}> <View style={{ marginTop: 25 }}> <Image style={{ width: 55, height: 55, marginLeft: (width - 55 * 4) / 8, marginRight: (width - 55 * 4) / 8, }} source={{ uri: item.icon }} /> <Text style={{ alignSelf: 'center', marginTop: 15, marginBottom: 10 }}> {item.name} </Text> <TouchableOpacity style={{ position: 'absolute', top: -10, right: 10 }} onPress={() => this.subAction(item)} > <Image source={require('./image/sub.png')} style={{ opacity: this.state.startEdit ? 1 : 0, width: 15, height: 15, }} /> </TouchableOpacity> </View> </TouchableOpacity> )) } </View> </View> ) } renderItem =(item)=>{ console.log(item) return( <View style={{marginTop:10}}> <HeaderView title={item.item.title}/> <ScrollableTabView style={{ backgroundColor: 'white' }} tabBarActiveTextColor="#118EE9" renderTabBar={() => <TabBar underlineColor="#118EE9" />} > {item.item.sub.map((item, index) => ( <View key={index} tabLabel={{ label: item.subTitle }}> <ScrollView> <View style={{ flexDirection: 'row', height: 250, flexWrap: 'wrap', }} > {item.sub.map((item, index) => ( <TouchableOpacity key={index} onPress={() => this.goDetailsAction(item)} > <View style={{ marginTop: 25 }}> <Image style={{ width: 55, height: 55, marginLeft: (width - 55 * 4) / 8, marginRight: (width - 55 * 4) / 8, }} source={{ uri: item.icon }} /> <Text style={{ alignSelf: 'center', marginTop: 15, marginBottom: 10, }} > {item.name} </Text> <TouchableOpacity style={{ position: 'absolute', top: -10, right: 10, }} onPress={() => this.otherAction(item)} > <Image source={ item.tag ? require('./image/sub.png') : require('./image/add.png') } style={{ opacity: this.state.startEdit ? 1 : 0, width: 15, height: 15, }} /> </TouchableOpacity> </View> </TouchableOpacity> ))} </View> </ScrollView> </View> ))} </ScrollableTabView> </View> ) } keyExtractor = item => item.title /** * 点击编辑 * */ editAction = () => { this.setState({ startEdit:!this.state.startEdit }) }}// 上方的编辑 我的应用框 UIclass MyApplyEdit extends React.Component { render() { const { applyEditData } = this.props return ( <View style={{ paddingTop: 5, paddingBottom: 5, alignItems: 'center', flexDirection: 'row', backgroundColor: '#ffffff', }} > <Text style={{ marginLeft: 10, fontWeight: '700' }}>我的应用</Text> <ScrollView showsHorizontalScrollIndicator={false} showsVerticalScrollIndicator={false} horizontal={true} style={{ marginRight: 15 }} > {applyEditData.map((item, index) => ( <Image key={index} style={{ width: 30, height: 30, marginLeft: 10 }} source={{ uri: item.icon }} /> ))} </ScrollView> <TouchableOpacity onPress={() => this.props.editAction()}> <View style={{ borderRadius: 4, marginRight: 10, paddingTop: 5, paddingBottom: 5, paddingLeft: 10, paddingRight: 10, borderWidth: 1, borderColor: 'blue', }} > <Text style={{ color: 'blue' }}>编辑</Text> </View> </TouchableOpacity> </View> ) }}// 头视图class HeaderView extends React.Component { render() { const { title } = this.props return ( <View style={{ backgroundColor:'#ffffff', flexDirection: 'row', alignItems: 'center', paddingTop:10 }} > <View style={{ marginLeft: 10, width: 8, height: 20, backgroundColor: 'blue', }} /> <Text style={{ marginLeft: 10, fontWeight: '700' }}> {title} </Text> </View> ) }}const styles = StyleSheet.create({ container: { flex: 1, }, icon: { width: 22, height: 22, },}) 以后就是两种实现,基本的功能实现了,至于细节优化点等日后慢慢再做吧.源码","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"React-Native\"收起-展开\"功能实现","slug":"移动端学习/RN~React-Native<收起-展开>功能实现","date":"2018-08-10T16:00:00.000Z","updated":"2023-12-20T06:08:34.828Z","comments":true,"path":"2018/08/11/移动端学习/RN~React-Native<收起-展开>功能实现/","link":"","permalink":"https://zhoushaoting.com/2018/08/11/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/RN~React-Native%3C%E6%94%B6%E8%B5%B7-%E5%B1%95%E5%BC%80%3E%E5%8A%9F%E8%83%BD%E5%AE%9E%E7%8E%B0/","excerpt":"近来公司的项目里面涉及到一个类似”收起-展开”的UI功能模块,先是在网上猥琐一番之后,并未发现什么.无奈之下,只能自己手撸一个了.","text":"近来公司的项目里面涉及到一个类似”收起-展开”的UI功能模块,先是在网上猥琐一番之后,并未发现什么.无奈之下,只能自己手撸一个了. 一开始做了个”互斥收起-展开”,公司可能不太满意,让我抄另外一个APP”非互斥收起-展开”,哎…先上图:非互斥缩一缩 互斥缩一缩 实现原理很简单:非互斥缩一缩: 先在json数据里面定义type标示,标示一级展开|收起状态,在UI里面就根据这个标示判断是收起还是展开,而二级收起|展开需要为每组数据定义一个唯一标示.互斥缩一缩:先定义一个state,根据该state来显示收起UI还是展开,因为state只有一个,也就形成了只有一组是展开.同时,在判断二次展开|收起这里也需要在json里面定义一个唯一标示.这些数据标示可以事先和后端人员商量好,让后端人员帮你弄好,也可以自己拿到数据之后再插入.废话不多少,戴起磨砂手套开始撸!!!直接上源码:非互斥缩一缩 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304/* eslint-disable */import React, { Component } from 'react'import { StyleSheet, View, Image, Text, Dimensions, TouchableOpacity, FlatList, Linking,} from 'react-native'const { width } = Dimensions.get('window')export default class One extends Component { // 构造 constructor(props) { super(props) // 初始状态 this.row = null this.state = { governmentData: [ { id: '0', name: '社会保障局', type: false, childs: [ { index: '00', name: '资料教室', type: false, tel: '5233', message: '查询资料,注销档案', }, { name: '后勤部', type: false, index: '01', tel: '1645682', message: '不对外服务', }, ], }, { id: '1', name: '卫生局', type: false, childs: [ { name: '妇产科', type: false, index: '10', tel: '112转678', message: '接待孕妇,待产孕妇,军嫂预先', }, { name: '儿科', type: false, index: '11', tel: '112转008', message: '小于12周岁儿童就医', }, { name: '失恋科', type: false, index: '12', tel: '112转出去', message: '回家玩蛋去!!!', }, ], }, { id: '2', name: '神盾局', type: false, childs: [ { name: '城管大队', type: false, index: '20', tel: '110', title: '打打打', message: '镇压起义', }, ], }, ], isRefresh: true, index: -1, //默认展开负一行 indexIndex: -1, } } componentDidMount() { this.flatlist = null this.setState({ isRefresh: false, }) } /** * 下拉刷新 * */ onRefresh = () => { this.setState({ isRefresh: false, }) } /** * 点击一级 * */ itemOnclick = item => { var data = this.state.governmentData data.map((itemm, i) => { if (i == item.index) { itemm.type = !itemm.type } }) this.setState({ governmentData: data, }) } /** * 点击二级 * */ itemitemOnclick = e => { var data = this.state.governmentData data.map((item, index) => { item.childs.map((item, index) => { if (item.index == e.index) { item.type = !item.type } }) }) this.setState({ governmentData: data, }) } /** * 打电话 * */ tellPhone = item => { Linking.openURL(`tel:${item.tel}`) } /** * FlatList render * */ renderRow = item => ( <View ref={item.index}> <View> <TouchableOpacity onPress={() => this.itemOnclick(item)}> <View style={{ flexDirection: 'row', backgroundColor: 'white', height: 30, alignItems: 'center', }} > <View style={{ marginLeft: 5, width: 4, height: 10, paddingTop: 7, paddingBottom: 7, backgroundColor:'red' }} /> <Text style={{ color: '#333333', marginLeft: 5, fontSize: 16 }}> {item.item.name} </Text> {/* 右边箭头 */} <Image source={ item.item.type ? require('./image/btnDown.png') : require('./image/right.png') } style={{ position: 'absolute', right: 15, width: 15, height: 15 }} /> </View> </TouchableOpacity> <View style={{ backgroundColor: 'rgb(228,228,228)', width: width, height: 1, }} /> </View> {item.item.type ? item.item.childs.map((item, index) => ( <View style={{ backgroundColor: 'white' }} key={index}> <View style={{ marginLeft: 7, marginTop: 10 }}> <View style={{ flexDirection: 'row' }}> <Image source={ item.type ? require('./image/btnDownB.png') : require('./image/btnRightB.png') } style={{ width: 10, height: 10 }} /> <Text onPress={() => this.itemitemOnclick(item)} style={{ marginLeft: 7 }} > {item.name} </Text> </View> <View style={{ backgroundColor: 'rgb(228,228,228)', width: width, height: 1, marginTop: 10, }} /> </View> {item.type ? ( <View> <View style={{ height: 40, alignItems: 'center', flexDirection: 'row', }} > <TouchableOpacity style={{ flexDirection: 'row' }} onPress={() => this.tellPhone(item)} > <Image source={require('./image/phone.png')} style={{ marginLeft: 10, width: 15, height: 15 }} /> <Text style={{ marginLeft: 10 }}>电话 {item.tel}</Text> </TouchableOpacity> </View> <View style={{ backgroundColor: 'rgb(228,228,228)', width: width, height: 1, }} /> <View style={{ height: 40, alignItems: 'center', flexDirection: 'row', }} > <Image source={require('./image/tlak.png')} style={{ marginLeft: 10, width: 15, height: 15 }} /> <Text style={{ marginLeft: 10 }}>留言 {item.message}</Text> </View> <View style={{ backgroundColor: 'rgb(228,228,228)', width: width, height: 1, }} /> </View> ) : null} </View> )) : null} </View> ) render() { return ( <FlatList data={this.state.governmentData} renderItem={this.renderRow} onRefresh={() => this.onRefresh()} refreshing={this.state.isRefresh} /> ) }}const styles = StyleSheet.create({ container: { flex: 1, }, icon: { width: 22, height: 22, },}) 互斥缩一缩 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277/* eslint-disable */import React, { Component } from 'react'import { StyleSheet, View, Image, Text, Dimensions, TouchableOpacity, FlatList, Linking,} from 'react-native'const { width } = Dimensions.get('window')export default class Two extends Component { // 构造 constructor(props) { super(props) // 初始状态 this.state = { governmentData: [ { name: '社会保障局', childs: [ { index:0, name: '资料教室', tel: '5233', message: '查询资料,注销档案', }, { index:1, name: '后勤部', tel: '1645682', message: '不对外服务', }, ], }, { name: '卫生局', childs: [ { index:0, name: '妇产科', tel: '112转678', message: '接待孕妇,待产孕妇,军嫂预先', }, { index:1, name: '儿科', tel: '112转008', message: '小于12周岁儿童就医', }, { index:2, name: '失恋科', tel: '112转出去', message: '回家玩蛋去!!!', }, ], }, { name: '神盾局', childs: [ { index:0, name: '城管大队', tel: '110', title: '打打打', message: '镇压起义', }, ], }, ], isRefresh: true, index: -1, //默认展开负一行 indexIndex: -1, } } componentDidMount() { this.setState({ isRefresh: false, }) } /** * 下拉刷新 * */ onRefresh = () => { this.setState({ isRefresh: false, }) } /** * 点击一级 * */ itemOnclick = item => { this.setState({ index: item.index, indexIndex: -1 }) } /** * 点击二级 * */ itemitemOnclick = e => { this.setState({ indexIndex: e.index, }) } /** * 打电话 * */ tellPhone = item => { Linking.openURL(`tel:${item.tel}`) } /** * FlatList render * */ renderRow = item => ( <View ref={item.index}> <View> <TouchableOpacity onPress={() => this.itemOnclick(item)}> <View style={{ flexDirection: 'row', backgroundColor: 'white', height: 30, alignItems: 'center', }} > <View style={{ marginLeft: 5, width: 4, height: 10, paddingTop: 7, paddingBottom: 7, backgroundColor:'red' }} /> <Text style={{ color: '#333333', marginLeft: 5, fontSize: 16 }}> {item.item.name} </Text> {/* 右边箭头 */} <Image source={ item.index == this.state.index ? require('./image/btnDown.png') : require('./image/right.png') } style={{ position: 'absolute', right: 15, width: 15, height: 15 }} /> </View> </TouchableOpacity> <View style={{ backgroundColor: 'rgb(228,228,228)', width: width, height: 1, }} /> </View> {item.index == this.state.index ? item.item.childs.map((item, index) => ( <View style={{ backgroundColor: 'white' }} key={index}> <View style={{ marginLeft: 7, marginTop: 10 }}> <View style={{ flexDirection: 'row' }}> <Image source={ index == this.state.indexIndex ? require('./image/btnDownB.png') : require('./image/btnRightB.png') } style={{ width: 10, height: 10 }} /> <Text onPress={() => this.itemitemOnclick(item)} style={{ marginLeft: 7 }} > {item.name} </Text> </View> <View style={{ backgroundColor: 'rgb(228,228,228)', width: width, height: 1, marginTop: 10, }} /> </View> {item.index == this.state.indexIndex ? ( <View> <View style={{ height: 40, alignItems: 'center', flexDirection: 'row', }} > <TouchableOpacity style={{ flexDirection: 'row' }} onPress={() => this.tellPhone(item)} > <Image source={require('./image/phone.png')} style={{ marginLeft: 10, width: 15, height: 15 }} /> <Text style={{ marginLeft: 10 }}>电话 {item.tel}</Text> </TouchableOpacity> </View> <View style={{ backgroundColor: 'rgb(228,228,228)', width: width, height: 1, }} /> <View style={{ height: 40, alignItems: 'center', flexDirection: 'row', }} > <Image source={require('./image/tlak.png')} style={{ marginLeft: 10, width: 15, height: 15 }} /> <Text style={{ marginLeft: 10 }}>留言 {item.message}</Text> </View> <View style={{ backgroundColor: 'rgb(228,228,228)', width: width, height: 1, }} /> </View> ) : null} </View> )) : null} </View> ) render() { return ( <FlatList data={this.state.governmentData} renderItem={this.renderRow} onRefresh={() => this.onRefresh()} refreshing={this.state.isRefresh} /> ) }}const styles = StyleSheet.create({ container: { flex: 1, }, icon: { width: 22, height: 22, },}) 源码","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"redux和react-navigation的结合使用","slug":"移动端学习/RN~redux和react-navigation的结合使用","date":"2018-07-29T10:36:14.000Z","updated":"2023-12-20T06:14:27.231Z","comments":true,"path":"2018/07/29/移动端学习/RN~redux和react-navigation的结合使用/","link":"","permalink":"https://zhoushaoting.com/2018/07/29/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/RN~redux%E5%92%8Creact-navigation%E7%9A%84%E7%BB%93%E5%90%88%E4%BD%BF%E7%94%A8/","excerpt":"react-navigation 和 redux 的使用,这个网上很多.这里介绍下两者的结合使用.","text":"react-navigation 和 redux 的使用,这个网上很多.这里介绍下两者的结合使用. 我代码的目录结构是: 使用的三方库是: 1234567891011121314151617181920212223242526272829{ "name": "RN_nav", "version": "0.0.1", "private": true, "scripts": { "start": "node node_modules/react-native/local-cli/cli.js start", "test": "jest" }, "dependencies": { "react": "16.3.0-alpha.1", "react-native": "0.54.0", "react-navigation": "^1.5.1", "react-navigation-redux-helpers": "^1.0.3", "react-redux": "^5.0.7", "redux": "^3.7.2", "redux-actions": "^2.3.0", "redux-logger": "^3.0.6", "redux-thunk": "^2.2.0" }, "devDependencies": { "babel-jest": "22.4.1", "babel-preset-react-native": "4.0.0", "jest": "22.4.2", "react-test-renderer": "16.3.0-alpha.1" }, "jest": { "preset": "react-native" }} 下面是各文件的源码.App.js1234567891011121314151617181920/** * Sample React Native App * https://github.com/facebook/react-native * @flow */import React,{ Component } from 'react';import {Provider} from 'react-redux';import store from './src/store';import NavigatorPages from './src/AllPages/TabNavigatorPage';type Props = {};export default class App extends Component<Props> { render() { return ( <Provider store={store}> <NavigatorPages/> </Provider> ); }} Allreducerstore.js123456789101112131415161718192021import {createStore,applyMiddleware} from 'redux';//中间件import logger from 'redux-logger';import thunk from 'redux-thunk';import { createReduxBoundAddListener, createReactNavigationReduxMiddleware,} from 'react-navigation-redux-helpers';//reducersimport reducers from './Allreducer/index';//引用react-navigation-redux-helpers组件手动创建中间件,接受state并返回新的state,让路由刷新// Note: createReactNavigationReduxMiddleware must be run before createReduxBoundAddListenerconst middleware = createReactNavigationReduxMiddleware( "App", state => state.nav,);export const addListener = createReduxBoundAddListener("App");const middleWares = [middleware,thunk,logger];export default applyMiddleware(...middleWares)(createStore)(reducers); FirstPageReducer.js1234567891011121314151617181920212223import {handleActions} from 'redux-actions';const initialState = { zglNum:0};export default handleActions({ ADD:(state,action)=>{ // alert(state.zglNum) return { ...state, zglNum:state.zglNum + 1, secondState:'iOS' } }, SUB:(state,action)=>{ return { ...state, zglNum:state.zglNum - 1, secondState:'Android' } }},initialState); SecondPageReducer.js12345678910111213141516/** * Created by shaotingzhou on 2018/3/6. */import {handleActions} from 'redux-actions';const initialState = { xxx:0};export default handleActions({ SECOND:(state,action)=>{ return { ...state, xxx:2 } },},initialState); index.js12345678910import { combineReducers } from 'redux';import TabNavigatorReducer from './TabNavigatorReducer';import FirstPageReducer from './FirstPageReducer';import SecondPageReducer from './SecondPageReducer';const reducers = combineReducers({ TabNavigatorReducer, FirstPageReducer, SecondPageReducer,});export default reducers; AllPagesFirstPage.js123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687import React, { PureComponent } from 'react';import { StyleSheet, Text, View, TouchableOpacity, Image} from 'react-native';import {connect} from 'react-redux';import {ADD,SUB} from '../Actions/FirstPageActions';class FirstPage extends PureComponent { static navigationOptions = { title:'首页', }; // componentWillUpdate(){ // alert(this.props.status); // // } // componentDidUpdate(){ // alert(this.props.status); // } // ES6 props // static defaultProps={ // zglNum:0, // } render() { return ( <View style={styles.container}> <TouchableOpacity onPress={()=>{ this.props.dispatch(ADD()); }} > <Text style={styles.welcome}> + </Text> </TouchableOpacity> <Text style={styles.welcome}> {this.props.zglNum} </Text> <TouchableOpacity onPress={()=>{ this.props.dispatch(SUB()); }} > <Text style={styles.welcome}> - </Text> </TouchableOpacity> </View> ); }}const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, welcome: { fontSize: 20, textAlign: 'center', margin: 10, }, textView: { fontSize: 16, textAlign: 'center', margin: 10, color:'red' },});const mapStateToProps = (store)=>({ zglNum: store.FirstPageReducer.zglNum //数字});export default connect(mapStateToProps)(FirstPage); SecondPage.js12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061import React, { PureComponent } from 'react';import { StyleSheet, Text, View, TouchableOpacity, Image} from 'react-native';import {connect} from 'react-redux';import {SECOND} from '../Actions/SecondPageActions';class SecondPage extends PureComponent { static navigationOptions = { title:'第二' }; // ES6 props static defaultProps={ secondState:'默认', } render() { return ( <View style={styles.container} > <Text onPress={()=>this.onClick()}>{this.props.secondState}</Text> </View> ); } onClick =() =>{ this.props.navigation.navigate('Snnn') }}const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, welcome: { fontSize: 20, textAlign: 'center', margin: 10, }, textView: { fontSize: 16, textAlign: 'center', margin: 10, color:'red' },});const mapStateToProps = (store)=>({ secondState:store.FirstPageReducer.secondState});export default connect(mapStateToProps)(SecondPage); NavigatorPage.js123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105import React, { Component } from 'react';import { StyleSheet, Text, View, TouchableOpacity, Image} from 'react-native';import FirstPage from './FirstPage';import SecondPage from './SecondPage';import {connect} from 'react-redux';import Snnn from './Snnn'import { StackNavigator, addNavigationHelpers, TabNavigator,} from 'react-navigation';import {addListener} from '../store';class NavigatorPages extends Component{ constructor(props){ super(props); } render(){ return( <SimpleAppNavigator navigation={addNavigationHelpers({ dispatch: this.props.dispatch, state: this.props.nav, addListener })} /> ) }}const Tab = TabNavigator({ page1: { screen: FirstPage, navigationOptions: ({ navigation }) => ({ tabBarLabel: '苹果233', tabBarIcon: ({ focused, tintColor }) => ( <Image source={focused ? require('../../image/one_selected.png') : require('../../image/one.png')} style={{ width: 25, height: 25 }} /> ) }), }, page2: { screen: SecondPage, navigationOptions: ({ navigation }) => ({ tabBarLabel: '安卓', tabBarIcon: ({ focused, tintColor }) => ( <Image source={focused ? require('../../image/two_selected.png') : require('../../image/two.png') } style={{ width: 25, height: 25 }} /> ) }), },},{ initialRouteName: 'page1', swipeEnabled: true, animationEnabled: true, tabBarPosition:'bottom', lazy: false, tabBarOptions: { showIcon: true, activeTintColor: '#979797', inactiveTintColor: '#979797', style: { backgroundColor: '#ffffff' }, }});export const SimpleAppNavigator = StackNavigator({ Tab: { screen: Tab, }, page1 : { screen: FirstPage, }, page2 : { screen: SecondPage }, Snnn : { screen: Snnn }});const mapStateToProps = (store)=>({ nav : store.TabNavigatorReducer,});export default connect(mapStateToProps)(NavigatorPages); ActionsActionsTypes.js123456//Tab1export const ADD = 'ADD';export const SUB = 'SUB';//Tab2 FirstPageActions.js1234import {createAction} from 'redux-actions';import * as TYPES from './ActionTypes';export const ADD = createAction(TYPES.ADD);export const SUB = createAction(TYPES.SUB); SccondPageActions.js12345/** * Created by shaotingzhou on 2018/3/6. */import {createAction} from 'redux-actions';import * as TYPES from './ActionTypes'; 源码效果图: 另外:dva+react-navigation另外:Mbox+react-navigation","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"dva和react-navigation的结合使用","slug":"移动端学习/RN~dva和react-navigation的结合使用","date":"2018-07-28T16:00:00.000Z","updated":"2023-12-20T05:23:44.375Z","comments":true,"path":"2018/07/29/移动端学习/RN~dva和react-navigation的结合使用/","link":"","permalink":"https://zhoushaoting.com/2018/07/29/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/RN~dva%E5%92%8Creact-navigation%E7%9A%84%E7%BB%93%E5%90%88%E4%BD%BF%E7%94%A8/","excerpt":"今天,学习下RN中的另外一个全家桶套餐架构:dva+React-Navigation,本文基于react-native:0.55.4,dva-core:^1.3.0,react-navigation:^2.5.1,react-navigation-redux-helpers:^2.0.4,react-redux:0^5.0.7所撸.","text":"今天,学习下RN中的另外一个全家桶套餐架构:dva+React-Navigation,本文基于react-native:0.55.4,dva-core:^1.3.0,react-navigation:^2.5.1,react-navigation-redux-helpers:^2.0.4,react-redux:0^5.0.7所撸. react-native的官方dva-demodva官方github 废话不多缩,首先,新建一个项目,添加一堆相应库:npm install mobx dva-core --save 引入dvanpm install mobx react-navigation-redux-helpers --save 引入react-navigation-redux-helpersnpm install mobx react-redux --save 引入react-reduxnpm install babel-plugin-transform-decorators-legacy babel-preset-react-native-stage-0 --save-dev 能够使用@标签npm install react-navigation --save 引入导航库然后修改一下工程里面的.babelrc: 12345{ "presets": ["react-native"], "plugins": ["transform-decorators-legacy"]} OK,基本的架子已经搭好.然后新建一个src目录.这里存放基本代码和基本图片之类的. 然后,在入口文件index.js中,修改一下代码: 12345678import './App';console.ignoredYellowBox = [ 'Warning: componentWillMount is deprecated', 'Warning: componentWillReceiveProps is deprecated', 'Warning: componentWillUpdate is deprecated', 'Warning: isMounted(...) is deprecated',] 在App.js中基础代码: 1234567891011121314151617181920import React from 'react'import { AppRegistry } from 'react-native'import dva from './src/Utils/dva'import Router, { routerMiddleware, routerReducer } from './router'import appModel from './src/models/app'const app = dva({ initialState: {}, models: [appModel], extraReducers: { router: routerReducer }, onAction: [routerMiddleware], onError(e) { console.log('onError', e) },})const App = app.start(<Router />)AppRegistry.registerComponent('rn_dva', () => App) ok,如上图所示,dva的代码相比redux来说少了很多,不在需要大量的赋值粘贴,基本的逻辑代码均可以放在models中,其中的Utils只是dva的一个工具组,其中的代码并不多.一如既往,对着IDE就是一堆疯狂输出.完成的功能和之前的redux和Mobx一样.models下的app.js 1234567891011121314151617181920212223242526import { createAction, NavigationActions } from '../Utils'export default { namespace: 'app', state: { num: 0, mineType: '红', }, reducers: { updateState(state, { payload }) { return { ...state, ...payload } }, }, effects: { *add({ payload }, { call, put }) { yield put(createAction('updateState')({ num:payload ,mineType:'红'})) }, *sub({ payload }, { call, put }) { yield put(createAction('updateState')({ num:payload ,mineType:'蓝' })) } }, subscriptions: { },} Utils下的dva.js 12345678910111213141516171819202122import React from 'react'import { create } from 'dva-core'import { Provider, connect } from 'react-redux'export { connect }export default function(options) { const app = create(options) // HMR workaround if (!global.registered) options.models.forEach(model => app.model(model)) global.registered = true app.start() // eslint-disable-next-line no-underscore-dangle const store = app._store app.start = container => () => <Provider store={store}>{container}</Provider> app.getStore = () => store return app} Utils下的index.js 1234export { NavigationActions, StackActions } from 'react-navigation'export const createAction = type => payload => ({ type, payload }) 剩下的就是两个UI界面了:One.js 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788/** * Sample React Native App * https://github.com/facebook/react-native * @flow */import React, { Component } from 'react';import { Platform, StyleSheet, Text, View, Image} from 'react-native';import { connect } from 'react-redux'import { createAction, NavigationActions } from '../Utils'import {Images} from "../Image";const instructions = Platform.select({ ios: 'Press Cmd+R to reload,\\n' + 'Cmd+D or shake for dev menu', android: 'Double tap R on your keyboard to reload,\\n' + 'Shake or press menu button for dev menu',});type Props = {};@connect(({ app }) => ({ ...app }))export default class One extends Component<Props> { static navigationOptions = { tabBarLabel: '苹果', tabBarIcon: ({ focused, tintColor }) => ( <Image source={focused ? Images.Tab.OneActive : Images.Tab.One} style={{ width: 25, height: 25 }} /> ), } render() { return ( <View style={styles.container}> <Text onPress={()=>this.add()}> + 红 </Text> <Text> One {this.props.num} </Text> <Text onPress={()=>this.sub()}> - 蓝 </Text> </View> ); } add =() =>{ this.props.dispatch(createAction('app/add')(this.props.num + 1)) } sub =() =>{ this.props.dispatch(createAction('app/sub')(this.props.num - 1)) } componentDidMount() { console.log(this.props) }}const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, welcome: { fontSize: 20, textAlign: 'center', margin: 10, }, instructions: { textAlign: 'center', color: '#333333', marginBottom: 5, },}); Two.js 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374/** * Sample React Native App * https://github.com/facebook/react-native * @flow */import React, { Component } from 'react';import { Platform, StyleSheet, Text, View, Image} from 'react-native';import { connect } from 'react-redux'import { createAction, NavigationActions } from '../Utils'import {Images} from "../Image";const instructions = Platform.select({ ios: 'Press Cmd+R to reload,\\n' + 'Cmd+D or shake for dev menu', android: 'Double tap R on your keyboard to reload,\\n' + 'Shake or press menu button for dev menu',});type Props = {};@connect(({ app }) => ({ ...app }))export default class Two extends Component<Props> { static navigationOptions = { tabBarLabel: '安卓', tabBarIcon: ({ focused, tintColor }) => ( <Image source={focused ? Images.Tab.TwoActive : Images.Tab.Two} style={{ width: 25, height: 25 }} /> ), } render() { return ( <View style={[styles.container,{backgroundColor:this.props.mineType == '红' ? 'red' : 'blue'}]}> <Text style={styles.welcome}> TWO + {this.props.mineType} </Text> </View> ); } componentDidMount() { console.log(this.props.mineType) }}const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, welcome: { fontSize: 20, textAlign: 'center', margin: 10, }, instructions: { textAlign: 'center', color: '#333333', marginBottom: 5, },}); OK,以上就是全部代码.效果图: 源码地址 另外:redux+react-navigation另外:Mbox+react-navigation","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"react-native七牛上传图片","slug":"移动端学习/RN~react-native七牛上传图片","date":"2018-06-25T16:00:00.000Z","updated":"2023-12-20T06:09:53.985Z","comments":true,"path":"2018/06/26/移动端学习/RN~react-native七牛上传图片/","link":"","permalink":"https://zhoushaoting.com/2018/06/26/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/RN~react-native%E4%B8%83%E7%89%9B%E4%B8%8A%E4%BC%A0%E5%9B%BE%E7%89%87/","excerpt":"今天试一下React Native 七牛上传图片.坑也就随之开始了.以demo为例.react-native:”0.55.4”,react-native-qiniu:’’0.3.0”","text":"今天试一下React Native 七牛上传图片.坑也就随之开始了.以demo为例.react-native:”0.55.4”,react-native-qiniu:’’0.3.0” 首先最大的坑就是七牛官方的这个库:react-native-qiniu,好像是该库的创始人离职了,导致了该库已经荒废,无人更新维护.如果你只是单纯按照github的说明导入该库就使用的话,无论你以什么姿势撸.结果都是Rpc.uploadFile的catch输出错误,错误信息为null………..这个错误信息真让我头大.可喜有人在网上做出了更新,更新代码(需要修改库中的两个源文件../react-native-qiniu/core)如下:rpc.js 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164import conf from './conf.js';import Auth from './auth';//发送管理和fop命令,总之就是不上传文件function post(uri, adminToken, content) { var headers = { 'Content-Type': 'application/x-www-form-urlencoded', }; let payload = { headers: headers, method: 'POST', dataType: 'json', timeout: conf.RPC_TIMEOUT, }; if (typeof content === 'undefined') { payload.headers['Content-Length'] = 0; } else { //carry data payload.body = content; } if (adminToken) { headers['Authorization'] = adminToken; } return fetch(uri, payload);}/*** 直传文件* formInput对象如何配置请参考七牛官方文档“直传文件”一节*/function uploadFile(dataParams, policy, callbackUpDate = function () { }, callBackMethod = function () { }) { let params = getParams(dataParams, policy); let uri = params.uri; let data = params.data; let oloaded = null; let responseObj = {}; return new Promise((resolve, reject) => { if (typeof uri != 'string' || uri == '' || typeof data.key == 'undefined') { reject && reject(null); return; } if (uri[0] == '/') { uri = "file://" + uri; } //创建xhr并open var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { responseObj.readyState = xhr.readyState; //状态0-4 responseObj.data = xhr.response;//返回值 responseObj.textData = xhr.responseText; //返回值Text responseObj.status = xhr.status; //状态码 // responseObj.message = "" switch (xhr.readyState) { case 0: callBackMethod(responseObj) break; case 1: callBackMethod(responseObj) break; case 2: callBackMethod(responseObj) break; case 3: callBackMethod(responseObj) break; case 4: if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) { if (xhr.status == 200) { callBackMethod(responseObj) } } else { callBackMethod(responseObj) } break; } }; xhr.open('POST', conf.UP_HOST); xhr.onload = () => { if (xhr.status !== 200) { reject && reject(responseObj); return; } resolve && resolve(JSON.parse(responseObj.data)); }; xhr.onerror = (evt) => { reject && reject(evt); return; }; //请求失败 xhr.upload.onloadstart = () => {//上传开始执行方法 oloaded = 0;//设置上传开始时,以上传的文件大小为0 console("上传开始") }; xhr.upload.onprogress = (evt) => { oloaded = evt.loaded;//重新赋值已上传文件大小,用以下次计算 callbackUpDate(Math.round(oloaded / evt.total * 100), oloaded, evt.total) }; xhr.upload.onloadend = (evt) => { console("上传结束") }; let formdata = creatFormData(params); xhr.send(formdata); });}//构造上传参数function getParams(data, policy) { let putPolicy = new Auth.Policy( policy ); let uptoken = putPolicy.token(); data.token = uptoken; let params = {}; params.uri = data.uri; delete data.uri; params.data = data; return params;}/*** 创建一个表单对象,用于上传参数* @param {*} params*/function creatFormData(params) { let formdata = new FormData(); let uri = params.uri; let formInput = creatFormInput(uri); let data = params.data; console.log(data) for (let key of Object.keys(data)) { let value = data[key]; if (key.charAt(0) === "_") { formdata.append("x:" + key.substring(1, key.length), value); } else { formdata.append(key, value); } } formdata.append("file", { uri: uri, type: formInput.type, name: formInput.name }); console.log(formdata) return formdata;}/*** 构造表单对象中file对象* @param {*} params*/function creatFormInput(uri) { let formInput = {}; if (typeof formInput.type == 'undefined') formInput.type = 'application/octet-stream'; if (typeof formInput.name == 'undefined') { var filePath = uri.split("/"); if (filePath.length > 0) formInput.name = filePath[filePath.length - 1]; else formInput.name = ""; } return formInput;}export default { uploadFile, post } auth.js 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154import base64 from 'base-64';import CryptoJS from "crypto-js";import conf from "./conf.js";import parse from 'url-parse';function urlsafeBase64Encode(jsonFlags) { var encoded = base64.encode(jsonFlags); return base64ToUrlSafe(encoded);};function base64ToUrlSafe(v) { return v.replace(/\\//g, '_').replace(/\\+/g, '-');};function hmacSha1(encodedFlags, secretKey) { var encoded = CryptoJS.HmacSHA1(encodedFlags, secretKey).toString(CryptoJS.enc.Base64); return encoded;};function generateAccessToken(url, body) { var u = parse(url, true); var path = u.pathname; var access = path + '\\n'; if (body) { access += body; } var digest = hmacSha1(access, conf.SECRET_KEY); var safeDigest = base64ToUrlSafe(digest); let token = 'QBox ' + conf.ACCESS_KEY + ':' + safeDigest; //console.log(token); return token;};class Policy { constructor(policy) { if (typeof (policy) == "undefined") { } else { this.policy = policy; if (typeof (policy.deadline) == "undefined" || policy.deadline == null) { this.policy.deadline = 3600 + Math.floor(Date.now() / 1000); } } } _parse2Str(putPolicy) { let str = "{"; let keys = Object.keys(putPolicy); keys.forEach((key, i) => { let value = putPolicy[key]; if (typeof (value) == "object") { str = `${str}"${key}":` str = `${str}"{` Object.keys(value).forEach((key2) => { let value2 = value[key2]; let re = /(\\$\\(.*?\\))/g; if (re.test(value2)) { str = `${str}\\\\\\"${key2}\\\\\\":${value2},` } else { str = `${str}\\\\\\"${key2}\\\\\\":"${value2}",` } }) console.log(keys.length + "::" + i) if (i >= keys.length) { str = `${str.substring(0, str.length - 1)}}"` } else { str = `${str.substring(0, str.length - 1)}}",` } } else if (typeof (value) == "number") { str = `${str}"${key}":${value},` } else if (typeof (value) == "string") { str = `${str}"${key}":"${value}",` } else { str = `${str}"${key}":"${value}",` } }) str = `${str.substring(0, str.length - 1)}}`; return str; } // _creatStr = (policy) => { // policy['deadline'] = this.expires + Math.floor(Date.now() / 1000); // let policyStr = JSON.stringify(policy); // let re = /(\\"\\$\\(.*?\\)\\")/g; // let newStr = policyStr.replace(re, (value) => { // return value.substring(1, value.length - 1); // }) // return newStr; // } token = () => { policStr = this._parse2Str(this.policy); console.log("policStr", policStr); var encodedPutPolicy = this._urlsafeBase64Encode(policStr); console.log("encodedPutPolicy", encodedPutPolicy); var sign = this._hmacSha1(encodedPutPolicy, conf.SECRET_KEY); var encodedSign = this._base64ToUrlSafe(sign); console.log("encodedSign", encodedSign); var uploadToken = conf.ACCESS_KEY + ':' + encodedSign + ':' + encodedPutPolicy; console.log("uploadToken", uploadToken); return uploadToken; } _urlsafeBase64Encode = (jsonFlags) => { var encoded = base64.encode(jsonFlags); return base64ToUrlSafe(encoded); }; _base64ToUrlSafe = (v) => { return v.replace(/\\//g, '_').replace(/\\+/g, '-'); }; _hmacSha1 = (encodedFlags, secretKey) => { var encoded = CryptoJS.HmacSHA1(encodedFlags, secretKey).toString(CryptoJS.enc.Base64); return encoded; };}class GetPolicy { constructor(expires) { this.expires = expires || 3600; } makeRequest(baseUrl) { var deadline = this.expires + Math.floor(Date.now() / 1000); if (baseUrl.indexOf('?') >= 0) { baseUrl += '&e='; } else { baseUrl += '?e='; } baseUrl += deadline; var signature = hmacSha1(baseUrl, conf.SECRET_KEY); var encodedSign = base64ToUrlSafe(signature); var downloadToken = conf.ACCESS_KEY + ':' + encodedSign; return baseUrl + '&token=' + downloadToken; }}export default { urlsafeBase64Encode, generateAccessToken, Policy, GetPolicy } ok,改完之后,你就可以愉快的撸自己的业务代码了.至于业务代码我就简单写个例子:其中,Conf.ACCESS_KEY和Conf.SECRET_KEY从七牛账号里面获取,Conf.UP_HOST 从https://developer.qiniu.com/kodo/manual/1671/region-endpoint 里面获取,其中,scope就是七牛里面你自己建立的存储空间名 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108/*** Sample React Native App* https://github.com/facebook/react-native* @flow*/import React, { Component } from 'react';import { Platform, StyleSheet, Text, View, Image} from 'react-native';import Qiniu, { Auth, ImgOps, Conf, Rs, Rpc } from 'react-native-qiniu';//对于七牛修改文件参考: https://blog.csdn.net/qq_33935895/article/details/78775819Conf.ACCESS_KEY = "从七牛账号里面获取";Conf.SECRET_KEY = "从七牛账号里面获取";Conf.UP_HOST = '从七牛账号里面获取'; // https://developer.qiniu.com/kodo/manual/1671/region-endpointtype Props = {};export default class App extends Component<Props> { // 构造 constructor(props) { super(props); // 初始状态 this.state = { img: '图片url' }; } render() { return ( <View style={styles.container}> <Text style={styles.instructions} onPress={() => this.upload()}> 上传</Text> <Text>{this.state.img}</Text> <Image source={{ uri: this.state.img }} style={{ width: 200, height: 400 }} /> </View> ); } /** * 先上传七牛 获取url * */ upload = () => { var img = '/Users/shaotingzhou/Desktop/qiniuDemo/uploadImg.jpg' //图片路径 如果是从相册获取图片的话,其相册会返回 var myDate = new Date(); const key = myDate.getTime() + '.jpg'; //上传成功后该key就是图片的url路径 //上传参数 let params = { uri: img,//图片路径 可以通过第三方工具 如:ImageCropPicker等获取本地图片路径 key: key,//要上传的key } //构建上传策略 let policy = { scope: "demo",//记得这里如果格式为<bucket>:<key>形式的话,key要与params里的key保持一致,详见七牛上传策略 returnBody://returnBody 详见上传策略 { name: "$(fname)",//获取文件名 size: "$(fsize)",//获取文件大小 w: "$(imageInfo.width)",//... h: "$(imageInfo.height)",//... hash: "$(etag)",//... }, } //进行文件上传 Rpc.uploadFile(params, policy).then((data) => { console.log('上传成功') var imgUrl = key //七牛上的图片URL 就是之前的key + 你公司域名 this.setState({ img: 'http://pax8cso07.bkt.clouddn.com/' + key }) }).catch((err) => { console.log(err) }); }}const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, welcome: { fontSize: 20, textAlign: 'center', margin: 10, }, instructions: { textAlign: 'center', color: '#333333', marginBottom: 5, },}); ok.下面是七牛里面的key对应图,修改后的输出图,最后的例子展示图:下面是源码.其中七牛的修改文件在0.3.0中.源码","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"react-navigation前置登录","slug":"移动端学习/RN~react-navigation前置登录","date":"2018-06-15T16:00:00.000Z","updated":"2023-12-20T06:13:35.498Z","comments":true,"path":"2018/06/16/移动端学习/RN~react-navigation前置登录/","link":"","permalink":"https://zhoushaoting.com/2018/06/16/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/RN~react-navigation%E5%89%8D%E7%BD%AE%E7%99%BB%E5%BD%95/","excerpt":"好多应用都需要涉及到前置登录,今天就来实操一下.所用的库有react-natigation导航库和mobx状态管理库.","text":"好多应用都需要涉及到前置登录,今天就来实操一下.所用的库有react-natigation导航库和mobx状态管理库. 这里贴一下各库的使用版本: 123456789101112131415161718192021222324252627 { "name": "lead_the_login", "version": "0.0.1", "private": true, "scripts": { "start": "node node_modules/react-native/local-cli/cli.js start", "test": "jest" }, "dependencies": { "mobx": "^4.3.0", "mobx-react": "^5.1.2", "react": "16.3.1", "react-native": "0.55.4", "react-navigation": "1.5.3" }, "devDependencies": { "babel-jest": "23.0.1", "babel-plugin-transform-decorators-legacy": "^1.3.5", "babel-preset-react-native": "4.0.0", "babel-preset-react-native-stage-0": "^1.0.1", "jest": "23.1.0", "react-test-renderer": "16.3.1" }, "jest": { "preset": "react-native" }} 前置登录主要使用的是react-navigation的tabbar的点击事件. 使用方法可以看 react navigation官方网站 或者 兔佬的简书,至于mbox和react-navigation的使用可以参考之前写的这篇文章:Mobx和react-navigation的使用. 这里不做细说.直接上代码.下面是基本代码结构.准备工作,先把mobx+react-navigation搭建完毕之后.我们在点击我的Tab时,对tabBar点击事件做处理即可,在点击事件里面先获取我们存在Store中的登录状态,根据状态做不同的事情,这里我未处理本地数据持久化工作,只是单纯的存在Mobx中的store中,是为了方便其他地方使用.实际开发中,我们还需要对数据进行持久化操作.主要代码有: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114/** * Sample React Native App * https://github.com/facebook/react-native * @flow */import React, { Component } from 'react';import { Platform, StyleSheet, Text, View} from 'react-native';import { observer, inject } from 'mobx-react'import { action, autorun, computed } from 'mobx'import { NavigationActions } from 'react-navigation'const resetAction = NavigationActions.reset({ index: 0, actions: [ NavigationActions.navigate({routeName: 'Tab', params: {}}) ]})@inject('rootStore')@observerexport default class TwoView extends Component<Props> { static navigationOptions = ({ navigation }) => ({ header:null, tabBarOnPress: (tab) => { //让tabBar可点击,做前置登录 // navigation.state.params.navigatePress() tab.jumpToIndex(tab.scene.index) }, }); render() { return ( <View style={styles.container}> <Text> 登录状态:{this.loginStatus ? '已经登录' : '未登录'} </Text> <Text onPress={()=>this.loginOutAction()}> 注销 </Text> </View> ); } @computed get loginStatus() { return this.props.rootStore.TwoStore.allDatas.loginStatus; } componentDidMount() { this.props.navigation.setParams({ navigatePress: this.needLogin() }) // 使用这个来调用this } /** * 判断是否需要登录 * */ needLogin =() =>{ //判断登录 console.log('loginStatus') console.log(this.loginStatus) if(this.loginStatus){ //已经登录 return; }else { //未登录 跳转至登录界面 this.props.navigation.navigate('LoginView',{callback:()=>this.getPersonalInfo()}) } } /** * 登录成功的回调方法 * */ getPersonalInfo =() =>{ // 请求数据赋值即可 } loginOutAction =() =>{ //注销登录 清空本地化数据 和 重置store中的loginStatus this.props.rootStore.TwoStore.allDatas.loginStatus = false this.props.navigation.dispatch(resetAction); }}const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, welcome: { fontSize: 20, textAlign: 'center', margin: 10, }, instructions: { textAlign: 'center', color: '#333333', marginBottom: 5, },}); 和登录之后修改store中状态 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273/** * Sample React Native App * https://github.com/facebook/react-native * @flow */import React, { Component } from 'react';import { Platform, StyleSheet, Text, View} from 'react-native';import { observer, inject } from 'mobx-react'import { action, autorun, computed } from 'mobx'@inject('rootStore')@observerexport default class LoginView extends Component<Props> { static navigationOptions = ({ navigation }) => ({ header:null, }); render() { return ( <View style={styles.container}> <Text onPress={()=>this.loginAction()}> 登录 </Text> </View> ); } /** * 登录/注销 可以在store里面执行也可以直接在这里执行,看你习惯 * */ loginAction =() =>{ //登录请求 //成功之后,修改loginStatus.本地化数据等等 this.props.rootStore.TwoStore.allDatas.loginStatus = true //返回上一个界面,并回调刷新 this.props.navigation.goBack() this.props.navigation.state.params.callback(); }}const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, welcome: { fontSize: 20, textAlign: 'center', margin: 10, }, instructions: { textAlign: 'center', color: '#333333', marginBottom: 5, },}); 效果如图:源码","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"Mobx与react-navigation的结合使用","slug":"移动端学习/RN~Mobx与react-navigation的使用","date":"2018-05-26T16:00:00.000Z","updated":"2023-12-20T06:07:30.082Z","comments":true,"path":"2018/05/27/移动端学习/RN~Mobx与react-navigation的使用/","link":"","permalink":"https://zhoushaoting.com/2018/05/27/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/RN~Mobx%E4%B8%8Ereact-navigation%E7%9A%84%E4%BD%BF%E7%94%A8/","excerpt":"今天,学习下RN中的另外一个全家桶套餐架构:Mobx+React-Navigation,本文基于react-native:0.55.4,Mbox:^4.3.0,react-navigation:^2.0.1所撸.","text":"今天,学习下RN中的另外一个全家桶套餐架构:Mobx+React-Navigation,本文基于react-native:0.55.4,Mbox:^4.3.0,react-navigation:^2.0.1所撸. Mbox中文文档另外一个基于Mbox和react-navigation的不错项目 废话不多缩,首先,新建一个项目,添加相应库:npm i mobx mobx-react --save 引入Mbox npm i babel-plugin-transform-decorators-legacy babel-preset-react-native-stage-0 --save-dev 能够使用@标签 npm i react-navigation --save 引入导航库然后修改一下工程里面的.babelrc: 12345{ "presets": ["react-native"], "plugins": ["transform-decorators-legacy"]} OK,基本的架子已经搭好.然后新建一个src目录.这里存放基本代码和基本图片之类的. 然后,在入口文件App.js中,先搭建tabBar和导航条.使用react-navigation这里,我使用全局注册并注入mobx,其他地方都可以使用store.import {Provider} from 'mobx-react';然后在src->Mobx这里新建一个根store的Store.js文件.Store.js 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950import { observable, computed, action } from 'mobx'import oneInfo from './OneInfo'import twoInfo from './TwoInfo'/** * 根store * OneInfo OneInfo数据 * TwoInfo TwoInfo数据*/class RootStore { constructor() { this.OneInfo = new OneInfo(oneInfo,this) this.TwoInfo = new TwoInfo(twoInfo,this) }}// Oneclass OneInfo { @observable allDatas = [] constructor(data,rootStore) { this.allDatas = data this.rootStore = rootStore } //加 @action add(num) { this.allDatas.oneNum = num + 1 this.rootStore.TwoInfo.allDatas.twoColor = 'red' } //减 @action sub(num) { this.allDatas.oneNum = num - 1 this.rootStore.TwoInfo.allDatas.twoColor = 'blue' }}// Twoclass TwoInfo { @observable allDatas = {} constructor(data,rootStore) { this.allDatas = data this.rootStore = rootStore }}export default new RootStore() 另外:OneInfo.js 12345678910const OneInfo = { "data": [ ], "isOne" : true, "oneNum" : 0}export default OneInfo; TwoInfo.js 123456789const TwoInfo = { "data": [ ], "isTwo" : false, "twoColor":'white'}export default TwoInfo; ok,回到App.js中: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106import React, {Component} from 'react';import {Platform, StyleSheet, Text, View, Image} from 'react-native';import {StackNavigator, TabNavigator, TabBarBottom} from 'react-navigation';// 全局注册并注入mobx,其他地方都可以使用storeimport {Provider} from 'mobx-react';// 获取store实例import store from './src/Mobx/Store';import One from './src/One/One';import Two from './src/Two/Two';export default class TwoDetails extends Component<Props> { render () { return ( <Provider rootStore={store}> <Navigator onNavigationStateChange={(prevState, currentState) => { // 只要切换tab,push,pop,这里一定走 console.log (prevState); console.log (currentState); }} /> </Provider> ); } componentDidMount = () => { console.disableYellowBox = true; //去除黄色弹框警告 };}const Tab = TabNavigator ( { One: { screen: One, navigationOptions: ({navigation}) => ({ tabBarLabel: '男孩', tabBarIcon: ({focused, tintColor}) => ( <Image source={ focused ? require ('./src/Image/boy_active.png') : require ('./src/Image/boy.png') } style={{width: 25, height: 25}} /> ), }), }, Two: { screen: Two, navigationOptions: ({navigation}) => ({ tabBarLabel: '女孩', tabBarIcon: ({focused, tintColor}) => ( <Image source={ focused ? require ('./src/Image/girl_active.png') : require ('./src/Image/girl.png') } style={{width: 25, height: 25}} /> ), }), }, }, { tabBarComponent: TabBarBottom, tabBarPosition: 'bottom', swipeEnabled: true, animationEnabled: true, lazy: true, tabBarOptions: { activeTintColor: '#979797', inactiveTintColor: '#979797', style: {backgroundColor: '#ffffff'}, }, });const Navigator = StackNavigator ({ Tab: { screen: Tab, },});const styles = StyleSheet.create ({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, welcome: { fontSize: 20, textAlign: 'center', margin: 10, }, instructions: { textAlign: 'center', color: '#333333', marginBottom: 5, },}); 然后就是基本的UI界面了:One.js 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889/** * Sample React Native App * https://github.com/facebook/react-native * @flow */import React, { Component } from 'react';import { Platform, StyleSheet, Text, View} from 'react-native';import { observer, inject } from 'mobx-react'import { action, autorun, computed } from 'mobx'@inject('rootStore')@observerexport default class One extends Component<Props> { // 构造 constructor(props) { super(props); // 初始状态 this.state = {}; } render() { return ( <View style={styles.container}> <Text onPress={()=>this.add()}> + 红 </Text> <Text> One {this.dataSource} </Text> <Text onPress={()=>this.sub()}> - 蓝 </Text> </View> ); } @computed get dataSource() { return this.props.rootStore.OneInfo.allDatas.oneNum; } /** * + * */ @action add() { this.props.rootStore.OneInfo.add(this.dataSource) } /** * - * */ @action sub() { this.props.rootStore.OneInfo.sub(this.dataSource) }}const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, welcome: { fontSize: 20, textAlign: 'center', margin: 10, }, instructions: { textAlign: 'center', color: '#333333', marginBottom: 5, },}); Two.js 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354/** * Sample React Native App * https://github.com/facebook/react-native * @flow */import React, { Component } from 'react';import { Platform, StyleSheet, Text, View} from 'react-native';import { observer, inject } from 'mobx-react'import { action, autorun, computed } from 'mobx'@inject('rootStore')@observerexport default class Two extends Component<Props> { render() { return ( <View style={[styles.container,{backgroundColor:this.bgColor}]}> <Text> TWO </Text> </View> ); } @computed get bgColor() { return this.props.rootStore.TwoInfo.allDatas.twoColor; }}const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, welcome: { fontSize: 20, textAlign: 'center', margin: 10, }, instructions: { textAlign: 'center', color: '#333333', marginBottom: 5, },}); ok,以上就是Mbox+react-navigation的Demo的基本代码了.效果图: 源码 另外:redux+react-navigation另外:dva+react-navigation","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"前端精灵图学习","slug":"前端学习/精灵图学习","date":"2018-05-17T13:54:44.000Z","updated":"2023-12-20T05:00:12.982Z","comments":true,"path":"2018/05/17/前端学习/精灵图学习/","link":"","permalink":"https://zhoushaoting.com/2018/05/17/%E5%89%8D%E7%AB%AF%E5%AD%A6%E4%B9%A0/%E7%B2%BE%E7%81%B5%E5%9B%BE%E5%AD%A6%E4%B9%A0/","excerpt":"今天学习下前端中的 精灵图.为何引入精灵图: 网页上面的每张图片都要经历一次请求才能展示给用户,小的图标频繁的请求服务器,降低页面的加载速度,为了有效地减少服务器接收和发送请求的次数,提高页面的加载速度,因此,产生了css精灵技术。","text":"今天学习下前端中的 精灵图.为何引入精灵图: 网页上面的每张图片都要经历一次请求才能展示给用户,小的图标频繁的请求服务器,降低页面的加载速度,为了有效地减少服务器接收和发送请求的次数,提高页面的加载速度,因此,产生了css精灵技术。 以本地图片为例:精灵图使用其实就是对background-position:x y对图片进行偏移显示而已.下面是代码: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849<html> <head> <style> *{ margin:0; padding:0; } .div0{ margin:10px; } .demo1,.demo2,.demo3,.demo4,.demo5,.demo6{ display:inline-block; width:17px; height:17px; background-color:transparent; background-image:url(img/精灵图.png); background-repeat:no-repeat; } .demo1{ background-position:-38px -37px;} .demo2{ background-position:-62px -37px;} .demo3{ background-position:-86px -37px;} .demo4{ background-position:-110px -37px;} .demo5{ background-position:-134px -37px;} .demo6{ background-position:-159px -37px;} ul{ list-style:none; } ul li { margin:10px; } </style> </head> <body> <div class="div0"> <ul> <li><div class="demo1"></div> 图标1</li> <li><div class="demo2"></div> 图标2</li> <li><div class="demo3"></div> 图标3</li> <li><div class="demo4"></div> 图标4</li> <li><div class="demo5"></div> 图标5</li> <li><div class="demo6"></div> 图标6</li> </ul> </div> </body></html> 效果图: 源码","categories":[{"name":"前端学习","slug":"前端学习","permalink":"https://zhoushaoting.com/categories/%E5%89%8D%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"前端学习","slug":"前端学习","permalink":"https://zhoushaoting.com/tags/%E5%89%8D%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"前端马赛克学习","slug":"前端学习/马赛克学习","date":"2018-05-17T13:45:31.000Z","updated":"2023-12-20T05:14:54.640Z","comments":true,"path":"2018/05/17/前端学习/马赛克学习/","link":"","permalink":"https://zhoushaoting.com/2018/05/17/%E5%89%8D%E7%AB%AF%E5%AD%A6%E4%B9%A0/%E9%A9%AC%E8%B5%9B%E5%85%8B%E5%AD%A6%E4%B9%A0/","excerpt":"上周去菜市场买菜,看到路边一个二维码,于是想起阿里巴巴,接着想起之前上班都会路过阿里巴巴,接着想起马云,然后想到马赛克!!!于是我回家了,想学习下前端中的马赛克.","text":"上周去菜市场买菜,看到路边一个二维码,于是想起阿里巴巴,接着想起之前上班都会路过阿里巴巴,接着想起马云,然后想到马赛克!!!于是我回家了,想学习下前端中的马赛克. 完成的功能 选择当地一张图片 点击绘制马赛克 拖动绘制马赛克 为了方便,相关js代码直接写在了index.html中,大致分为加载图片,添加监听,方法触发,绘制小方块(马赛克),计算颜色,数组升维这几个方法,UI部分通过canvas 标签和input 标签绘制.下面是完整的index.html代码 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109<!doctype html><html><head> <title></title> <meta charset="UTF-8"> <meta name="Keywords" content="马赛克 canvas"> <meta name="Description" content="学习前端马赛克"></head><style> canvas { border: 1px black solid; }</style><body> <div id="mask"> <canvas width="600" height="600"> </canvas> <input type="file" class='input'> </div> <script> var canvas = document.querySelector('canvas'); var input = document.querySelector('#mask .input'); var ctx = canvas.getContext('2d'); var mousedown = false; var t = 15; input.onchange = function () { loadimg(); } /** *加载图片 */ function loadimg() { var img = new Image(); img.src = window.URL.createObjectURL(input.files[0]); img.onload = function () { ctx.drawImage(img, 0, 0, 600, 600); } register(); } /** *添加监听 */ function register() { canvas.addEventListener('mousedown', action) canvas.addEventListener('mousemove', action) canvas.addEventListener('mouseup', action) } /** *方法触发 */ function action(e) { var dx = parseInt(((e.offsetX - t / 2) / t) * t) var dy = parseInt(((e.offsetY - t / 2) / t) * t) if (e.type == "mousedown") { mousedown = true; computeColor(dx, dy); } if (mousedown && e.type == "mousemove") { computeColor(dx, dy); } if (e.type == "mouseup") { mousedown = false; } } /* * 画小方块 */ function rect(x, y, c) { ctx.beginPath(); ctx.fillStyle = c; var dx = parseInt(x / t)*t,dy = parseInt(y / t)*t ctx.rect(dx, dy, t, t); ctx.fill(); } /** 计算颜色 */ function computeColor(x, y) { var arrList = restore(ctx.getImageData(x,y,t,t).data,4); //一维数组 var vr = 0, vg = 0, vb = 0,al = arrList.length; for(var i = 0; i < al; i++){ vr += arrList[i][0]; vg += arrList[i][1]; vb += arrList[i][2]; var color = 'rgb(' + Math.floor(vr/al) + ',' + Math.floor(vg/al) + ',' + Math.floor(vb/al) + ')'; rect(x,y,color); } } /** *数组升维 [1,2,3,4] => [[1,2],[3,4]] */ function restore(arr, step) { var list = []; var index = 0; var n = Math.floor(arr.length/step); for(var i = 0; i < n ;i++){ list.push(arr.slice(index,index+step)); index += step; } return list; } </script></body></html> 效果图: 源码","categories":[{"name":"前端学习","slug":"前端学习","permalink":"https://zhoushaoting.com/categories/%E5%89%8D%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"前端学习","slug":"前端学习","permalink":"https://zhoushaoting.com/tags/%E5%89%8D%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"后端学习","slug":"后端学习/后端学习","date":"2018-05-16T05:31:45.000Z","updated":"2018-05-16T05:31:45.000Z","comments":true,"path":"2018/05/16/后端学习/后端学习/","link":"","permalink":"https://zhoushaoting.com/2018/05/16/%E5%90%8E%E7%AB%AF%E5%AD%A6%E4%B9%A0/%E5%90%8E%E7%AB%AF%E5%AD%A6%E4%B9%A0/","excerpt":"加紧施工中…","text":"加紧施工中…","categories":[{"name":"后端学习","slug":"后端学习","permalink":"https://zhoushaoting.com/categories/%E5%90%8E%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"后端学习","slug":"后端学习","permalink":"https://zhoushaoting.com/tags/%E5%90%8E%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]},{"title":"react-navigation的基本使用","slug":"移动端学习/RN~react-navigation的基本使用","date":"2018-05-15T18:41:51.000Z","updated":"2023-12-20T06:12:46.420Z","comments":true,"path":"2018/05/16/移动端学习/RN~react-navigation的基本使用/","link":"","permalink":"https://zhoushaoting.com/2018/05/16/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/RN~react-navigation%E7%9A%84%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8/","excerpt":"本文基于npm 5.6.6 react-native-li 2.0.1 react-native 0.54.0 react-navigation ^1.4.0所写.","text":"本文基于npm 5.6.6 react-native-li 2.0.1 react-native 0.54.0 react-navigation ^1.4.0所写. 实现的基本功能 tabBar 导航栏 在static中使用this 回调 跳多级界面 防止连续点击多次跳转界面并且上面这些方法均未修改三方库的源码,原先需要修改源码实现:原版对于react-navigation的使用看官方文档即可.防止连续点击多次跳转界面 通过state判断:先定义个state waiting 为 false ,再定义点击事件的 disabled={this.state.waiting} ,在响应方法里面先行修改statewaiting 为 true,然后跳转界面,最后在下方定一个计时器,2秒后再次修改 state 为 false 即可.完整代码: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546import React, { Component } from 'react';import { Platform, StyleSheet, Text, View, Image, TouchableOpacity} from 'react-native';type Props = {};export default class OneDetails extends Component<Props> { // 构造 constructor(props) { super(props); // 初始状态 this.state = { waiting:false//防止多次重复点击 }; } render() { return ( <View style={styles.container}> <TouchableOpacity onPress={()=>this.onclickBtn()} disabled={this.state.waiting}> <Text>点击跳转</Text> </TouchableOpacity> </View> ); } onclickBtn =() =>{ this.setState({waiting: true}); this.props.navigation.navigate('OneDetailsFlat') setTimeout(()=> { this.setState({waiting: false}) }, 2000);//2秒后重置state中的waiting状态 }} ### 在static中使用this 先在`componentDidMount `里 1234567//设置在static中使用this componentDidMount(){ this.props.navigation.setParams({ navigatePress:this.navigatePress }) } 然后就可以: 123456789static navigationOptions = ({ navigation, screenProps }) => ({ title: 'ListView', headerStyle:{backgroundColor:'red'}, headerRight:( <Text onPress={()=>navigation.state.params.navigatePress()}> 点击 </Text> ) }); 使用: 1234//导航条按钮点击 navigatePress = () => { alert(this.state.text) } 回调 在A push 到 B的时候先行定义个方法,然后在B pop 回A调用即可.A -> B 123456789101112//导航条按钮点击navigatePress = () => { this.props.navigation.navigate('OneDetailsFlatDetails',{ // 跳转的时候携带一个参数去下个页面 callback: (data)=>{ alert(data) } });} B -> A 1234popTwo =() =>{ this.props.navigation.state.params.callback('回调参数'); this.props.navigation.goBack() } 回到首页 this.props.navigation.popToTop()回到相应页 this.props.navigation.pop(2)效果图: 源码","categories":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/categories/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"移动端学习","slug":"移动端学习","permalink":"https://zhoushaoting.com/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AD%A6%E4%B9%A0/"}]}]}