DartでTier表を作成する
公開日: 2024-07-19 01:20:52
更新日: 2024-07-19 01:38:31
今回はdartでtier表を作成する方法を勉強したので、解説していきます。
Tier List アプリの大まかな説明
アプリの目的
このアプリは、ユーザーがキャラクターをランク付けして、ドラッグ&ドロップで各ランクに配置するためのシンプルなTier Listを作成するものです。
Character クラス
Character クラスはキャラクターの名前、画像のパス、ランクを保持するシンプルなクラスです。これにより、キャラクターの基本情報を管理します。
ランクごとのキャラクターリスト
アプリでは、各ランク(SS, S, A, B, C, D)ごとのキャラクターリストを保持するためにMapを使用しています。このMapは、ユーザーがキャラクターをドラッグ&ドロップで追加するための空のリストで初期化されています。また、初期キャラクターリストも設定されており、これには名前、画像のパス、ランクが含まれています。
ランクの背景色
各ランクの背景色を定義するMapもあります。これにより、各ランクが視覚的に区別しやすくなります。
画面構成
アプリの上部には「Tier List」というタイトルを表示するAppBarがあります。
ランク部分
上部のランク部分には、各ランクに対応する領域があり、ユーザーがキャラクターをドラッグ&ドロップで追加できます。この部分はListViewで表示され、IntrinsicHeightを使用して、キャラクターの行数に応じて高さが動的に調整されます。
キャラクターリスト
下部には初期キャラクターリストが表示されます。ユーザーはここからキャラクターをドラッグして上部のランク部分に配置できます。キャラクターが配置されると、このリストから削除されますが、ランク部分から再度ドラッグ&ドロップで戻すことも可能です。
ドラッグ&ドロップの動作
ドラッグ&ドロップの動作について説明します。各キャラクターはDraggableウィジェットで包まれており、ドラッグ可能です。ランク部分およびキャラクターリストはDragTargetウィジェットで包まれており、ドラッグされたキャラクターを受け入れます。onAcceptコールバックが呼び出されると、キャラクターは元のリストから削除され、ターゲットリストに追加されます。
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
// MyAppクラス:アプリのエントリーポイント
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: TierView(),
);
}
}
// Characterクラス:キャラクターの情報を保持
class Character {
final String name; // キャラクターの名前
final String imagePath; // 画像のパス
final String rank; // ランク
Character({required this.name, required this.imagePath, required this.rank});
}
// TierViewクラス:Tierリストの表示
class TierView extends StatefulWidget {
@override
_TierViewState createState() => _TierViewState();
}
class _TierViewState extends State<TierView> {
// ドラッグ&ドロップ用のリストを定義
final Map<String, List<Character>> tieredCharacters = {
'SS': [],
'S': [],
'A': [],
'B': [],
'C': [],
'D': [],
};
// 初期キャラクターリスト
final List<Character> characters = [
Character(name: 'Character 1', imagePath: 'assets/images/testImage1.png', rank: 'SS'),
Character(name: 'Character 2', imagePath: 'assets/images/testImage2.png', rank: 'S'),
Character(name: 'Character 3', imagePath: 'assets/images/testImage3.png', rank: 'A'),
Character(name: 'Character 4', imagePath: 'assets/images/testImage4.png', rank: 'B'),
Character(name: 'Character 5', imagePath: 'assets/images/testImage5.png', rank: 'C'),
Character(name: 'Character 6', imagePath: 'assets/images/testImage6.png', rank: 'D'),
Character(name: 'Character 7', imagePath: 'assets/images/testImage7.png', rank: 'SS'),
Character(name: 'Character 8', imagePath: 'assets/images/testImage8.png', rank: 'S'),
Character(name: 'Character 9', imagePath: 'assets/images/testImage9.png', rank: 'A'),
Character(name: 'Character 10', imagePath: 'assets/images/testImage10.png', rank: 'B'),
];
// 各ランクの色設定
final Map<String, Color> rankColors = {
'SS': Colors.pinkAccent,
'S': Colors.purpleAccent,
'A': Colors.blueAccent,
'B': Colors.lightBlueAccent,
'C': Colors.greenAccent,
'D': Colors.orangeAccent,
};
@override
Widget build(BuildContext context) {
// 端末の横幅を取得
double screenWidth = MediaQuery.of(context).size.width;
// 画像の幅を端末の横幅の1/10に設定(8キャラで1行とするため)
double imageWidth = screenWidth / 10;
return Scaffold(
appBar: AppBar(
title: Text('Tier List'),
),
body: Column(
children: [
Expanded(
flex: 2, // 上部のランク部分の高さを調整
child: ListView(
padding: EdgeInsets.zero,
children: tieredCharacters.entries.map((entry) {
int numRows = (entry.value.length / 8).ceil(); // 行数を計算
numRows = numRows == 0 ? 1 : numRows; // 画像がない場合でも1行分の高さを確保
return IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ランク表示部分
Container(
width: imageWidth,
color: rankColors[entry.key],
child: Center(
child: Text(
entry.key,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
),
),
// キャラクター表示部分
Expanded(
child: Container(
height: imageWidth * numRows, // 高さを動的に設定
child: DragTarget<Character>(
onWillAccept: (data) => true,
onAccept: (character) {
setState(() {
// 既存のリストからキャラクターを削除
_removeCharacterFromAllTiers(character);
// ドロップされたリストにキャラクターを追加
tieredCharacters[entry.key]!.add(character);
});
},
builder: (context, candidateData, rejectedData) {
return Wrap(
spacing: 4.0,
runSpacing: 4.0,
children: entry.value.map((character) {
return Draggable<Character>(
data: character,
feedback: Material(
child: Image.asset(
character.imagePath,
width: imageWidth,
height: imageWidth,
),
),
childWhenDragging: Container(),
child: Image.asset(
character.imagePath,
width: imageWidth,
height: imageWidth,
),
);
}).toList(),
);
},
),
),
),
],
),
);
}).toList(),
),
),
Expanded(
flex: 1, // 下部の画像部分の高さを調整
child: DragTarget<Character>(
onWillAccept: (data) => true,
onAccept: (character) {
setState(() {
// 既存のリストからキャラクターを削除
_removeCharacterFromAllTiers(character);
// 下部のリストにキャラクターを追加
characters.add(character);
});
},
builder: (context, candidateData, rejectedData) {
return GridView.builder(
padding: EdgeInsets.all(4.0),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 10, // 10キャラで1行に表示
childAspectRatio: 1,
mainAxisSpacing: 4.0, // 縦方向の間隔を追加
crossAxisSpacing: 4.0, // 横方向の間隔を追加
),
itemCount: characters.length,
itemBuilder: (context, index) {
final character = characters[index];
return Draggable<Character>(
data: character,
feedback: Material(
child: Image.asset(
character.imagePath,
width: imageWidth,
height: imageWidth,
),
),
childWhenDragging: Container(),
child: Image.asset(
character.imagePath,
width: imageWidth,
height: imageWidth,
),
);
},
);
},
),
),
],
),
);
}
// 既存のリストからキャラクターを削除するヘルパーメソッド
void _removeCharacterFromAllTiers(Character character) {
tieredCharacters.forEach((key, value) {
value.remove(character);
});
characters.remove(character);
}
}
実行結果
■補足
初期キャラクターリスト
初期キャラクターリストcharactersには、すべてのキャラクターがCharacterクラスのインスタンスとして格納されています。このリストはアプリの下部に表示され、ユーザーがキャラクターをドラッグする元の位置となります。
ランクごとのキャラクターリスト
各ランク(SS, S, A, B, C, D)のキャラクターリストはtieredCharactersというMapに格納されています。このMapは、ランクをキーとして、キャラクターのリストを値として持ちます。
ドラッグ&ドロップの動作
ユーザーがキャラクターをドラッグして、特定のランク部分にドロップすると、DragTargetのonAcceptコールバックが呼び出されます。このコールバック内で以下の処理が行われます。
ドロップされたキャラクターを_removeCharacterFromAllTiersメソッドで、既存のリスト(初期キャラクターリストおよび他のランクリスト)から削除します。
ドロップされたキャラクターを、対応するランクのキャラクターリスト(tieredCharactersの対応するランクのリスト)に追加します。
キャラクターリストの表示更新
キャラクターリストが更新されると、FlutterのsetStateメソッドにより、UIが再レンダリングされ、最新の状態が反映されます。
この仕組みにより、キャラクターの現在の位置はCharacter型のクラス配列により管理され、ドラッグ&ドロップ操作が行われるたびにリストが更新されて、位置が適切に保持されます。