flutter特别篇:flutter嵌入h5页面

Posted LCQ_CG

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了flutter特别篇:flutter嵌入h5页面相关的知识,希望对你有一定的参考价值。

博客学习

学习的博客地址

  1. 主要是通过webViewScaffold来进行页面的渲染
  2. 属性有:
    1. url,页面跳转的url
    2. appBar,页面的头表
    3. initialChild:还没有加载出来的时候显示的页面
    4. hidden:如果设置为true,将会出现默认的“转圈圈图标”
import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';

/// WebView使用flutter_webview_plugin插件
/// https://pub.dev/packages/flutter_webview_plugin
// 此dart在真机上可以正常运行,但是虚拟机没法联网就不能运行,为什么不能联网这个问题还没有解决
class EmbeddH5 extends StatefulWidget 
  String title = "百度";
  String url = "https://www.baidu.com";

  @override
  _EmbeddH5State createState() =>_EmbeddH5State(title: title, url: url);


class _EmbeddH5State extends State<EmbeddH5> 
  String url;
  String title;
  _EmbeddH5State(required this.url, required this.title);
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      ///隐藏Debug标志
      debugShowCheckedModeBanner: false,
      home: Scaffold(
          body: Container(
            child: WebviewScaffold(
              url: url,
              appBar: AppBar(
                title: Text(title),
              ),
              //当WebView没加载出来前显示
              initialChild: Container(
                color: Colors.white,
                child: Center(
                  child: Text("正在加载中...."),
                ),
              ),
            ),
          )),
    );
  

调用该怎么调用呢?下面这样再main.dart中调用就行

WidgetsFlutterBinding.ensureInitialized();
runApp(EmbeddH5('百度','https://www.baidu.com'));

Flutter中所有的运转都是在各种Binding中调度的,也正是这些绑定器的存在彻底解耦了Widget、 Element 、RenderObject 对 Platform端的依赖

官方学习

官方文档

  1. webView加载出来的页面在最顶部,没有其他的flutter widget能覆盖
    官方代码如下:
import 'dart:async';

import 'package:flutter/material.dart';

import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';

const kandroidUserAgent =
    'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/62.0.3202.94 Mobile Safari/537.36';

String selectedUrl = 'https://flutter.io';

// ignore: prefer_collection_literals
final Set<javascriptChannel> jsChannels = [
  JavascriptChannel(
      name: 'Print',
      onMessageReceived: (JavascriptMessage message) 
        print(message.message);
      ),
].toSet();

class EmbeddH5OfficeDemo extends StatelessWidget 
  final flutterWebViewPlugin = FlutterWebviewPlugin();
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      title: 'Flutter WebView Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      routes: 
        '/': (_) => const MyHomePage(title: 'Flutter WebView Demo'),
        '/widget': (_) 
          return WebviewScaffold(
            url: selectedUrl,
            javascriptChannels: jsChannels,
            mediaPlaybackRequiresUserGesture: false,
            appBar: AppBar(
              title: const Text('Widget WebView'),
            ),
            withZoom: true,
            withLocalStorage: true,
            hidden: true,
            initialChild: Container(
              color: Colors.redAccent,
              child: const Center(
                child: Text('Waiting.....'),
              ),
            ),
            bottomNavigationBar: BottomAppBar(
              child: Row(
                children: <Widget>[
                  IconButton(
                    icon: const Icon(Icons.arrow_back_ios),
                    onPressed: () 
                      flutterWebViewPlugin.goBack();
                    ,
                  ),
                  IconButton(
                    icon: const Icon(Icons.arrow_forward_ios),
                    onPressed: () 
                      flutterWebViewPlugin.goForward();
                    ,
                  ),
                  IconButton(
                    icon: const Icon(Icons.autorenew),
                    onPressed: () 
                      flutterWebViewPlugin.reload();
                    ,
                  ),
                ],
              ),
            ),
          );
        ,
      ,
    );
  


class MyHomePage extends StatefulWidget 
  const MyHomePage(Key? key, required this.title) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();


class _MyHomePageState extends State<MyHomePage> 
  // Instance of WebView plugin
  final flutterWebViewPlugin = FlutterWebviewPlugin();

  // On destroy stream
  late StreamSubscription _onDestroy;

  // On urlChanged stream
  late StreamSubscription<String> _onUrlChanged;

  // On urlChanged stream
  late StreamSubscription<WebViewStateChanged> _onStateChanged;

  late StreamSubscription<WebViewHttpError> _onHttpError;

  late StreamSubscription<double> _onProgressChanged;

  late StreamSubscription<double> _onScrollYChanged;

  late StreamSubscription<double> _onScrollXChanged;

  final _urlCtrl = TextEditingController(text: selectedUrl);

  final _codeCtrl = TextEditingController(text: 'window.navigator.userAgent');

  final _scaffoldKey = GlobalKey<ScaffoldState>();

  final _history = [];

  @override
  void initState() 
    super.initState();

    flutterWebViewPlugin.close();

    _urlCtrl.addListener(() 
      selectedUrl = _urlCtrl.text;
    );

    // Add a listener to on destroy WebView, so you can make came actions.
    _onDestroy = flutterWebViewPlugin.onDestroy.listen((_) 
      if (mounted) 
        // Actions like show a info toast.
        ScaffoldMessenger.of(context)
            .showSnackBar(const SnackBar(content: Text('Webview Destroyed')));
      
    );

    // Add a listener to on url changed
    _onUrlChanged = flutterWebViewPlugin.onUrlChanged.listen((String url) 
      if (mounted) 
        setState(() 
          _history.add('onUrlChanged: $url');
        );
      
    );

    _onProgressChanged =
        flutterWebViewPlugin.onProgressChanged.listen((double progress) 
          if (mounted) 
            setState(() 
              _history.add('onProgressChanged: $progress');
            );
          
        );

    _onScrollYChanged =
        flutterWebViewPlugin.onScrollYChanged.listen((double y) 
          if (mounted) 
            setState(() 
              _history.add('Scroll in Y Direction: $y');
            );
          
        );

    _onScrollXChanged =
        flutterWebViewPlugin.onScrollXChanged.listen((double x) 
          if (mounted) 
            setState(() 
              _history.add('Scroll in X Direction: $x');
            );
          
        );

    _onStateChanged =
        flutterWebViewPlugin.onStateChanged.listen((WebViewStateChanged state) 
          if (mounted) 
            setState(() 
              _history.add('onStateChanged: $state.type $state.url');
            );
          
        );

    _onHttpError =
        flutterWebViewPlugin.onHttpError.listen((WebViewHttpError error) 
          if (mounted) 
            setState(() 
              _history.add('onHttpError: $error.code $error.url');
            );
          
        );
  

  @override
  void dispose() 
    // Every listener should be canceled, the same should be done with this stream.
    _onDestroy.cancel();
    _onUrlChanged.cancel();
    _onStateChanged.cancel();
    _onHttpError.cancel();
    _onProgressChanged.cancel();
    _onScrollXChanged.cancel();
    _onScrollYChanged.cancel();

    flutterWebViewPlugin.dispose();

    super.dispose();
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        title: const Text('Plugin example app'),
      ),
      body: SingleChildScrollView(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              padding: const EdgeInsets.all(24.0),
              child: TextField(controller: _urlCtrl),
            ),
            ElevatedButton(
              onPressed: () 
                flutterWebViewPlugin.launch(
                  selectedUrl,
                  rect: Rect.fromLTWH(
                      0.0, 0.0, MediaQuery.of(context).size.width, 300.0),
                  userAgent: kAndroidUserAgent,
                  invalidUrlRegex:
                  r'^(https).+(twitter)', // prevent redirecting to twitter when user click on its icon in flutter website
                );
              ,
              child: const Text('Open Webview (rect)'),
            ),
            ElevatedButton(
              onPressed: () 
                flutterWebViewPlugin.launch(selectedUrl, hidden: true);
              ,
              child: const Text('Open "hidden" Webview'),
            ),
            ElevatedButton(
              onPressed: () 
                flutterWebViewPlugin.launch(selectedUrl);
              ,
              child: const Text('Open Fullscreen Webview'),
            ),
            ElevatedButton(
              onPressed: () 
                Navigator.of(context).pushNamed('/widget');
              ,
              child: const Text('Open widget webview'),
            ),
            Container(
              padding: const EdgeInsets.all(24.0),
              child: TextField(controller: _codeCtrl),
            ),
            ElevatedButton(
              onPressed: () 
                final future =
                flutterWebViewPlugin.evalJavascript(_codeCtrl.text);
                future.then((String? result) 
                  setState(() 
                    _history.add('eval: $result');
                  );
                );
              ,
              child: const Text('Eval some javascript'),
            ),
            ElevatedButton(
              onPressed: () 
                final future = flutterWebViewPlugin
                    .evalJavascript('alert("Hello World");');
                future.then((String? result) 
                  setState(() 
                    _history.add('eval: $result');
                  );
                );
              ,
              child: const Text('Eval javascript alert()'),
            ),
            ElevatedButton(
              onPressed: () 
                setState(() 
                  _history.clear();
                );
                flutterWebViewPlugin.close();
              ,
              child: const Text('Close'),
            ),
            ElevatedButton(
              onPressed: () 
                flutterWebViewPlugin.getCookies().then((m) 
                  setState(() 
                    _history.add('cookies: $m');
                  );
                );
              ,
              child: const Text('Cookies'),
            ),
            Text(_history.join('\\n'))
          ],
        ),
      ),
    );
  

flutterWebViewPlugin.launch(url)可以实现H5页面的加载,也就是说既可以通过WebViewScaffold中的url来实现页面的加载,也可以通过button响应式这样的加载

launnch里的属性

Future<Null> launch(String url, 
    Map<String, String> headers: null,
    Set<JavascriptChannel> javascriptChannels: null,
    bool withJavascript: true,
    bool clearCache: false,
    bool clearCookies: false,
    bool hidden: false,
    bool enableAppScheme: true,
    Rect rect: null,
    String userAgent: null,
    bool withZoom: false,
    bool displayZoomControls: 

FlutterFlutter 混合开发 ( 关联 Android 工程与 Flutter 工程 | 安卓页面中嵌入 Flutter 页面 | 安卓中启动 Flutter 页面 )

前言

在上一篇博客 【Flutter】Flutter 混合开发 ( 简介 | Flutter 混合开发集成步骤 | 创建 Flutter Module ) 中 , 创建了 Flutter Module 工程 ;

本篇博客开始创建 Android 工程 , 并将两个工程进行关联 ;


Flutter 混合开发集成步骤 :

  • ① 在 Android Studio 中创建 Flutter Module ;
  • ② 为 Native 应用添加 Flutter Module 依赖 ;
  • ③ 在 Native 应用 ( Android / iOS 应用 ) 中 , 调用 Flutter Module 模块 ;
  • ④ 编写 Flutter Module 中的 Dart 代码 ;
  • ⑤ 运行 Flutter 混合应用 ;
  • ⑥ 项目的 热重启 / 重新加载 ;
  • ⑦ 调试 Dart 代码 ;
  • ⑧ 应用发布 ;




一、创建 Android 项目



在 Android Studio 中 , 在菜单栏中 , 选择 " File -> New -> New Project … " 选项 ;

选择创建 " Empty Activity " ;

这里要特别注意路径的设置 ,

Flutter Module 工程的路径是 : D:\\002_Project\\002_Android_Learn\\flutter_hybrid\\flutter_module

Android 工程的路径是 : D:\\002_Project\\002_Android_Learn\\flutter_hybrid\\flutter_native

上面两个工程的路径都在 D:\\002_Project\\002_Android_Learn\\flutter_hybrid 目录下 ;

Android 应用创建完成 :

Android 工程 与 Flutter Module 工程 , 都在同一个目录中 ;





二、关联 Android 工程与 Flutter Module 工程



Android 工程的路径 与 Flutter Module 工程路径 , 否符合如下要求 :

  • Flutter Module 工程的路径是 : D:\\002_Project\\002_Android_Learn\\flutter_hybrid\\flutter_module
  • Android 工程的路径是 : D:\\002_Project\\002_Android_Learn\\flutter_hybrid\\flutter_native
  • 上面两个工程的路径都在 D:\\002_Project\\002_Android_Learn\\flutter_hybrid 目录下 ;

1、配置 Flutter Module工程


在 Android 工程的 settings.gradle 进行如下配置 : 这样配置后 , Flutter 工程就会被编译成一个 Android Library Module , 名称是 flutter ;

rootProject.name = "flutter_native"
include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(
        settingsDir.parentFile,
        'flutter_module/.android/include_flutter.groovy'
))
include ':flutter_module'
project(':flutter_module').projectDir = new File('../flutter_module')


2、配置 build.gradle


在 Android Module 下的 build.gradle 中 :

① 配置最低支持版本 minSdkVersion 16+ , 因为 Flutter 最低支持版本是 16 ;

// Flutter 最低支持版本是 16
minSdkVersion 18

② 添加工程依赖 :

// 在 settings.gradle 中配置的脚本 , 会自动关联到 Flutter 模块
implementation project(':flutter')

完整的配置文件如下 :

plugins {
    id 'com.android.application'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.example.flutter_native"
        // Flutter 最低支持版本是 16
        minSdkVersion 18
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'com.google.android.material:material:1.4.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    // 在 settings.gradle 中配置的脚本 , 会自动关联到 Flutter 模块
    implementation project(':flutter')
}

3、配置 AndroidManifest.xml


将 io.flutter.embedding.android.FlutterActivity 配置到 AndroidManifest.xml 清单文件中 ;

<activity
    android:name="io.flutter.embedding.android.FlutterActivity"
    android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
    android:hardwareAccelerated="true"
    android:windowSoftInputMode="adjustResize" />

完整配置文件如下 :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.flutter_native">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Flutter_native">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="io.flutter.embedding.android.FlutterActivity"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize" />
    </application>

</manifest>




三、Activity 中嵌入 FlutterFragment 页面



在 Activity 中 , 将 Flutter 页面作为 Fragment , 嵌入到 Activity 中 ;

findViewById(R.id.flutter1).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        FragmentTransaction fragmentTransaction =
                getSupportFragmentManager().beginTransaction();
        // 创建 FlutterFragment
        fragmentTransaction.replace(R.id.frame, FlutterFragment.createDefault());
        fragmentTransaction.commit();
    }
});

执行结果 :





四、Activity 中启动 FlutterActivity 页面



将 Flutter 页面当做一个新的 Activity 启动 ;

findViewById(R.id.flutter2).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = FlutterActivity
                .withNewEngine()
                .initialRoute("Android 启动 FlutterActivity")
                .build(MainActivity.this);
        //intent.putExtra("initParams", "Android 中 Activity 启动 Flutter");
        startActivity(intent);
    }
});

执行结果 : 点击 方式二 按钮 , 弹出 FlutterActivity ;





五、完整代码示例



这里只贴出主界面完整代码 , 具体的配置参数 , 查看 GitHub 或者 CSDN 源码快照 ;


1、Android 主界面代码示例


package com.example.flutter_native;

import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentTransaction;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;

import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.android.FlutterFragment;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.flutter1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentTransaction fragmentTransaction =
                        getSupportFragmentManager().beginTransaction();
                // 创建 FlutterFragment
                fragmentTransaction.replace(R.id.frame, FlutterFragment.createDefault());
                fragmentTransaction.commit();
            }
        });

        findViewById(R.id.flutter2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = FlutterActivity
                        .withNewEngine()
                        .initialRoute("Android 启动 FlutterActivity")
                        .build(MainActivity.this);
                //intent.putExtra("initParams", "Android 中 Activity 启动 Flutter");
                startActivity(intent);
            }
        });

    }
}

2、Flutter 完整代码示例


import 'package:flutter/material.dart';
import 'dart:ui';

void main() => runApp(
    /// 该构造方法中传入从 Android 中传递来的参数
    MyApp(initParams: window.defaultRouteName,)
);

class MyApp extends StatelessWidget {
  /// 这是从 Android 中传递来的参数
  final String initParams;
  /// 构造方法 , 获取从 Android 中传递来的参数
  const MyApp({Key? key, required this.initParams}):super(key: key);

  // 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: initParams),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}





四、相关资源



参考资料 :


重要的专题 :


博客源码下载 :

以上是关于flutter特别篇:flutter嵌入h5页面的主要内容,如果未能解决你的问题,请参考以下文章

NA嵌入Flutter页面

FlutterFlutter 混合开发 ( 安卓端向 Flutter 传递数据 | FlutterFragment 数据传递 | FlutterActivity 数据传递 )

FlutterFlutter 混合开发 ( 关联 Android 工程与 Flutter 工程 | 安卓页面中嵌入 Flutter 页面 | 安卓中启动 Flutter 页面 )

Flutter 页面嵌入原生AndroidView.

flutter_webview_plugin 与dart页面跳转使用

Flutter之禅 内存优化篇