Call a function inside a TextSpan? - Flutter

Viewed 771

I want to call a function checkProportion() inside a TextSpan. Is it possible?

Here is the code that I want to include the function:

 child: RichText(
            text: TextSpan(
              children: [
                TextSpan(
                  text: widget.result + '  ',
                  style: TextStyle(
                    fontSize: 20.0,
                    fontWeight: FontWeight.bold,
                    color: checkdominantA(widget.predominant, widget.result),
                    height: 2.5,
                    letterSpacing: 0.7,
                  ),
            ),
            //...

Edit:

I want to highlight that the color configuration might change (it is not always the same proportion) as well as the TextSpans change too. The conditions are:

  • If it contains 'A'and 'P' the color is brown;
  • If it contains 'A' and 'pp' the color is black;
  • If it contains 'aa' the color is white;

The following images are going to make it clearer:

Example 1:

enter image description here

Example 2:

enter image description here

2 Answers

How to call a function when user taps on a TextSpan:

Pass a TapGestureRecognizer to recognizer property, for example:

TextSpan(
  text: "hello world",
  style: TextStyle(
    fontSize: 20.0,
    fontWeight: FontWeight.bold,
    color: Colors.blue,
    height: 2.5,
    letterSpacing: 0.7,
  ),
  recognizer: TapGestureRecognizer()
    ..onTap = () {
      print("TextSpan is clicked.");
      // callMyFunction();
    },
); 

Edit:

Based on your newest edits in the question, the best way to approach this task is to lift up the function that determines color based on the genetic code (I'm just calling it getColorFromCode, you can call it checkdominant or whatever). This way, you can calculate "proportion" when needed.

I've made a small demo project for you to see how to call the helper function in two different widgets: one displays the proportion and the other one displays the 16 codes in various colors.

This is how it looks like:

demo

To run the demo, create a new Flutter project and replace its entire main.dart file with the code below:

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

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

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

class MyHomePage extends StatelessWidget {
  final data = [
    'AAPP', 'AApP', 'aAPP', 'aApP',
    'AAPp', 'AApp', 'aAPp', 'aApp',
    'AaPP', 'AapP', 'aaPP', 'aapP',
    'AaPp', 'Aapp', 'aaPp', 'aapp',
  ];

  @override
  Widget build(BuildContext context) {
    final colors = data.map(getColorFromCode);
    final browns = colors.where((color) => color == Colors.brown).length;
    final blacks = colors.where((color) => color == Colors.black).length;
    final whites = colors.where((color) => color == Colors.white).length;

    return Scaffold(
      appBar: AppBar(title: Text('Flutter Demo')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Proportion: $browns:$blacks:$whites'),
            Container(
              width: 200,
              decoration: BoxDecoration(
                border: Border.all(color: Colors.blue, width: 4.0),
                borderRadius: BorderRadius.circular(16),
                color: Colors.lightGreen,
              ),
              padding: EdgeInsets.all(16),
              child: RichText(
                  text: TextSpan(
                children: [
                  for (final code in data)
                    TextSpan(
                      text: '$code ',
                      style: TextStyle(
                        color: getColorFromCode(code),
                        fontFamily: 'courier',
                      ),
                    ),
                ],
              )),
            ),
          ],
        ),
      ),
    );
  }
}

Color getColorFromCode(String code) {
  if (code.contains('A') && code.contains('P')) return Colors.brown;
  if (code.contains('A') && code.contains('pp')) return Colors.black;
  if (code.contains('aa')) return Colors.white;
  return Colors.red; // not covered in the question description
}

Edit again:

If you have more than a few data, you can also refactor the business logic into a standalone widget, so you can reuse the code easily.

For example, we can pass in 3 sets of data and build something like this:

demo showing 3 sets of data

Full source code:

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

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

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

class MyHomePage extends StatelessWidget {
  final data1 = ['AAPP', 'AApP', 'aAPP', 'aApP', 'AAPp', 'AApp', 'aAPp'];
  final data2 = ['AaPP', 'AapP', 'aaPP', 'aapP', 'AaPp', 'Aapp', 'aaPp'];
  final data3 = ['aaPp', 'AapP', 'aAPP', 'aApP', 'AaPp', 'aaPP'];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Flutter Demo')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(MyWidget.getProportion(data1)),
            MyWidget(data: data1),
            Divider(),
            Text(MyWidget.getProportion(data2)),
            MyWidget(data: data2),
            Divider(),
            Text(MyWidget.getProportion(data3)),
            MyWidget(data: data3),
          ],
        ),
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  final List<String> data;

  const MyWidget({Key? key, required this.data}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 200,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.blue, width: 4.0),
        borderRadius: BorderRadius.circular(16),
        color: Colors.lightGreen,
      ),
      padding: EdgeInsets.all(16),
      child: RichText(
          text: TextSpan(
        children: [
          for (final code in data)
            TextSpan(
              text: '$code ',
              style: TextStyle(
                color: getColorFromCode(code),
                fontFamily: 'courier',
              ),
            ),
        ],
      )),
    );
  }

  static String getProportion(List<String> data) {
    final colors = data.map(getColorFromCode);
    final browns = colors.where((color) => color == Colors.brown).length;
    final blacks = colors.where((color) => color == Colors.black).length;
    final whites = colors.where((color) => color == Colors.white).length;
    return 'Proportion: $browns:$blacks:$whites';
  }

  static Color getColorFromCode(String code) {
    if (code.contains('A') && code.contains('P')) return Colors.brown;
    if (code.contains('A') && code.contains('pp')) return Colors.black;
    if (code.contains('aa')) return Colors.white;
    return Colors.red; // not covered in the question description
  }
}

If you're wanting to call a function when your TextSpan Widget is tapped, you can use a WidgetSpan instead and wrap a Text Widget with an InkWell like so:

WidgetSpan(
  child: InkWell(
    onTap: () => checkPropportion(),
    child: Text(
      widget.result + '  ',
      style: TextStyle(
        fontSize: 20.0,
        fontWeight: FontWeight.bold,
        color: checkdominantA(widget.predominant, widget.result),
        height: 2.5,
        letterSpacing: 0.7,
      ),
    ),
  )
),
Related