flutter 공식 문서 예제에서 볼 수 있듯이
PopupMenuButton은
1) 처음에 누르는 버튼 (PopupMenuButton)
2) 누르는 이벤트에 의해 나열되는 버튼들 (PopupMenuItem) 로 구성되어 있다.
문제점 무조건 아래로만 열린다는 점이다!!
Flutter에서 제공하는 PopupMenuButton의 속성에서 PupupMenuItem들이 옆으로 나열되게 하는 속성은 없다.
const PopupMenuButton({
super.key,
required this.itemBuilder,
this.initialValue,
this.onOpened,
this.onSelected,
this.onCanceled,
this.tooltip,
this.elevation,
this.shadowColor,
this.surfaceTintColor,
this.padding = const EdgeInsets.all(8.0),
this.child,
this.splashRadius,
this.icon,
this.iconSize,
this.offset = Offset.zero,
this.enabled = true,
this.shape,
this.color,
this.enableFeedback,
this.constraints,
this.position,
this.clipBehavior = Clip.none,
}) : assert(itemBuilder != null),
assert(enabled != null),
assert(
!(child != null && icon != null),
'You can only pass [child] or [icon], not both.',
);
따라서 옆으로 열리는 PopupMenuButton을 만들기 위해서는 커스텀이 필요하다.
Flutter에서 'abstract'는 클래스를 추상화(abstract)하는 키워드입니다.
추상화는 해당 클래스나 멤버가 구체적인 구현을 갖지 않고 선언만 되어 있는 상태를 의미합니다.
추상 멤버가 뭐야?
추상 멤버는 추상 클래스 내에서 선언되는 멤버(메서드, 속성)입니다.
추상 멤버는 구현을 가지지 않고 선언만 되어 있는 멤버입니다.
이 멤버는 서브 클래스에서 반드시 구현되어야 합니다.
추상 멤버를 가진 클래스는 반드시 추상 클래스로 선언되어야 합니다.
추상 클래스를 상속받은 서브클래스에서는 추상 멤버를 모두 구현해야만 해당 클래스를 인스턴스화 할 수 있습니다.
abstract class Animal {
//추상 멤버
late String name;
//추상 멤버
void makeSound();
void eat() {
print('$name is eating.');
}
}
class Dog extends Animal {
late String name;
Dog(this.name);
void makeSound() {
print('Dog barks!');
}
}
void main() {
var dog = Dog('Buddy');
dog.makeSound();
dog.eat();
}
추상 클래스는 인스턴스화할 수 없으며, 다른 클래스에서 상속받아 사용되는 것을 목적으로 합니다.
추상 클래스는 일반 클래스와 달리 추상 멤버를 포함할 수 있습니다.
추상 멤버는 선언만하고 실제 구현은 서브 클래스에서 해야 합니다.
추상 클래스를 선언할 때는 'abstract' 키워드를 클래스 앞에 붙입니다.
class PopupMenuItem<T> extends PopupMenuEntry<T>
PopupMenuItem은 PopupMenuEntry를 상속받고 있고
abstract class PopupMenuEntry<T> extends StatefulWidget {
provide
const PopupMenuEntry({ super.key });
double get height;
bool represents(T? value);
}
PopupMenuEntry는 height와 represents를 추상멤버로 가지는 추상 클래스라는걸 알 수 있다.
즉, height와 represents를 설정해주면 다른 요소들을 커스텀하여 사용할 수 있다는 것이다.
import 'package:eot_flutter/widgets/button/hover_button.dart';
import 'package:flutter/material.dart';
class Popup extends StatefulWidget {
const Popup({Key? key}) : super(key: key);
State<Popup> createState() => _PopupState();
}
class _PopupState extends State<Popup> {
bool isLiked = true;
Widget build(BuildContext context) {
return Column(
children: [
PopupMenuButton(
shadowColor: Colors.transparent,
color: Colors.transparent,
position: PopupMenuPosition.under,
icon : isLiked ? Icon(Icons.thumb_up_off_alt,color: Colors.black,) : Icon(Icons.thumb_down_off_alt,color: Colors.black,),
onSelected: (dynamic? value){
setState(() {
isLiked = value;
});
},
itemBuilder: (BuildContext context){
return [
_PopupMenuWidget(height: 80)
];
}
),
],
);
}
}
class _PopupMenuWidget<T> extends PopupMenuEntry<T> {
const _PopupMenuWidget({
Key? key,
required this.height,
// required this.child,
}) : super(key: key);
//final Widget child;
final double height;
_PopupMenuWidgetState createState() => _PopupMenuWidgetState();
bool represents(T? value) => false;
}
class _PopupMenuWidgetState extends State<_PopupMenuWidget> {
bool _hovering = false;
Color get color {
Color baseColor = Colors.red;
if (_hovering) {
baseColor = Color.alphaBlend(Colors.black.withOpacity(0.1), baseColor);
}
return baseColor;
}
Widget build(BuildContext context) {
return Row(
children: [
IconButton(onPressed: (){}, icon: Icon(Icons.close_rounded)),
IconButton(onPressed: (){}, icon: Icon(Icons.thumb_up_off_alt)),
IconButton(onPressed: (){}, icon: Icon(Icons.thumb_down_off_alt)),
],
);
}
}
import 'package:flutter/material.dart';
class HoverBtn extends StatefulWidget {
final BorderRadiusGeometry? borderStyle;
final Icon emoticon;
final bool? value;
final String name;
const HoverBtn({required this.name, this.borderStyle, required this.emoticon, this.value,Key? key}) : super(key: key);
State<HoverBtn> createState() => _HoverBtnState();
}
class _HoverBtnState extends State<HoverBtn> {
bool _hovering = false;
Color get color {
Color baseColor = Color(0xffC4C4C4);
if (_hovering) {
baseColor = Color.alphaBlend(Colors.black.withOpacity(0.1), baseColor);
}
return baseColor;
}
void _handleHoveHighlight(bool value) {
setState(() {
_hovering = value;
});
}
Widget build(BuildContext context) {
return FocusableActionDetector(
onShowHoverHighlight: _handleHoveHighlight,
child: Container(
decoration: BoxDecoration(
shape: widget.name == "cancel" ? BoxShape.circle: BoxShape.rectangle,
borderRadius: widget.borderStyle,
color: color
),
child: PopupMenuItem(
value: widget.value,
child: widget.emoticon,
),
),);
}
}
출근길에 코드팩토리님 동영상을 보고 있는데 abstract키워드를 보고 퍼뜩 커스텀 해야겠다고 하시는 모습이 인상 깊었다.