Flutter笔记--Flutter页面嵌入Android Fragment中

  • 在上一篇中,我们讲了Flutter页面嵌入Android Activity,那么在这一篇中,我们来实现让Flutter页面嵌入Android Fragment中,并且补充上一篇中未说明的内容(另一种嵌入方式)

  • 在了解了上一篇内容,如何嵌入Fragment其实就非常容易实现了。在上一篇中,我们了解到了一个叫做FlutterView的哥们,它是继承了SurfaceView,显然它就是用来渲染在flutter端的widget页面的,先不理它是如何渲染显示的。首先它是一个SurfaceView,现在我们要实现的是,将Flutter页面嵌入到Fragment中,可能我们还不清楚,那如何将一个SurfaceView添加到Fragment中显示出来,想必还是比较简单的吧

  • 先创建一个MyFlutterFragment

public class MyFlutterFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        return Flutter.createView(getActivity(),getLifecycle(),"fragment_flutter");
    }
}
  • 然后显示出来
class MyFlutterFragmentActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my_flutter_fragment)
        supportFragmentManager.beginTransaction().add(R.id.fl_container,MyFlutterFragment()).commit()
    }
}

  • 为了避免错觉(没有找到就会默认启动home配置的MyHomePage),我们新建一个flutter page fragment_page.dart来测试
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class FragmentPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _FragmentPageState();
  }

}

class _FragmentPageState<FragmentPage> extends State {
  @override
  Widget build(BuildContext context) {
    return Scaffold(appBar: _buildAppBar(), body: _buildBody(),);
  }


  ///构建AppBar
  _buildAppBar() {
    return AppBar(
      backgroundColor: Colors.white,
      elevation: 0,
      leading: IconButton(
          icon: Image.asset(
            'static/imgs/icon_back.png', height: 20,),
          onPressed: () {

          }),
      title: new Text("Flutter in Fragment", style: new TextStyle(
          color: Color(0xff333333), fontWeight: FontWeight.bold, fontSize: 18),
        maxLines: 1,),
      centerTitle: true,);
  }

  _buildBody() {
    return Container(constraints: BoxConstraints(
        minWidth: double.infinity, maxHeight: double.infinity),
      child: Center(child: Text("我是嵌入Fragment中的flutter界面"),),);
  }
}
  • 还是在MainActivity中进行跳转
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val dumpIntent = Intent(this, MainFlutterActivity::class.java)
        btnJumpToFlutter.setOnClickListener { startActivity(dumpIntent) }
        //跳转到flutter fragment
        val dumpIntent2 = Intent(this, MyFlutterFragmentActivity::class.java)
        btnJumpToFlutterFragment.setOnClickListener { startActivity(dumpIntent2) }
    }
}
  • 到在这一步应该都没有很难理解的内容,我们先启动跳转过去看看效果


    跳转到flutter fragment.gif
  • 可以看到,直接跳转成功了,看过上一篇文章的可能会有个疑问,上一篇需要一个FlutterFragmentActivity的辅助,怎么到了Fragment就直接继承之前的Fragment就可以了,然后承载Fragment的MyFlutterFragmentActivity继承 AppCompatActivity也可以?

  • 这个问题其实也不难理解,在上一篇中,我们了解到了FlutterFragmentActivity本质上还是一个FragmentActivity,只不过是实现了Provider, PluginRegistry, ViewFactory

public class FlutterFragmentActivity extends FragmentActivity implements Provider, PluginRegistry, ViewFactory {
    private final FlutterActivityDelegate delegate = new FlutterActivityDelegate(this, this);
    private final FlutterActivityEvents eventDelegate;
    private final Provider viewProvider;
    private final PluginRegistry pluginRegistry;

    //other code
}

//可以自己重写方法创建FlutterView 和FlutterNativeView
public interface ViewFactory {
      FlutterView createFlutterView(Context var1);

      FlutterNativeView createFlutterNativeView();

      boolean retainFlutterNativeView();
  }

//提供获取FlutterView 方法
 public interface Provider {
        FlutterView getFlutterView();
 }

//提供插件注册等功能,用于android native端和flutter端通讯
public interface PluginRegistry {
    PluginRegistry.Registrar registrarFor(String var1);

    boolean hasPlugin(String var1);

    <T> T valuePublishedByPlugin(String var1);

    public interface PluginRegistrantCallback {
        void registerWith(PluginRegistry var1);
    }

    public interface ViewDestroyListener {
        boolean onViewDestroy(FlutterNativeView var1);
    }

    public interface UserLeaveHintListener {
        void onUserLeaveHint();
    }

    public interface NewIntentListener {
        boolean onNewIntent(Intent var1);
    }

    public interface ActivityResultListener {
        boolean onActivityResult(int var1, int var2, Intent var3);
    }

    public interface RequestPermissionsResultListener {
        boolean onRequestPermissionsResult(int var1, String[] var2, int[] var3);
    }

    public interface Registrar {
    //other code
}
  • 而接口就是一种规范,通过接口抽象出来这些公共的行为,或者说封装出变化性,除了FlutterFragmentActivity ,还有FlutterActivity也实现了这三个接口
public class FlutterActivity extends Activity implements Provider, PluginRegistry, ViewFactory {
//other code
}
  • 它们各自有各自的实现,那么在嵌入Activity的时候,需要FlutterFragmentActivity 的辅助,实际上就是需要它提供的注册插件、生命周期委托等功能,用于实现两端之间的通讯和管理flutter的生命周期等,那么没有继承FlutterFragmentActivity是因为在这里仅仅演示了如何嵌入fragment,并没有实现通讯等功能,所以即使没有继承FlutterFragmentActivity也是可以实现的。实际上若是单纯将flutter嵌入Activity中,不继承FlutterFragmentActivity也是可以实现的

  • 回到MainFlutterActivity中修改如下

//注意区别,这里直接继承AppCompatActivity
class MainFlutterActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        FlutterMain.startInitialization(applicationContext)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main_flutter)
//        val mFlutterView: View = Flutter.createView(this, lifecycle, "main_flutter")
        //为了区别是可以成功的(前面说过,如果找不到,会默认走home配置的MyHomePage),把嵌入fragment中的page嵌入activity中
        val mFlutterView: View = Flutter.createView(this, lifecycle, "fragment_flutter")
        val mParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT)
        addContentView(mFlutterView, mParams)
    }
}
Activity直接继承AppCompatActivity.gif
  • 可以看到没有继承FlutterFragmentActivity依旧可以实现嵌入。FlutterView就是一个SurfaceView,添加一个SurfaceView到Activity中或者Fragment相信大家都可以理解的,只不过这个SurfaceView稍稍有些特别,它渲染的内容是Flutter界面,至于是哪一个界面的内容是由传入的initialRoute指定的,那么为什么要继承FlutterFragmentActivity 就可以理解了,而嵌入Fragment中的Flutter如果想要进行通讯等功能的话,承载该Fragment的Activity也是需要继承FlutterFragmentActivity,但是单纯显示出来不继承也是可以实现的
  • 另外说个小贴士,当我们创建出项目的时候,flutter 工具就已经告知我们如何将Flutter页面嵌入Fragment中了。在引入的Flutter module中,有个自动生成的文件FlutterFragment

    自动生成的FlutterFragment.png

  • 里面的内容很简单

package io.flutter.facade;

import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.ViewGroup;

import io.flutter.view.FlutterView;

/**
 * A {@link Fragment} managing a {@link FlutterView}.
 *
 * <p><strong>Warning:</strong> This file is auto-generated by Flutter tooling.
 * DO NOT EDIT.</p>
 */
public class FlutterFragment extends Fragment {
  public static final String ARG_ROUTE = "route";
  private String mRoute = "/";

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (getArguments() != null) {
      mRoute = getArguments().getString(ARG_ROUTE);
    }
  }

  @Override
  public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) {
    super.onInflate(context, attrs, savedInstanceState);
  }

  @Override
  public FlutterView onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    return Flutter.createView(getActivity(), getLifecycle(), mRoute);
  }
}
  • 可以看到它就是这么干的,所以我们也是“模仿”的有理有据

  • 到了这里,就已经实现将Flutter页面嵌入Fragment了,下面继续补充上一篇中遗留下来的部分--用另外一种方式将Flutter页面嵌入Activity

  • 还记得FlutterActivityDelegate吗,FlutterFragmentActivity的生命周期就交由FlutterActivityDelegate管理,再把它拿过来看看

public void onCreate(Bundle savedInstanceState) {
        //other code
    
        //重点看下面的部分
        if(!this.loadIntent(this.activity.getIntent())) { //查看activity 的Intent中的内容是否符合条件,返回true说明loadIntent自己完成了启动处理,不走下面的流程
            if(!this.flutterView.getFlutterNativeView().isApplicationRunning()) { //没有running
 
                //获取flutter资源路径
                String appBundlePath = FlutterMain.findAppBundlePath(this.activity.getApplicationContext());
                if(appBundlePath != null) {
                //配置启动参数
                    FlutterRunArguments arguments = new FlutterRunArguments();
                    arguments.bundlePath = appBundlePath;
                    arguments.entrypoint = "main";
                    //启动 该方法兜兜转转最后会调用native方法private static native void nativeRunBundleAndSnapshotFromLibrary(long var0, String var2, String var3, String var4, String var5, AssetManager var6); ,暂时先不管底层是如何启动的
                    this.flutterView.runFromBundle(arguments);
                }
            }
        }
    }
  • 看看loadIntent方法里面做了什么
   private boolean loadIntent(Intent intent) {
        String action = intent.getAction();
        if("android.intent.action.RUN".equals(action)) { //匹配action
            String route = intent.getStringExtra("route"); //获取key为‘route’的String 
            String appBundlePath = intent.getDataString();  //获取data路径,在这也就是flutter资源路径
            if(appBundlePath == null) {  
                //为空,获取flutter资源路径
                appBundlePath = FlutterMain.findAppBundlePath(this.activity.getApplicationContext());
            }

            //又见到了老朋友
            if(route != null) {
                //如果route不为空的话,指定flutterView渲染的界面是哪一个
                this.flutterView.setInitialRoute(route);
            }
          
            if(!this.flutterView.getFlutterNativeView().isApplicationRunning()) { //没有running
               //配置启动参数
                FlutterRunArguments args = new FlutterRunArguments();
                args.bundlePath = appBundlePath;
                args.entrypoint = "main";
                //同样的,启动,该方法兜兜转转最后会调用native方法private static native void nativeRunBundleAndSnapshotFromLibrary(long var0, String var2, String var3, String var4, String var5, AssetManager var6); ,暂时先不管底层是如何启动的
                this.flutterView.runFromBundle(args);
            }
            return true;
        } else {
            return false;
        }
    }
  • 看到这里,相信可以想到另外一种方法是如何实现的

  • 新建一个MainFlutterActivity2

class MainFlutterActivity2 : FlutterFragmentActivity() {

    companion object {
        fun startCurrentActivity(context: Context, initRoute: String) {
            //配置启动Intent
            val intent = Intent(context, MainFlutterActivity2::class.java)
            intent.action = "android.intent.action.RUN"
            intent.putExtra("route", initRoute)
            context.startActivity(intent)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        FlutterMain.startInitialization(applicationContext)
        super.onCreate(savedInstanceState)
        //需要注意的是在这没有setContentView()
    }
}
  • 在flutter module写一个新的other_page.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class OtherPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _OtherPageState();
  }

}

class _OtherPageState<OtherPage> extends State {
  @override
  Widget build(BuildContext context) {
    return Scaffold(appBar: _buildAppBar(), body: _buildBody(),);
  }


  ///构建AppBar
  _buildAppBar() {
    return AppBar(
      backgroundColor: Colors.white,
      elevation: 0,
      leading: IconButton(
          icon: Image.asset(
            'static/imgs/icon_back.png', height: 20,),
          onPressed: () {

          }),
      title: new Text("Flutter in Activity", style: new TextStyle(
          color: Color(0xff333333), fontWeight: FontWeight.bold, fontSize: 18),
        maxLines: 1,),
      centerTitle: true,);
  }

  _buildBody() {
    return Container(constraints: BoxConstraints(
        minWidth: double.infinity, maxHeight: double.infinity),
      child: Center(child: Text("我是嵌入Activity中的flutter界面(intent方式启动)"),),);
  }
}
  • main.dart中配置
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: MyHomePage(title: 'Flutter Demo Home Page'),
      routes: <String, WidgetBuilder>{
        "main_flutter": (context) => MyHomePage(title: 'Flutter in Android'),
        "fragment_flutter": (context) => FragmentPage(),
        "other_flutter": (context) => OtherPage()
      },
    );
  }
}
  • 依旧在MainActivity启动
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val dumpIntent = Intent(this, MainFlutterActivity::class.java)
        btnJumpToFlutter.setOnClickListener { startActivity(dumpIntent) }

        val dumpIntent2 = Intent(this, MyFlutterFragmentActivity::class.java)
        btnJumpToFlutterFragment.setOnClickListener { startActivity(dumpIntent2) }

        //另外一种方式跳转
        btnJumpToFlutter2.setOnClickListener { MainFlutterActivity2.startCurrentActivity(this, "other_flutter") }
    }
}
  • 看看运行效果


    Intent方式嵌入.gif
  • 可以看到,在intent中配置action为android.intent.action.RUN,再在key为route的extra中指定FlutterView渲染的界面也可以完成嵌入,在这里需要注意的是去掉了setContentView,否则FlutterView渲染的界面会被setContentView的内容覆盖,因为FlutterView渲染的界面是在super.onCreate()之前被添加进来的,所以super.onCreate()之后的setContentView理所当然就会覆盖之前的界面

  • 至此,将flutter页面嵌入activity以及fragment中就已经实现了,在下一篇,我们继续了解一下两端是如何进行通讯的。如果喜欢这篇文章的话,希望可以点个喜欢支持一下,谢谢大家

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容