Skip to content

Latest commit

 

History

History
753 lines (607 loc) · 20.7 KB

File metadata and controls

753 lines (607 loc) · 20.7 KB

Basics - ElevatedButton


🗂️ Widget Catalog Index

├ Basics  
│   └ ✅ ElevatedButton ← 현재 문서  
├ Layout  
├ Text  
├ Input  
...
└ Accessibility  

개념

ElevatedButton은 Material Design에서 제공하는 기본 버튼 위젯이다.
평면적인 레이아웃에 입체감을 주는 방식으로, 눌렀을 때 살짝 떠오르는 효과가 있다.
보통 강조 버튼이나 시각적으로 강조하고 싶은 액션에 사용된다.

이 버튼은 on/off 개념의 toggle 버튼과는 다르게, 현재 눌렸는지 여부만 판단하는 단발성 액션 버튼이다.
버튼을 누르면 약속된 동작을 실행한다.


동작 방식

  • 자식으로 보통 Text, Icon, Row 등을 받아 레이블을 구성한다.
  • 눌렸을 때 elevation(입체감)이 증가하며, 배경색과 전경색은 스타일로 지정 가능하다.
  • onPressed에 콜백이 지정되지 않으면 버튼은 비활성(disabled) 상태가 된다.
  • 스타일 지정은 styleFrom 또는 ButtonStyle 클래스를 통해 할 수 있다.
  • 버튼이 눌리면 약간 떠오르는 듯한 입체 효과가 적용되며, 이는 elevation 속성을 통해 제어할 수 있다. 기본값은 2이며, 눌리면 8까지 증가한다.

생성자

ElevatedButton({
  Key? key,
  required VoidCallback? onPressed,
  Widget? child,
  ButtonStyle? style,
  ...
})

아이콘과 텍스트를 함께 넣으려면 ElevatedButton.icon 생성자를 사용한다.
자세한 내용과 스타일 커스터마이징 예시는 아이콘 버튼 섹션 참고.



onPressed란?

onPressed는 버튼이 눌렸을 때 실행할 콜백을 정의하는 속성이다.
ElevatedButton에서 이 속성은 필수이며, null이면 버튼은 비활성화된다.

onPressed는 버튼이 눌렸을 때 실행되는 콜백 함수이다. 이 속성은 null이면 버튼은 비활성화되고, 사용자가 눌러도 반응하지 않는다.

ElevatedButton(
  onPressed: () {
    print("버튼이 눌렸습니다");
  },
  child: Text('Press Me'),
)
  • null이면 버튼은 비활성 상태가 되며, 클릭할 수 없다.
ElevatedButton(
  onPressed: null,
  child: Text('Disabled'),
)

// 별도 함수로 정의하는 경우
void handleClick() {
  print("clicked");
}

ElevatedButton(
  onPressed: handleClick,
  child: Text('Press'),
)

⚠️ onPressed는 생략할 수 없으며, null을 명시적으로 지정해야 비활성 상태가 된다.



버튼 비활성화시 헷갈릴 수 있는 점

기능개발을 우선하고 추후에 버튼 활성화 여부를 수정하고자 할 때 자칫 헷갈릴 수 있다.

onPressed: nullonPressed: () => null은 비슷해 보이지만 동작이 완전히 다르다.

  • onPressed: null

    • 버튼이 완전히 비활성(disabled) 상태가 된다.
    • 버튼 색상이 흐려지고, 눌러도 아무 반응이 없다.
    • 실제로 버튼이 비활성화되어 사용자 입력을 받지 않는다.
  • onPressed: () => null

    • onPressed에 함수가 할당되어 있기 때문에 버튼은 활성(enabled) 상태로 보인다.
    • 버튼을 누르면 함수가 실행되지만, 함수 내용이 아무 동작도 하지 않을 뿐이다.
    • 즉, 시각적으로 버튼이 활성 상태이며, 눌렀을 때 "눌림 효과"가 나타난다.

예시 코드

// 버튼 비활성화
ElevatedButton(
  onPressed: null,
  child: Text('Disabled'),
)

// 여전히 활성 상태 (누르면 실행되지만 아무 일도 안 함)
ElevatedButton(
  onPressed: () => null,
  child: Text('Still Enabled'),
)

⚠️ 비활성화를 원한다면 반드시 onPressed 자체를 null로 지정해야 한다.
onPressed: () => null은 버튼이 활성 상태로 남으니 주의!



Style

ElevatedButton은 Style을 styleFromButtonStyle 방식으로 구현할 수 있다.

styleFrom은 ButtonStyle을 내부적으로 생성하는 팩토리 함수이다.
때문에 간단하고 일괄적으로 생성할 때, 사용한다.

ButtonStyle은 각 비주얼적인 요소마다 WidgetStateProperty를 이용해 클릭하는 경우, 마우스가 올라가는 경우 등 버튼의 상태를 체크하며 부여한다.

그렇기 때문에 조건처리가 다양해질수록 길어진다.

모든 상태 동일하게 적용하려면 ButtonStyle 에서 상태를 .all로 생성하거나 styleFrom을 사용하면 된다.

아래는 대표적인 스타일 지정 예시다.

// styleFrom로 일괄 적용하기
ElevatedButton(
  onPressed: () {},
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.green,
    foregroundColor: Colors.white,
    elevation: 4,
    padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(12),
    ),
  ),
  child: Text('StyleFrom'),
),

// ButtonStyle로 상태별 적용하기 - 일괄
ElevatedButton(
  onPressed: () {},
  style: ButtonStyle(
    // 일괄 적용
    backgroundColor: WidgetStateProperty.all(Colors.red),
    foregroundColor: WidgetStateProperty.all(Colors.white),
    shape: WidgetStateProperty.all(
      RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(16),
      ),
    ),
    
    // 상태별 적용 - 예1
    elevation:
      WidgetStateProperty.resolveWith((states) {
        return states.contains(WidgetState.pressed) ? 8 : 4;
    }),
    
    // 상태별 적용 - 예2
    overlayColor: WidgetStateProperty.resolveWith<Color?>(
                            (Set<WidgetState> states) {
        // 클릭 액션
        if (states.contains(WidgetState.pressed)) {
          return Colors.blue.withValues(alpha : 0.2);
        }
      
        // 호버 액션, 웹에서만 동작
        if (states.contains(WidgetState.hovered)) {
          return Colors.red.withValues(alpha: 0.1);
        }
      
        return null; // 기본 없음
      },
    ),
    
  ),
  child: Text('ButtonStyle'),
),




아이콘 버튼

아이콘이 포함된 버튼은 ElevatedButton.icon 생성자를 사용한다.
텍스트와 아이콘을 나란히 배치할 수 있으며, 일반 버튼과 동일하게 스타일 지정이 가능하다.

ElevatedButton.icon(
  onPressed: () {},
  icon: Icon(Icons.thumb_up),
  label: Text('Like'),
)

아이콘 버튼도 styleFrom 또는 ButtonStyle로 스타일 커스터마이징이 가능하다.

// styleFrom 예시
ElevatedButton.icon(
  onPressed: () {},
  icon: Icon(Icons.thumb_up),
  label: Text('Like'),
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.indigo,
    foregroundColor: Colors.white,
    padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
    elevation: 6,
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(12),
    ),
  ),
)

아이콘 버튼의 다양한 스타일 예시는 아래 예제 2에서 확인할 수 있다.



🧪 Sample Code


예제 1: 기본 버튼

// 1-1. 가장 기본적인 ElevatedButton
ElevatedButton(
  onPressed: () {
    print("Button pressed");
  },
  child: Text('Click Me'),
)

// 1-2. 여러 동작을 한 번에 처리
ElevatedButton(
  onPressed: () => {
    print("Button pressed"),
    print("Hello Flutter")
  },
  child: Text('Click Me'),
)

// 1-3. 동작을 별도 함수로 분리
void doSomething() {
  print('button Pressed');
}

ElevatedButton(
  onPressed: doSomething,
  child: Text('Click Me'),
)
// 버튼 동작과 비활성화 처리

ElevatedButton(
  onPressed: null,
  child: Text('Disabled'),
)

onPressed에는 클릭시 동작을 선언하는 곳이다.
여기에 null이 들어가게 되면 비활성화 처리된다.

혹은 상태변수를 두고 클릭시 비활성화 처리할 수도 있다.

bool isEnabled = true;

ElevatedButton(
  onPressed: isEnabled ? () {
    setState(() {
      isEnabled = false;
    });
  } : null,
  child: Text('Disabled'),
)

아래 전체코드에서는 비동기로 1초 기다린후 다시 원래대로 돌아가는 소스가 추가되어있다.

예제 2: 아이콘 버튼

2-1. 기본 아이콘 버튼

ElevatedButton.icon(
  onPressed: () {},
  icon: Icon(Icons.thumb_up),
  label: Text('Like'),
)

2-2. 스타일 지정 (styleFrom)

ElevatedButton.icon(
  onPressed: () {},
  icon: Icon(Icons.thumb_up),
  label: Text('Like'),
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.indigo,
    foregroundColor: Colors.white,
    padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
    elevation: 6,
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(12),
    ),
  ),
)

2-3. 스타일 지정 (ButtonStyle)

ElevatedButton.icon(
  onPressed: () {},
  icon: Icon(Icons.thumb_up),
  label: Text('Icon ButtonStyle'),
  style: ButtonStyle(
    backgroundColor: WidgetStateProperty.all(Colors.red),
    foregroundColor: WidgetStateProperty.all(Colors.white),
  ),
)

예제 3: 스타일 커스터마이징

3-1. styleFrom 사용

ElevatedButton(
  onPressed: () {},
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.green,
    foregroundColor: Colors.white,
    elevation: 4,
    padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(12),
    ),
  ),
  child: Text('StyleFrom'),
),

3-2. ButtonStyle 사용 (상태별)

  ElevatedButton(
    onPressed: () {},
    // 일괄 적용
    style: ButtonStyle(
      backgroundColor: WidgetStateProperty.all(Colors.red),
      foregroundColor: WidgetStateProperty.all(Colors.white),
      
    shape: WidgetStateProperty.all(
      RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(16),
      ),
    ),
    
    
    // 상태별 분기처리
    elevation: WidgetStateProperty.resolveWith((states) {
      return states.contains(WidgetState.pressed) ? 8 : 4;
    }),
    
    overlayColor: WidgetStateProperty.resolveWith<Color?>(
      (Set<WidgetState> states) {
        if (states.contains(WidgetState.pressed)) {
          return Colors.blue.withValues(alpha: 0.2);
        }
        if (states.contains(WidgetState.hovered)) {
          return Colors.red.withValues(alpha: 0.1);
        }
        return null;
      },
    ),
  ),
  child: Text('Custom Style'),
),

예제 4: 상태 기반 처리

4-1. 비활성화/활성화 (onPressed: null)

ElevatedButton(
  onPressed: null,
  child: Text('Disabled'),
)

4-2. 상태 변수로 동적 활성/비활성화

bool isEnabled = true;

ElevatedButton(
  onPressed: isEnabled ? () {
    setState(() {
      isEnabled = false;
    });
  } : null,
  child: Text('Disabled'),
)

4-3. 1초간 비활성화/로딩 처리

bool isEnabled = true;

ElevatedButton(
  onPressed: 
    isEnabled ? () {
      setState(() {
        isEnabled = false;
      });
      
      Future.delayed(Duration(seconds: 1), () {
        setState(() {
          isEnabled = true;
        });
      });
    } : null,
  child: Text(isEnabled ? 'Disable Me' : 'Disabled'),
)


// State의 변수로 선언
bool isLoading = false;

ElevatedButton(
  onPressed: isLoading ? null : () {
    debugPrint('start Loading');
    setState(() {
      isLoading = true;
    });
    Future.delayed(Duration(seconds: 1), () {
      debugPrint('stop Loading');
      setState(() {
        isLoading = false;
      });
    });
  },
  child: isLoading
      ? SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2))
      : Text('Start Loading'),
)

예제 5: 통합 예제 (다양한 스타일, 상태, 아이콘 등)

class MainScreen extends StatefulWidget {
  const MainScreen({super.key});

  @override
  State<MainScreen> createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {
  bool isEnabled = true;
  bool isLoading = false;

  @override
  Widget build(BuildContext context) {
    return  Scaffold(
        body: SafeArea(
            child: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [

                  Text('기본 버튼'),
                  ElevatedButton(
                    onPressed: () {
                     debugPrint('Simple Click');
                    },
                    child: Text('Simple'),
                  ),

                  SizedBox(height: 30),

                  Text('Button Style 상태별 제어',
                    style: TextStyle(fontWeight: FontWeight.bold),
                  ),

                  SizedBox(height: 5),
                  Text('클릭 : elevation 변경'),
                  ElevatedButton(
                    onPressed: () {},
                    style: ButtonStyle(
                      backgroundColor: WidgetStateProperty.all(Colors.red),
                      foregroundColor: WidgetStateProperty.all(Colors.white),
                      shape: WidgetStateProperty.all(
                        RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(16),
                        ),
                      ),
                      elevation: WidgetStateProperty.resolveWith((states) {
                        return states.contains(WidgetState.pressed) ? 8 : 4;
                      }),
                    ),
                    child: Text('Elevation'),
                  ),

                  Text('호버 : red, 클릭 : blue'),

                  ElevatedButton(
                    onPressed: () {},
                    style: ButtonStyle(
                      overlayColor: WidgetStateProperty.resolveWith<Color?>(
                            (Set<WidgetState> states) {
                          // 클릭 액션
                          if (states.contains(WidgetState.pressed)) {
                            return Colors.blue.withValues(alpha : 0.2);
                          }
                          // 호버 액션, 웹에서만 동작
                          if (states.contains(WidgetState.hovered)) {
                            return Colors.red.withValues(alpha: 0.1);
                          }
                          return null; // 기본 없음
                        },
                      ),
                    ),
                    child: Text('Custom Highlight'),
                  ),

                  SizedBox(height: 20),
                  Text('StyleFrom 적용',
                    style: TextStyle(fontWeight: FontWeight.bold),
                  ),
                  ElevatedButton(
                    onPressed: () {},
                    style: ElevatedButton.styleFrom(
                      backgroundColor: Colors.green,
                      foregroundColor: Colors.white,
                      elevation: 4,
                      padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(12),
                      ),
                    ),
                    child: Text('StyleFrom'),
                  ),

                  SizedBox(height: 30),
                  Text('Icon 버튼'),
                  ElevatedButton.icon(
                    onPressed: () {},
                    icon: Icon(Icons.thumb_up),
                    label: Text('Simple Icon'),
                  ),

                  SizedBox(height: 5),
                  Text('ButtonStyle'),
                  ElevatedButton.icon(
                    onPressed: () {},
                    icon: Icon(Icons.thumb_up),
                    label: Text('Icon ButtonStyle'),
                    style: ButtonStyle(
                      backgroundColor: WidgetStateProperty.all(Colors.red),
                      foregroundColor: WidgetStateProperty.all(Colors.white),
                    ),
                  ),

                  SizedBox(height: 5),
                  Text('StyleFrom'),
                  ElevatedButton.icon(
                    onPressed: () {},
                    icon: Icon(Icons.thumb_up),
                    label: Text('Icon StyleFrom'),
                    style: ElevatedButton.styleFrom(
                      backgroundColor: Colors.indigo,
                      foregroundColor: Colors.white,
                      padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
                      elevation: 6,
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(8),
                      ),
                    ),
                  ),

                  SizedBox(height: 30),
                  Text('1초간 비활성화 모드'),
                  ElevatedButton(
                    onPressed: isEnabled
                        ? () {
                      setState(() {
                        isEnabled = false;
                      });
                      Future.delayed(Duration(seconds: 1), () {
                        setState(() {
                          isEnabled = true;
                        });
                      });
                    }
                        : null,
                    child: Text(isEnabled ? 'Disable Me' : 'Disabled'),
                  ),

                  SizedBox(height: 5),
                  Text('1초간 로딩 처리'),
                  ElevatedButton(
                    onPressed: isLoading ? null : () {
                      debugPrint('start Loading');
                      setState(() {
                        isLoading = true;
                      });

                      Future.delayed(Duration(seconds: 1), () {
                        debugPrint('stop Loading');
                        setState(() {
                          isLoading = false;
                        });
                      });
                    },
                    child: isLoading
                        ? SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2))
                        : Text('Start Loading'),
                  ),

                ],
              ),
            )
        )
    );
  }
}
  • 이 예제는 다양한 스타일, 상태 제어, 로딩 UI, 커스텀 highlight 등을 통합적으로 보여준다.

  • 참고영상



ElevatedButtonTheme를 이용한 일괄 스타일 적용하기

  • ElevatedButtonTheme를 사용하면 트리 하위의 모든 ElevatedButton에 공통 스타일을 지정할 수 있다.
  • 앱 전체에 버튼 스타일을 일괄 적용하고 싶을 때 유용하다.
// 현재 하위에만 테마를 적용하고 싶은 경우
Column(
  children: [
    ElevatedButtonTheme(
      data: ElevatedButtonThemeData(
        style: ElevatedButton.styleFrom(backgroundColor: Colors.orange),
      ),
      child: Column(
        children: [
          ElevatedButton(onPressed: () {}, child: Text('오렌지 1')),
          ElevatedButton(onPressed: () {}, child: Text('오렌지 2')),
        ],
      ),
    ),
    ElevatedButton(onPressed: () {}, child: Text('디폴트')), // 적용 안됨
  ],
)

// 앱전체에 적용하고 싶은 경우
MaterialApp(
  theme: ThemeData(
    elevatedButtonTheme: ElevatedButtonThemeData(
      style: ElevatedButton.styleFrom(
        backgroundColor: Colors.deepPurple,
        foregroundColor: Colors.white,
        textStyle: TextStyle(fontSize: 16),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(10),
        ),
      ),
    ),
  ),
  home: MyHomePage(),
)



✅ FilledButton과의 차이

  • ElevatedButton: 기본적으로 눌렀을 때 elevation이 생기며 입체감이 강조된다.
  • FilledButton: 배경은 있으나 elevation이 없고, 평면적인 느낌이다. → Flat하고 미니멀한 UI를 만들고 싶다면 FilledButton도 고려할 수 있다.



✅ iconAlignment 속성

  • ElevatedButton.icon에서 iconAlignment 속성을 통해 아이콘의 정렬 위치를 조정할 수 있다.
  • 아이콘과 텍스트 배치 위치를 더 정밀하게 컨트롤하고 싶을 때 사용한다.



관련 위젯

  • TextButton – 배경 없이 텍스트로만 구성된 버튼
  • OutlinedButton – 외곽선만 있는 버튼
  • FilledButton – 배경은 있으나 elevation은 없음
  • IconButton – 아이콘만 있는 버튼
  • ButtonStyleButton – 커스텀 버튼 구현용 베이스 클래스



History

  • 250708 : 초안 작성
  • 250709 : ElevatedButtonTheme 추가
  • 250818 : 비활성화 관련 내용 추가