기본적으로 가로로 스크롤하기, 이미지URL 사용하기, 텍스트필드 클리어, 키보드활용시 바디가 밀려올라오지 않게하기.
를 집중적으로 해보았다. 아직 배워가는 과정이라 그 외에의 추가적인 기능을 넣기에는 힘들었지만. 일단 오늘의 목표치까지 한것에 대해서 작성하겠습니다.
완성코드
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final fieldText = TextEditingController();
bool showIcon = false;
clearText() {
fieldText.clear();
}
@override
Widget build(BuildContext context) {
// list
List<Map<String, dynamic>> nowList = [
{
"category": "챌린저스",
"imgUrl":
"https://pics.filmaffinity.com/challengers-325765300-large.jpg",
},
{
"category": "보이킬즈월드",
"imgUrl":
"https://pics.filmaffinity.com/boy_kills_world-795596169-large.jpg",
},
{
"category": "신데렐라의 복수",
"imgUrl":
"https://pics.filmaffinity.com/cinderella_s_revenge-197605583-large.jpg",
},
{
"category": "캐쉬아웃",
"imgUrl": "https://pics.filmaffinity.com/cash_out-787149656-large.jpg",
},
{
"category": "브레스",
"imgUrl": "https://pics.filmaffinity.com/breathe-938032787-large.jpg",
},
];
List<Map<String, dynamic>> dataList = [
{
"category": "탑건: 매버릭",
"imgUrl": "https://i.ibb.co/sR32PN3/topgun.jpg",
},
{
"category": "마녀2",
"imgUrl": "https://i.ibb.co/CKMrv91/The-Witch.jpg",
},
{
"category": "범죄도시2",
"imgUrl": "https://i.ibb.co/2czdVdm/The-Outlaws.jpg",
},
{
"category": "헤어질 결심",
"imgUrl": "https://i.ibb.co/gM394CV/Decision-to-Leave.jpg",
},
{
"category": "브로커",
"imgUrl": "https://i.ibb.co/MSy1XNB/broker.jpg",
},
{
"category": "문폴",
"imgUrl": "https://i.ibb.co/4JYHHtc/Moonfall.jpg",
},
];
return Scaffold(
appBar: AppBar(
actions: [
Padding(
padding: const EdgeInsets.all(8.0),
child: IconButton(
onPressed: () {
print("tap person icon");
},
icon: Icon(Icons.person),
),
)
],
centerTitle: false,
title: Padding(
padding: const EdgeInsets.all(2.0),
child: Text(
"MOVIE REVIEW",
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
),
),
resizeToAvoidBottomInset: false,
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(15.0),
child: TextField(
controller: fieldText,
onChanged: (value) {
setState(() {
showIcon = value.isNotEmpty;
});
},
decoration: InputDecoration(
hintText: "영화제목을 입력해주세요.",
suffixIcon: showIcon
? IconButton(
onPressed: () {
setState(() {
clearText();
showIcon = false;
print("clearText");
});
},
icon: Icon(Icons.clear),
)
: null,
labelStyle: TextStyle(color: Colors.black),
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Row(
children: [
Text(
"추천 영화",
style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
textAlign: TextAlign.left,
),
],
),
),
Expanded(
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: dataList.length,
itemBuilder: (context, index) {
String category = dataList[index]["category"];
String imgUrl = dataList[index]["imgUrl"];
return Stack(
alignment: Alignment.center,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 3.0),
child: Image.network(
imgUrl,
width: 250,
height: 250,
fit: BoxFit.cover,
),
),
Container(
width: 250,
height: 250,
color: Colors.black.withOpacity(0.5),
),
Text(
category,
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Colors.white,
),
)
],
);
},
),
),
Row(
children: [
Text(
"현재 상영중인 영화",
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
],
),
Expanded(
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: nowList.length,
itemBuilder: (context, index) {
String category = nowList[index]["category"];
String imgUrl = nowList[index]["imgUrl"];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 3.0),
child: Stack(
alignment: Alignment.center,
children: [
Image.network(
imgUrl,
width: 250,
height: 250,
fit: BoxFit.cover,
),
Container(
width: 250,
height: 250,
color: Colors.black.withOpacity(0.5),
),
Text(
category,
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Colors.white,
),
)
],
),
);
},
),
),
],
),
);
}
}
우선 첫번째로
TextField의 모양새와 clear하기 버튼만들기, 텍스트에 입력값을 받아야 아이콘을 나타내기 부터 해보도록 하겠습니다.
textClear
1. 우선 textClear 를 하려면 내가 사용하고자 하는 페이지의 스테이트를 StatefulWidget으로 바꿔줘야합니다.
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
2. TextField의 상태를 감지하는 TextEditingController를 선언해주고 clearText라는 함수를 선언해주고 내용에 Textfield를 clear시키는 코드를 넣어주고, 그 아래 icon을 보여줄지 말지를 하는 bool 변수를 선언해준다.
class _HomePageState extends State<HomePage> {
final fieldText = TextEditingController();
clearText() {
fieldText.clear();
}
bool showIcon = false;
3. TextField에 controller를 달아준뒤, onChanged에 값을 받을때마다 setState가 실행되며 그 setState는 showIcon에게 inNotEmpty를 전달하게 해줍니다.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(50.0),
child: TextField(
controller: fieldText, // 컨트롤러
// 텍스트 입력이 변경될 때마다 호출되는 콜백 함수.
onChanged: (value) {
setState(() {
showIcon = value.isNotEmpty;
});
},
4. TextField 우측에 아이콘을 달기위한 suffixIcon을 사용하며 선언했던 ShowIcon 아래로 ShowIcon을 만족하면 IconButton 변수를 실행하고 아닐시에 null을 실행하는 코드를 짜줍니다.
decoration: InputDecoration(
hintText: "Hint Text", // 힌트텍스트
suffixIcon: showIcon
? IconButton(
onPressed: () {
setState(() {
clearText();
showIcon = false;
});
},
icon: Icon(Icons.clear),
)
: null,
5. 그 외에 TextField내부에 decoration도 함께 이것저것 탐험하면서 이상 TextField clear 및 버튼활성, 비활성화 코드입니다.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final fieldText = TextEditingController();
clearText() {
fieldText.clear();
}
bool showIcon = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(50.0),
child: TextField(
controller: fieldText, // 컨트롤러
// 텍스트 입력이 변경될 때마다 호출되는 콜백 함수.
onChanged: (value) {
setState(() {
showIcon = value.isNotEmpty;
});
},
decoration: InputDecoration(
hintText: "Hint Text", // 힌트텍스트
suffixIcon: showIcon
? IconButton(
onPressed: () {
setState(() {
clearText();
showIcon = false;
});
},
icon: Icon(Icons.clear),
)
: null,
border: OutlineInputBorder(
// 텍스트박스 아웃라인
borderRadius: BorderRadius.all(
// 텍스트박스 아웃라인 둥글게
Radius.circular(30),
),
),
focusedBorder: OutlineInputBorder(
// 텍스트박스가 활성화 되었을때
// 활성화 됐을때 아웃라인 둥글게 할지말지
borderRadius: BorderRadius.circular(double.infinity),
// 활성화 됐을때 아웃라인 색깔 검정
borderSide: BorderSide(color: Colors.black),
),
),
),
),
),
);
}
}
ListView 가로로 활용하기
1. Map형식 List를 사용했지만 일반적인 imgUrl만 사용한다면 일반적인 List도 상관없이 사용가능합니다.
우선 List명은 nowList라고 지정해주었습니다.
각기 원하는 방식으로 하위항목에 Listview.builder를 선언해줍니다.
그리고 중요한 ScrollDirection: Axis.horizontal을 선언해주면 가로형식으로 스크롤이 가능한 ListView가 완성이 됩니다.
그 아래에 itemCount는 nowList의 index 숫자 만큼 나오게 설정해주고
itemBuilder로는 List 내부 index의 "imgUrl"이란 문자열이 imgUrl 이란 변수가 되게 선언해줍니다.
return Scaffold(
body: Center(
child: Stack(
children: [
ListView.builder(
scrollDirection: Axis.horizontal, // 가로 리스트뷰
itemCount: nowList.length, // 리스트뷰 갯수
itemBuilder: (context, index) {
String imgUrl = nowList[index]["imgUrl"]; // index 정보 변수화
2. alignment로 center값을 줬고 그 아래 Image.network를 통해 URL에 있는 이미지들을 Stack에 입혀주었습니다.
return Stack(
alignment: Alignment.center,
children: [
Image.network(
imgUrl,
width: 250,
height: 250,
fit: BoxFit.cover,
3. ListView 가로 스크롤 전체 코드
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
// list
List<Map<String, dynamic>> nowList = [
{
"category": "챌린저스",
"imgUrl":
"https://pics.filmaffinity.com/challengers-325765300-large.jpg",
},
{
"category": "보이킬즈월드",
"imgUrl":
"https://pics.filmaffinity.com/boy_kills_world-795596169-large.jpg",
},
{
"category": "신데렐라의 복수",
"imgUrl":
"https://pics.filmaffinity.com/cinderella_s_revenge-197605583-large.jpg",
},
{
"category": "캐쉬아웃",
"imgUrl": "https://pics.filmaffinity.com/cash_out-787149656-large.jpg",
},
{
"category": "브레스",
"imgUrl": "https://pics.filmaffinity.com/breathe-938032787-large.jpg",
},
];
return Scaffold(
body: Center(
child: Stack(
children: [
ListView.builder(
scrollDirection: Axis.horizontal, // 가로 리스트뷰
itemCount: nowList.length, // 리스트뷰 갯수
itemBuilder: (context, index) {
String imgUrl = nowList[index]["imgUrl"]; // index 정보 변수화
return Stack(
alignment: Alignment.center,
children: [
Image.network(
imgUrl,
width: 250,
height: 250,
fit: BoxFit.cover,
),
],
);
},
),
],
),
),
);
}
}
여담으로
간혹 텍스트 필드를 사용하며 키보드를 사용할 때에 키보드가 올라옴과 동시에 body영역이 한꺼번에 밀려 올라오는 경우가 있습니다.
이같은 경우에는 body영역 위에다
resizeToAvoidBottomInset: false,
이 코드를 사용해주면
이렇듯 정상적으로 키보드만 올라오는걸 확인할 수 있습니다.
'Flutter > App Making Learn' 카테고리의 다른 글
플러터 당근마켓 앱 카피코딩3 (피드정리) (0) | 2024.05.06 |
---|---|
플러터 당근마켓 앱 카피코딩2 (좋아요 기능 구현, 피드리스트) (0) | 2024.05.02 |
플러터 당근마켓 앱 카피코딩1 (appBar, body, floatingActionButton, bottomNavigationBar) (4) | 2024.04.30 |
[플러터] 로그인화면 만들기 (1) | 2024.04.26 |
간단한 로그인 화면 만들기 (2) | 2024.03.13 |