在上一篇中,我們講了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)
}
}
- 可以看到沒有繼承
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ù)了解一下兩端是如何進行通訊的。如果喜歡這篇文章的話,希望可以點個喜歡支持一下,謝謝大家