Reputation: 515
I'm creating a text field like Text or RichText. And after that, I want to zoom in/out the size of text using pinching. For now, I tried implementing GestureDetector
but it zooms in/out with one finger too. And it is really hard to aim pinching detection. Sometimes is freezing. I added a video that shows when after pinching it freezes and suddenly get bigger. The second video is with the case that image zoom in only when I tap on the text with one finger and move to up left corner. The ideal implementation is to detect pinch and zoom in/out all text area. And disable zooming when I use only one finger. Could you send me some hints, link or code how to solve or where to find the solution?
body: GestureDetector(
onScaleUpdate: (details) {
setState(() {
_textSize =
_initTextSize + (_initTextSize * (details.scale * .35));
});
},
onScaleEnd: (ScaleEndDetails details) {
setState(() {
_initTextSize = _textSize;
});
},
child: Center(
child: SizedBox(
height: _textSize,
child: FittedBox(
child: Text("Test"),
),
))),
Upvotes: 24
Views: 29511
Reputation: 511558
Google software engineers Gary Qian and Chris Yang demonstrated this in their Google Developer Days talk. The video is viewable here:
There code is similar to some of the other answers here, but they notably add a clamp so that it doesn't get too big or small.
Here is a summary of their scalable text bubble:
Because scaling still gets called even for a single finger touch, I added a check for scaleUpdateDetails.scale == 1.0
. That means the UI won't be updated if there was no change in scale.
class Bubble extends StatefulWidget {
@override
_BubbleState createState() => _BubbleState();
}
class _BubbleState extends State<Bubble> {
double _fontSize = 20;
final double _baseFontSize = 20;
double _fontScale = 1;
double _baseFontScale = 1;
@override
Widget build(BuildContext context) {
return GestureDetector(
onScaleStart: (ScaleStartDetails scaleStartDetails) {
_baseFontScale = _fontScale;
},
onScaleUpdate: (ScaleUpdateDetails scaleUpdateDetails) {
// don't update the UI if the scale didn't change
if (scaleUpdateDetails.scale == 1.0) {
return;
}
setState(() {
_fontScale = (_baseFontScale * scaleUpdateDetails.scale).clamp(0.5, 5.0);
_fontSize = _fontScale * _baseFontSize;
});
},
child: ...
// descendant with a Text widget that uses the _fontSize
);
}
}
Notes:
StatefulWidget
so that you can store the current font size and scale at all timesGestureDetector
onScaleStart
onScaleUpdate
setState
to rebuild the widget with the new sizeUpvotes: 9
Reputation: 1584
In Stateful widget with these configuration
double _scaleFactor = 1.0;
double _baseScaleFactor = 1.0;
And use setState
only on update, using the scaleFactor
on textScaleFactor
property of RichText
.
Only one setState to rebuild widget and store the initial factor when scale starts
GestureDetector(
onScaleStart: (details) {
_baseScaleFactor = _scaleFactor;
},
onScaleUpdate: (details) {
setState(() {
_scaleFactor = _baseScaleFactor * details.scale;
});
},
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
color: Colors.red,
child: Center(
child: Text(
'Test',
textScaleFactor: _scaleFactor,
),
),
),
);
The height and width I put just to expand and simulate area of gesture detector.
Upvotes: 21
Reputation: 52366
Full code. Hope it helps.
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final appTitle = 'Demo';
return MaterialApp(
title: appTitle,
home: MyHomePage(title: appTitle),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
MyHomePage({Key key, this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: TransformText());
}
}
class TransformText extends StatefulWidget {
TransformText({Key key}) : super(key: key); // changed
@override
_TransformTextState createState() => _TransformTextState();
}
class _TransformTextState extends State<TransformText> {
double scale = 0.0;
double _scaleFactor = 1.0;
double _baseScaleFactor = 1.0;
double _savedVal = 1.0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GestureDetector Test'), // changed
),
body: Column(
children: <Widget>[
RaisedButton(
child: Text('get'),
onPressed: () {
_savedVal = _scaleFactor;
}),
RaisedButton(
child: Text('set'),
onPressed: () {
setState(() {
_scaleFactor = _savedVal;
});
}),
Expanded(
child: Center(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onScaleStart: (details) {
_baseScaleFactor = _scaleFactor;
},
onScaleUpdate: (details) {
setState(() {
_scaleFactor = _baseScaleFactor * details.scale;
});
},
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Center(
child: Text(
'Test',
textScaleFactor: _scaleFactor,
),
),
),
)),
),
],
),
);
}
}
Upvotes: 1
Reputation: 7318
Solution: Two finger zoom-in and zoom-out.
import 'package:flutter/material.dart';
import 'package:matrix_gesture_detector/matrix_gesture_detector.dart';
class TransformText extends StatefulWidget {
TransformText({Key key}) : super(key: key); // changed
@override
_TransformTextState createState() => _TransformTextState();
}
class _TransformTextState extends State<TransformText> {
double scale = 0.0;
@override
Widget build(BuildContext context) {
final ValueNotifier<Matrix4> notifier = ValueNotifier(Matrix4.identity());
return Scaffold(
appBar: AppBar(
title: Text('Single finger Rotate text'), // changed
),
body: Center(
child: MatrixGestureDetector(
onMatrixUpdate: (m, tm, sm, rm) {
notifier.value = m;
},
child: AnimatedBuilder(
animation: notifier,
builder: (ctx, child) {
return Transform(
transform: notifier.value,
child: Center(
child: Stack(
children: <Widget>[
Container(
color: Colors.red,
padding: EdgeInsets.all(10),
margin: EdgeInsets.only(top: 50),
child: Transform.scale(
scale:
1, // make this dynamic to change the scaling as in the basic demo
origin: Offset(0.0, 0.0),
child: Container(
height: 100,
child: Text(
"Two finger to zoom!!",
style:
TextStyle(fontSize: 26, color: Colors.white),
),
),
),
),
],
),
),
);
},
),
),
),
);
}
}
Upvotes: 2