美文网首页
Flutter 与 iOS 原生项目混编配置(二)

Flutter 与 iOS 原生项目混编配置(二)

作者: Coder_Answer | 来源:发表于2020-09-21 18:45 被阅读0次

    用户的关注列表

    主要涉及基类封装、Native与Flutter间相互通信、网络数据请求、列表展示、上下拉刷新,以及MVVM设计模式。

    • Native 实现

      @objc private func pushToFlutter() {
            let flutterVC = CJMFlutterViewController.share()
            flutterVC.setInitialRoute("Follow")
            let channel = FlutterMethodChannel.init(name: "flutter_ios/follow", binaryMessenger: flutterVC.binaryMessenger)
            channel.setMethodCallHandler { [weak flutterVC] (methodCall, result) in
                switch methodCall.method {
                case "backAction":
                    /// 退出FlutterViewController
                    flutterVC?.navigationController?.popViewController(animated: true)
                case "userIconAction":
                    /// 跳转到用户中心
                    if let params = methodCall.arguments as? [String : Int] {
                        DCURLRouter.cjmPushVC(router: RouterURL.userProfile.rawValue, params: params)
                    }
                default: break
                }
            }
            UIViewController.current()?.navigationController?.pushViewController(flutterVC, animated: true)
        }
      

      CJMFlutterViewController

      import UIKit
      
      class CJMFlutterViewController: FlutterViewController {
        class func share() -> CJMFlutterViewController {
            let vc = CJMFlutterViewController.init()
            vc.splashScreenView = {
                let view = UIView.init(frame: UIScreen.main.bounds)
                view.backgroundColor = kBackgroundColor
                return view
            }()
            return vc
        }
        
        public var eventChannelName : String? {
            didSet{
                guard let name = eventChannelName else { return }
                let eventChannel = FlutterEventChannel.init(name: name, binaryMessenger: self.binaryMessenger)
                eventChannel.setStreamHandler(self)
            }
        }
        
        override func setInitialRoute(_ route: String) {
            super.setInitialRoute(route)
            /// 配置公共参数
            let eventChannel = FlutterEventChannel.init(name: "ios_flutter/commonParams", binaryMessenger: self.binaryMessenger)
            eventChannel.setStreamHandler(self)
        }
        
        override func viewDidLoad() {
            super.viewDidLoad()
        }
        
        override func didMove(toParent parent: UIViewController?) {
            super.didMove(toParent: parent)
            if parent == nil {
                DispatchQueue.main.async {
                    self.engine?.viewController = nil
                    self.engine?.destroyContext()
                }
            }
        }
        deinit {
            log.debug("\(#file)  ===>  \(#function)")
        }
      }
      extension CJMFlutterViewController : FlutterStreamHandler {
        /// 这个onListen是Flutter端开始监听这个channel时的回调,第二个参数 EventSink是用来传数据的载体。
        func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
            if let arg = arguments as? String,
                arg == "commonParams" {
                /// 以回调的形式,给flutter传递网络请求的公共参数
                let headerParams = CJMApiManager.empty.headers
                events(headerParams ?? [:])
            }
            return nil
        }
        
        /// flutter不再接收
        func onCancel(withArguments arguments: Any?) -> FlutterError? {
            return nil
        }
      }
      
    • Flutter 实现
      main.dart

      import 'package:flutter/material.dart';
      import 'dart:ui';
      import 'package:flutter/rendering.dart';
      import 'package:flutter_module/class/follow/follow.dart';
      
      void main() {
        debugPaintSizeEnabled = false;
        runApp(_widgetForRoutes(window.defaultRouteName));
      }
      
      Widget _widgetForRoutes(String route) {
        switch (route) {
         case 'Follow':  return Follow();
          default:
            return Center(child: Text('Unknow route: $route', textDirection: TextDirection.ltr,));
        }
      }
      

      pubspec.yaml

      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.3
       fluttertoast: ^7.0.2    #提示框
       dio: ^3.0.10            #网络请求
       rxdart: ^0.24.0         #响应式编程
       pull_to_refresh: ^1.5.7 #上下拉刷新
      

      Follow.dart

      import 'package:flutter/material.dart';
      import 'package:flutter/services.dart';
      import 'package:flutter_module/common/base/base_container.dart';
      import 'follow_listView.dart';
      
      class Follow extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
          // TODO: implement build
          return MaterialApp(
            home: FollowPage(),
            color: Colors.blue,
          );
        }
      }
      
      // 继承基类 BaseContainer
      class FollowPage extends BaseContainer {
        @override
        BaseContainerState<BaseContainer> getState() {
          // TODO: implement getState
          return FollowPageState();
        }
      }
      
      // 继承基类 BaseContainerState
      class FollowPageState extends BaseContainerState<FollowPage> {
      
        // 创建一个channel(类似flutter给iOS发通知)
        static const methodChannel = const MethodChannel('flutter_ios/follow');
        // 点击flutter页面的用户头像给Navite发送消息的回调(带参数用户id)
        Function(int userId) onUserIconButtonHandle;
      
        @override
        void dispose() {
          super.dispose();
          /// 销毁组件
        }
      
        @override
        void initState() {
          super.initState();
          /// 初始化组件
        }
      
        @override
        backItemAction() async {
          // 返回上一级控制器(原生控制器)
          await methodChannel.invokeMethod('backAction');
        }
      
        @override
        Widget setContentView(BuildContext context) {
          final size = MediaQuery.of(context).size;
          return Container(
              height: size.height - 80,
              alignment: Alignment.center,
              child: FollowListView(
                  onUserIconButtonHandle: (userId){
                    _userIconAction(userId);
                  })
          );
        }
        /// 用户点击事件监听
        _userIconAction(int userId) async {
          /// 给原生控制器发送通知,跳转到用户中心控制器
          await methodChannel.invokeMethod('userIconAction', {'userId':userId});
        }
      }
      

      FollowListView.dart

      import 'package:flutter/material.dart';
      import 'package:flutter_module/common/https/https.dart';
      import 'follow_list_item.dart';
      import 'follow_viewModel.dart';
      import 'follow_items_entity.dart';
      import 'package:pull_to_refresh/pull_to_refresh.dart';
      
      class FollowListView extends StatefulWidget {
        /// 用户头像点击事件的回调
        final Function(int userId) onUserIconButtonHandle;
        /// 初始化函数
        const FollowListView({
          Key key,
          @required this.onUserIconButtonHandle
        }) : super(key:key);
      
        @override
        State<StatefulWidget> createState() {
          // TODO: implement createState
          return FollowListViewState();
        }
      }
      
      class FollowListViewState extends State<FollowListView> {
        /// ListView的数据源
        List<FollowItemsDataPostListList> list = List();
        /// 刷新组件
        RefreshController _refreshController = RefreshController(initialRefresh: false);
        /// 处理数据的viewModel
        FollowViewModel _viewModel = FollowViewModel();
      
        @override
        void initState() {
          // TODO: implement initState
          super.initState();
          /// 当Hppts类中接收到从Native传递过来的公共参数时,通知此处开始做网络请求
          Https().initRequest = (){
            _onRefreshHeader();
          };
        }
      
        @override
        Widget build(BuildContext context) {
          // TODO: implement build
          return SmartRefresher(
            enablePullDown: true,
            enablePullUp: true,
            controller: _refreshController,
            onRefresh: _onRefreshHeader,
            onLoading: _onRefreshFooter,
            header: WaterDropHeader(
              completeDuration: Duration(milliseconds: 300),
              waterDropColor: Colors.red,
              complete: Center(child: Text('加载完成', style: TextStyle(color: Colors.white),),),
            ),
            footer: CustomFooter(
              builder: (BuildContext context, LoadStatus status){
                String text;
                Widget body;
                if(status == LoadStatus.idle) {
                  text = "上拉加载更多";
                }else if(status == LoadStatus.loading) {
                  text = "加载中...";
                }else if(status == LoadStatus.failed) {
                  text = "数据加载失败!";
                }else if(status == LoadStatus.canLoading) {
                  text = "松手立马加载";
                }else{
                  text = "到底了";
                }
                body = Text(text, style: TextStyle(color: Colors.white));
                return Container(
                  height: 55.0,
                  child: Center(child: body,),
                );
              },
            ),
            child: ListView.separated(
              padding: EdgeInsets.all(0),
              itemCount: this.list.length ?? 0,
              itemBuilder: (context, idx) {
                return FollowListItemView(
                  itemModel: this.list[idx],
                  onFollowButtonHandle: (){
                    var itemModel = this.list[idx];
                    this._viewModel.followUser(
                        '${itemModel.id}',
                        (itemModel.attentionUser == '1' ? false : true), (isSuccess){
                      if(isSuccess){
                        setState(() {
                          this.list[idx].attentionUser = (itemModel.attentionUser == '1' ? '2' : '1');
                        });
                      }
                    });
                  },
                  onUserIconButtonHandle: this.widget.onUserIconButtonHandle,
                );
              },
              separatorBuilder: (context, idx) {
                return Divider( height: 0 );
              },
              physics: const AlwaysScrollableScrollPhysics(),
            ),
          );
       }
      
       @override
        void dispose() {
          super.dispose();
          _refreshController.dispose();
        }
        /// 下拉刷新数据请求
        _onRefreshHeader() async {
          await this._viewModel.load((data) {
            if (_refreshController.isRefresh) { _refreshController.refreshCompleted(); }
            if (_refreshController.isLoading) { _refreshController.loadComplete(); }
            if(data != null) {
              this.list.clear();
              setState(() {
                this.list.addAll(data);
              });
            }
          });
        }
        /// 上拉刷新数据请求
        _onRefreshFooter() async {
          await this._viewModel.loadMore((data) {
            if (_refreshController.isLoading) { _refreshController.loadComplete(); }
            if(data != null) {
              if (data.length > 0) {
                _refreshController.loadComplete();
              } else {
                _refreshController.loadNoData();
              }
             setState(() {
                this.list.addAll(data);
              });
            }
          });
         }
      }
      

      FollowListItemView

      import 'package:flutter/material.dart';
      import 'package:flutter_module/common/base/base_fluttertoast.dart';
      import 'follow_items_entity.dart';
      
      class FollowListItemView extends StatefulWidget {
        /// 数据模型
        final FollowItemsDataPostListList itemModel;
        /// 关注按钮事件回调
        final Function onFollowButtonHandle;
        /// 用户头像事件回调
        final Function(int userId) onUserIconButtonHandle;
      
        const FollowListItemView({
          Key key,
          this.itemModel,
         this.onFollowButtonHandle,
          this.onUserIconButtonHandle
        }) : super(key: key);
      
        @override
        State<StatefulWidget> createState() {
          return FollowListItemViewState();
        }
      }
      
      class FollowListItemViewState extends State<FollowListItemView> {
        @override
         Widget build(BuildContext context) {
           // TODO: implement build
            return Container(
              height: 60,
              padding: EdgeInsets.all(0),
              alignment: Alignment.center,
              child: Row(
                children: <Widget>[
                  Padding(padding: EdgeInsets.only(left: 35),),
      
                  GestureDetector(
                    onTap: (){
                      this.widget.onUserIconButtonHandle(this.widget.itemModel.id);
                    },
                    child: ClipRRect(
                     borderRadius: BorderRadius.circular(20.0),
                      child: Image.network(
                        this.widget.itemModel.userAvatar,
                        width: 40,
                        height: 40,
                        fit: BoxFit.fill,
                      ),
                  ),
                ),
      
                  Padding(padding: EdgeInsets.only(left: 12),),
      
                  Expanded(
                    child: Text(
                      "${this.widget.itemModel.nick}",
                      style: TextStyle(
                        fontSize: 12,
                        color: Colors.white,
                        decoration: TextDecoration.none,
                      ),
                      maxLines: 1,
                      overflow: TextOverflow.ellipsis,
                    ),
                 ),
      
                  Padding(padding: EdgeInsets.only(left: 10),),
      
                  Container(
                    width: 80,
                   height: 24,
                    child: IconButton(
                        padding: EdgeInsets.all(0),
                        icon: Image(
                          image: AssetImage(
                              this.widget.itemModel.attentionUser == "1"
                                  ? 'resource/follow_has_attention@3x.png'
                                  : 'resource/follow_attention@3x.png'
                          ),
                          fit: BoxFit.cover,
                        ),
                        onPressed: this.widget.onFollowButtonHandle
                    ),
                  ),
      
                  Padding(padding: EdgeInsets.only(left: 35),)
               ],
              )
         );
        }
      }
      

      FollowViewModel

      import 'follow_items_entity.dart';
      import 'package:flutter_module/common/base/base_fluttertoast.dart';
      import 'package:flutter_module/common/https/https.dart';
      
      class FollowViewModel {
        // 是否下拉刷新
        bool isDown = true;
        // 请求页数
        int pageNo = 1;
        // 每页条数
        int pageSize = 1;
      
        /// 下拉加载
        load(Function(List<FollowItemsDataPostListList>) handle) async {
          this.isDown = true;
          this.pageNo = 1;
          await _request(handle);
        }
      
        /// 加载更多
        loadMore(Function(List<FollowItemsDataPostListList>) handle) async {
          this.isDown = false;
          this.pageNo += 1;
          await _request(handle);
        }
      
        /// 请求列表数据
        _request(Function(List<FollowItemsDataPostListList>) handle) async {
          String urlStr = 'http://dev-api.chaojimei.cn/user/attentionList';
          Map<String, dynamic> params = {"pageNo" : this.pageNo, "pageSize" : this.pageSize};
          await Https().post(urlStr, params, (response) {
            if(response != null) {
              var entity = FollowItemsEntity().fromJson(response);
              handle(entity.data.postList.lists);
            }else{
              BaseFluttertoast.show(response['msg']);
              handle(null);
            }
          }, (obj) {
            if(obj != null) { BaseFluttertoast.show('$obj'); }
            handle(null);
          });
        }
      
        /// 关注、取消关注
        followUser(String userId, bool isFollow, Function(bool) handle) async{
          String urlStr = "http://dev-api.chaojimei.cn/user/attentionUser";
          Map<String,dynamic> params = {'userId' : userId, 'operationType' : (isFollow ? "1" : "2")};
          await Https().post(urlStr, params, (response) {
            if(response != null) {
              BaseFluttertoast.show(isFollow ? '关注成功' : "取消关注成功");
              handle(true);
            }else{
              BaseFluttertoast.show(isFollow ? "关注失败" : "取消关注失败");
              handle(false);
            }
          }, (obj) {
            if(obj != null) { BaseFluttertoast.show('$obj'); }
            handle(false);
          });
        }
      }
      

      网络请求类Https(单例)

      import 'package:dio/dio.dart';
      import 'dart:io';
      import 'dart:convert';
      import 'package:flutter/services.dart';
      
      /// 成功回调
      typedef OnSuccess = void Function(Map response);
      /// 失败回调
      typedef OnFailure = void Function(Object obj);
      
      class Https {
        // 注册一个通知 (用于接收iOS的通知)
        static const eventChannel = const EventChannel('ios_flutter/commonParams');
      
        Map<String,dynamic> headers = {};
      
        String contentType;
      
        // 单列
        factory Https() => _share();
      
        static Https get instance => _share();
      
        static Https _instance;
      
        Https._internal() {
          // 监听事件,同事可发送参数
          eventChannel.receiveBroadcastStream('commonParams').listen(_configure);
        }
      
        static Https _share() {
          if(_instance == null){
            _instance = new Https._internal();
          }
          return _instance;
        }
      
        Function initRequest;
      
        /// 配置公共参数
        void _configure(Object obj) {
          var params = Map<String, dynamic>.from(obj);
          print('$params');
          contentType = params['Content-Type'] ?? "application/x-www-form-urlencoded";
          params.forEach((key, value) {
            if(key != 'Content-Type') { headers[key] = value; }
          });
         initRequest();
        }
      
        post(String url, Map<String,dynamic> params, OnSuccess onSuccess, OnFailure onFailure) async{
          try {
            Response response = await new Dio().post(
              url,
              data: FormData.fromMap(params),
              onSendProgress: (int progress, int total) {
                print('$progress   =====   $total');
              },
              options: Options(
                  method: 'Post',
                  sendTimeout: 1000 * 15,
                  contentType: contentType,
                 headers: headers,
              ),
            );
      
            if (response.statusCode == HttpStatus.ok) {
              print('======$url======$params============ $response');
              Map data = json.decode(response.toString());
              if(data["resultCode"] == "0000") {
                onSuccess(data);
              }else{
                onFailure(response);
              }
            }else{
              print('$response   =====  response');
              onFailure(response);
            }
          } catch (exception) {
            print('$exception   =====  exception');
            onFailure(exception);
          }
        }
      }
      

      基类封装
      BaseContainer

      import 'package:flutter/material.dart';
      import 'base_navigation_bar.dart';
      
      abstract class BaseContainer extends StatefulWidget {
        // 标题
        final String title;
        // 导航条高度(默认80)
        final double barHeight;
      
        const BaseContainer({
          Key,key,
          this.title,
          this.barHeight = 80}) : super(key:key);
      
        @override
        State<StatefulWidget> createState() {
          // TODO: implement createState
          return getState();
        }
      
        // 子类实现
        BaseContainerState getState();
      }
      
       abstract class BaseContainerState<T extends BaseContainer> extends State<T> {
        @override
        void initState() {
          // TODO: implement initState
          super.initState();
        }
      
        @override
        Widget build(BuildContext context) {
          // TODO: implement build
          return _buildWidgetDefault();
        }
      
        /// 子类实现,构建各自页面UI控件 相当于setContentView()
        Widget setContentView(BuildContext context);
      
        // 导航条返回按钮事件
        backItemAction();
      
        /// 构建默认布局
        Widget _buildWidgetDefault() {
          final size = MediaQuery.of(context).size;
          return new Scaffold(
            body: Container(
              color: Colors.red,
              constraints: BoxConstraints.expand(),
              child: Column(
                children: <Widget>[
                  NavigationBar(
                    title: this.widget.title,
                    height: this.widget.barHeight,
                    backCallback: backItemAction,
                  ),
      
                  Container(
                    color: Colors.black,
                    height: size.height - 80,
                    alignment: Alignment.center,
                    child: setContentView(context),
                 ),
                ],
              ),
            ),
          );
        }
      }
      

      NavigationBar

      import 'package:flutter/material.dart';
      
      class NavigationBar extends StatefulWidget {
        // 标题
        final String title;
        // 高度
        final double height;
      
        // 导航条返回按钮事件回调
        final VoidCallback backCallback;
        // 初始方法
        const NavigationBar({
          Key,
          key,
          this.title,
          this.height,
          @required this.backCallback}) : super(key:key);
      
        @override
        State<StatefulWidget> createState() {
          // TODO: implement createState
          return NavigationBarState();
        }
      }
      
      class NavigationBarState extends State<NavigationBar> {
        @override
        Widget build(BuildContext context) {
          // TODO: implement build
          return Container(
              color: Colors.black,
              height: this.widget.height,
              child: Stack(
                alignment: Alignment.center,
                children: <Widget>[
                  Positioned(
                    bottom: 10,
                    height: 20,
                    child: Text("关注",style: TextStyle(fontSize: 15, color: Colors.white, decoration: TextDecoration.none),),
                  ),
                  Positioned(
                    bottom: 12,
                    left: 28.5,
                    height: 16,
                    child: IconButton(
                      // 点击事件
                     onPressed: this.widget.backCallback,
                      // 必须的参数 一般是 Icon 或者是 ImageIcono
                      icon: Image(
                        image: AssetImage('resource/common_navigationBar_backIcon_white@2x.png'),
                     ),
                      padding: EdgeInsets.all(0),
                      alignment: Alignment.centerLeft,
                      color: Colors.white,
                    ),
                  )
                ],
              )
          );
        }
      }
      

    相关文章

      网友评论

          本文标题:Flutter 与 iOS 原生项目混编配置(二)

          本文链接:https://www.haomeiwen.com/subject/cwavyktx.html