How to make opaque tutorial screen in flutter?

7,887

Solution 1

As RoyalGriffin mentioned, you can use highlighter_coachmark library, and I am also aware of the error you are getting, the error is there because you are using RangeSlider class which is imported from 2 different packages. Can you try this example in your app and check if it is working?

  1. Add highlighter_coachmark to your pubspec.yaml file

    dependencies:
      flutter:
        sdk: flutter
    
      highlighter_coachmark: ^0.0.3
    
  2. Run flutter packages get


Example:

import 'package:highlighter_coachmark/highlighter_coachmark.dart';

void main() => runApp(MaterialApp(home: HomePage()));

class HomePage extends StatefulWidget {
  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  GlobalKey _fabKey = GlobalObjectKey("fab"); // used by FAB
  GlobalKey _buttonKey = GlobalObjectKey("button"); // used by RaisedButton

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        key: _fabKey, // setting key
        onPressed: null,
        child: Icon(Icons.add),
      ),
      body: Center(
        child: RaisedButton(
          key: _buttonKey, // setting key
          onPressed: showFAB,
          child: Text("RaisedButton"),
        ),
      ),
    );
  }

  // we trigger this method on RaisedButton click
  void showFAB() {
    CoachMark coachMarkFAB = CoachMark();
    RenderBox target = _fabKey.currentContext.findRenderObject();

    // you can change the shape of the mark
    Rect markRect = target.localToGlobal(Offset.zero) & target.size;
    markRect = Rect.fromCircle(center: markRect.center, radius: markRect.longestSide * 0.6);

    coachMarkFAB.show(
      targetContext: _fabKey.currentContext,
      markRect: markRect,
      children: [
        Center(
          child: Text(
            "This is called\nFloatingActionButton",
            style: const TextStyle(
              fontSize: 24.0,
              fontStyle: FontStyle.italic,
              color: Colors.white,
            ),
          ),
        )
      ],
      duration: null, // we don't want to dismiss this mark automatically so we are passing null
      // when this mark is closed, after 1s we show mark on RaisedButton
      onClose: () => Timer(Duration(seconds: 1), () => showButton()),
    );
  }

  // this is triggered once first mark is dismissed
  void showButton() {
    CoachMark coachMarkTile = CoachMark();
    RenderBox target = _buttonKey.currentContext.findRenderObject();

    Rect markRect = target.localToGlobal(Offset.zero) & target.size;
    markRect = markRect.inflate(5.0);

    coachMarkTile.show(
      targetContext: _fabKey.currentContext,
      markRect: markRect,
      markShape: BoxShape.rectangle,
      children: [
        Positioned(
          top: markRect.bottom + 15.0,
          right: 5.0,
          child: Text(
            "And this is a RaisedButton",
            style: const TextStyle(
              fontSize: 24.0,
              fontStyle: FontStyle.italic,
              color: Colors.white,
            ),
          ),
        )
      ],
      duration: Duration(seconds: 5), // this effect will only last for 5s
    );
  }
}

Output:

enter image description here


Solution 2

You can use this library to help you achieve what you need. It allows you to mark views which you want to highlight and how you want to highlight them.

Solution 3

Wrap your current top widget with a Stack widget, having the first child of the Stack your current widget. Below this widget add a Container with black color, wrapped with Opacity like so:

return Stack(
  children: <Widget>[
    Scaffold( //first child of the stack - the current widget you have
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: <Widget>[
              Text("Foo"),
              Text("Bar"),
            ],
          ),
        )),
    Opacity( //seconds child - Opaque layer
      opacity: 0.7,
      child: Container(
        decoration: BoxDecoration(color: Colors.black),
      ),
    )
  ],
);

you then need to create image assets of the descriptions and arrows, in 1x, 2x, 3x resolutions, and place them in your assets folder in the appropriate structure as described here: https://flutter.dev/docs/development/ui/assets-and-images#declaring-resolution-aware-image-assets

you can then use Image.asset(...) widget to load your images (they will be loaded in the correct resolution), and place these widgets on a different container that will also be a child of the stack, and will be placed below the black container in the children list (the Opacity widget on the example above).

Solution 4

It should be mentioned that instead of an opaque approach the Material-oriented feature_discovery package uses animation and integrates into the app object hierarchy itself and therefore requires less custom highlight programming. The turnkey solution also supports multi-step highlights.

Solution 5

Screenshot (Using null-safety):

enter image description here


Since highlighter_coachmark doesn't support null-safety as of this writing, use tutorial_coach_mark which supports null-safety.

Full Code:

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late final List<TargetFocus> targets;

  final GlobalKey _key1 = GlobalKey();
  final GlobalKey _key2 = GlobalKey();
  final GlobalKey _key3 = GlobalKey();

  @override
  void initState() {
    super.initState();
    targets = [
      TargetFocus(
        identify: 'Target 1',
        keyTarget: _key1,
        contents: [
          TargetContent(
            align: ContentAlign.bottom,
            child: _buildColumn(title: 'First Button', subtitle: 'Hey!!! I am the first button.'),
          ),
        ],
      ),
      TargetFocus(
        identify: 'Target 2',
        keyTarget: _key2,
        contents: [
          TargetContent(
            align: ContentAlign.top,
            child: _buildColumn(title: 'Second Button', subtitle: 'I am the second.'),
          ),
        ],
      ),
      TargetFocus(
        identify: 'Target 3',
        keyTarget: _key3,
        contents: [
          TargetContent(
            align: ContentAlign.left,
            child: _buildColumn(title: 'Third Button', subtitle: '... and I am third.'),
          )
        ],
      ),
    ];
  }

  Column _buildColumn({required String title, required String subtitle}) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        Text(
          title,
          style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
        ),
        Padding(
          padding: const EdgeInsets.only(top: 10.0),
          child: Text(subtitle),
        )
      ],
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: Stack(
          children: [
            Align(
              alignment: Alignment.topLeft,
              child: ElevatedButton(
                key: _key1,
                onPressed: () {},
                child: Text('Button 1'),
              ),
            ),
            Align(
              alignment: Alignment.center,
              child: ElevatedButton(
                key: _key2,
                onPressed: () {
                  TutorialCoachMark(
                    context,
                    targets: targets,
                    colorShadow: Colors.cyanAccent,
                  ).show();
                },
                child: Text('Button 2'),
              ),
            ),
            Align(
              alignment: Alignment.bottomRight,
              child: ElevatedButton(
                key: _key3,
                onPressed: () {},
                child: Text('Button 3'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Thanks to @josxha for the suggestion.

Share:
7,887
questionasker
Author by

questionasker

I'm Web, Unity3D &amp; Flutter Developer. I love to share my ideas at my web, please visit my website for any tutorial related to marketing, programming, docker, linux, etc

Updated on December 12, 2022

Comments

  • questionasker
    questionasker over 1 year

    I want to make tutorial screen that show to user at beginning. it's like below :

    enter image description here

    my specific question, how to make some certain elements will show normally and other are opaque ?

    also the arrow and text, how to make them point perfectly based on mobile device screen size (mobile responsiveness) ?