html - flutter:bottomNavigationBar 无法使用 WebView 在 html 页面之间切换

标签 html flutter dart

目标

我使用 bottomNavigationBar 在几个页面之间切换。其中两个页面使用 WebView 来显示本地 html 文件。

问题

我发现当我在这些页面之间切换时,纯flutter widgets的页面可以完美加载。但是 HTML 页面在切换时只会显示最初加载的页面。

例如,如果我有两个指向 Page1 和 Page2 的导航器按钮,那么在运行时,如果我先点击 Page1 按钮,然后点击 Page2 按钮,则 WebView 仍会显示 Page1 而不是 Page2。这是错误的。

代码

这是我使用的HTML页面代码

class LocalLoader {
  Future<String> loadLocal(String filename) async {
    return await rootBundle.loadString('assets/doc/$filename');
  }
}
class HtmlPage extends StatelessWidget {
  final String htmlFile;
  HtmlPage({
    @required this.htmlFile
  });
  @override
  Widget build(BuildContext context) {
    return Container(
      child: FutureBuilder<String>(
        future: LocalLoader().loadLocal(htmlFile),
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            return WebView(
              initialUrl: Uri.dataFromString(snapshot.data,
                  mimeType: 'text/html',
                  // CAUTION
                  // - required for non-ascii chars
                  encoding: Encoding.getByName("UTF-8")
              ).toString(),
              javascriptMode: JavascriptMode.unrestricted,
            );
          } else if (snapshot.hasError) {
            return Text("${snapshot.error}");
          } else {
            print('undefined behaviour');
          }
          return CircularProgressIndicator();
        },
      ),);
  }
}

然后用我的 bottomNavigationBar 处理点击事件:

class MyFlutterView extends StatefulWidget {
  @override
  _MyFlutterViewState createState() => _MyFlutterViewState();
}
class _MyFlutterViewState extends State<MyFlutterView> {
  final Keys keys = Keys();
  int _iSelectedDrawerItem = 3; // self
  int _iSelectedNavItem = 0;
  static List<Widget> _widgetOptions = <Widget>[
    MyFlutterPlaceholder(title: 'Index 0: MyFlutter'),
    MyPage(htmlFile: 'page1.html'),
    MyPage(htmlFile: 'page2.html'),
  ];

  void _onItemTapped(int index) {
    setState(() {
      _iSelectedNavItem = index;
    });
  }
  @override
  Widget build(BuildContext context) {
    final deviceSize = MediaQuery.of(context).size;
    final appBar = AppBar(
      backgroundColor: WidgetColors.menubar,
      title: Text('MyFlutter'),
    );
    return Scaffold(
      appBar: appBar,
      endDrawer: NavDrawer(
        keys: keys,
        iSelectedDrawerItem: _iSelectedDrawerItem,
      ),
      body: Container(
        decoration: BoxDecoration(
            gradient: WidgetColors.canvas,
        ),
        child: _widgetOptions.elementAt(_iSelectedNavItem),
      ),
      bottomNavigationBar: BottomNavigationBar(
          currentIndex : _iSelectedNavItem,
          type: BottomNavigationBarType.fixed,
          backgroundColor: WidgetColors.menubar,
          fixedColor: WidgetColors.myColor,
          // selectedItemColor: WidgetColors.myColor,
          unselectedItemColor: Colors.white,
          selectedIconTheme: IconThemeData(color: WidgetColors.myColor),
          // unselectedIconTheme: IconThemeData(color: Colors.white),
          items: [
            BottomNavigationBarItem(
              label: 'MyFlutter',
              icon: Icon(Icons.build)
            ),
            BottomNavigationBarItem(
              label: 'Page1-HTML',
              icon: Icon(Icons.help,),
            ),
            BottomNavigationBarItem(
              label: 'Page2-HTML',
              icon: Icon(Icons.info_outline_rounded),
            ),
          ],
          onTap: _onItemTapped),
    );
  }
}

我也尝试过 StatefulWidgets 但问题仍然存在。

解决方法

我现在唯一的解决方法是为我拥有的每个页面派生自 HtmlPage 类,如下所示:

class Page1 extends HtmlPage {
  Page1() : super(htmlFile: 'page1.html');
}

class Page2 extends HtmlPage {
  Page2() : super(htmlFile: 'page2.html');
}

在此之后,HTML 页面将按预期切换和加载。

问题

我应该如何解决这个问题?我应该更明确地加载 HTML 文件吗?我认为 setState 会自动为我处理加载,这当然适用于纯 flutter 小部件页面(上面代码中的 MyFlutterPlaceholder 类)。

此外,我确保每次通过导航栏切换页面时都会调用 url 加载。

最佳答案

您可以在下面复制粘贴运行完整代码
我用下面的完整代码模拟这种情况
第 1 步:使用 AutomaticKeepAliveClientMixin

class _WebViewKeepAlive extends State<WebViewKeepAlive>
    with AutomaticKeepAliveClientMixin {
    
    @override
  bool get wantKeepAlive => true;
  
  @override
  Widget build(BuildContext context) {
    super.build(context);

第二步:不要直接把函数放在future属性中,future: LocalLoader().loadLocal(htmlFile),请使用下面的方式

Future<String> _future;

 @override
  void initState() {
    _future = _getUrl(widget.url);
    super.initState();
  }
 
return FutureBuilder(
        future: _future, 

第 3 步:在这种情况下我使用 PageView

工作演示

enter image description here

完整代码

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyPortalPage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyPortalPage extends StatefulWidget {
  MyPortalPage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyPortalPageState extends State<MyPortalPage> {
  int _currentIndex = 0;
  PageController _pageController = PageController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: SizedBox.expand(
        child: PageView(
          controller: _pageController,
          children: <Widget>[
            Page1(),
            WebViewKeepAlive(url: "https://flutter.dev/"),
            WebViewKeepAlive(url: "https://stackoverflow.com/"),
            Center(child: Text("Settings")),
          ],
          onPageChanged: (int index) {
            print("onPageChanged");
            setState(() {
              _currentIndex = index;
            });
          },
        ),
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        selectedItemColor: Colors.amber[800],
        unselectedItemColor: Colors.blue,
        onTap: (index) {
          print("onItemSelected");
          setState(() => _currentIndex = index);
          _pageController.jumpToPage(index);
        },
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.apps),
            label: 'Challenges',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.people),
            label: 'Users',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.message),
            label: 'Messages',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            label: 'Settings',
          ),
        ],
      ),
    );
  }
}

class Page1 extends StatefulWidget {
  const Page1({Key key}) : super(key: key);

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

class _Page1State extends State<Page1> with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return ListView.builder(itemBuilder: (context, index) {
      return ListTile(
        title: Text('Lorem Ipsum'),
        subtitle: Text('$index'),
      );
    });
  }

  @override
  bool get wantKeepAlive => true;
}

class WebViewKeepAlive extends StatefulWidget {
  final String url;
  WebViewKeepAlive({Key key, this.url}) : super(key: key);

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

class _WebViewKeepAlive extends State<WebViewKeepAlive>
    with AutomaticKeepAliveClientMixin {

  Future<String> _future;

  @override
  bool get wantKeepAlive => true;

  Future<String> _getUrl(String url) async {
    await Future.delayed(Duration(seconds: 1), () {});
    return Future.value(url);
  }

  @override
  void initState() {
    _future = _getUrl(widget.url);
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return FutureBuilder(
        future: _future,
        builder: (context, AsyncSnapshot<String> snapshot) {
          switch (snapshot.connectionState) {
            case ConnectionState.none:
              return Text('none');
            case ConnectionState.waiting:
              return Center(child: CircularProgressIndicator());
            case ConnectionState.active:
              return Text('');
            case ConnectionState.done:
              if (snapshot.hasError) {
                return Text(
                  '${snapshot.error}',
                  style: TextStyle(color: Colors.red),
                );
              } else {
                return WebView(
                  initialUrl: snapshot.data,
                  javascriptMode: JavascriptMode.unrestricted,
                );
              }
          }
        });
  }
}

关于html - flutter:bottomNavigationBar 无法使用 WebView 在 html 页面之间切换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63788536/

相关文章:

html - html5 Canvas 中的滚动/滑动背景

android - 断言失败 : line 6075 pos 12: 'child == _child' : is not true

dart - Dart泛型未按照文档进行分类

javascript - 了解 Dart 1.11 的新 appendHTML 清理

javascript - 如何一次更改多个 div 类?

javascript - 多文件上传,onchange-action 未触发

Dart或Flutter如何读取一个float32 little endian编码的二进制文件并转换成List<double>?

flutter - 如何将数据从workmananger的isolate发送到主isolate?

flutter - Flutter:具有CachedNetworkImageProvider的BottomNavigationBarItem

html - 隐藏在其他div下的子菜单