Flutter || Scroll back to top button

Viewed 781

In my flutter project I made a floating button that help to auto scroll to the top of the page with one click, and when it reach the top it disappear. It work perfectly. But my problem is that I need to double click in it so it can disappear I want it to automatically disappear if it reach the top. Any help is highly appreciated.

     void scrollToTop(){
    _controller.runJavascript("window.scrollTo({top: 0, behavior: 'smooth'});");
    floatingButtonVisibility();
  }

  void floatingButtonVisibility() async {
    int y = await  _controller.getScrollY();
    if(y>50){
      setState(() {
        buttonshow = true;
      });
    }else {
      setState(() {
        buttonshow = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter WebView'),
      ),
      body:  WebView(
          initialUrl: 'https://flutter.dev',
          javascriptMode: JavascriptMode.unrestricted,
          onWebViewCreated: (WebViewController webViewController) {
            _controller = webViewController;
          },
          gestureRecognizers: Set()
            ..add(
                Factory<VerticalDragGestureRecognizer>(() => VerticalDragGestureRecognizer()
                  ..onDown = (tap) {
                    floatingButtonVisibility();
                  }))

      ),
      floatingActionButton: Visibility(
        visible: buttonshow,
        child: FloatingActionButton(
          onPressed: () {
            scrollToTop();
          },
          backgroundColor: Colors.blue,
          child: const Icon(Icons.navigation),
        ),
      ),
    );
  }
}
2 Answers

Here is my solution.

  1. Register 'window.onscroll' in to send webview's scroll position to outside of Webview widget.
  2. Register receiver to receive event from webview.
  3. If scroll is 0, change 'buttonshow' value and rebuild widget.

enter image description here

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

void main() {
  runApp(MyApp());
}

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

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

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  WebViewController _controller;
  bool buttonshow = false;

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

  void scrollToTop() {
    _controller.evaluateJavascript(
        "window.onscroll = function () {scrollEventChannel.postMessage(window.scrollY)};");
    _controller
        .evaluateJavascript("window.scrollTo({top: 0, behavior: 'smooth'});");
    floatingButtonVisibility();
  }

  void floatingButtonVisibility() async {
    int y = await _controller.getScrollY();
    if (y > 50) {
      setState(() {
        buttonshow = true;
      });
    } else {
      setState(() {
        buttonshow = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter WebView'),
      ),
      body: WebView(
          initialUrl: 'https://flutter.dev',
          javascriptMode: JavascriptMode.unrestricted,
          onWebViewCreated: (WebViewController webViewController) {
            _controller = webViewController;
          },
          javascriptChannels: {
            JavascriptChannel(
                name: 'scrollEventChannel',
                onMessageReceived: (JavascriptMessage message) {
                  print('>>>>: ${message.message}');
                  if (message.message == '0') {
                    setState(() {
                      buttonshow = false;
                    });
                  }
                }),
          },
          gestureRecognizers: Set()
            ..add(Factory<VerticalDragGestureRecognizer>(
                () => VerticalDragGestureRecognizer()
                  ..onDown = (tap) {
                    floatingButtonVisibility();
                  }))),
      floatingActionButton: Visibility(
        visible: buttonshow,
        child: FloatingActionButton(
          onPressed: () {
            scrollToTop();
          },
          backgroundColor: Colors.blue,
          child: const Icon(Icons.navigation),
        ),
      ),
    );
  }
}

I changed button show trigger from gestureRecognizers to postion event.

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

void main() {
  runApp(MyApp());
}

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

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

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  WebViewController _controller;
  bool buttonshow = false;

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

  void scrollToTop() {
    _controller
        .evaluateJavascript("window.scrollTo({top: 0, behavior: 'smooth'});");
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter WebView'),
      ),
      body: WebView(
        initialUrl: 'https://flutter.dev',
        javascriptMode: JavascriptMode.unrestricted,
        onWebViewCreated: (WebViewController webViewController) {
          _controller = webViewController;
        },
        onPageFinished: (String url) async {
          _controller.evaluateJavascript(
              "window.onscroll = function () {scrollEventChannel.postMessage(window.scrollY)};");
        },
        javascriptChannels: {
          JavascriptChannel(
              name: 'scrollEventChannel',
              onMessageReceived: (JavascriptMessage message) {
                print('>>>>: ${message.message}');

                int position = int.parse(message.message);
                if (position == 0) {
                  setState(() {
                    buttonshow = false;
                  });
                } else if (position > 60) {
                  setState(() {
                    buttonshow = true;
                  });
                }
              }),
        },
      ),
      floatingActionButton: Visibility(
        visible: buttonshow,
        child: FloatingActionButton(
          onPressed: () {
            scrollToTop();
          },
          backgroundColor: Colors.blue,
          child: const Icon(Icons.navigation),
        ),
      ),
    );
  }
}
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Home()
    );
  }
}

class Home extends  StatefulWidget {
  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  ScrollController scrollController = ScrollController();
  bool showbtn = false;

  List<String> countries = ["USA", "United Kingdom", "China", "Russia", "Brazil",
    "India", "Pakistan", "Nepal", "Bangladesh", "Sri Lanka",
    "Japan", "South Korea", "Mongolia"];

  @override
  void initState() {
    scrollController.addListener(() { //scroll listener 
      double showoffset = 10.0; //Back to top botton will show on scroll offset 10.0

      if(scrollController.offset > showoffset){
        showbtn = true;
        setState(() {
          //update state 
        });
      }else{
        showbtn = false;
        setState(() {
          //update state 
        });
      }
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            title: Text("Scroll Back to Top Button"),
            backgroundColor: Colors.redAccent
        ),
        floatingActionButton: AnimatedOpacity(
          duration: Duration(milliseconds: 1000),  //show/hide animation
          opacity: showbtn?1.0:0.0, //set obacity to 1 on visible, or hide
          child: FloatingActionButton(
            onPressed: () {
              scrollController.animateTo( //go to top of scroll
                  0,  //scroll offset to go
                  duration: Duration(milliseconds: 500), //duration of scroll
                  curve:Curves.fastOutSlowIn //scroll type
              );
            },
            child: Icon(Icons.arrow_upward),
            backgroundColor: Colors.redAccent,
          ),
        ),
        body: SingleChildScrollView(
            controller: scrollController, //set controller
            child:Container(
                child:Column(
                  children: countries.map((country){
                    return Card(
                        child:ListTile(
                            title: Text(country)
                        )
                    );
                  }).toList(),
                )
            )
        )
    );
  }
Related