Flutter 기본

Flutter 초기 폴더 구조

스크린샷 2023-01-27 오전 9.10.07.png

assets

Main 페이지 만들기

class MyApp extends StatelessWidget { 
	const MyApp({Key? key}) : super(key: key); 
	
	@override 
	Widget build(BuildContext context) {
	
		return MaterialApp( 
			home: Text('안녕'), 
		); 
	} 
}

기본적인 위젯

Text()

return MaterialApp(
	home: Text('안녕')
)

Icon()

return MaterialApp(
	home: Icon(Icons.star)
)

Image()

return MaterialApp(
	home: Image.asset('assets/dog.png')
)

Container()

return MaterialApp(
	home: Container()
)
return MaterialApp(  
  home: Container(width: 50, height: 50, color: Colors.blue)  
);

! 300

return MaterialApp(  
  home: Center(  
   child: Container(width: 50, height: 50, color: Colors.blue),  
  )  
);

Flutter의 레이아웃

return MaterialApp(  
  home: Scaffold(  
    appBar: 상단에 넣을 위젯,  
    body: 중단에 넣을 위젯,  
    bottomNavigationBar: 하단에 넣을 위젯,  
  ),  
);
return MaterialApp(  
  home: Scaffold(  
    appBar: AppBar(  
      title:  
      Text('앱제목'),  
    ),  
    body: Text('안녕'),  
    bottomNavigationBar: BottomAppBar(  
      child: Text('하단바임 ㅅㄱ'),  
    ),  
  ),  
);

스크린샷 2023-01-27 오전 10.22.07.png|300

return MaterialApp(  
  home: Scaffold(  
    appBar: AppBar(  
      title:  
      Text('앱제목'),  
    ),  
    body: Text('안녕'),  
    bottomNavigationBar: BottomAppBar(  
        child: Container(  
            height: 60,  
            child: Row(  
              mainAxisAlignment: MainAxisAlignment.spaceAround,  
              children: [  
                Icon(Icons.phone),  
                Icon(Icons.message),  
                Icon(Icons.contact_page),  
              ],  
            )  
        )  
    ),  
  ),  
);

! 500

Flutter에서 Box 디자인

Margin, Padding

return MaterialApp(  
  home: Scaffold(  
    appBar: AppBar( ... ),  
    body: SizedBox(  
      child: Container(  
		// 아래의 EdgeInsets.all은 모든 방향      
		margin: EdgeInsets.all(30),  
		//fromLTRB는 각각의 방향을 제어 할 수 있다.
        padding: EdgeInsets.fromLTRB(10, 20, 30, 40),  
          
	    color: Colors.green  
      ),  
    ),  
    bottomNavigationBar: BottomAppBar( ... ),  
  ),  
);

! 500

참고

Row(), Colume() 이런 곳은 안되고 오직 Container() 에만 여백을 줄 수 있습니다.
Row() 에 여백을 주고 싶으면 Container() 위젯을 안쪽이나 바깥쪽이나 아무데나 추가하면 된다.

그외 기타

return MaterialApp(
	Container(
		decoration: BoxDecoration(
			border : Border.all(color : Colors.black)
		)
	)
)

Typography

return MaterialApp(
	home: Scaffold(  
		body: SizedBox(  
			child: Container(  
			    child: Text(  
				    '글자임',  
				    style: TextStyle(  
			        color: Colors.red,  
			        backgroundColor: Colors.orange,  
			        fontSize: 30,  
			        fontWeight: FontWeight.w600,  
			        letterSpacing: 3,  
			        ),  
			    ),  
			),  
		),
	)
)

색상 넣는 방법

color: Colors.red,
color: Color.fromRGBO(20, 130, 50, 0.8),
color: Color(0xffffffff)

자주 사용하는 button style

return MaterialApp(  
  home: Scaffold(  
    appBar: AppBar( ... ),  
    body: SizedBox( ... ),  
    bottomNavigationBar: BottomAppBar(  
        child: Container(  
            height: 60,  
            child: Row(  
              mainAxisAlignment: MainAxisAlignment.spaceAround,  
              children: [  
                TextButton(onPressed: (){},  
                    child: Text('버튼임')  
                ),  
                ElevatedButton(  
                    child:  
                    Text('버튼임',),  
                    onPressed: (){},  
                ),  
                IconButton(  
                  icon:Icon(Icons.star),  
                  onPressed: (){},  
                ),  
              ],  
            )  
        )  
    ),  
  ),  
);

! 500

AppBar 디자인

스크린샷 2023-01-27 오후 2.57.17.png

AppBar( 
	title : Text('앱제목'), 
	leading : Icon(Icons.star), 
	actions : 
		[ 
			Icon(Icons.star), 
			Icon(Icons.star) 
		] 
	)
문법 설명
title 제목
leading 제목 왼쪽 아이콘
actions 제목 오른쪽 아이콘

기본적인 디자인 레이아웃 실습

Pasted image 20230127150054.png

body: Container( 
	height: 150, 
	child: Row( 
		children: [ 
			Image.asset('camera.jpg', width : 150), 
			Expanded( 
				child : Column( 
					crossAxisAlignment: CrossAxisAlignment.start, 
					children: [ 
						Text('카메라팝니다'), 
						Text('금호동 3가'), 
						Text('7000원'), 
						Row( 
							children: const [ 
								Icon(Icons.favorite), 
								Text('4') 
							], 
						), //Row 
					], 
				), //Column 
			), 
		], 
	), 
),

Flexible(), Expanded()

Flexible() 위젯

Row(
	Childern:[
		Flexible( child: Container(color : Colors.blue ), flex: 1),
		Flexible( child: Container(color: Colors.green ), flex: 1)
	]
)

Expended() 위젯

Row( 
	children : [ 
		Expanded( child: Container(color : Colors.blue), flex : 1 ), 
		Container(Color : Colors.green, width : 100), 
	] 
)

ListView.builder 버튼

ListTile()

스크린샷 2023-02-01 오전 8.39.19.png|400

ListTile(){
	leading : Image.asset('assets/profile.png'),
	title: Text('홍길동')
}

목록을 동적으로 만들어야할 때 - ListView.builder()

! 400


ListView.builder(
  itemCount: 3,
  itemBuilder: (context, i) {
    print(i);
    return ListTile(
      leading : Image.asset('profile.png'),
      title : Text('홍길동'),
    )
  }
);  

floatingActionButton

! 400

onPressed(){}

class MyApp extends StatelessWidget { 
	MyApp({Key? key}) : super(key: key); 
	var a = 1; 
	
	@override 
	Widget build(BuildContext context) { 
		return MaterialApp( home : Scaffold( 
			floatingActionButton: FloatingActionButton( 
				child: Text(a.toString()), 
				onPressed: (){ a++; print(a); }, ), 
				body: , 
				appBar: AppBar(), 
			) 
		); 
	} 
}

StatefullWidget

위젯 또한 재렌더링을 해야 변경사항이 보인다.

StatefullWidget 만들기

커스텀 위젯으로 만들기

class 테스트 extends StatefulWidget { 
	const 테스트({Key? key}) : super(key: key); 
	@override 
	_테스트State createState() => _테스트State();
} 
class _테스트State extends State { 
	var a = 1; //여기 만드는 변수는 state가 됩니다 
	@override 
	Widget build(BuildContext context) { 
		return Container(); 
	} 
}

기존 위젯을 StatefullWidget으로 변경하기

class MyApp extends StatefulWidget { 
	const MyApp({Key? key}) : super(key: key); 
	@override 
	_MyAppState createState() => _MyAppState(); 
} 
class _MyAppState extends State { 
	var a = 1; //여기 만드는 변수는 state가 됩니다 
	@override 
	Widget build(BuildContext context) { 
		return Scaffold(생략); 
	} 
}
MaterialApp(   
	home: Scaffold( 
		floatingActionButtion: FloatingActionButton( 
			child : Text(a.toString()), 
			onPressed: (){ 
				setState((){ 
					a++ 
				}); 
			} 
		),
		appBar: AppBar(),    
	body: 생략   
	), 
)

Text를 동적으로 만들기

class _MyAppState extends State { \
	var a = 1; 
	var name = ['김영숙', '홍길동', '피자집']; 
	@override 
	(생략) 
}
ListView.builder( 
	itemCount: 3, 
	itemBuilder: (context, i) { 
		return ListTile( 
			leading : Image.asset('profile.png'), 
			title : Text(name[i]), 
		) 
	} 
);

Dialog/모달창

버튼 만들기

MaterialApp( 
	home: Scaffold( 
		floatingActionButton: FloatingActionButton( 
		child: Text('버튼'), 
		onPressed: (){}, 
	), 
(생략)

showDialog() 함수

FloatingActionButton( 
	child: Text('버튼'), 
	onPressed: () { 
		showDialog( 
			context: context, 
			builder: (context){ 
				return Dialog( 
					child: Text('AlertDialog Title'), 
				);
			}, 
		); 
	}, 
),
void main() { 
	runApp( 
		MaterialApp( 
			home : MyApp() 
		) 
	); 
} 
class _MyAppState extends State<MyApp> { 
	@override 
	Widget build(BuildContext context) { 
		return Scaffold( 안쪽 생략 );

Why?

class 위젯명 extends StatelessWidget { 
	@override 
	build (context) { 
		어쩌구 생략
Context에 대한 이야기

! 400

class MyApp extends StatelessWidget { 
	(생략) 
	build (context) { 
		return MaterialApp( 
			home : Scaffold( 
				body : 커스텀위젯()
			) 
		); 
	} 
} 
class 커스텀위젯 extends StatelessWidget { 
	(생략)
	build (context) { 
		return Text('안녕'); 
	} 
}
Class 커스텀 위젯안에 Context에 담겨있는 정보?

커스텀 위젯의 모든 조상들의 정보를 담고 있습니다.
MaterialApp
Scaffold
이런 것들이 담겨있습니다.

그럼 ClassMyApp 안에 있는 context는?

MyApp 위젯의 모든 조상들에 대한 정보를 담겨 있는 변수 입니다.
하지만, 최상위이기 때문에 아무것도 든게 없습니다.

Flutter에는 특별한 함수들이 있습니다.

showDialog()
Navigator()
Theme.of()
Scaffold.of()
이런 함수들은 context를 (족보를)소괄호 안에 집어넣어야 잘 작동하는 함수 입니다.

showDialog()
showDialog( context : MaterialApp이 부모로 들어있는 족보)

자식 위젯이 부모 위젯의 state를 사용하고 싶다면

부모 -> 자식 state 전송하는 법 #1

부모 -> 자식 state 전송 순서

  1. 보내기
  2. 자식은 state 이름을 등록
  3. 자식은 사용

보내기

(MyApp 안에 DialogUI() 쓰던 곳)
DialogUI(state : a)

자식 위젯 정의 부분에서 파라미터 등록

class DialogUI extends StatelessWidget{
	DialogUI({Key? key, this.state}) : super(key, key);
	final state;
}

자식 위젯에서 사용

부모 위젯 -> 자식 위젯만 가능하며 그 반대는 성립하지 않습니다.

스크린샷 2023-02-01 오후 1.44.54.png
혹은 아예 관련 없는 옆집 위젯끼리의 전송은 안됩니다.

그래서 많은 위젯을 사용해야하는 경우, 중요한 state는 최대한 위에 보관하는 것이 좋습니다.

부모 -> 자식 state 전송하는 법 - input message

TextEditingController()를 담는 변수를 하나 만듭니다.

class DialogUI extends StatelessWidget{
	DialogUI({Key? key, this.addOne}) : super(key: key);
	final addOne;
	var inputData = TextEditingController();
}

TextField() 위젯에 controller:

TextField(
	controller: inputData,
),

User에게 앱 권한 요청하기

Package 설치 필요

dependencies: 
	flutter: 
		sdk: flutter 
	permission_handler: ^8.3.0
import 'package:permission_handler/permission_handler.dart';

Andriod Setting

gradle.properties

android.useAndroidX=true 
android.enableJetifier=true

build.gradle

android{
	compileSdkVerion 31
	...
}

AndriodManifest.xml

<manifest 어쩌구> 

		<uses-permission android:name="android.permission.READ_CONTACTS"/> 
		<uses-permission android:name="android.permission.WRITE_CONTACTS"/> 
	
	<application 어쩌구>

iOS의 경우 M1 Mac

명령어 입력

cd ios
sudo arch -x86_64 gem install ffi
sudo arch -x86_64 gen install cocoapods

Podfile

post_install do |installer| 
	installer.pods_project.targets.each do |target| 
		flutter_additional_ios_build_settings(target) 
		target.build_configurations.each do |config| 
			config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ 
				'$(inherited)', 
				'PERMISSION_CONTACTS=1', 
				#추가할거 더 있으면 이 자리에 
			] 
		end 
	end 
end

Info.plist

<key>NSContactsUsageDescription</key>
<string>님 폰의 연락처 권한이 필요합니다 제발 주셈</string>

유저에게 사용 권한을 요청 후 Action

유저에게 사용 권한 요청하기

getPermission() async { 
	var status = await Permission.contacts.status; 
	if (status.isGranted) { 
		print('허락됨'); 
	} else if (status.isDenied) { 
		print('거절됨'); 
		Permission.contacts.request(); 
	} 
}
Andriod 11 버전 이상과 iOS에서는 유저가 한, 두번 이상 거절하면 다시는 팝업을 띄울 수 없습니다.

그 경우 앱 설정을 오픈해서 유저가 직접 설정을 바꿔야하기 때문에, 그래서 앱을 처음 로드할 때 말고,
진짜 연락처 기능이 필요해질 때 띄우는게 요즘 유행입니다.

AppBar( 
	title : Text('앱제목'), 
	actions : [ 
		IconButton(onPressed: (){ getPermission(); }, icon : Icon(Icons.contacts)) 
	] 
)
if(status.isPermanentlyDenied){
	openAppSetting();
}

유저의 연락처를 가져오는 법

사용자 연락처 가져오는 패키지

dependencies: 
	flutter: 
		sdk: flutter 
	permission_handler: ^8.2.6 
	contacts_service: ^0.6.3
import 'package:contacts_service/contacts_service.dart'; 

연락처를 다루는 패키지 사용법

var contacts = await ContactsService.getContacts();
var contacts = await ContractsService.getContacts(withThumbnails: false);
var contacts = await ContactsService.getContacts(withThumbnails: false); 
print(contacts[0].givenName)
var newContact = new Contact(); 
newContact.givenName = '민수'; 
await ContactsService.addContact(newContact);

실제 연락처를 list로 보여주기

(getPermission 함수 안쪽)

var contacts = await ContactsService.getContacts(withThumbnails: false);
print(contacts[0].givenName)

setState(() {
	name = contacts;
})

#Mobile #Flutter