κ°μ μκ°
μλ νμΈμ μ½λν©ν 리μ λλ€! μ κ° μ΅κ·Όμ νμ§λΌλ μνλ₯Ό λ΄€λλ°μ μ―λ€λ₯Ό μΉλ μ μΈκ²½μ΄er λ‘ νν¬ μΉ΄λ λ€μ§λ μ λλ©μ΄μ μ λ§λ€μ΄λ³΄λ €κ³ ν©λλ€.
μ΄λ―Έμ§ λ€μ΄λ‘λ
μ μΌλ¨μ μλ μ΄λ―Έμ§λ€μ λ€μ΄λ°μ μ£ΌμΈμ. μλΆν° μλλ‘ κ°κ° νν¬μ λ·λ©΄, 3κ΄, 8κ΄μ λλ€.
μ΄ μΉ΄λλ₯Ό λ λλ§νλ ν¨μλ₯Ό λ¨Όμ μμ± ν΄λ³΄κ² μ΅λλ€.
renderCard({
Key key,
bool isBack = true,
bool isThree = true,
}) {
assert(key != null);
String basePath = 'assets/card_flip/';
if (isBack) {
basePath += 'back.jpg';
} else {
if (isThree) {
basePath += '3.jpg';
} else {
basePath += '8.jpg';
}
}
}
μμ£Ό κ°λ¨ν ν¨μμμ. ValueKey
λ₯Ό λ°μμ Container
μ
μΈμ νΈ ν΄μ£Όκ³ (μ λλ©μ΄μ
μ ν λ κ°μ μμ ―μ μ¬μ©ν κ²½μ° μμ ―κ° κ΅¬λΆμ
νκΈ°μν΄ key κ°μ΄ κΌ νμν©λλ€) isBack
κ³Ό isThree
νλΌλ―Έν°λ₯Ό
μ¬μ©ν΄μ νΉμ μ΄λ―Έμ§λ₯Ό λΆλ¬λ΄κ³ μμ΄μ.
μ΄λ―Έμ§ λ λλ§νκΈ°
λ€μμ renderBack
κ³Ό renderFront
ν¨μλ₯Ό μμ±ν΄μ 38 κ΄λ‘μ
μ μ΄λ―Έμ§ λλ λ€ μ΄λ―Έμ§λ₯Ό μμ ―ν ν΄λ³΄κ² μ΅λλ€.
renderFront({
bool isThree = true,
}) {
return renderCard(
key: ValueKey(isThree ? 3 : 2),
isBack: false,
isThree: isThree,
);
}
renderBack() {
return renderCard(
key: ValueKey(false),
isBack: true,
);
}
λ³λ‘ μμ΄λ ΅μ£ ?
AnimatedSwitcher μ¬μ©νκΈ°
κ·ΈλΌ AnimatedSwitcher
μμ ―μ μ¬μ©ν΄μ κ°λ¨νκ²
Fade μ λλ©μ΄μ
μ λ§λ€μ΄λ³Όκ²μ
bool showFront;
void initState() {
super.initState();
showFront = false;
}
Widget build(BuildContext context) {
return DefaultAppbarLayout(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: GestureDetector(
onTap: () {
setState(() {
showFront = !showFront;
});
},
child: AnimatedSwitcher(
duration: Duration(milliseconds: 300),
child: showFront ? renderFront() : renderBack(),
),
),
),
],
),
);
}
μ΄λ―Έμ§λ₯Ό νλ©΄μ μ€μμ ν¬μ§μ νκ³ μ΄λ―Έμ§λ₯Ό λλ₯Όλλ§λ€ μκ³Ό λ€κ° λ°λλ μ λλ©μ΄μ μ λ£μμ΅λλ€.
μ¬κΈ°κΉμ§ λ³λ‘ μμ΄λ ΅μ£ ? κ·Έλ°λ° μ λλ©μ΄μ μ΄ μ ν¬κ° μνλ μ λλ©μ΄μ μ΄ μλμμ. Fade μ λλ©μ΄μ μ΄ μλκ³ μλ©΄μ΄ λ·λ©΄μΌλ‘ λ€μ§νκ³ λ·λ©΄μ΄ μλ©΄μΌλ‘ λ€μ§νλ μ λλ©μ΄μ μ λ§λ€κ³ μΆμ΄μ.
AnimatedBuilder λ‘ λ€μ§λ μ λλ©μ΄μ λ§λ€κΈ°
AnimatedSwitcher
μ transitionBuilder
νλΌλ―Έν°λ₯Ό
μ΄μ©νλ©΄ μ½κ² Flip μ λλ©μ΄μ
μ λ§λ€ μ μμ΄μ.
Widget wrapAnimatedBuilder(Widget widget, Animation<double> animation){
final rotate = Tween(begin: pi, end: 0.0).animate(animation);
return AnimatedBuilder(
animation: rotate,
child: widget,
builder: (_, widget){
return Transform(
transform: Matrix4.rotationY(rotate.value),
child: widget,
alignment: Alignment.center,
);
},
);
}
Widget build(BuildContext context) {
return DefaultAppbarLayout(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: GestureDetector(
onTap: () {
setState(() {
showFront = !showFront;
});
},
child: AnimatedSwitcher(
transitionBuilder: wrapAnimatedBuilder,
duration: Duration(milliseconds: 300),
child: showFront ? renderFront() : renderBack(),
),
),
),
],
),
);
}
wrapAnimatedBuilder
ν¨μλ₯Ό μμ±νκ³ AnimatedSwitcher
μ
transitionBuilder
νλΌλ―Έν°μ λ£μ΄μ€¬μ΄μ.
μ΄μ μλμ κ°μ΄ Flip μ λλ©μ΄μ μ΄ μλν΄μ. μ΄λμ λλ μ§μ§ μΉ΄λλ₯Ό λ€μ§λΈκ² κ°μ λλμ΄ λκΈ΄ νλ€μ.
νμ§λ§ μμ§ λΆμ‘±ν©λλ€. λκ° λΆμμ°μ€λ½κ² μΉ΄λκ° λ€ λ€μ§νκΈ° μ μ μ΄λ―Έμ§κ° λ³κ²½λλκ±Έ λ³Ό μ μμ΄μ. μ΄κ±Έ νλ² ν΄κ²° ν΄λ³Όκ²μ.
μλ©΄μ λ°λ§ λ리기
μλ©΄μ΄ λ° μ΄μ λμκ°λ©΄ λ·λ©΄ μ΄λ―Έμ§λ‘ μμ ―μ λ³κ²½μ ν΄μΌν©λλ€. κ·ΈλμΌ
μμ°μ€λ½κ² μλ©΄μ΄ λ·λ©΄μΌλ‘ λ°λλ μ λλ©μ΄μ
μ μμ±ν μ μμ΄μ. κ·Έλ¬κΈ°
μν΄μ μ΄λ―Έμ§λ₯Ό ν νμλ νμ¬ λ·λ©΄μ΄ μ΄λ€ μ΄λ―Έμ§μΈμ§ νμ
μ ν΄μΌλΌμ.
μ ν¬κ° μ€μ ν΄λμ key
νλΌλ―Έν°λ₯Ό μ΄μ©νλ©΄ μλ©΄κ³Ό λ·λ©΄μ μμ£Ό μ½κ²
ꡬλΆν μ μμ΅λλ€.
Widget wrapAnimatedBuilder(Widget widget, Animation<double> animation) {
final rotate = Tween(begin: pi, end: 0.0).animate(animation);
return AnimatedBuilder(
animation: rotate,
child: widget,
builder: (_, widget) {
final isBack = showFront
? widget.key == ValueKey(false)
: widget.key != ValueKey(false);
final value = isBack ? min(rotate.value, pi / 2) : rotate.value;
return Transform(
transform: Matrix4.rotationY(value),
child: widget,
alignment: Alignment.center,
);
},
);
}
μΆκ°μ μΌλ‘ layoutBuilder
λ₯Ό μλμ κ°μ΄ κ³ μ³μ£ΌμΈμ. AnimatedSwitcher
λ μνλ³κ²½μ λ°λ μκ° λ°λ‘ μμ ―μ λ³κ²½νλλ° μ ν¬λ μμ°μ€λ½κ² 첫λ²μ§Έ
μμ ―μ΄ λλ²μ§Έ μμ ―μΌλ‘ λ³κ²½λλκ±Έ μνκΈ° λλ¬Έμ νμ¬ μμ ―μ λ€μ μ΅μμλ‘
μ¬λ €μ£Όλ μμ
μ ν΄μΌν©λλ€.
Widget build(BuildContext context) {
return DefaultAppbarLayout(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: GestureDetector(
onTap: () {
setState(() {
showFront = !showFront;
});
},
child: AnimatedSwitcher(
transitionBuilder: wrapAnimatedBuilder,
layoutBuilder: (widget, list) {
return Stack(
children: [widget, ...list],
);
},
duration: Duration(milliseconds: 1000),
child: showFront ? renderFront() : renderBack(),
),
),
),
],
),
);
}
μ΄μ νν¬κ° μ λ€μ§μ΄μ§λ κ±Έ λ³Ό μ μμ΄μ. νμ§λ§ μμ§ 1νλ‘ λΆμ‘±ν΄μ. μ λ§ μμ°μ€λ½κ² λ€μ§μΌλ €λ©΄ μκ°μ ν¨κ³Όλ₯Ό 첨κ°ν νμκ° μμ΄μ.
Perspective νμ©νκΈ°
Flutter Perspective Matrix μ λν΄μλ μ¬κΈ°
μμ λ μμΈν μμλ³Ό μ μμ΄μ. μ΄λ²μλ wrapAnimationBuilder
μ perspective λ₯Ό μΆκ°ν΄μ λ©λ¦¬μλ λΆλΆμ΄ λ μ§§κ³ κ°κΉμ΄ μλλΆλΆμ΄
λ κΈΈκ² λ³΄μ΄κ² ν¨κ³Όλ₯Ό μ€μ μμ°μ€λ½κ² μΉ΄λκ° λ€μ§νλ ν¨κ³Όλ₯Ό ꡬν ν΄λ³Όκ²μ.
Widget wrapAnimatedBuilder(Widget widget, Animation<double> animation) {
final rotate = Tween(begin: pi, end: 0.0).animate(animation);
return AnimatedBuilder(
animation: rotate,
child: widget,
builder: (_, widget) {
final isBack = showFront
? widget.key == ValueKey(false)
: widget.key != ValueKey(false);
final value = isBack ? min(rotate.value, pi / 2) : rotate.value;
var tilt = ((animation.value - 0.5).abs() - 0.5) * 0.0025;
tilt *= isBack ? -1.0 : 1.0;
return Transform(
transform: Matrix4.rotationY(value)..setEntry(3, 0, tilt),
child: widget,
alignment: Alignment.center,
);
},
);
}
μ μ½λλ₯Ό μ μ©νλ©΄ μλμ κ°μ κ²°κ³Όλ₯Ό μ»μ μ μμ΄μ.
μ΄λ€κ°μ? ν¨μ¬ λ μμ°μ€λ¬μμ‘μ£ ? μ΄μ 38 κ΄λ‘μ λλν μμΉμμΌ λ€μ§λ μμ ―μ μ μνκ³ λ§λ¬΄λ¦¬ ν κ²μ.
List<bool> showFronts;
void initState() {
super.initState();
showFronts = [false, false];
}
Widget build(BuildContext context) {
return DefaultAppbarLayout(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
GestureDetector(
onTap: () {
setState(() {
showFronts = [!showFronts[0], showFronts[1]];
});
},
child: AnimatedSwitcher(
transitionBuilder: (Widget widget, Animation<double> animation){
return wrapAnimatedBuilder(widget, animation, showFronts[0]);
},
layoutBuilder: (widget, list) {
return Stack(
children: [widget, ...list],
);
},
duration: Duration(milliseconds: 1000),
child: showFronts[0] ? renderFront() : renderBack(),
),
),
GestureDetector(
onTap: () {
setState(() {
showFronts = [showFronts[0], !showFronts[1]];
});
},
child: AnimatedSwitcher(
transitionBuilder: (Widget widget, Animation<double> animation){
return wrapAnimatedBuilder(widget, animation, showFronts[1]);
},
layoutBuilder: (widget, list) {
return Stack(
children: [widget, ...list],
);
},
duration: Duration(milliseconds: 1000),
child: showFronts[1] ? renderFront(
isThree: false,
) : renderBack(),
),
),
],
),
],
),
);
}
κ²°κ³Όλ¬Όμ μλμ κ°μ΅λλ€~