Just My Life & My Work

網頁已有的功能,想要完整移植到 App,可以怎麼做呢?刻一個原生的,會是個較佳的選項,因為使用者體驗可以很棒!然而就是得花時間去實現。🤪

我們想到可以在 App 載入網頁檔案,省去從頭刻畫面的時間,再來實現 WebView 與 App 互動的部分。

來探討技術層面的議題⋯⋯🤔

原先以為第三方套件 webview_flutter 可以本機載入網頁相關檔案,實際嘗試後發現無法實現,倒是搜尋到另一個第三方套件 webview_flutter_plus,就可以做到本機載入網頁相關檔案。

我們做一個簡單的範例,將相關的程式碼檔案放在 assets 資料夾底下,並在 pubspec.yaml 設定檔案相對位置,以便專案可存取。

main.dart 主程式內容

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

void main() {
  runApp(const WebViewPlusExample());
}

class WebViewPlusExample extends StatelessWidget {
  const WebViewPlusExample({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: WebViewPlusExampleMainPage(),
    );
  }
}

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

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

class _WebViewPlusExampleMainPageState
    extends State<WebViewPlusExampleMainPage> {
  WebViewPlusController? _controller;
  double _height = 300;

  @override
  Widget build(BuildContext context) {
    Future<void> callJS() async {
      print('callJS');
      _controller?.webViewController.runJavascript('callJS("visible");');
    }

    return Scaffold(
      appBar: AppBar(
        title: const Text('webview_flutter_plus Example'),
      ),
      body: ListView(
        children: [
          TextButton(
              onPressed: (() {
                callJS();
              }),
              child: Container(
                alignment: Alignment.center,
                width: 100,
                height: 100,
                child: Text('CallJS'),
                color: Colors.amber,
              )),
          Text("Height of WebviewPlus: $_height",
              style: const TextStyle(fontWeight: FontWeight.bold)),
          SizedBox(
            height: _height,
            child: WebViewPlus(
              javascriptChannels: {
                JavascriptChannel(
                    name: 'Printx',
                    onMessageReceived: (JavascriptMessage message) {
                      print('Printx message.message');
                      print(message.message);
                    }),
                JavascriptChannel(
                    name: 'Toast',
                    onMessageReceived: (JavascriptMessage message) {
                      print('Toast message.message');
                      print(message.message);
                    }),
              },
              initialUrl: 'assets/index.html',
              onWebViewCreated: (controller) {
                _controller = controller;
              },
              onPageFinished: (url) {
                _controller?.getHeight().then((double height) {
                  debugPrint("Height: " + height.toString());
                  debugPrint("url: " + url);
                  setState(() {
                    // _height = height;
                  });
                });
              },
              javascriptMode: JavascriptMode.unrestricted,
            ),
          )
        ],
      ),
    );
  }
}

..

index.html 內容

<!DOCTYPE HTML>
<html>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<head>
    <title>webview_flutter_plus</title>
    <link crossorigin="anonymous" href="test/style.css" rel="stylesheet">

    <script type='text/javascript'>
        Printx.postMessage('Happy Message');
    </script>
    <script type="text/javascript">
        function callJS(message){
            document.getElementById("p1").style.visibility = message;
        }
    </script>
    <script type="text/javascript">
        function callFlutter(){
            Toast.postMessage('js call flutter');
        }
    </script>
</head>
<body>
<div id="testDiv">
    webview_flutter_plus is an extension of webview_flutter to load HTML, CSS and Javascript even from Assets or String.
    <br>
    <br>
    <br>
    Please tap the text to see Javascript execution.
</div>
<button onclick="callFlutter()">callFlutter</button>
<p id="p1" style="visibility:hidden;">
    Flutter 調用了 JS 函式.
    </p>
<script src="test/script.js"></script>
</body>
</html>

..

script.js

var testDiv = document.getElementById("testDiv");
testDiv.addEventListener('click', function f(e) {
    testDiv.setAttribute('style', 'background:black;')
    console.log("style changed");
})

..

style.css 內容:

#testDiv {
    background: rgb(212, 171, 23);
    color: rgb(212, 241, 105);
}

..

pubspec.yaml 內容如此設定:

  assets:
    - assets/index.html
    - assets/style.css
    - assets/script.js

..

花了一番功夫,將較大的線圖套件載入 WebView,已成功能顯示。就當我以為可以在原本專案整合此套件,卻發現居然版本有衝突⋯⋯

  • Running “flutter pub get" in bitstreetx…
  • Because webview_flutter_plus >=0.3.0+2 depends on webview_flutter ^3.0.1 and bitstreetx depends on webview_flutter ^4.2.0, webview_flutter_plus >=0.3.0+2 is forbidden.
  • So, because bitstreetx depends on webview_flutter_plus ^0.3.0+2, version solving failed.
  • pub get failed (1; So, because bitstreetx depends on webview_flutter_plus ^0.3.0+2, version solving failed.)

意思:套件 webview_flutter 上次已升級到 4.2.0,是因為要讓 Android 選擇圖片+上傳圖片(很傻眼,iOS 有支援,Android 必須自行實作),然而套件 webview_flutter_plus 僅能用在 webview_flutter 3.0.1。

這下子要降版,才能使用新套件啦⋯⋯以下省略~😬

來看成果,WebView 和 App 互動已經在主程式中,來看最後的畫面~

1~

黃色按鈕 CallJS 是原生介面

黃色按鈕是一個位於使用者介面中的按鈕元素,它具有原生的外觀和功能。這表示該按鈕是使用原生程式語言(例如 Java、Objective-C)所建立,並與原生操作系統相關聯。原生介面的好處是能夠提供更高效且無縫的使用者體驗,並且能夠直接存取操作系統的功能和資源。

其下方是 WebView

在黃色按鈕下方,有一個稱為 WebView 的元素。WebView 是一個內置於應用程式內的瀏覽器視窗,可以顯示網頁內容。透過 WebView,您可以在應用程式中嵌入網頁,並與網頁內容進行互動。使用者可以在 WebView 中瀏覽網頁、填寫表格、觀看視頻等等。

當點擊黃色按鈕,則會出現 Flutter 調用了 JS 函式(原本隱藏)

當使用者點擊黃色按鈕時,應用程式會執行特定的程式碼,這段程式碼稱為「Flutter 調用了 JS 函式」。跳用(invoke)JS 函式意指 Flutter 應用程式呼叫(invoke)了一個 JavaScript 函式。原本這個函式可能是隱藏的,也就是在介面上看不到,但透過呼叫函式,我們可以執行該函式內的程式邏輯。

這樣的做法通常用於實現 Flutter 與網頁技術之間的互動,例如在 Flutter 應用程式中顯示動態的網頁內容、資料交換、或是調用網頁中的某些功能。透過 Flutter 與 JavaScript 的互動,您可以打造更加豐富和具有互聯網功能的應用程式。

2~

灰色按鈕 callFlutter 是一個位於 WebView 元件中的按鈕。當使用者點擊這個按鈕時,它將會觸發一個特定的 Flutter 函式。

WebView 是一個在行動應用程式開發中常用的元件,它允許開發人員將網頁內容嵌入到應用程式中,並以原生的方式展示。這意味著你可以在應用程式中使用網頁的功能和內容,而不需要離開應用程式,並且能夠與 Flutter 的其他元件進行互動。

當你在應用程式中放置一個 WebView,你可以載入指定的網頁或者呈現本地的 HTML 內容。在這個特定的案例中,當使用者按下灰色按鈕 callFlutter 時,它將觸發一個與 Flutter 整合的函式,該函式可以是事先定義好的 Flutter 代碼或函式,用於處理特定的行為或邏輯。

透過該功能,你可以實現使用者與 WebView 中網頁內容的互動,並將該互動傳遞給 Flutter 部分的應用程式代碼,以便進一步處理或更新應用程式的狀態。

3~

當你在 WebView 中間那一串文字上點擊時,它會改變顏色。這是為了測試 JavaScript (JS) 和 CSS 樣式表是否正常運作,以便應用在你的網頁或應用程式中。

使用 WebView 可以在原生應用程式中嵌入網頁內容,並以原生方式呈現。這種互動效果的測試可以確保所撰寫的程式碼在 WebView 環境中正確運作,並且可以提供更好的使用者體驗。在你的程式開發過程中,測試各種功能和效果是至關重要的,這包括檢查 JavaScript 和 CSS 的正確實作。

參考:

Comments on: "[Flutter] 本機 WebView 載入網頁檔案並與 App 互動" (1)

  1. HappyMan 的大頭貼

    ChatGPT 給的解答居然沒有這篇好用~

    它給的還要寫區分兩平台的程式碼,我測試 Android 無反應⋯⋯

    如下:

    if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers[action]) {
    // iOS 通道
    window.webkit.messageHandlers[action].postMessage(messageData);
    console.log(‘已發送消息到 iOS 原生’, messageData);
    } else if (window.testInterface && typeof window.testInterface[action] === ‘function’) {
    // Android 通道
    window.testInterfaceaction;
    console.log(‘已發送消息到 Android 原生’, messageData);
    } else {
    console.log(‘未偵測到原生通道,模擬返回 false’);
    resolve({
    captchaResult: false,
    bizResult: false
    });
    delete window[callbackName];
    }

隨意留個言吧:)~

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料

標籤雲