Flutter筆記--Flutter頁面嵌入Android Fragment中

  • 在上一篇中,我們講了Flutter頁面嵌入Android Activity,那么在這一篇中,我們來實現讓Flutter頁面嵌入Android Fragment中,并且補充上一篇中未說明的內容(另一種嵌入方式)

  • 在了解了上一篇內容,如何嵌入Fragment其實就非常容易實現了。在上一篇中,我們了解到了一個叫做FlutterView的哥們,它是繼承了SurfaceView,顯然它就是用來渲染在flutter端的widget頁面的,先不理它是如何渲染顯示的。首先它是一個SurfaceView,現在我們要實現的是,將Flutter頁面嵌入到Fragment中,可能我們還不清楚,那如何將一個SurfaceView添加到Fragment中顯示出來,想必還是比較簡單的吧

  • 先創(chuàng)建一個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
}

//可以自己重寫方法創(chuàng)建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
}
  • 而接口就是一種規(guī)范,通過接口抽象出來這些公共的行為,或者說封裝出變化性,除了FlutterFragmentActivity ,還有FlutterActivity也實現了這三個接口
public class FlutterActivity extends Activity implements Provider, PluginRegistry, ViewFactory {
//other code
}
  • 它們各自有各自的實現,那么在嵌入Activity的時候,需要FlutterFragmentActivity 的輔助,實際上就是需要它提供的注冊插件、生命周期委托等功能,用于實現兩端之間的通訊和管理flutter的生命周期等,那么沒有繼承FlutterFragmentActivity是因為在這里僅僅演示了如何嵌入fragment,并沒有實現通訊等功能,所以即使沒有繼承FlutterFragmentActivity也是可以實現的。實際上若是單純將flutter嵌入Activity中,不繼承FlutterFragmentActivity也是可以實現的

  • 回到MainFlutterActivity中修改如下

//注意區(qū)別,這里直接繼承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")
        //為了區(qū)別是可以成功的(前面說過,如果找不到,會默認走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,但是單純顯示出來不繼承也是可以實現的
  • 另外說個小貼士,當我們創(chuàng)建出項目的時候,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了,下面繼續(xù)補充上一篇中遺留下來的部分--用另外一種方式將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中就已經實現了,在下一篇,我們繼續(xù)了解一下兩端是如何進行通訊的。如果喜歡這篇文章的話,希望可以點個喜歡支持一下,謝謝大家

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容