TextField안의 상단에 prefix 넣기

custom_form_field.dart

import 'package:flutter/material.dart';

import '../constants.dart';

class CustomFormField extends StatelessWidget {
  const CustomFormField({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        TextFormField(
            textAlignVertical: TextAlignVertical.bottom,
            decoration: InputDecoration(
                contentPadding: EdgeInsets.only(top: 30, left: 20, bottom: 10),
                hintText: "근처 추천 장소",
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(10),
                ))),
        Positioned(
          left: 20,
          top: 8,
          child: Text(
            "위치",
            style: kTextFormBody2Style,
          ),
        ),
      ],
    );
  }
}

설명

Stack안에 

TextFormField를 디자인하고 

position을 통해 prefix를 디자인 해준다.

 

728x90

키보드 높이 만큼 페이지 이동(동적스크롤링)

pages / login_page.dart

CustomTextFormField에 scrollAnimate 함수 넘겨줌

// statefulwidget으로 변경
class LoginPage extends StatefulWidget {
  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  final _formKey = GlobalKey<FormState>();
  //scrollController 생성
  late ScrollController scrollController;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    //초기화
    scrollController = new ScrollController();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: ListView(
          controller: scrollController,
          children: [
            SizedBox(height: 100),
            Logo("Login"),
            SizedBox(height: 50),
            buildLoginForm(),
            SizedBox(height: 50),
            TextButton(
              onPressed: () {
                if (_formKey.currentState!.validate()) {
                  Navigator.pushNamed(context, "/home");
                }
              },
              child: Text("Login"),
            ),
            SizedBox(height: 10),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text("New Here? "),
                GestureDetector(
                  onTap: () {
                    print("Join 클릭");
                  },
                  child: Text(
                    "Join",
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      decoration: TextDecoration.underline,
                    ),
                  ),
                )
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget buildLoginForm() {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          CustomTextFormField("Email", scrollAnimate),
          CustomTextFormField("Password", scrollAnimate),
        ],
      ),
    );
  }

  void scrollAnimate() {
    print("탭 클릭됨");
    // 6초 후에 실행
    Future.delayed(Duration(milliseconds: 600), () {
      // MediaQuery.of(context).viewInsets.bottom 하단 inset(사용못하는영역)크기 리턴
      // 사용못하는 영역만큼 1초 동안 easeIn으로 이동
      scrollController.animateTo(MediaQuery.of(context).viewInsets.bottom,
          duration: Duration(milliseconds: 100), curve: Curves.easeIn);
    });
  }
}

components / custom_text_form_field.dart

import 'package:flutter/material.dart';

class CustomTextFormField extends StatelessWidget {
  final String subtitle;
  //scrollAnimate 함수 받아오기
  final Function scrollAnimate;
  const CustomTextFormField(this.subtitle, this.scrollAnimate, {Key? key})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(subtitle),
        SizedBox(height: 5),
        TextFormField(
          onTap: () {
          // text field tab할때 실행
            scrollAnimate();
          },
          validator: (value) =>
              value!.isEmpty ? "Please Enter some text" : null,
          decoration: InputDecoration(
            hintText: "Enter $subtitle",
            focusedBorder: OutlineInputBorder(
              borderRadius: BorderRadius.circular(20),
            ),
            enabledBorder: OutlineInputBorder(
              borderRadius: BorderRadius.circular(20),
            ),
            errorBorder: OutlineInputBorder(
              borderRadius: BorderRadius.circular(20),
            ),
            focusedErrorBorder: OutlineInputBorder(
              borderRadius: BorderRadius.circular(20),
            ),
          ),
        ),
        SizedBox(height: 10),
      ],
    );
  }
}

 

728x90

Tab menu 구현

main.dart

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primaryColor: Colors.white,
        appBarTheme: AppBarTheme(
          iconTheme: IconThemeData(color: kColor1),
        ),
      ),
      home: Profile(),
    );
  }
}

// TabController 객체를 멤버로 만들어서 상태를 유지하기 때문에 StatefulWidget 클래스 사용
class Profile extends StatefulWidget {
  @override
  _ProfileState createState() => _ProfileState();
}

// SingleTickerProviderStateMixin 클래스는 애니메이션을 처리하기 위한 헬퍼 클래스
// 상속에 포함시키지 않으면 탭바 컨트롤러를 생성할 수 없다.
// mixin은 다중 상속에서 코드를 재사용하기 위한 한 가지 방법으로 with 키워드와 함께 사용
class _ProfileState extends State<Profile> with SingleTickerProviderStateMixin {
//tab controller 생성
  late TabController _tabController;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    //tabcontroller 초기화
    //vsync에 this를 넣기위해 initState안에서 초기화
    _tabController = new TabController(length: 2, vsync: this);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(15.0),
        child: Column(
          children: [
            TabBar(
              controller: _tabController,
              tabs: [
              	//첫번째 tab menu
                Tab(icon: Icon(Icons.directions_car)),
              	//두번째 tab menu               
                Tab(icon: Icon(Icons.directions_transit)),
              ],
            ),
            Expanded(
              child: TabBarView(
                controller: _tabController,
                //tab content
                children: [
                  // 첫번째 tab menu content
                  GridView.builder(
                    gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                      crossAxisCount: 3,
                      crossAxisSpacing: 10,
                    ),
                    itemBuilder: (context, index) {
                      return Image.network(
                          "https://picsum.photos/id/${index + 1}/200/200");
                    },
                  ),
                  // 두번째 tab menu content
                  Image.asset("assets/tab1.jpeg"),
                ],
              ),
            )
          ],
        ),
      ),
    );
  }
}

참고

https://www.youtube.com/watch?v=8B2uPE4lSeE&list=PL93mKxaRDidG6CYF5M_RbOXGkAGLtM_Ct&index=8 

 

728x90

하단 아이콘 클릭시 아이콘 배경 색 및 상단의 이미지 변경

main.dart

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primaryColor: kPrimaryColor,
        scaffoldBackgroundColor: kPrimaryColor,
      ),
      home: ShoppingCartPage(),
    );
  }
}

class ShoppingCartPage extends StatefulWidget {
  const ShoppingCartPage({Key? key}) : super(key: key);

  @override
  _ShoppingCartPageState createState() => _ShoppingCartPageState();
}

class _ShoppingCartPageState extends State<ShoppingCartPage> {
  int selectedIconNum = 0;
  List<String> selectPic = [
    "assets/p1.jpeg",
    "assets/p2.jpeg",
    "assets/p3.jpeg",
    "assets/p4.jpeg",
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          _productPic(),
          _productSelector(),
        ],
      ),
    );
  Widget _productSelector() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 10),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: [
        //버튼에 자신의 번호, 선택된 번호, 아이콘, setState를 넘겨줌
          ProductIcon(0, selectedIconNum, Icons.directions_bike, changeIcon),
          ProductIcon(1, selectedIconNum, Icons.motorcycle, changeIcon),
          ProductIcon(
              2, selectedIconNum, CupertinoIcons.car_detailed, changeIcon),
          ProductIcon(3, selectedIconNum, CupertinoIcons.airplane, changeIcon),
        ],
      ),
    );
  }

  Widget _productPic() {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: AspectRatio(
        aspectRatio: 5 / 3,
        child: Image.asset(
        //setState로 현재 선택된 num을 받아와 리스트에서 이미지를 찾음
          selectPic[selectedIconNum],
          fit: BoxFit.cover,
        ),
      ),
    );
  }

  void changeIcon(int num) {
  //components의 버튼에 함수를 넘겨 main에서 state를 변경
    setState(() {
      selectedIconNum = num;
    });
  }
}

components/product_icon.dart

import 'package:flutter/material.dart';

import '../constants.dart';

class ProductIcon extends StatelessWidget {
  final int productNum;
  final int selectedIconNum;
  final IconData mIcon;
  final Function changeIcon;

  const ProductIcon(
  //main에서 받아와야하는 값 정의
    this.productNum,
    this.selectedIconNum,
    this.mIcon,
    this.changeIcon, {
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 70,
      height: 70,
      decoration: BoxDecoration(
      // 선택된 버튼, 선택안된 버튼 배경색 삼항 연산자로 정의
        color: productNum == selectedIconNum ? kAccentColor : kSecondaryColor,
        borderRadius: BorderRadius.circular(20),
      ),
      child: IconButton(
        onPressed: () {
        //받아온 change함수에 선택된 num 보내서 main에서 state 변경
          changeIcon(productNum);
        },
        icon: Icon(
          mIcon,
          color: Colors.black,
        ),
      ),
    );
  }
}

참고

https://www.youtube.com/watch?v=adA3vPVlh4Q&list=PL93mKxaRDidG6CYF5M_RbOXGkAGLtM_Ct&index=19

 

728x90

+ Recent posts