์ด๋ฒ์ ํญ๊ณต๊ถ ๊ฒ์ ๊ธฐ๋ฅ์ ๋ง๋ค ๊ฒ์ด๋ค.
๋จผ์ ํ์ํ UI ์์๋ฅผ ์๊ฐํด๋ณด๋ฉด
<์
๋ ฅ>
์ฌํ์ง ์ ํ (๋๋กญ๋ค์ด)
๋ ์ง ์
๋ ฅ (์บ๋ฆฐ๋)
๊ฒ์ ์์ (๋ฒํผ)
์ถ๋ฐํธ / ๋์ฐฉํธ (ํ ๊ธ)
<์ถ๋ ฅ>
1. ํญ๊ณต์ฌ ์ด๋ฆ
2. ์ถ๋ฐ ์๊ฐ
3. ๊ฐ๊ฒฉ
4. ์๋งค ๋ฒํผ
(์ 4๊ฐ์ง๋ฅผ ๋ฌถ์ด ๊ฐ๊ฒฉ ์ค๋ฆ์ฐจ์์ผ๋ก ๋ฆฌ์คํธ)
์ ๋๊ฐ ์์ ๊ฒ ๊ฐ๋ค.
์
๋ ฅ ์์๋ถํฐ ๊ตฌํํด๋ณด์.
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
๋๋กญ๋ค์ด์ด๋ DatePicker, Switch๋ฑ์ ์ํ๊ฐ ๋ฐ๋๋ UI์ด๊ธฐ ๋๋ฌธ์
setState()๋ก ์ ํ๊ฐ์ ํ๋ฉด์ ๋ฐ์ํ๊ธฐ ์ํด
๊ฒ์ํ์ด์ง๋ StatefulWidget์ ์์๋ฐ๋๋ค.
class _SearchPageState extends State<SearchPage> {
String _selectedDestination = '๋์ฟ';
DateTime? _selectedDate;
int _flightTypeIndex = 0; // 0:์ถ๋ฐํธ, 1:๋์ฐฉํธ
}
๋ค์์ผ๋ก ์ฌํ์ง, ๋ ์ง, ์ถ๋ฐ/๋์ฐฉ ์ํ๋ณ์๋ฅผ ์ ์ํ๊ณ (Dropdown์ ์ด๊ธฐ๊ฐ ํ์)
๊ฐ๋ก๋ก ์์๋ค์ ๋ฐฐ์นํ๊ธฐ ์ํด ๊ตฌ์กฐ๋ฅผ ์ ์ํ๋ค.
ํ๋ฉด ํฌ๊ธฐ์ ๋ฐ๋ผ ๋์ํ๊ธฐ ์ํด Expanded๋ฅผ ์ฌ์ฉํ์๋ค.
* Expanded๋ ๋ฐ๋์ ๋ถ๋ชจ๊ฐ Row, Column, Flex ์ค ํ๋์ฌ์ผ ํจ
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInputRow(), const SizedBox(height: 16),
// ์ฌ๊ธฐ์ ๊ฒฐ๊ณผ ์ถ๋ ฅํ ์์
],
),
);
}
Widget _buildInputRow() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(flex: 1, child: Padding(padding: const EdgeInsets.symmetric(horizontal: 8), child: _buildDestinationDropdown(),),),
Expanded(flex: 1, child: Padding(padding: const EdgeInsets.symmetric(horizontal: 8), child: _buildDateButton(),),),
Expanded(flex: 1, child: Padding(padding: const EdgeInsets.symmetric(horizontal: 8), child: _buildFlightTypeToggle(),),),
Expanded(flex: 1, child: Padding(padding: const EdgeInsets.symmetric(horizontal: 8), child: _buildSearchButton(),),),
],
);
}
์ ๋ ฅ ๋ถ๋ถ์ ๋ค์ด๊ฐ ์์๋ค์ ํ๋์ฉ ์ดํด๋ณด๋ฉด
/// ์ฌํ์ง ์ ํ
Widget _buildDestinationDropdown() {
return FittedBox(
fit: BoxFit.scaleDown,
child: DropdownButton<String>(
value: _selectedDestination,
items: ['๋์ฟ', '์ค์ฌ์นด', 'ํ์ฟ ์ค์นด', '๋ฐฉ์ฝ']
.map((destination) => DropdownMenuItem(value: destination, child: Text(destination),)).toList(),
onChanged: (value) {
setState(() {
_selectedDestination = value!;
});
},
),
);
}
์ฌํ์ง ์ ํ์ DropdownButton์ ์ฌ์ฉํ์๋ค.
์ ์ ๊ฐ ๋๋กญ๋ค์ด์์ ์ฌํ์ง๋ฅผ ์ ํํ ๋ onChanged -> setState๊ฐ ํธ์ถ๋์ด ๊ฐ์ ๊ฐฑ์ ํ๋ค.
/// ๋ ์ง ์ ํ
Widget _buildDateButton() {
return ElevatedButton(
onPressed: () async {
final pickedDate = await showDatePicker(
context: context,
initialDate: _selectedDate ?? DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime.now().add(const Duration(days: 365)),
);
if (pickedDate != null) {
setState(() {
_selectedDate = pickedDate;
});
}
},
child: Text(
_selectedDate == null ? '๋ ์ง' : '${_selectedDate!.month}/${_selectedDate!.day}',
),
);
}
๋ ์ง ์ ํ์ ๋ฒํผ๊ณผ DatePicker๋ฅผ ์ฌ์ฉํ์๋ค.
ElevateButton์ ๋๋ฅด๋ฉด showDatePicker๋ก ๋ ์ง๋ฅผ ์ ํํ ์ ์๊ฒ ๋ฌ๋ ฅ์ ํ์ํ๊ณ ๋ ์ง๊ฐ ์ ํ๋๋ฉด ๋ง์ฐฌ๊ฐ์ง๋ก ๊ฐ์ ๊ฐฑ์ ํ๋ค.
/// ์ถ๋ฐํธ/๋์ฐฉํธ ํ ๊ธ
Widget _buildFlightTypeToggle() {
return Row(
children: [
Expanded(
child: GestureDetector(
onTap: () {
setState(() {
_flightTypeIndex = 0;
});
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 8),
decoration: BoxDecoration(
border: Border.all(color: _flightTypeIndex == 0 ? Colors.blue : Colors.grey,),
borderRadius: const BorderRadius.only(topLeft: Radius.circular(4), bottomLeft: Radius.circular(4),),
color: _flightTypeIndex == 0 ? Colors.blue.withValues(alpha: 0.1) : Colors.transparent,
),
child: FittedBox(fit: BoxFit.scaleDown, child: Text('์ถ๋ฐ', textAlign: TextAlign.center,),),
),
),
),
Expanded(
child: GestureDetector(
onTap: () {
setState(() {
_flightTypeIndex = 1;
});
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 8),
decoration: BoxDecoration(
border: Border.all(color: _flightTypeIndex == 1 ? Colors.blue : Colors.grey,),
borderRadius: const BorderRadius.only(topRight: Radius.circular(4),bottomRight: Radius.circular(4),),
color: _flightTypeIndex == 1 ? Colors.blue.withValues(alpha: 0.1) : Colors.transparent,
),
child: FittedBox(fit: BoxFit.scaleDown, child: Text('๋์ฐฉ', textAlign: TextAlign.center,),),
),
),
),
],
);
}
์ถ๋ฐํธ/๋์ฐฉํธ ํ ๊ธ์ ํ์ฌ ์ด๋ค ์ํ์ธ์ง ๋ณด์ฌ์ฃผ๊ธฐ ์ํด ํ
์คํธ๋ฅผ ์ถ๊ฐํ๊ณ
ToggleButtons๊ฐ ๋ ๊ฐํธํ์ง๋ง, ToggleButtons๋ UI ์ต์ ํฌ๊ธฐ๋ฅผ ๊ฐ์ ํ์ฌ ๋ด ๊ฒฝ์ฐ์ฒ๋ผ ํ ์ค์ ์ฌ๋ฌ UI๋ฅผ ๋ฐฐ์นํ ๊ฒฝ์ฐ UI๋ผ๋ฆฌ ๊ฒน์น๋ ์ํฉ์ด ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ ๋์ GestureDetector๋ฅผ ์ฌ์ฉํ๋ค.
๋ง์ง๋ง์ผ๋ก ๊ฒ์ ์์ ๋ฒํผ์ ๋ง๋ค์ด ์ฌ์ฉ์๊ฐ ์
๋ ฅํ ๊ฐ์ ์ด์ฉํด ๊ฒ์์ ์ฌ์ฉํ ๊ฒ์ด๋ค.
์ผ๋จ ๋๋ฏธ๋ฐ์ดํฐ๋ก ๋ฆฌ์คํธ ์ถ๋ ฅ๋ถํฐ ๊ตฌํํด๋ณด์.
class TicketResult {
final String airline;
final String departTime;
final int price;
TicketResult({
required this.airline,
required this.departTime,
required this.price,
});
}
List<TicketResult> _results = [];
List<TicketResult> testTickets({required String destination, required DateTime date,}) {
return [
TicketResult(
airline: 'Korean Air',
departTime: '08:30',
price: 180000,
),
TicketResult(
airline: 'Asiana',
departTime: '10:10',
price: 150000,
),
TicketResult(
airline: 'Jeju Air',
departTime: '14:45',
price: 120000,
),
];
}
ํ์์ ์ ์ํด์ค ๋ค ํ ์คํธ์ฉ ๋ฐ์ดํฐ๋ฅผ ๋ง๋ค๊ณ , ๊ฒ์ ๋ฒํผ์ onPressed์ ํจ์๋ฅผ ํ๋ ๋ง๋ค์ด ์ฐ๊ฒฐํด์ค๋ค.
void onSearchPressed() {
final results = testTickets(
destination: _selectedDestination,
date: _selectedDate,
);
results.sort((a, b) => a.price.compareTo(b.price));
setState(() {
_results = results;
});
}
๊ทธ๋ฆฌ๊ณ ์๊น ๋ง๋ ์ ๋ ฅ UI ์๋์ ๋ฆฌ์คํธ๋ทฐ๋ฅผ ์ถ๊ฐํด์ค๋ค.
Widget _buildOutput()
{
return Expanded(
child: _results.isEmpty ? const Center(child: Text('๊ฒ์ ๊ฒฐ๊ณผ ์์')) :
ListView.builder(
itemCount: _results.length,
itemBuilder: (context, index) {
final flight = _results[index];
return ListTile(
title: Text(flight.airline),
subtitle: Text('์ถ๋ฐ ${flight.departTime}'),
trailing: Text('${flight.price}์'),
);
},
),
);
}

ํ
์คํธ์ฉ์ผ๋ก ํ๋์ฝ๋ฉํ ๋ฐ์ดํฐ๊ฐ ์ ๋์ค๋ ๋ชจ์ต์ด๋ค.
๋ค์์ ์ค์ ๋ฐ์ดํฐ ๋ก๋๋ฅผ ๊ตฌํํด ๋ณผ ์์ ์ด๋ค.