// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; void main() => runApp(const TextMagnifierExampleApp(text: 'Hello world!')); class TextMagnifierExampleApp extends StatelessWidget { const TextMagnifierExampleApp({ super.key, this.textDirection = TextDirection.ltr, required this.text, }); final TextDirection textDirection; final String text; @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData(useMaterial3: true), home: Scaffold( body: Padding( padding: const EdgeInsets.symmetric(horizontal: 48.0), child: Center( child: TextField( textDirection: textDirection, // Create a custom magnifier configuration that // this `TextField` will use to build a magnifier with. magnifierConfiguration: TextMagnifierConfiguration( magnifierBuilder: (_, __, ValueNotifier<MagnifierInfo> magnifierInfo) => CustomMagnifier( magnifierInfo: magnifierInfo, ), ), controller: TextEditingController(text: text), ), ), ), ), ); } } class CustomMagnifier extends StatelessWidget { const CustomMagnifier({super.key, required this.magnifierInfo}); static const Size magnifierSize = Size(200, 200); // This magnifier will consume some text data and position itself // based on the info in the magnifier. final ValueNotifier<MagnifierInfo> magnifierInfo; @override Widget build(BuildContext context) { // Use a value listenable builder because we want to rebuild // every time the text selection info changes. // `CustomMagnifier` could also be a `StatefulWidget` and call `setState` // when `magnifierInfo` updates. This would be useful for more complex // positioning cases. return ValueListenableBuilder<MagnifierInfo>( valueListenable: magnifierInfo, builder: (BuildContext context, MagnifierInfo currentMagnifierInfo, _) { // We want to position the magnifier at the global position of the gesture. Offset magnifierPosition = currentMagnifierInfo.globalGesturePosition; // You may use the `MagnifierInfo` however you'd like: // In this case, we make sure the magnifier never goes out of the current line bounds. magnifierPosition = Offset( clampDouble( magnifierPosition.dx, currentMagnifierInfo.currentLineBoundaries.left, currentMagnifierInfo.currentLineBoundaries.right, ), clampDouble( magnifierPosition.dy, currentMagnifierInfo.currentLineBoundaries.top, currentMagnifierInfo.currentLineBoundaries.bottom, ), ); // Finally, align the magnifier to the bottom center. The initial anchor is // the top left, so subtract bottom center alignment. magnifierPosition -= Alignment.bottomCenter.alongSize(magnifierSize); return Positioned( left: magnifierPosition.dx, top: magnifierPosition.dy, child: RawMagnifier( magnificationScale: 2, // The focal point starts at the center of the magnifier. // We probably want to point below the magnifier, so // offset the focal point by half the magnifier height. focalPointOffset: Offset(0, magnifierSize.height / 2), // Decorate it however we'd like! decoration: const MagnifierDecoration( shape: StarBorder( side: BorderSide( color: Colors.green, width: 2, ), ), ), size: magnifierSize, ), ); }); } }