Flutterの基礎でつまづく 2023/03/21
「一度レーン(Lane)が生成した後だと、追加されたアイディア(Idea)がうまく描画されない」という問題が直らない。。。
Flutterの基礎やRiverpodなどを学ぶも、結構難しくまだ理解しきれていない。
Keyの問題なのか。データは生成されているはずだが描画がされないように見える。
なぜEnterキーででさらに新しいIdeaを作ると、古いものも含めて描画されるのか。分からない。
再現専用のessential版を作った。
main_essential.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(child: LanesManager()),
),
);
}
}
class LanesManager extends StatefulWidget {
@override
State<LanesManager> createState() => _LanesManagerState();
}
class _LanesManagerState extends State<LanesManager> {
List<Lane> lanes = [
Lane(
key: GlobalKey<_LaneState>(),
ideas: [Idea(key: GlobalKey<_IdeaState>(), index: 0)])
];
@override
void initState() {
super.initState();
for (int i = 0; i < lanes.length; i++) {
GlobalKey<_LaneState> laneKey = GlobalKey<_LaneState>();
lanes[i] = Lane(
key: laneKey, ideas: [Idea(key: GlobalKey<_IdeaState>(), index: 0)]);
}
}
void addNewLane() {
GlobalKey<_LaneState> newLaneKey = GlobalKey<_LaneState>();
setState(() {
lanes.add(Lane(
key: newLaneKey,
ideas: [Idea(key: GlobalKey<_IdeaState>(), index: 0)]));
});
}
void focusNextLane(int targetLaneIndex, int targetIdeaIndex) {
if (targetLaneIndex < 0 || targetIdeaIndex < 0) return;
if (targetLaneIndex == lanes.length) {
addNewLane();
}
if (targetLaneIndex < lanes.length) {
for (int i = lanes[targetLaneIndex].ideas.length - 1;
i < targetIdeaIndex;
i++) {
GlobalKey<_IdeaState> newIdeaKey = GlobalKey<_IdeaState>();
lanes[targetLaneIndex].ideas.add(Idea(key: newIdeaKey, index: i + 1));
for (int i = 0; i < lanes[targetLaneIndex].ideas.length; i++) {
lanes[targetLaneIndex].ideas[i].index = i;
}
}
;
lanes[targetLaneIndex]
.ideas[targetIdeaIndex]
.key
.currentState
?._textFocusNode
.requestFocus();
}
}
@override
Widget build(BuildContext context) {
return LanesManagerInheritedWidget(
laneManagerState: this,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: lanes.length,
itemBuilder: (context, index) {
return Container(
width: MediaQuery.of(context).size.width * 0.4,
child: lanes[index],
);
},
),
);
}
}
class LanesManagerInheritedWidget extends InheritedWidget {
final _LanesManagerState laneManagerState;
LanesManagerInheritedWidget(
{required this.laneManagerState, required Widget child})
: super(child: child);
@override
bool updateShouldNotify(LanesManagerInheritedWidget oldWidget) => true;
static _LanesManagerState of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<LanesManagerInheritedWidget>()!
.laneManagerState;
}
}
class Lane extends StatefulWidget {
final List<Idea> ideas;
Lane({required GlobalKey<_LaneState> key, required this.ideas});
@override
_LaneState createState() => _LaneState();
}
class _LaneState extends State<Lane> {
List<Idea> get ideas => widget.ideas;
void addNewIdea(int currentIndex) {
setState(() {
int currentIndentLevel =
ideas[currentIndex].key.currentState?.indentLevel ?? 0;
ideas.insert(
currentIndex + 1,
Idea(
key: GlobalKey<_IdeaState>(),
index: currentIndex + 1,
),
);
for (int i = currentIndex + 2; i < ideas.length; i++) {
ideas[i].index = i;
}
});
}
void focusAdjacentIdea(int currentIndex, int direction) {
if (currentIndex + direction >= 0 &&
currentIndex + direction < ideas.length) {
ideas[currentIndex + direction]
.key
.currentState
?._textFocusNode
.requestFocus();
}
}
void redrawAffectedIdeas(int startIndex, int endIndex) {
if (startIndex < 0 || endIndex >= ideas.length) return;
setState(() {});
}
@override
Widget build(BuildContext context) {
return LaneInheritedWidget(
laneState: this,
child: ListView.builder(
itemCount: ideas.length,
itemBuilder: (context, index) {
return ideas[index];
},
),
);
}
}
class LaneInheritedWidget extends InheritedWidget {
final _LaneState laneState;
LaneInheritedWidget({required this.laneState, required Widget child})
: super(child: child);
@override
bool updateShouldNotify(LaneInheritedWidget oldWidget) => true;
static _LaneState of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<LaneInheritedWidget>()!
.laneState;
}
}
class Idea extends StatefulWidget {
int index;
final GlobalKey<_IdeaState> key;
final double boxSpacing; //他のエディタとの間隔
Idea({
required this.key,
required this.index,
this.boxSpacing = 8.0,
}) : super(key: ValueKey('Idea ${index}'));
@override
_IdeaState createState() => _IdeaState();
}
class _IdeaState extends State<Idea> {
TextEditingController _textController = TextEditingController();
int indentLevel = 0;
FocusNode _textFocusNode = FocusNode();
void updateIndex(int newIndex) {
setState(() {
widget.index = newIndex;
});
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
FocusScope.of(context).requestFocus(_textFocusNode);
});
}
@override
Widget build(BuildContext context) {
return Focus(
onKey: (FocusNode node, RawKeyEvent event) {
if (event.runtimeType == RawKeyDownEvent) {
if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
LaneInheritedWidget.of(context).focusAdjacentIdea(widget.index, -1);
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
if (widget.index ==
LaneInheritedWidget.of(context).ideas.length - 1) {
LaneInheritedWidget.of(context).addNewIdea(widget.index);
} else {
LaneInheritedWidget.of(context)
.focusAdjacentIdea(widget.index, 1);
}
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight &&
_textController.selection.baseOffset ==
_textController.text.length) {
int laneIndex = LanesManagerInheritedWidget.of(context)
.lanes
.indexOf(LaneInheritedWidget.of(context).widget);
LanesManagerInheritedWidget.of(context)
.focusNextLane(laneIndex + 1, widget.index);
} else if (event.logicalKey == LogicalKeyboardKey.arrowLeft &&
_textController.selection.baseOffset == 0) {
int laneIndex = LanesManagerInheritedWidget.of(context)
.lanes
.indexOf(LaneInheritedWidget.of(context).widget);
LanesManagerInheritedWidget.of(context)
.focusNextLane(laneIndex - 1, widget.index);
}
}
return KeyEventResult.ignored;
},
child: RawKeyboardListener(
focusNode: FocusNode(),
child: Container(
margin:
EdgeInsets.symmetric(horizontal: 16, vertical: widget.boxSpacing),
child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [
Expanded(
child: Container(
height: 48,
child: TextField(
controller: _textController,
focusNode: _textFocusNode,
maxLines: 1,
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
keyboardType: TextInputType.multiline,
textInputAction: TextInputAction.done,
onSubmitted: (value) {
_textFocusNode.unfocus();
LaneInheritedWidget.of(context).addNewIdea(widget.index);
},
),
),
),
]),
),
),
);
}
}
ひとまず問題は解消 2023/03/25
色々苦しんだ結果、ようやく描画の問題の解消方法は分かった。
まだうまい書き方にはなっていないがまずは前に進めそうだ。長かった……。
再現専用の main_essential2.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: CanvasZone(),
),
);
}
}
class CanvasZone extends StatefulWidget {
@override
State<CanvasZone> createState() => _CanvasZoneState();
}
class _CanvasZoneState extends State<CanvasZone> {
List<Lane> lanes = [];
@override
void initState() {
super.initState();
addNewLane(0);
}
void addNewLane(int newIndex) {
print("addNewLane");
setState(() {
lanes.add(Lane(
key: GlobalKey<_LaneState>(),
index: newIndex,
ideas: [Idea(key: GlobalKey<_IdeaState>(), index: 0)]));
});
}
void focusNextLane(int targetLaneIndex, int targetIdeaIndex) {
print("--focusNextLane targetLaneIndex:"+ targetLaneIndex.toString() + ' targetIdeaIndex:'+ targetIdeaIndex.toString());
if (targetLaneIndex < 0 || targetIdeaIndex < 0) return;
if (targetLaneIndex == lanes.length) {
addNewLane(targetLaneIndex);
}
if (targetLaneIndex < lanes.length) {
print("lanes[targetLaneIndex].ideas.length:"+ lanes[targetLaneIndex].ideas.length.toString());
for (int i = lanes[targetLaneIndex].ideas.length - 1;
i < targetIdeaIndex;
i++) {
lanes[targetLaneIndex].key.currentState?.addNewIdea(i);
/*
GlobalKey<_IdeaState> newIdeaKey = GlobalKey<_IdeaState>();
setState(() {
print("i:"+ i.toString());
lanes[targetLaneIndex].ideas.add(Idea(key: newIdeaKey, index: i + 1));
for (int j = 0; j < lanes[targetLaneIndex].ideas.length; j++) {
lanes[targetLaneIndex].ideas[j].index = j;
}
});
*/
};
print("lanes[targetLaneIndex].ideas[targetIdeaindex].key:"+ lanes[targetLaneIndex].ideas[targetIdeaIndex].key.toString());
lanes[targetLaneIndex]
.key
.currentState?.build(context);
lanes[targetLaneIndex].ideas[targetIdeaIndex].key.currentState?.build(context);
lanes[targetLaneIndex]
.ideas[targetIdeaIndex]
.key
.currentState
?._textFocusNode
.requestFocus();
}
}
@override
Widget build(BuildContext context) {
return CanvasZoneInheritedWidget(
canvasZoneState: this,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: lanes.length,
itemBuilder: (context, index) {
return Container(
width: MediaQuery.of(context).size.width * 0.4,
child: lanes[index],
);
},
),
);
}
}
class CanvasZoneInheritedWidget extends InheritedWidget {
final _CanvasZoneState canvasZoneState;
CanvasZoneInheritedWidget(
{required this.canvasZoneState, required Widget child})
: super(child: child);
@override
bool updateShouldNotify(CanvasZoneInheritedWidget oldWidget) => true;
static _CanvasZoneState of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<CanvasZoneInheritedWidget>()!
.canvasZoneState;
}
}
class Lane extends StatefulWidget {
int index;
final GlobalKey<_LaneState> key;
List<Idea> ideas;
Lane({
required this.key,
required this.index,
required this.ideas
}) : super(key: ValueKey('Lane'+key.toString()));
@override
_LaneState createState() => _LaneState();
}
class _LaneState extends State<Lane> {
List<Idea> get ideas => widget.ideas;
void addNewIdea(int currentIndex) {
setState(() {
ideas.insert(
currentIndex + 1,
Idea(
key: GlobalKey<_IdeaState>(),
index: currentIndex + 1,
),
);
for (int i = currentIndex + 2; i < ideas.length; i++) {
ideas[i].index = i;
}
});
}
void focusAdjacentIdea(int currentIndex, int direction) {
if (currentIndex + direction >= 0 &&
currentIndex + direction < ideas.length) {
ideas[currentIndex + direction]
.key
.currentState
?._textFocusNode
.requestFocus();
}
}
void redrawAffectedIdeas(int startIndex, int endIndex) {
if (startIndex < 0 || endIndex >= ideas.length) return;
setState(() {});
}
@override
Widget build(BuildContext context) {
print("build_Lane"+widget.index.toString());
return LaneInheritedWidget(
laneState: this,
child: ListView.builder(
itemCount: ideas.length,
itemBuilder: (context, index) {
return ideas[index];
},
),
);
}
}
class LaneInheritedWidget extends InheritedWidget {
final _LaneState laneState;
LaneInheritedWidget({required this.laneState, required Widget child})
: super(child: child);
@override
bool updateShouldNotify(LaneInheritedWidget oldWidget) => true;
static _LaneState of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<LaneInheritedWidget>()!
.laneState;
}
}
class Idea extends StatefulWidget {
int index;
final GlobalKey<_IdeaState> key;
Idea({
required this.key,
required this.index,
}) : super(key: ValueKey('Idea'+key.toString()));
@override
_IdeaState createState() => _IdeaState();
}
class _IdeaState extends State<Idea> {
TextEditingController _textController = TextEditingController();
int indentLevel = 0;
FocusNode _textFocusNode = FocusNode();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
FocusScope.of(context).requestFocus(_textFocusNode);
});
}
@override
Widget build(BuildContext context) {
print("IdeaState_build key:"+ widget.key.toString());
return Focus(
onKey: (FocusNode node, RawKeyEvent event) {
if (event.runtimeType == RawKeyDownEvent) {
if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
LaneInheritedWidget.of(context).focusAdjacentIdea(widget.index, -1);
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
if (widget.index ==
LaneInheritedWidget.of(context).ideas.length - 1) {
LaneInheritedWidget.of(context).addNewIdea(widget.index);
} else {
LaneInheritedWidget.of(context)
.focusAdjacentIdea(widget.index, 1);
}
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight &&
_textController.selection.baseOffset ==
_textController.text.length) {
int laneIndex = CanvasZoneInheritedWidget.of(context)
.lanes
.indexOf(LaneInheritedWidget.of(context).widget);
CanvasZoneInheritedWidget.of(context)
.focusNextLane(laneIndex + 1, widget.index);
} else if (event.logicalKey == LogicalKeyboardKey.arrowLeft &&
_textController.selection.baseOffset == 0) {
int laneIndex = CanvasZoneInheritedWidget.of(context)
.lanes
.indexOf(LaneInheritedWidget.of(context).widget);
CanvasZoneInheritedWidget.of(context)
.focusNextLane(laneIndex - 1, widget.index);
}
}
return KeyEventResult.ignored;
},
child: RawKeyboardListener(
focusNode: FocusNode(),
child: Container(
height: 48,
child: TextField(
controller: _textController,
focusNode: _textFocusNode,
maxLines: 1,
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
keyboardType: TextInputType.multiline,
textInputAction: TextInputAction.done,
onSubmitted: (value) {
_textFocusNode.unfocus();
LaneInheritedWidget.of(context).addNewIdea(widget.index);
},
),
),
),
);
}
}
現時点でのメインコードは次のようになっている。
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'indent_line_painter.dart';
import 'dart:math';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(child: CanvasZone()),
),
);
}
}
class CanvasZone extends StatefulWidget {
@override
State<CanvasZone> createState() => _CanvasZoneState();
}
class _CanvasZoneState extends State<CanvasZone> {
double _scale = 1.0;
List<Lane> lanes = [];
@override
void initState() {
super.initState();
addNewLane(0);
}
void addNewLane(int newIndex) {
setState(() {
lanes.add(Lane(
key: GlobalKey<_LaneState>(),
index: newIndex,
ideas: [Idea(key: GlobalKey<_IdeaState>(), index: 0)]));
});
}
void removeLane() {
setState(() {
if (lanes.length > 1) {
lanes.removeLast();
}
});
}
void focusNextLane(int targetLaneIndex, int targetIdeaIndex) {
if (targetLaneIndex < 0 || targetIdeaIndex < 0) return;
if (targetLaneIndex == lanes.length) {
addNewLane(targetLaneIndex);
}
if (targetLaneIndex < lanes.length) {
for (int i = lanes[targetLaneIndex].ideas.length - 1;
i < targetIdeaIndex;
i++) {
if(lanes[targetLaneIndex].key.currentState == null) {
setState(() {
lanes[targetLaneIndex].ideas.add(Idea(key: GlobalKey<_IdeaState>(), index: i + 1));
for (int j = 0; j < lanes[targetLaneIndex].ideas.length; j++) {
lanes[targetLaneIndex].ideas[j].index = j;
}
});
} else {
lanes[targetLaneIndex].key.currentState?.addNewIdea(i);
}
};
lanes[targetLaneIndex]
.ideas[targetIdeaIndex]
.key
.currentState
?._textFocusNode
.requestFocus();
}
}
void scaleUp() {
setState(() {
_scale += 0.1;
});
}
void scaleDown() {
setState(() {
_scale = max(_scale - 0.1, 0.1);
});
}
@override
Widget build(BuildContext context) {
return CanvasZoneInheritedWidget(
canvasZoneState: this,
child: Column(
children: [
Align(
alignment: Alignment.topCenter,
child: Container(
margin: EdgeInsets.only(top: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: null,//addNewLane(lanes.length),
child: Text("レーン追加"),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: removeLane,
child: Text("レーン削除"),
),
SizedBox(width: 30),
ElevatedButton(
onPressed: scaleUp,
child: Text("拡大"),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: scaleDown,
child: Text("縮小"),
),
],
),
),
),
const Divider(),
Expanded(
child: Transform.scale(
scale: _scale,
alignment: Alignment.topLeft,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: lanes.length,
itemBuilder: (context, index) {
return Container(
width: MediaQuery.of(context).size.width * 0.4,
child: lanes[index],
);
},
),
),
),
],
),
);
}
}
class CanvasZoneInheritedWidget extends InheritedWidget {
final _CanvasZoneState canvasZoneState;
CanvasZoneInheritedWidget(
{required this.canvasZoneState, required Widget child})
: super(child: child);
@override
bool updateShouldNotify(CanvasZoneInheritedWidget oldWidget) => true;
static _CanvasZoneState of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<CanvasZoneInheritedWidget>()!
.canvasZoneState;
}
}
class Lane extends StatefulWidget {
int index;
final GlobalKey<_LaneState> key;
List<Idea> ideas;
Lane({
required this.key,
required this.index,
required this.ideas
}) : super(key: ValueKey('Lane'+key.toString()));
@override
_LaneState createState() => _LaneState();
}
class _LaneState extends State<Lane> {
List<Idea> get ideas => widget.ideas;
void addNewIdea(int currentIndex) {
setState(() {
int currentIndentLevel =
ideas[currentIndex].key.currentState?.indentLevel ?? 0;
ideas.insert(
currentIndex + 1,
Idea(
key: GlobalKey<_IdeaState>(),
index: currentIndex + 1,
textFieldHeight: 48.0,
initialIndentLevel: currentIndentLevel,
),
);
for (int i = currentIndex + 2; i < ideas.length; i++) {
ideas[i].index = i;
}
});
}
void moveIdea(int currentIndex, int direction) {
if (currentIndex + direction >= 0 &&
currentIndex + direction < ideas.length) {
setState(() {
Idea temp = ideas[currentIndex];
ideas[currentIndex] = ideas[currentIndex + direction];
ideas[currentIndex + direction] = temp;
ideas[currentIndex].key.currentState?.updateIndex(currentIndex);
ideas[currentIndex + direction]
.key
.currentState
?.updateIndex(currentIndex + direction);
});
}
}
void deleteIdea(int currentIndex) {
if (ideas.length > 1) {
setState(() {
ideas.removeAt(currentIndex);
for (int i = currentIndex; i < ideas.length; i++) {
ideas[i].index = i;
}
if (currentIndex > 0) {
ideas[currentIndex - 1]
.key
.currentState
?._textFocusNode
.requestFocus();
} else {
ideas[currentIndex].key.currentState?._textFocusNode.requestFocus();
}
});
}
}
void focusAdjacentIdea(int currentIndex, int direction) {
if (currentIndex + direction >= 0 &&
currentIndex + direction < ideas.length) {
ideas[currentIndex + direction]
.key
.currentState
?._textFocusNode
.requestFocus();
}
}
void redrawAffectedIdeas(int startIndex, int endIndex) {
if (startIndex < 0 || endIndex >= ideas.length) return;
setState(() {
for (int i = startIndex; i <= endIndex; i++) {
ideas[i].key.currentState?.updateIndentLevel();
}
});
}
@override
Widget build(BuildContext context) {
return LaneInheritedWidget(
laneState: this,
child: ListView.builder(
itemCount: ideas.length,
itemBuilder: (context, index) {
return ideas[index];
},
),
);
}
}
class LaneInheritedWidget extends InheritedWidget {
final _LaneState laneState;
LaneInheritedWidget({required this.laneState, required Widget child})
: super(child: child);
@override
bool updateShouldNotify(LaneInheritedWidget oldWidget) => true;
static _LaneState of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<LaneInheritedWidget>()!
.laneState;
}
}
class Idea extends StatefulWidget {
int index;
final GlobalKey<_IdeaState> key;
final double indentWidth; //インデントの下げ幅
final double boxSpacing; //他のエディタとの間隔
final double textFieldHeight; //テキスト欄の高さ
final int initialIndentLevel;
Idea({
required this.key,
required this.index,
this.indentWidth = 40.0,
this.boxSpacing = 8.0,
this.textFieldHeight = 48.0, //デフォルトは48
this.initialIndentLevel = 0,
}) : super(key: ValueKey('Idea'+key.toString()));
@override
_IdeaState createState() => _IdeaState();
}
class _IdeaState extends State<Idea> {
TextEditingController _textController = TextEditingController();
int indentLevel = 0;
FocusNode _textFocusNode = FocusNode();
void updateIndex(int newIndex) {
setState(() {
widget.index = newIndex;
});
}
void updateIndentLevel() {
setState(() {
indentLevel = widget.key.currentState?.indentLevel ?? indentLevel;
});
}
// インデントの線を描画する
void _indentLinePaint(Canvas canvas, Size size) {
indentLinePaint(canvas, size, context, widget.index, indentLevel,
widget.textFieldHeight, widget.boxSpacing, widget.indentWidth);
}
@override
void initState() {
super.initState();
indentLevel = widget.initialIndentLevel;
WidgetsBinding.instance.addPostFrameCallback((_) {
FocusScope.of(context).requestFocus(_textFocusNode);
});
}
@override
Widget build(BuildContext context) {
return Focus(
onKey: (FocusNode node, RawKeyEvent event) {
if (event.runtimeType == RawKeyDownEvent) {
if (event.logicalKey == LogicalKeyboardKey.capsLock) {
// CapsLockキーを無視する条件を追加
return KeyEventResult.ignored;
} else if (event.isControlPressed) { //コントロールキー
bool needRepaint = false;
if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
LaneInheritedWidget.of(context).moveIdea(widget.index, -1);
needRepaint = true;
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
LaneInheritedWidget.of(context).moveIdea(widget.index, 1);
needRepaint = true;
}
if (needRepaint) {
findAndRepaintAffectedIdeas(context, widget.index, indentLevel,
widget.index, widget.index + 1 /*Ctrlキーの場合、1つ下からチェックが必要*/);
}
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) { //カーソルキー ↑
LaneInheritedWidget.of(context)
.focusAdjacentIdea(widget.index, -1);
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
if (widget.index ==
LaneInheritedWidget.of(context).ideas.length - 1) {
LaneInheritedWidget.of(context).addNewIdea(widget.index);
} else {
LaneInheritedWidget.of(context)
.focusAdjacentIdea(widget.index, 1);
}
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight && //カーソルキー →
_textController.selection.baseOffset ==
_textController.text.length) {
int laneIndex = CanvasZoneInheritedWidget.of(context)
.lanes
.indexOf(LaneInheritedWidget.of(context).widget);
CanvasZoneInheritedWidget.of(context)
.focusNextLane(laneIndex + 1, widget.index);
} else if (event.logicalKey == LogicalKeyboardKey.arrowLeft &&
_textController.selection.baseOffset == 0) {
int laneIndex = CanvasZoneInheritedWidget.of(context)
.lanes
.indexOf(LaneInheritedWidget.of(context).widget);
CanvasZoneInheritedWidget.of(context)
.focusNextLane(laneIndex - 1, widget.index);
} else if (event.logicalKey == LogicalKeyboardKey.tab) { //TABキー
if (event.isShiftPressed) {
setState(() {
indentLevel = max(0, indentLevel - 1);
});
} else {
setState(() {
indentLevel += 1;
});
}
findAndRepaintAffectedIdeas(context, widget.index, indentLevel,
widget.index, widget.index);
return KeyEventResult.handled; //デフォルトのタブの挙動を無効化して、フォーカスの移動を防ぐ
} else if ((event.logicalKey == LogicalKeyboardKey.delete ||
event.logicalKey == LogicalKeyboardKey.backspace) &&
_textController.text.isEmpty) {
LaneInheritedWidget.of(context).deleteIdea(widget.index);
}
}
return KeyEventResult.ignored;
},
child: RawKeyboardListener(
focusNode: FocusNode(),
child: Container(
margin: EdgeInsets.symmetric(
horizontal: 16, vertical: widget.boxSpacing),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(
width: 26,
child: DecoratedBox(
decoration: BoxDecoration(color: Colors.red))),
Expanded(
flex: 4,
child: Row(children: [
CustomPaint(
painter: _CustomPainter(_indentLinePaint),
child: Container(width: widget.indentWidth * indentLevel),
),
Expanded(
child: Container(
height: widget.textFieldHeight,
child: TextField(
controller: _textController,
focusNode: _textFocusNode,
maxLines: 1,
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
keyboardType: TextInputType.multiline,
textInputAction: TextInputAction.done,
onSubmitted: (value) {
_textFocusNode.unfocus();
LaneInheritedWidget.of(context)
.addNewIdea(widget.index);
},
),
),
),
]),
),
],
),
),
));
}
}
class _CustomPainter extends CustomPainter {
final void Function(Canvas, Size) _paint;
_CustomPainter(this._paint);
@override
void paint(Canvas canvas, Size size) {
_paint(canvas, size);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
※本記事は、過去の記録(メモ)をもとに、他の方が読んで分かるように加筆・補足して掲載しています
テキストベースの思考整理ツール「アイディア・レーン」最新版はこちら。
コメント