I'd like to achieve a bidirectional Pageview or a fullscreen Gridview.
i.e. The layout would look like this.
| | |
| | |
_____|_______|_______|____
| | |
| | |
.....| i,j | i+1,j |.....
| | |
| | |
_____|_______|_______|_____
| | |
| | |
.....| i,j+1 |i+1,j+1|.....
| | |
| | |
_____|_______|_______|_____
| | |
| | |
| | |
Each i,j represents a fullscreen. Thus, the viewport of the device will only be able to view a particular (i,j)
at any point in time.
And from that position on swiping the screen
left, viewport goes to i+1, j
right, viewport goes to i-1, j
up, viewport goes to i, j+1
down, viewport goes to i, j-1
I'd like to specify the number of rows, columns. (Not just 4)
This is my code so far.
Which renders 4 such screens Video
(SVG)
(I haven't handled the controller logic)
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
// Hide the status bar
SystemChrome.setEnabledSystemUIOverlays([]);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Experiments',
theme: ThemeData.dark(),
home: MyHomePage(title: 'FlutterExps'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
children: [
PageView(
scrollDirection: Axis.vertical,
children: [
ColoredWidget(
color: Colors.cyan,
direction: ">",
),
ColoredWidget(
color: Colors.orange,
direction: ">>",
),
],
),
PageView(
scrollDirection: Axis.vertical,
children: [
ColoredWidget(
color: Colors.green,
direction: "<",
),
ColoredWidget(
color: Colors.yellow,
direction: "<<",
),
],
),
],
),
);
}
}
class ColoredWidget extends StatefulWidget {
final Color color;
final String direction;
const ColoredWidget({
Key key,
#required this.color,
#required this.direction,
}) : super(key: key);
#override
_ColoredWidgetState createState() => _ColoredWidgetState();
}
class _ColoredWidgetState extends State<ColoredWidget>
with AutomaticKeepAliveClientMixin<ColoredWidget> {
#override
Widget build(BuildContext context) {
super.build(context);
return Container(
color: widget.color,
child: Center(
child: Text(
widget.direction,
style: TextStyle(
fontSize: 100,
color: Colors.black,
),
),
));
}
#override
bool get wantKeepAlive => true;
}
But this clearly wouldn't work as I need to control all the connected adjacent PageViews etc.. which I don't understand how to proceed.
I was able to do it.
A minimal code on Github
// import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
SystemChrome.setEnabledSystemUIOverlays([]);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Experiments',
theme: ThemeData.dark(),
home: MyHomePage(title: 'FlutterExps'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<PageController> _controllers;
PageController _rowController;
// No need to use valuenotifiers here
// But I need pass by reference thus using it as a wrapper
ValueNotifier<int> currIdxNotifier = ValueNotifier(0);
ValueNotifier<int> currUpNotifier = ValueNotifier(0);
#override
void initState() {
_controllers = [
PageController(),
PageController(),
PageController(),
];
_rowController = PageController(
// initialPage: 0,
// keepPage: true,
);
currIdxNotifier.value = _rowController.initialPage;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
// pageSnapping: true,
controller: _rowController,
onPageChanged: (pno) {
// MUST set state and trigger a rebuild
// As horizontal viewport changed
setState(() {
currIdxNotifier.value = pno;
// print("${currIdxNotifier.value} horizz");
});
},
children: [
ColPageView(
idx: 0,
currup: currUpNotifier,
notifier: currIdxNotifier,
controllers: _controllers,
children: <Widget>[
ColoredWidget(
color: Colors.orange[50],
text: "0, 0",
),
ColoredWidget(
color: Colors.orange[100],
text: "0, 1",
),
ColoredWidget(
color: Colors.orange[200],
text: "0, 2",
),
ColoredWidget(
color: Colors.orange[300],
text: "0, 3",
),
],
),
ColPageView(
idx: 1,
currup: currUpNotifier,
notifier: currIdxNotifier,
controllers: _controllers,
children: [
ColoredWidget(
color: Colors.green[100],
text: "1, 0",
),
ColoredWidget(
color: Colors.green[200],
text: "1, 1",
),
ColoredWidget(
color: Colors.green[300],
text: "1, 2",
),
ColoredWidget(
color: Colors.green[400],
text: "1, 3",
),
],
),
ColPageView(
idx: 2,
currup: currUpNotifier,
notifier: currIdxNotifier,
controllers: _controllers,
children: [
ColoredWidget(
color: Colors.teal[100],
text: "2, 0",
),
ColoredWidget(
color: Colors.teal[200],
text: "2, 1",
),
ColoredWidget(
color: Colors.teal[300],
text: "2, 2",
),
ColoredWidget(
color: Colors.teal[400],
text: "2, 3",
),
],
),
],
),
);
}
}
/// All the vertical pageviews are here
class ColPageView extends StatefulWidget {
final List<Widget> children;
final List<PageController> controllers;
final ValueNotifier<int> notifier;
final ValueNotifier<int> currup;
final int idx;
const ColPageView({
Key key,
this.children = const <Widget>[],
#required this.idx,
#required this.currup,
#required this.notifier,
#required this.controllers,
}) : super(key: key);
#override
_ColPageViewState createState() => _ColPageViewState();
}
class _ColPageViewState extends State<ColPageView> {
#override
void initState() {
// Just initialized
// Set the start value to be the current vertical value
widget.controllers[widget.idx] = PageController(
initialPage: widget.currup.value ?? 0,
keepPage: true,
);
// print("INIT STATE ${widget.idx}");
// print("INIT STATE ${widget.currup.value}");
super.initState();
}
#override
Widget build(BuildContext context) {
return PageView(
// pageSnapping: true,
controller: widget.controllers[widget.idx],
// controller: widget.controller,
scrollDirection: Axis.vertical,
children: widget.children,
onPageChanged: (widget.notifier.value == widget.idx)
? (pno) {
// if the global horizontal page is the current widget
// var rand = Random();
// var randnn = rand.nextDouble();
widget.controllers.forEach((colpv) {
if (widget.controllers[widget.idx] == colpv) {
// print("same widget so return $randnn");
return;
}
// https://github.com/flutter/flutter/issues/20621#issuecomment-445504085
// Only if the controller has clients
bool isSelected = colpv.hasClients
? colpv.page == pno
: colpv.initialPage == pno;
// Not the same page as everyone
if (!isSelected) {
// print("not selected");
if (colpv.hasClients) {
colpv.animateToPage(
pno,
duration: Duration(milliseconds: 200),
curve: Curves.easeIn,
);
}
}
// set the current updated value of the vertical coord
widget.currup.value = pno;
// print("$pno $isSelected");
});
// print("col-${widget.idx} changed to $pno");
// set horizontal coord to be null
// As we've finished dealing with it
widget.notifier.value = null;
}
: (_) {
// Others which are not the currently moving pageview
// SHOULD not have any listeners
// Spent 5hrs trying to figure this out
// print("nope ${widget.notifier.value} == ${widget.idx}");
},
);
}
}
/// A Widget that simply displays a color and an input text
/// NOTE: This is a StatefulWidget because needs to use keepalive
class ColoredWidget extends StatefulWidget {
/// Color to display the widget in
final Color color;
final String text;
const ColoredWidget({
Key key,
#required this.color,
#required this.text,
}) : super(key: key);
#override
_ColoredWidgetState createState() => _ColoredWidgetState();
}
class _ColoredWidgetState extends State<ColoredWidget>
with AutomaticKeepAliveClientMixin<ColoredWidget> {
#override
Widget build(BuildContext context) {
super.build(context);
return Container(
color: widget.color,
child: Center(
child: Text(
widget.text,
style: TextStyle(
fontSize: 100,
color: Colors.black,
),
),
));
}
// Need to use this or the state of the pageview will be lost
// In this case, if not using keepalive it would still work but
// it will scroll down every time the page gets changed horizontally
// TODO: Destroy if not next to current pageview
#override
bool get wantKeepAlive => true;
}
A similar question (by me) on how to link to pageviews.
Related
I'm making a toggle button to switch between the unit system, I need to do it using Getx for state management.
This code works, but its using setState() instead
This is the (simplified) code:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_toggle_tab/flutter_toggle_tab.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({this.title});
final String? title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var _tabTextIndexSelected = 0;
final _listTextTabToggle = ["km / m", "m / ft"];
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: SingleChildScrollView(
child: Column(
children:[
FlutterToggleTab(
selectedIndex: _tabTextIndexSelected,
selectedBackgroundColors: const [
Colors.blue,
Colors.blueAccent
],
selectedTextStyle: const TextStyle(
color: Colors.white),
unSelectedTextStyle: const TextStyle(),
labels: _listTextTabToggle,
selectedLabelIndex: (index) {
setState(() {
_tabTextIndexSelected = index;
});
},
isScroll: false,
),
Text(
"Index selected : $_tabTextIndexSelected",
),
],
),
),
),
);
}
}
Tried to add obs to the variable _tabTextIndexSelected and obx to everything that is supposed to change, but it doesn't work.
Also, I'm using https://pub.dev/packages/flutter_toggle_tab
this is what I tried (two codes are from different files, I like to try first rather than doing it in my project):
RxInt _tabTextIndexSelected = 0.obs;
final _listTextTabToggle = ["km / m", "m / ft"];
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Column(
children: [
Obx(
()=> FlutterToggleTab(
selectedIndex: _tabTextIndexSelected,
selectedBackgroundColors: const [
Colors.blue,
Colors.blueAccent
],
selectedTextStyle: const TextStyle(
color: Colors.white),
unSelectedTextStyle: const TextStyle(),
labels: _listTextTabToggle,
selectedLabelIndex: (index) {
_tabTextIndexSelected = index.obs;
},
isScroll: false,
),
),
Obx(
()=>Text(
"Index selected : $_tabTextIndexSelected",
),
),
The reactive variable and list of tabs string declaration inside the getx controller.
Below is the working snippet to toggle the tabbar.
import 'package:flutter/material.dart';
import 'package:flutter_toggle_tab/flutter_toggle_tab.dart';
import 'package:get/get.dart';
class TestController extends GetxController {
final listTextTabToggle = ["km / m", "m / ft"];
RxInt tabTextIndexSelected = 0.obs;
toggle(int index) => tabTextIndexSelected.value = index;
}
class TestPage extends StatelessWidget {
const TestPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final ctrl = Get.put(TestController());
return SafeArea(
child: Scaffold(
body: Column(children: [
Obx(
() => FlutterToggleTab(
selectedIndex: ctrl.tabTextIndexSelected.value,
selectedBackgroundColors: const [Colors.blue, Colors.blueAccent],
selectedTextStyle: const TextStyle(color: Colors.white),
unSelectedTextStyle: const TextStyle(),
labels: ctrl.listTextTabToggle,
selectedLabelIndex: (index) => ctrl.toggle(index),
isScroll: false,
),
),
Obx(
() => Text(
"Index selected : ${ctrl.tabTextIndexSelected.value}",
),
)
])),
);
}
}
Output:
First of all, my English is not good, sorry!
Here is my description of the problem:
consists of the following components: (contained in CustomScrollView)
1: SilverToBoxAdapter
2: SilverPersistentHeader
3: SliverFillRemaining
The SilverPersistentHeader only appears when the SilverToBoxAdapter disappears from the screen, and the SilverToBoxAdapter is hidden by default. After the SilverPersistentHeader appears, it will have the effect of pined=true as the page slides.
When SilverPersistentHeader appeared, I found that SilverPersistentHeader would block part of SliverFillRemaining.
here is my code:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
late ScrollController _mainController;
late PageController _pageController;
late bool isScrollable = true;
bool isVisiable = false;
#override
void initState() {
super.initState();
_mainController = ScrollController();
_pageController = PageController();
_pageController.addListener(() {
debugPrint('offset:${_pageController.page}');
if (_pageController.page == 0.0) {
setState(() {
isScrollable = true;
});
} else {
setState(() {
isScrollable = false;
});
}
});
_mainController.addListener(() {
// debugPrint('offset:${_mainController.offset}');
if (_mainController.offset >= 200) {
setState(() {
isVisiable = true;
});
} else {
setState(() {
isVisiable = false;
});
}
});
}
#override
void dispose() {
super.dispose();
_mainController.dispose();
_pageController.dispose();
}
#override
Widget build(BuildContext context) {
return CustomScrollView(
physics: isScrollable
? const AlwaysScrollableScrollPhysics()
: const NeverScrollableScrollPhysics(),
controller: _mainController,
slivers: <Widget>[
const SliverAppBar(
title: Text('SliverDemo'),
pinned: true,
),
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.amber,
),
),
SliverPersistentHeader(
delegate: MyPersistentHeader(
isVisiable: isVisiable,
child: Container(
color: Colors.green,
)),
pinned: true,
),
SliverFillRemaining(
hasScrollBody: true,
child: PageView(
controller: _pageController,
children: [
Container(
color: Colors.primaries[0],
child: Column(
children: [
Container(
color: Colors.white,
height: 100,
)
],
),
),
Container(
color: Colors.primaries[1],
child: Column(
children: [
Container(
color: Colors.black,
height: 100,
)
],
),
),
Container(
color: Colors.primaries[2],
child: Column(
children: [
Container(
color: Colors.white,
height: 100,
)
],
),
),
Container(
color: Colors.primaries[3],
child: Column(
children: [
Container(
color: Colors.black,
height: 100,
)
],
),
),
Container(
color: Colors.primaries[4],
child: Column(
children: [
Container(
color: Colors.white,
height: 100,
)
],
),
)
],
),
),
],
);
}
}
class MyPersistentHeader extends SliverPersistentHeaderDelegate {
final Widget child;
final bool isVisiable;
MyPersistentHeader({required this.child, required this.isVisiable});
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return child;
}
#override
double get maxExtent => isVisiable ? 60 : 0;
#override
double get minExtent => isVisiable ? 60 : 0;
#override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
return false;
}
}
I've searched online for a long time and can't find a solution. I hope you can give me a good suggestion, thanks.
I'm working on mathematics app. Currently I'm working on matrices. I want when user select dimension of matrix then on the next screen text fields with same dimension appears.
And how can I handle TextEditing controllers for each text field?
Here is how I'm taking matrix dimension from user
I made a simple text fields to take input from user
class Matrice extends StatelessWidget {
Matrice({Key? key}) : super(key: key);
final controller = Get.find<MatricesController>();
#override
Widget build(BuildContext context) {
return Container(
// color: Colors.green,
// height: 50.0,
width: double.infinity,
child: Row(
children: [
Text(
'[',
style: AppTextStyle.kBlackBold
.copyWith(fontSize: 80.sp, fontWeight: FontWeight.w300),
),
MathsField(
controller: controller.a11,
),
SizedBox(
width: 15.0,
),
MathsField(
controller: controller.a12,
),
Text(
']',
style: AppTextStyle.kBlackBold
.copyWith(fontSize: 80.sp, fontWeight: FontWeight.w300),
),
Text(
'+',
style: AppTextStyle.kBlackBold
.copyWith(fontSize: 80.sp, fontWeight: FontWeight.w300),
),
Text(
'[',
style: AppTextStyle.kBlackBold
.copyWith(fontSize: 80.sp, fontWeight: FontWeight.w300),
),
MathsField(
controller: controller.a21,
),
SizedBox(
width: 15.0,
),
MathsField(
controller: controller.a22,
),
Text(
']',
style: AppTextStyle.kBlackBold
.copyWith(fontSize: 80.sp, fontWeight: FontWeight.w300),
),
],
),
);
}
}
The output looks like this
And made controller for each textfield
TextEditingController a11 = TextEditingController();
TextEditingController a12 = TextEditingController();
TextEditingController a21 = TextEditingController();
TextEditingController a22 = TextEditingController();
Thanks :)
I made a simple example that you can view on DartPad: Matrix Example
Simply, you need to write the logic for creating text fields with controllers for each cell of the Matrix. This will translate into a List<List<TextEditingController>> based on a Matrix custom definition (i.e. has the number of rows and number of columns).
I see in your example that you're creating the controllers elsewhere (outside the widget tree). I'm not sure if this is the approach you would want to take. It's better to manage the Matrix data because that's what you're interested in. Given the data at hand, you can rebuild the matrix as shown below on MatrixPage. This will also allow you to save the data and use it else where (see: printMatrix method on the page, you can do the same to export the data).
The code for reference:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter Demo Home Page'),
),
body: const MyHomePage(),
),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
MatrixOption(matrix: Matrix(1, 1)),
MatrixOption(matrix: Matrix(2, 2)),
MatrixOption(matrix: Matrix(3, 3)),
],
),
);
}
}
class Matrix {
final int rows;
final int columns;
const Matrix(this.rows, this.columns);
}
class MatrixOption extends StatelessWidget {
final Matrix matrix;
const MatrixOption({
Key? key,
required this.matrix,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) {
return Scaffold(
appBar: AppBar(),
body: MatrixPage(matrix: matrix),
);
},
),
);
},
child: Container(
height: 50,
width: 100,
margin: const EdgeInsets.all(8),
color: Colors.orange,
child: Center(child: Text('${matrix.rows}' 'x' '${matrix.columns} Matrix ')),
),
);
}
}
class MatrixPage extends StatefulWidget {
final Matrix matrix;
const MatrixPage({
Key? key,
required this.matrix,
}) : super(key: key);
#override
State<MatrixPage> createState() => _MatrixPageState();
}
class _MatrixPageState extends State<MatrixPage> {
// List of lists (outer list is the rows, inner list is the columns)
final controllers = <List<TextEditingController>>[];
late final rows = widget.matrix.rows;
late final columns = widget.matrix.columns;
#override
void initState() {
super.initState();
createControllers();
}
void createControllers() {
for (var i = 0; i < rows; i++) {
controllers.add(List.generate(columns, (index) => TextEditingController(text: '0')));
}
}
void printMatrix() {
final strings = <List<String>>[];
for (var controllerRow in controllers) {
final row = controllerRow.map((e) => e.text).toList();
strings.add(row);
}
print(strings);
}
#override
void dispose() {
for (var controllerRow in controllers) {
for (final c in controllerRow) {
c.dispose();
}
}
super.dispose();
}
#override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
controllers.length,
(index1) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
controllers[index1].length,
(index2) => Center(
child: MatrixField(
controller: controllers[index1][index2],
),
),
),
),
),
),
TextButton(
onPressed: printMatrix,
child: const Text('Print Matrix'),
)
],
),
);
}
}
class MatrixField extends StatelessWidget {
final TextEditingController controller;
const MatrixField({
Key? key,
required this.controller,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return SizedBox(
height: 50,
width: 50,
child: TextField(
controller: controller,
),
);
}
}
Let me know if you've any questions about the code.
Thanks in advance, I am fairly new to this and have followed a few tutorials and read countless posts here.
I am working on a personal project and have struck a dead end.
I wish to change the text within a text widget by use of a checkbox, in essence turning it on and off.
Unfortunately what at first seemed simple has driven me insane for the last week or so, I apologize if this has been answered but I can't find the help I need, I have found things similar but nothing has worked so far.
I'm trying to change my int value with an onchanged and set staff function with a list/array and an int value, I effectively want to set the value from 0 to 1 and then back again.
var textChange = [" could be ", " have become "];
int t = 0;
with;
onChanged: (value) {
setState(() {
boxChecked = value!;
if (boxChecked) {
t = 1;
} else if (!boxChecked){
t = 0;
}
}
);
},
I have made sure I am extending a Stateful Widget tried setting up different functions and methods to pass through, I have tried using a material button instead of a checkbox but still hit the same issue.
I have tried a few different ways to write my onset function including something as simple as [t++ or t = (d + 1) % textChange.length;] but these would ultimately end in error once the int = >2, but I couldn't even get the value of my int to change.
If I changed it manually and run the program, it works. however, I can't seem to get my onchanged and set state code to affect my array or int.
I even had my original array as List, changed it to var in hopes it would make it mutable.
I hope I said that right, I am clearly missing something but I've looked at so much I'm just lost.
Below is the code I have used with the fat trimmed from the rest of my project. I'm using android studios with the latest updates.
Thanks for your help and time.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'alternate text code',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'alternate text'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Divider(),
Container(
width: MediaQuery.of(context).size.width / 1.2,
child:
Text(
'I am having trouble getting some code to work, I have tried '
'a lot of things but can't work it out.'
'I would like the check box to change the line of text below,
),
),
Divider(),
Container(
width: MediaQuery.of(context).size.width / 1.2,
child:
ChangeText(),
),
Divider(),
Container(
width: MediaQuery.of(context).size.width / 1.2,
child:
Text(
'you' + textChange[t] + 'a App Developer.',
),
),
],
),
),
);
}
}
bool boxChecked = false;
class ChangeText extends StatefulWidget {
const ChangeText({Key? key}) : super(key: key);
#override
State<ChangeText> createState() => _ChangeText();
}
var textChange = [" could be ", " have become "];
int t = 0;
class _ChangeText extends State<ChangeText> {
#override
Widget build(BuildContext context) {
return Expanded(
child:
Container(
child: CheckboxListTile(
title: const Text('learn Flutter', style: TextStyle(fontSize: 14),),
value: boxChecked,
onChanged: (value) {
setState(() {
boxChecked = value!;
if (boxChecked) {
t = 1;
} else if (!boxChecked){
t = 0;
}
}
);
},
),
),
);
}
}
From ChangeText widget, you are calling setState mean it will only change the UI within ChangeText widget. However, Text reflect need to happened on MyHomePage. You can use global key to change the parent state or just use callback method like
class ChangeText extends StatefulWidget {
final Function callBack;
const ChangeText({
Key? key,
required this.callBack,
}) : super(key: key);
........
onChanged: (value) {
setState(() {
boxChecked = value!;
if (boxChecked) {
t = 1;
} else if (!boxChecked) {
t = 0;
}
});
widget.callBack();
},
and use like
child: ChangeText(
callBack: () {
setState(() {});
},
),
Full Widgets
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'alternate text code',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'alternate text'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Divider(),
Container(
width: MediaQuery.of(context).size.width / 1.2,
child: Text(
'I am having trouble getting some code to work, i have tried '
'a lot of things but cant work it out.'
'I would like the check box to change the line of text below',
),
),
Divider(),
Container(
width: MediaQuery.of(context).size.width / 1.2,
child: ChangeText(
callBack: () {
setState(() {});
},
),
),
Divider(),
Container(
width: MediaQuery.of(context).size.width / 1.2,
child: Text(
'you' + textChange[t] + 'a App Developer.',
),
),
],
),
),
);
}
}
bool boxChecked = false;
class ChangeText extends StatefulWidget {
final Function callBack;
const ChangeText({
Key? key,
required this.callBack,
}) : super(key: key);
#override
State<ChangeText> createState() => _ChangeText();
}
var textChange = [" could be ", " have become "];
int t = 0;
class _ChangeText extends State<ChangeText> {
#override
Widget build(BuildContext context) {
return
// Expanded(
// child:
Container(
child: CheckboxListTile(
title: const Text(
'learn Flutter',
style: TextStyle(fontSize: 14),
),
value: boxChecked,
onChanged: (value) {
setState(() {
boxChecked = value!;
if (boxChecked) {
t = 1;
} else if (!boxChecked) {
t = 0;
}
});
widget.callBack();
},
),
// ),
);
}
}
How to link two pageviews in flutter?
i.e. if one of them goes to page x the other should go to page x as well.
I thought two PageViews having the same controller would do the trick.
But that doesn't seem to be the case.
I tried having a list of controllers and when one of the pageviews' page changes, I'm calling jumpToPage on all the other pageviews' controllers but all the other PageViews are not in the widget runtime tree initially (They're outside the screen) thus giving out errors.
In my case PageView(children:[Pageview(...), Pageview(...)]) is the structure.
And after I open the other pageviews once, the errors are all gone but the current pageview is also getting jumped even though I removed it.
There're no infinite loops because of the other pageview's event firing at the same time.
/// Inside a stateful widget
PageView(
controller: widget.controller,
onPageChanged: (pno) {
widget.controllers.where((x) {
return x != widget.controllers[widget.idx];
}).forEach((colpv) {
colpv.controller?.jumpToPage(pno);
});
},
);
This is a minimal example that reproduces what I'm doing. It's in the ColPageView widget.
The full code
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
SystemChrome.setEnabledSystemUIOverlays([]);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Experiments',
theme: ThemeData.dark(),
home: MyHomePage(title: 'FlutterExps'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<PageControllerC> _controllers;
PageController _rowController;
PageController _mainController;
#override
void initState() {
_controllers = [
PageControllerC(
controller: PageController(keepPage: true),
recorded: 0,
),
PageControllerC(
controller: PageController(keepPage: true),
recorded: 1,
),
];
_controllers.forEach((f) {
f.controller.addListener(() {
print("Listener on ${f.recorded}");
});
});
_mainController = PageController();
_rowController = PageController();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
controller: _rowController,
children: [
ColPageView(
idx: 0,
controllers: _controllers,
controller: _mainController,
children: <Widget>[
ColoredWidget(
color: Colors.cyan,
direction: ">",
),
ColoredWidget(
color: Colors.orange,
direction: ">>",
),
],
),
ColPageView(
idx: 1,
controllers: _controllers,
controller: _mainController,
children: [
ColoredWidget(
color: Colors.green,
direction: "<",
),
ColoredWidget(
color: Colors.yellow,
direction: "<<",
),
],
),
],
),
);
}
}
class PageControllerC {
PageController controller;
int recorded;
PageControllerC({
this.recorded,
this.controller,
});
}
class ColPageView extends StatefulWidget {
final List<Widget> children;
final List<PageControllerC> controllers;
final int idx;
final PageController controller;
const ColPageView({
Key key,
this.children = const <Widget>[],
#required this.controllers,
#required this.idx,
this.controller,
}) : super(key: key);
#override
_ColPageViewState createState() => _ColPageViewState();
}
class _ColPageViewState extends State<ColPageView> {
#override
Widget build(BuildContext context) {
return PageView(
controller: widget.controllers[widget.idx].controller,
// controller: widget.controller,
scrollDirection: Axis.vertical,
children: widget.children,
onPageChanged: (pno) {
widget.controllers.where((x) {
return x != widget.controllers[widget.idx];
}).forEach((colpv) {
// if (colpv != widget.controllers[widget.idx]) {
colpv.controller?.jumpToPage(pno);
// }
// else{
print("memmem ${widget.idx}");
// }
});
print("col-${widget.idx} changed to $pno");
},
);
}
}
class ColoredWidget extends StatefulWidget {
final Color color;
final String direction;
const ColoredWidget({
Key key,
#required this.color,
#required this.direction,
}) : super(key: key);
#override
_ColoredWidgetState createState() => _ColoredWidgetState();
}
class _ColoredWidgetState extends State<ColoredWidget>
with AutomaticKeepAliveClientMixin<ColoredWidget> {
#override
Widget build(BuildContext context) {
super.build(context);
return Container(
color: widget.color,
child: Center(
child: Text(
widget.direction,
style: TextStyle(
fontSize: 100,
color: Colors.black,
),
),
));
}
#override
bool get wantKeepAlive => true;
}
I was able to link two pageviews given that they both reside in a pageview.
Note: They're discretely linked.
Maintain a list of controllers
Track the current vertical position in the HomePage widget.
And also the current horizontal pageview's position.
If a widget's page is being changed and it is visible in the viewport then make all other pages jump to where this goes. Check if it is in the widget tree before making it jump.
Else if it's not in the viewport don't apply the same callback as it should only be affected by the one in the viewport (or the currently scrolling one).
When initializing any pageview check the current vertical position and jump to that page.
This is not efficient as I'm keeping all the pageviews in the widget tree alive even if they are not visible. (I will update the answer if I come up with one that is efficient)
This is working because both pageviews are in a single pageview which is horizontal.
I will try to provide another example where both the pageviews are in the viewport (in a row for example) and the linking is continuous.
This can be extended to multiple page views and which leads to a fullscreen GridView.
Full code.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
SystemChrome.setEnabledSystemUIOverlays([]);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Experiments',
theme: ThemeData.dark(),
home: MyHomePage(title: 'FlutterExps'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<PageController> _controllers;
PageController _rowController;
ValueNotifier<int> _horizPage = ValueNotifier(0);
ValueNotifier<int> _vertPage = ValueNotifier(0);
#override
void initState() {
_controllers = [
PageController(keepPage: true),
PageController(keepPage: true),
];
_rowController = PageController();
_horizPage.value = _rowController.initialPage;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
controller: _rowController,
onPageChanged: (pno) {
setState(() {
_horizPage.value = pno;
});
},
children: [
ColPageView(
idx: 0,
currHoriz: _horizPage,
vertstate: _vertPage,
controllers: _controllers,
children: <Widget>[
ColoredWidget(
color: Colors.cyan,
direction: ">",
),
ColoredWidget(
color: Colors.orange,
direction: ">>",
),
],
),
ColPageView(
idx: 1,
currHoriz: _horizPage,
vertstate: _vertPage,
controllers: _controllers,
children: [
ColoredWidget(
color: Colors.green,
direction: "<",
),
ColoredWidget(
color: Colors.yellow,
direction: "<<",
),
],
),
],
),
);
}
}
class ColPageView extends StatefulWidget {
final int idx;
final List<Widget> children;
final List<PageController> controllers;
final ValueNotifier<int> currHoriz;
final ValueNotifier<int> vertstate;
const ColPageView({
Key key,
this.children = const <Widget>[],
#required this.controllers,
#required this.currHoriz,
#required this.vertstate,
#required this.idx,
}) : super(key: key);
#override
_ColPageViewState createState() => _ColPageViewState();
}
class _ColPageViewState extends State<ColPageView> {
#override
void initState() {
widget.controllers[widget.idx] = PageController(
initialPage: widget.vertstate.value ?? 0,
keepPage: true,
);
super.initState();
}
#override
Widget build(BuildContext context) {
return PageView(
controller: widget.controllers[widget.idx],
scrollDirection: Axis.vertical,
children: widget.children,
onPageChanged: (widget.idx == widget.currHoriz.value)
? (pno) {
widget.controllers.forEach((colpv) {
if (colpv != widget.controllers[widget.idx]) {
if (colpv.hasClients && colpv.page != pno) {
colpv.jumpToPage(pno);
}
}
});
// Set latest vertical position
widget.vertstate.value = pno;
// print("col-${widget.idx} changed to $pno");
// set horizontal coord to be null
// As we've finished dealing with it
widget.currHoriz.value = null;
}
: null,
);
}
}
class ColoredWidget extends StatefulWidget {
final Color color;
final String direction;
const ColoredWidget({
Key key,
#required this.color,
#required this.direction,
}) : super(key: key);
#override
_ColoredWidgetState createState() => _ColoredWidgetState();
}
class _ColoredWidgetState extends State<ColoredWidget>
with AutomaticKeepAliveClientMixin<ColoredWidget> {
#override
Widget build(BuildContext context) {
super.build(context);
return Container(
color: widget.color,
child: Center(
child: Text(
widget.direction,
style: TextStyle(
fontSize: 100,
color: Colors.black,
),
),
));
}
#override
bool get wantKeepAlive => true;
}