১. ভূমিকা
| Material Components (MDC), ডেভেলপারদের Material Design প্রয়োগ করতে সাহায্য করে। Google-এর ইঞ্জিনিয়ার ও UX ডিজাইনারদের একটি টিম এটি তৈরি করেছে। MDC-তে অনেক সুন্দর ও কার্যকরী UI কম্পোনেন্ট আছে এবং এটি Android, iOS, ওয়েব ও Flutter-এর জন্য উপলভ্য। flutter.material.io/develop |
MDC-103 কোডল্যাবে, আপনার অ্যাপকে স্টাইল করার জন্য Material Components (MDC)-এর কালার, এলিভেশন, টাইপোগ্রাফি ও শেপ কাস্টমাইজ করেছেন।
মেটেরিয়াল ডিজাইন সিস্টেমের একটি কম্পোনেন্ট পূর্বনির্ধারিত টাস্কের সেট পারফর্ম করে এবং বোতামের মতো নির্দিষ্ট বৈশিষ্ট্য থাকে। তবে, বোতাম শুধু ব্যবহারকারীর অ্যাকশন করার উপায় নয়, এটি আকার, সাইজ ও রঙের ভিজ্যুয়াল এক্সপ্রেশন যা ব্যবহারকারীকে জানায় যে এটি ইন্টার্যাক্টিভ এবং টাচ বা ক্লিক করলে কিছু ঘটবে।
মেটেরিয়াল ডিজাইন নির্দেশিকা একজন ডিজাইনারের দৃষ্টিকোণ থেকে উপাদানগুলি বর্ণনা করে। এগুলি বিভিন্ন প্ল্যাটফর্ম জুড়ে উপলভ্য মৌলিক ফাংশনের বিস্তৃত পরিসর এবং প্রতিটি উপাদান তৈরি করে এমন শারীরবৃত্তীয় উপাদানগুলি বর্ণনা করে। যেমন, ব্যাকড্রপে একটি ব্যাক লেয়ার ও তার কন্টেন্ট, ফ্রন্ট লেয়ার ও তার কন্টেন্ট, মোশন সংক্রান্ত নিয়ম ও ডিসপ্লে বিকল্প থাকে। প্রতিটি অ্যাপের প্রয়োজন, ব্যবহারের ক্ষেত্র এবং কন্টেন্টের জন্য এইসব উপাদানের প্রতিটি কাস্টমাইজ করা যেতে পারে।
আপনি কী তৈরি করবেন
এই কোডল্যাবে, আপনি Shrine অ্যাপের UI পরিবর্তন করে "ব্যাকড্রপ" নামক একটি দুই-লেভেল প্রেজেন্টেশনে পরিণত করবেন। ব্যাকড্রপে একটি মেনু অন্তর্ভুক্ত থাকে যা অসমমিত গ্রিডে দেখানো প্রোডাক্ট ফিল্টার করতে ব্যবহৃত বাছাইযোগ্য বিভাগগুলির তালিকা করে। এই কোডল্যাবে, আপনি নিম্নলিখিত বিষয়গুলি ব্যবহার করবেন:
- আকার
- মোশন
- Flutter উইজেট (যা আপনি আগের কোডল্যাবে ব্যবহার করেছেন)
Android | iOS |
|
|
|
|
এই কোডল্যাবে Material Flutter কম্পোনেন্ট এবং সাবসিস্টেম
- আকার
Flutter ডেভেলপমেন্টের ক্ষেত্রে আপনার অভিজ্ঞতার লেভেলকে আপনি কত রেটিং দেবেন?
২. আপনার Flutter ডেভেলপমেন্ট এনভায়রনমেন্ট সেট-আপ করুন
এই ল্যাব সম্পূর্ণ করতে আপনার দুটি সফ্টওয়্যার প্রয়োজন— Flutter SDK এবং একটি এডিটর।
আপনি এইসব ডিভাইসের যেকোনও একটি ব্যবহার করে কোডল্যাব চালাতে পারবেন:
- আপনার কম্পিউটারের সাথে কানেক্ট করা এবং ডেভেলপার মোডে সেট করা একটি ফিজিক্যাল Android বা iOS ডিভাইস।
- iOS সিমুলেটর (Xcode টুল ইনস্টল করতে হবে)।
- Android Emulator (Android Studio-তে সেট-আপ করতে হবে)।
- ব্রাউজার (ডিবাগিংয়ের জন্য Chrome প্রয়োজন)।
- Windows, Linux, অথবা macOS ডেস্কটপ অ্যাপ্লিকেশন হিসেবে। আপনি যে প্ল্যাটফর্মে ডেপ্লয় করার পরিকল্পনা করছেন, সেখানেই আপনাকে ডেভেলপ করতে হবে। তাই, আপনি যদি Windows ডেস্কটপ অ্যাপ ডেভেলপ করতে চান, তাহলে উপযুক্ত বিল্ড চেইন অ্যাক্সেস করার জন্য আপনাকে অবশ্যই Windows-এ ডেভেলপ করতে হবে। অপারেটিং সিস্টেম-নির্দিষ্ট প্রয়োজনীয়তা আছে যা docs.flutter.dev/desktop লিঙ্কে বিস্তারিতভাবে উল্লেখ করা আছে।
৩. কোডল্যাব স্টার্টার অ্যাপ ডাউনলোড করুন
MDC-103 থেকে চালিয়ে যাচ্ছেন?
আপনি MDC-103 সম্পূর্ণ করে থাকলে, এই কোডল্যাবের জন্য আপনার কোড রেডি থাকা উচিত। এই ধাপে যান: ব্যাকড্রপ মেনু যোগ করুন।
একদম শুরু থেকে শুরু করছেন?
স্টার্টার অ্যাপটি material-components-flutter-codelabs-104-starter_and_103-complete/mdc_100_series ডিরেক্টরিতে আছে।
...অথবা GitHub থেকে ক্লোন করুন
GitHub থেকে এই কোডল্যাব ক্লোন করতে, এই কমান্ডগুলি রান করুন:
git clone https://github.com/material-components/material-components-flutter-codelabs.git cd material-components-flutter-codelabs/mdc_100_series git checkout 104-starter_and_103-complete
প্রোজেক্ট খুলে অ্যাপ রান করানো
- আপনার পছন্দের এডিটরে প্রোজেক্টটি খুলুন।
- আপনার বেছে নেওয়া এডিটরের জন্য শুরু করুন: টেস্ট ড্রাইভ-এ "অ্যাপ রান করুন" বিকল্পের জন্য নির্দেশাবলী অনুসরণ করুন।
হয়ে গেছে! আপনার ডিভাইসে আগের কোডল্যাব থেকে Shrine লগ-ইন পৃষ্ঠা দেখতে পাওয়া উচিত।
Android | iOS |
|
|
৪. ব্যাকড্রপ মেনু যোগ করুন
অন্যান্য সব কন্টেন্ট ও কম্পোনেন্টের পিছনে একটি ব্যাকড্রপ দেখা যায়। এটি দুটি লেয়ার নিয়ে গঠিত: একটি ব্যাক লেয়ার (যা অ্যাকশন ও ফিল্টার দেখায়) এবং একটি ফ্রন্ট লেয়ার (যা কন্টেন্ট দেখায়)। আপনি ইন্টার্যাক্টিভ তথ্য ও অ্যাকশন দেখানোর জন্য ব্যাকড্রপ ব্যবহার করতে পারেন, যেমন নেভিগেশন বা কন্টেন্ট ফিল্টার।
হোম অ্যাপ বার সরানো
হোমপেজ উইজেট আমাদের ফ্রন্ট লেয়ারের কন্টেন্ট হবে। বর্তমানে এতে একটি অ্যাপ বার আছে। আমরা অ্যাপ বারটিকে ব্যাক লেয়ারে সরিয়ে দেব এবং হোমপেজে শুধুমাত্র AsymmetricView অন্তর্ভুক্ত থাকবে।
home.dart-এ, build() ফাংশন পরিবর্তন করে শুধু AsymmetricView রিটার্ন করুন:
// TODO: Return an AsymmetricView (104)
return AsymmetricView(products: ProductsRepository.loadProducts(Category.all));
ব্যাকড্রপ উইজেট যোগ করুন
ব্যাকড্রপ নামের একটি উইজেট তৈরি করুন যাতে frontLayer ও backLayer অন্তর্ভুক্ত থাকে।
backLayer-এ একটি মেনু থাকে যা আপনাকে তালিকা ফিল্টার করার জন্য একটি বিভাগ বেছে নিতে দেয় (currentCategory)। যেহেতু আমরা চাই মেনু বেছে নেওয়ার বিষয়টি যেন থেকে যায়, তাই আমরা Backdrop-কে একটি স্টেটফুল উইজেট হিসেবে তৈরি করব।
/lib-এ backdrop.dart নামের নতুন ফাইল যোগ করুন:
import 'package:flutter/material.dart';
import 'model/product.dart';
// TODO: Add velocity constant (104)
class Backdrop extends StatefulWidget {
final Category currentCategory;
final Widget frontLayer;
final Widget backLayer;
final Widget frontTitle;
final Widget backTitle;
const Backdrop({
required this.currentCategory,
required this.frontLayer,
required this.backLayer,
required this.frontTitle,
required this.backTitle,
Key? key,
}) : super(key: key);
@override
_BackdropState createState() => _BackdropState();
}
// TODO: Add _FrontLayer class (104)
// TODO: Add _BackdropTitle class (104)
// TODO: Add _BackdropState class (104)
লক্ষ্য করুন যে আমরা নির্দিষ্ট কিছু প্রপার্টি required হিসেবে চিহ্নিত করি। কনস্ট্রাক্টরে প্রপার্টির জন্য এটি একটি সেরা পদ্ধতি যার কোনও ডিফল্ট ভ্যালু নেই এবং null হতে পারে না এবং তাই ভুলে যাওয়া উচিত নয়।
Backdrop ক্লাস ডেফিনিশনের অধীনে, _BackdropState ক্লাস যোগ করুন:
// TODO: Add _BackdropState class (104)
class _BackdropState extends State<Backdrop>
with SingleTickerProviderStateMixin {
final GlobalKey _backdropKey = GlobalKey(debugLabel: 'Backdrop');
// TODO: Add AnimationController widget (104)
// TODO: Add BuildContext and BoxConstraints parameters to _buildStack (104)
Widget _buildStack() {
return Stack(
key: _backdropKey,
children: <Widget>[
// TODO: Wrap backLayer in an ExcludeSemantics widget (104)
widget.backLayer,
widget.frontLayer,
],
);
}
@override
Widget build(BuildContext context) {
var appBar = AppBar(
elevation: 0.0,
titleSpacing: 0.0,
// TODO: Replace leading menu icon with IconButton (104)
// TODO: Remove leading property (104)
// TODO: Create title with _BackdropTitle parameter (104)
leading: Icon(Icons.menu),
title: Text('SHRINE'),
actions: <Widget>[
// TODO: Add shortcut to login screen from trailing icons (104)
IconButton(
icon: Icon(
Icons.search,
semanticLabel: 'search',
),
onPressed: () {
// TODO: Add open login (104)
},
),
IconButton(
icon: Icon(
Icons.tune,
semanticLabel: 'filter',
),
onPressed: () {
// TODO: Add open login (104)
},
),
],
);
return Scaffold(
appBar: appBar,
// TODO: Return a LayoutBuilder widget (104)
body: _buildStack(),
);
}
}
build() ফাংশনটি HomePage-এর মতো অ্যাপ বার সহ একটি স্ক্যাফোল্ড রিটার্ন করে। কিন্তু Scaffold-এর বডি হল একটি Stack। স্ট্যাকের চাইল্ড ওভারল্যাপ করতে পারে। Stack-এর পেরেন্টের সাপেক্ষে প্রতিটি চাইল্ডের সাইজ ও লোকেশন নির্দিষ্ট করা হয়।
এখন ShrineApp-এ একটি Backdrop ইনস্ট্যান্স যোগ করুন।
app.dart-এ, backdrop.dart ও model/product.dart ইমপোর্ট করুন:
import 'backdrop.dart'; // New code
import 'colors.dart';
import 'home.dart';
import 'login.dart';
import 'model/product.dart'; // New code
import 'supplemental/cut_corners_border.dart';
app.dart,-এ / রুট পরিবর্তন করতে, Backdrop রিটার্ন করুন যার frontLayer হিসেবে HomePage আছে:
// TODO: Change to a Backdrop with a HomePage frontLayer (104)
'/': (BuildContext context) => Backdrop(
// TODO: Make currentCategory field take _currentCategory (104)
currentCategory: Category.all,
// TODO: Pass _currentCategory for frontLayer (104)
frontLayer: HomePage(),
// TODO: Change backLayer field value to CategoryMenuPage (104)
backLayer: Container(color: kShrinePink100),
frontTitle: Text('SHRINE'),
backTitle: Text('MENU'),
),
আপনার প্রোজেক্ট সেভ করুন, আপনি দেখতে পাবেন যে আমাদের হোম পেজ এবং অ্যাপ বার দেখা যাচ্ছে:
Android | iOS |
|
|
backLayer-এ frontLayer হোম পেজের পিছনে একটি নতুন লেয়ারে গোলাপি রঙের অংশটি দেখানো হয়েছে।
Stack-এর HomePage-এর পিছনে যে Container আছে তা যাচাই করতে আপনি Flutter Inspector ব্যবহার করতে পারেন। এটি এর মতো হতে হবে:

আপনি এখন দুটি লেয়ারের ডিজাইন ও কন্টেন্ট অ্যাডজাস্ট করতে পারবেন।
৫. শেপ যোগ করা
এই ধাপে, উপরের বাঁদিকের কোণে একটি কাট যোগ করতে, আপনি ফ্রন্ট লেয়ার স্টাইল করবেন।
Material Design-এ এই ধরনের কাস্টমাইজেশনকে আকার হিসেবে উল্লেখ করা হয়। মেটেরিয়াল সারফেসের আকার যেকোনও ধরনের হতে পারে। আকৃতি সারফেসে জোর দেয় এবং স্টাইল যোগ করে এবং ব্র্যান্ডিং প্রকাশ করতে ব্যবহার করা যেতে পারে। সাধারণ আয়তাকার শেপকে কার্ভ বা অ্যাঙ্গেল করা কোণ ও প্রান্ত এবং যেকোনও সংখ্যক বাহু দিয়ে কাস্টমাইজ করা যেতে পারে। এগুলি প্রতিসম বা অনিয়মিত হতে পারে।
সামনের লেয়ারে একটি আকার যোগ করুন
Shrine অ্যাপের জন্য অ্যাঙ্গেল করা Shrine লোগো থেকে অনুপ্রাণিত হয়ে শেপ স্টোরি তৈরি করা হয়েছে। শেপ স্টোরি হল এমন একটি সাধারণ ব্যবহার যা অ্যাপ জুড়ে প্রয়োগ করা হয়। যেমন, লোগোর আকারটি লগ-ইন পৃষ্ঠার এলিমেন্টে প্রতিধ্বনিত হয়, যেখানে আকার প্রয়োগ করা হয়। এই ধাপে, আপনি উপরের-বাঁদিকের কোণে একটি কৌণিক কাট দিয়ে সামনের লেয়ারটি স্টাইল করবেন।
backdrop.dart-এ, একটি নতুন ক্লাস _FrontLayer যোগ করুন:
// TODO: Add _FrontLayer class (104)
class _FrontLayer extends StatelessWidget {
// TODO: Add on-tap callback (104)
const _FrontLayer({
Key? key,
required this.child,
}) : super(key: key);
final Widget child;
@override
Widget build(BuildContext context) {
return Material(
elevation: 16.0,
shape: const BeveledRectangleBorder(
borderRadius: BorderRadius.only(topLeft: Radius.circular(46.0)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
// TODO: Add a GestureDetector (104)
Expanded(
child: child,
),
],
),
);
}
}
তারপর, _BackdropState's _buildStack() ফাংশনে, ফ্রন্ট লেয়ারকে _FrontLayer-এ র্যাপ করুন:
Widget _buildStack() {
// TODO: Create a RelativeRectTween Animation (104)
return Stack(
key: _backdropKey,
children: <Widget>[
// TODO: Wrap backLayer in an ExcludeSemantics widget (104)
widget.backLayer,
// TODO: Add a PositionedTransition (104)
// TODO: Wrap front layer in _FrontLayer (104)
_FrontLayer(child: widget.frontLayer),
],
);
}
আবার লোড করুন।
Android | iOS |
|
|
আমরা Shrine-এর প্রাথমিক সারফেসকে একটি কাস্টম আকার দিয়েছি। তবে, আমরা চাই এটি যেন অ্যাপ বারের সাথে দৃশ্যত কানেক্ট করে।
অ্যাপ বারের রঙ পরিবর্তন করা
app.dart-এ, _buildShrineTheme() ফাংশনটি নিম্নলিখিত ফাংশনে পরিবর্তন করুন:
ThemeData _buildShrineTheme() {
final ThemeData base = ThemeData.light(useMaterial3: true);
return base.copyWith(
colorScheme: base.colorScheme.copyWith(
primary: kShrinePink100,
onPrimary: kShrineBrown900,
secondary: kShrineBrown900,
error: kShrineErrorRed,
),
textTheme: _buildShrineTextTheme(base.textTheme),
textSelectionTheme: const TextSelectionThemeData(
selectionColor: kShrinePink100,
),
appBarTheme: const AppBarTheme(
foregroundColor: kShrineBrown900,
backgroundColor: kShrinePink100,
),
inputDecorationTheme: const InputDecorationTheme(
border: CutCornersBorder(),
focusedBorder: CutCornersBorder(
borderSide: BorderSide(
width: 2.0,
color: kShrineBrown900,
),
),
floatingLabelStyle: TextStyle(
color: kShrineBrown900,
),
),
);
}
হট রিস্টার্ট। নতুন রঙিন অ্যাপ বারটি এখন দেখা যাবে।
Android | iOS |
|
|
এই পরিবর্তনের ফলে, ব্যবহারকারীরা দেখতে পাবেন যে সামনের সাদা লেয়ারের ঠিক পিছনে কিছু আছে। আসুন মোশন যোগ করি যাতে ব্যবহারকারীরা ব্যাকড্রপের ব্যাক লেয়ার দেখতে পান।
৬. মোশন যোগ করুন
মোশন হল আপনার অ্যাপকে জীবন্ত করে তোলার একটি উপায়। এটি বড় ও নাটকীয়, সূক্ষ্ম ও ন্যূনতম অথবা এর মাঝামাঝি যেকোনও কিছু হতে পারে। তবে মনে রাখবেন যে আপনি যে ধরনের মোশন ব্যবহার করেন তা পরিস্থিতির জন্য উপযুক্ত হতে হবে। পুনরাবৃত্তিমূলক, নিয়মিত অ্যাকশনের ক্ষেত্রে প্রয়োগ করা মোশন ছোট ও সূক্ষ্ম হওয়া উচিত, যাতে অ্যাকশনগুলি ব্যবহারকারীর মনোযোগ বিক্ষিপ্ত না করে বা নিয়মিতভাবে খুব বেশি সময় না নেয়। তবে, কিছু উপযুক্ত পরিস্থিতি আছে, যেমন, কোনও ব্যবহারকারী প্রথমবার অ্যাপ খুললে, সেটি আরও আকর্ষণীয় হতে পারে এবং কিছু অ্যানিমেশন ব্যবহারকারীকে আপনার অ্যাপ কীভাবে ব্যবহার করতে হয় তা শেখাতে সাহায্য করতে পারে।
মেনু বোতামে রিভিল মোশন যোগ করুন
backdrop.dart-এর সবচেয়ে উপরে, কোনও ক্লাস বা ফাংশনের স্কোপের বাইরে, আমরা অ্যানিমেশনের যে ভেলোসিটি চাই তা বোঝাতে একটি কনস্ট্যান্ট যোগ করুন:
// TODO: Add velocity constant (104)
const double _kFlingVelocity = 2.0;
_BackdropState-এ AnimationController উইজেট যোগ করুন, initState() ফাংশনে এটি ইনস্ট্যানশিয়েট করুন এবং স্টেটের dispose() ফাংশনে এটি ডিসপোজ করুন:
// TODO: Add AnimationController widget (104)
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 300),
value: 1.0,
vsync: this,
);
}
// TODO: Add override for didUpdateWidget (104)
@override
void dispose() {
_controller.dispose();
super.dispose();
}
// TODO: Add functions to get and change front layer visibility (104)
AnimationController, অ্যানিমেশন কোঅর্ডিনেট করে এবং অ্যানিমেশন চালাতে, রিভার্স করতে ও বন্ধ করতে API প্রদান করে। এখন আমাদের এমন ফাংশন দরকার যা এটিকে সরাতে পারে।
এমন ফাংশন যোগ করুন যা সামনের লেয়ারের দৃশ্যমানতা নির্ধারণ ও পরিবর্তন করে:
// TODO: Add functions to get and change front layer visibility (104)
bool get _frontLayerVisible {
final AnimationStatus status = _controller.status;
return status == AnimationStatus.completed ||
status == AnimationStatus.forward;
}
void _toggleBackdropLayerVisibility() {
_controller.fling(
velocity: _frontLayerVisible ? -_kFlingVelocity : _kFlingVelocity);
}
ExcludeSemantics উইজেটে backLayer র্যাপ করুন। ব্যাক লেয়ার দেখা না গেলে, এই উইজেট ব্যাকলেয়ারের মেনু আইটেমকে সিম্যান্টিক্স ট্রি থেকে বাদ দেবে।
return Stack(
key: _backdropKey,
children: <Widget>[
// TODO: Wrap backLayer in an ExcludeSemantics widget (104)
ExcludeSemantics(
child: widget.backLayer,
excluding: _frontLayerVisible,
),
...
BuildContext ও BoxConstraints নেওয়ার জন্য _buildStack() ফাংশন পরিবর্তন করুন। এছাড়াও, একটি PositionedTransition যোগ করুন যা একটি RelativeRectTween অ্যানিমেশন নেয়:
// TODO: Add BuildContext and BoxConstraints parameters to _buildStack (104)
Widget _buildStack(BuildContext context, BoxConstraints constraints) {
const double layerTitleHeight = 48.0;
final Size layerSize = constraints.biggest;
final double layerTop = layerSize.height - layerTitleHeight;
// TODO: Create a RelativeRectTween Animation (104)
Animation<RelativeRect> layerAnimation = RelativeRectTween(
begin: RelativeRect.fromLTRB(
0.0, layerTop, 0.0, layerTop - layerSize.height),
end: const RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),
).animate(_controller.view);
return Stack(
key: _backdropKey,
children: <Widget>[
// TODO: Wrap backLayer in an ExcludeSemantics widget (104)
ExcludeSemantics(
child: widget.backLayer,
excluding: _frontLayerVisible,
),
// TODO: Add a PositionedTransition (104)
PositionedTransition(
rect: layerAnimation,
child: _FrontLayer(
// TODO: Implement onTap property on _BackdropState (104)
child: widget.frontLayer,
),
),
],
);
}
অবশেষে, Scaffold-এর বডির জন্য _buildStack ফাংশন কল করার পরিবর্তে, একটি LayoutBuilder উইজেট রিটার্ন করুন যা এর বিল্ডার হিসেবে _buildStack ব্যবহার করে:
return Scaffold(
appBar: appBar,
// TODO: Return a LayoutBuilder widget (104)
body: LayoutBuilder(builder: _buildStack),
);
আমরা LayoutBuilder ব্যবহার করে লেআউট টাইম পর্যন্ত ফ্রন্ট/ব্যাক লেয়ার স্ট্যাকের বিল্ডে দেরি করেছি যাতে আমরা ব্যাকড্রপের আসল সামগ্রিক উচ্চতা অন্তর্ভুক্ত করতে পারি। LayoutBuilder হল একটি বিশেষ উইজেট যার বিল্ডার কলব্যাক সাইজ সংক্রান্ত সীমাবদ্ধতা প্রদান করে।
build() ফাংশনে, অ্যাপ বারে থাকা লিডিং মেনু আইকনকে IconButton-এ পরিবর্তন করুন এবং বোতামে ট্যাপ করা হলে ফ্রন্ট লেয়ারের দৃশ্যমানতা টগল করতে এটি ব্যবহার করুন।
// TODO: Replace leading menu icon with IconButton (104)
leading: IconButton(
icon: const Icon(Icons.menu),
onPressed: _toggleBackdropLayerVisibility,
),
রিলোড করুন, তারপর সিমুলেটরে মেনু বোতামে ট্যাপ করুন।
Android | iOS |
|
|
সামনের লেয়ারটি নিচের দিকে অ্যানিমেট (স্লাইড) করে। কিন্তু আপনি যদি নিচে দেখেন, তাহলে একটি লাল রঙের ত্রুটি এবং একটি ওভারফ্লো ত্রুটি দেখতে পাবেন। এর কারণ হল, AsymmetricView সংকুচিত হয়ে যায় এবং এই অ্যানিমেশনের মাধ্যমে ছোট হয়ে যায়, যার ফলে কলামের জন্য কম জায়গা থাকে। অবশেষে, কলামগুলি প্রদত্ত স্পেসের সাথে নিজেদের লেআউট করতে পারে না এবং এর ফলে একটি সমস্যা হয়। আমরা যদি কলামগুলিকে ListViews দিয়ে প্রতিস্থাপন করি, তাহলে কলামের আকার অ্যানিমেট করার সাথে সাথে একই থাকা উচিত।
ListView-তে প্রোডাক্ট কলাম র্যাপ করুন
supplemental/product_columns.dart-এ, OneProductCardColumn-এর কলামটি ListView দিয়ে প্রতিস্থাপন করুন:
class OneProductCardColumn extends StatelessWidget {
const OneProductCardColumn({required this.product, Key? key}) : super(key: key);
final Product product;
@override
Widget build(BuildContext context) {
// TODO: Replace Column with a ListView (104)
return ListView(
physics: const ClampingScrollPhysics(),
reverse: true,
children: <Widget>[
ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 550,
),
child: ProductCard(
product: product,
),
),
const SizedBox(
height: 40.0,
),
],
);
}
}
কলামে MainAxisAlignment.end অন্তর্ভুক্ত আছে। নিচ থেকে লেআউট শুরু করতে, reverse: true চিহ্নিত করুন। পরিবর্তনের জন্য ক্ষতিপূরণ হিসেবে বাচ্চাদের অর্ডারটি রিভার্স করা হয়।
রিলোড করে মেনু বোতামে ট্যাপ করুন।
Android | iOS |
|
|
OneProductCardColumn-এ ধূসর ওভারফ্লো সতর্কতা আর নেই! এখন অন্য সমস্যাটির সমাধান করা যাক।
supplemental/product_columns.dart-এ, imageAspectRatio যেভাবে গণনা করা হয় তা পরিবর্তন করুন এবং TwoProductCardColumn-এ কলামটি ListView দিয়ে প্রতিস্থাপন করুন:
// TODO: Change imageAspectRatio calculation (104)
double imageAspectRatio = heightOfImages >= 0.0
? constraints.biggest.width / heightOfImages
: 49.0 / 33.0;
// TODO: Replace Column with a ListView (104)
return ListView(
physics: const ClampingScrollPhysics(),
children: <Widget>[
Padding(
padding: const EdgeInsetsDirectional.only(start: 28.0),
child: top != null
? ProductCard(
imageAspectRatio: imageAspectRatio,
product: top!,
)
: SizedBox(
height: heightOfCards,
),
),
const SizedBox(height: spacerHeight),
Padding(
padding: const EdgeInsetsDirectional.only(end: 28.0),
child: ProductCard(
imageAspectRatio: imageAspectRatio,
product: bottom,
),
),
],
);
আমরা imageAspectRatio-এ কিছু নিরাপত্তা সংক্রান্ত ফিচারও যোগ করেছি।
আবার লোড করুন। তারপর মেনু বোতামে ট্যাপ করুন।
Android | iOS |
|
|
আর ওভারফ্লো হবে না।
৭. পিছনের লেয়ারে একটি মেনু যোগ করুন
মেনু হল ট্যাপ করা যায় এমন টেক্সট আইটেমের তালিকা যা টেক্সট আইটেম স্পর্শ করা হলে শ্রোতাদের বিজ্ঞপ্তি দেয়। এই ধাপে, আপনি বিভাগ অনুযায়ী ফিল্টারিং মেনু যোগ করবেন।
মেনু যোগ করুন
সামনের লেয়ারে মেনু এবং পিছনের লেয়ারে ইন্ট্যার্যাক্টিভ বোতাম যোগ করুন।
lib/category_menu_page.dart নামে একটি নতুন ফাইল তৈরি করুন:
import 'package:flutter/material.dart';
import 'colors.dart';
import 'model/product.dart';
class CategoryMenuPage extends StatelessWidget {
final Category currentCategory;
final ValueChanged<Category> onCategoryTap;
final List<Category> _categories = Category.values;
const CategoryMenuPage({
Key? key,
required this.currentCategory,
required this.onCategoryTap,
}) : super(key: key);
Widget _buildCategory(Category category, BuildContext context) {
final categoryString =
category.toString().replaceAll('Category.', '').toUpperCase();
final ThemeData theme = Theme.of(context);
return GestureDetector(
onTap: () => onCategoryTap(category),
child: category == currentCategory
? Column(
children: <Widget>[
const SizedBox(height: 16.0),
Text(
categoryString,
style: theme.textTheme.bodyLarge,
textAlign: TextAlign.center,
),
const SizedBox(height: 14.0),
Container(
width: 70.0,
height: 2.0,
color: kShrinePink400,
),
],
)
: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Text(
categoryString,
style: theme.textTheme.bodyLarge!.copyWith(
color: kShrineBrown900.withAlpha(153)
),
textAlign: TextAlign.center,
),
),
);
}
@override
Widget build(BuildContext context) {
return Center(
child: Container(
padding: const EdgeInsets.only(top: 40.0),
color: kShrinePink100,
child: ListView(
children: _categories
.map((Category c) => _buildCategory(c, context))
.toList()),
),
);
}
}
এটি একটি GestureDetector যা একটি কলামকে র্যাপ করে যার চাইল্ড এলিমেন্ট হল বিভাগের নাম। বেছে নেওয়া বিভাগ বোঝাতে আন্ডারলাইন ব্যবহার করা হয়।
app.dart-এ, ShrineApp উইজেটকে স্টেটলেস থেকে স্টেটফুল হিসেবে কনভার্ট করুন।
ShrineApp.হাইলাইট করুন- আপনার IDE-এর উপর ভিত্তি করে, কোড অ্যাকশন দেখান:
- Android Studio: ⌥Enter (macOS) বা alt + enter প্রেস করুন
- VS Code: ⌘. (macOS) বা Ctrl+. প্রেস করুন
- "StatefulWidget-এ কনভার্ট করো" বিকল্প বেছে নিন।
- ShrineAppState ক্লাস পরিবর্তন করে ব্যক্তিগত (_ShrineAppState) করুন। ShrineAppState-এর উপর মাউস রেখে ডানদিকের বোতামে ক্লিক করুন এবং
- Android Studio: 'রিফ্যাক্টর করুন > নাম পরিবর্তন করুন' বিকল্প বেছে নিন
- VS Code: 'চিহ্নটির নাম পরিবর্তন করুন' বিকল্প বেছে নিন
- ক্লাসটি ব্যক্তিগত করতে _ShrineAppState লিখুন।
app.dart-এ, বেছে নেওয়া বিভাগের জন্য _ShrineAppState-এ একটি ভেরিয়েবল যোগ করুন এবং এটি ট্যাপ করা হলে একটি কলব্যাক যোগ করুন:
class _ShrineAppState extends State<ShrineApp> {
Category _currentCategory = Category.all;
void _onCategoryTap(Category category) {
setState(() {
_currentCategory = category;
});
}
তারপর, ব্যাক লেয়ারটি CategoryMenuPage-এ পরিবর্তন করুন।
app.dart-এ, CategoryMenuPage ইমপোর্ট করুন:
import 'backdrop.dart';
import 'category_menu_page.dart';
import 'colors.dart';
import 'home.dart';
import 'login.dart';
import 'model/product.dart';
import 'supplemental/cut_corners_border.dart';
build() ফাংশনে, backLayer ফিল্ডকে CategoryMenuPage এবং currentCategory ফিল্ডকে ইনস্ট্যান্স ভেরিয়েবল নিতে পরিবর্তন করুন।
'/': (BuildContext context) => Backdrop(
// TODO: Make currentCategory field take _currentCategory (104)
currentCategory: _currentCategory,
// TODO: Pass _currentCategory for frontLayer (104)
frontLayer: HomePage(),
// TODO: Change backLayer field value to CategoryMenuPage (104)
backLayer: CategoryMenuPage(
currentCategory: _currentCategory,
onCategoryTap: _onCategoryTap,
),
frontTitle: const Text('SHRINE'),
backTitle: const Text('MENU'),
),
আবার লোড করে 'মেনু' বোতামে ট্যাপ করুন।
Android | iOS |
|
|
আপনি কোনও মেনু বিকল্পে ট্যাপ করলে, কিছু হয় না...এখনও পর্যন্ত। এটার সমাধান করা যাক।
home.dart-এ, বিভাগের জন্য একটি ভেরিয়েবল যোগ করুন এবং সেটি AsymmetricView-তে পাস করুন।
import 'package:flutter/material.dart';
import 'model/product.dart';
import 'model/products_repository.dart';
import 'supplemental/asymmetric_view.dart';
class HomePage extends StatelessWidget {
// TODO: Add a variable for Category (104)
final Category category;
const HomePage({this.category = Category.all, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// TODO: Pass Category variable to AsymmetricView (104)
return AsymmetricView(
products: ProductsRepository.loadProducts(category),
);
}
}
app.dart-এ, frontLayer-এর জন্য _currentCategory পাস করুন:।
// TODO: Pass _currentCategory for frontLayer (104)
frontLayer: HomePage(category: _currentCategory),
আবার লোড করুন। সিমুলেটরে মেনু বোতামে ট্যাপ করে একটি বিভাগ বেছে নিন।
Android | iOS |
|
|
সেগুলি ফিল্টার করা হয়েছে!
মেনু বেছে নেওয়ার পরে সামনের লেয়ার বন্ধ করুন
backdrop.dart-এ, _BackdropState-এ didUpdateWidget() (উইজেট কনফিগারেশন পরিবর্তন হলে কল করা হয়) ফাংশনের জন্য একটি ওভাররাইড যোগ করুন:
// TODO: Add override for didUpdateWidget() (104)
@override
void didUpdateWidget(Backdrop old) {
super.didUpdateWidget(old);
if (widget.currentCategory != old.currentCategory) {
_toggleBackdropLayerVisibility();
} else if (!_frontLayerVisible) {
_controller.fling(velocity: _kFlingVelocity);
}
}
হট রিলোড ট্রিগার করতে আপনার প্রোজেক্ট সেভ করুন। মেনু আইকনে ট্যাপ করে একটি বিভাগ বেছে নিন। মেনু অটোমেটিক বন্ধ হয়ে যাবে এবং বেছে নেওয়া আইটেমের বিভাগ দেখতে পাবেন। এখন আপনি সেই কার্যকারিতা সামনের লেয়ারেও যোগ করবেন।
সামনের লেয়ার টগল করা
backdrop.dart-এ, ব্যাকড্রপ লেয়ারে অন-ট্যাপ কলব্যাক যোগ করুন:
class _FrontLayer extends StatelessWidget {
// TODO: Add on-tap callback (104)
const _FrontLayer({
Key? key,
this.onTap, // New code
required this.child,
}) : super(key: key);
final VoidCallback? onTap; // New code
final Widget child;
তারপর _FrontLayer-এর চাইল্ড: Column-এর চিলড্রেনে একটি GestureDetector যোগ করুন:।
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
// TODO: Add a GestureDetector (104)
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: Container(
height: 40.0,
alignment: AlignmentDirectional.centerStart,
),
),
Expanded(
child: child,
),
],
),
তারপর _buildStack() ফাংশনে _BackdropState-এ নতুন onTap প্রপার্টি প্রয়োগ করুন:
PositionedTransition(
rect: layerAnimation,
child: _FrontLayer(
// TODO: Implement onTap property on _BackdropState (104)
onTap: _toggleBackdropLayerVisibility,
child: widget.frontLayer,
),
),
রিলোড করে সামনের লেয়ারের উপরের দিকে ট্যাপ করুন। সামনের লেয়ারের উপরে ট্যাপ করলে প্রতিবার লেয়ারটি খোলা ও বন্ধ হওয়া উচিত।
৮. ব্র্যান্ডেড আইকন যোগ করুন
ব্র্যান্ডেড আইকনোগ্রাফি পরিচিত আইকনেও প্রসারিত হয়। আসুন, 'প্রকাশ করুন' আইকন কাস্টমাইজ করি এবং অনন্য, ব্র্যান্ডেড লুকের জন্য এটিকে আমাদের শীর্ষকের সাথে মার্জ করি।
মেনু বোতামের আইকন পরিবর্তন করুন
Android | iOS |
|
|
backdrop.dart-এ, একটি নতুন ক্লাস _BackdropTitle তৈরি করুন।
// TODO: Add _BackdropTitle class (104)
class _BackdropTitle extends AnimatedWidget {
final void Function() onPress;
final Widget frontTitle;
final Widget backTitle;
const _BackdropTitle({
Key? key,
required Animation<double> listenable,
required this.onPress,
required this.frontTitle,
required this.backTitle,
}) : _listenable = listenable,
super(key: key, listenable: listenable);
final Animation<double> _listenable;
@override
Widget build(BuildContext context) {
final Animation<double> animation = _listenable;
return DefaultTextStyle(
style: Theme.of(context).textTheme.titleLarge!,
softWrap: false,
overflow: TextOverflow.ellipsis,
child: Row(children: <Widget>[
// branded icon
SizedBox(
width: 72.0,
child: IconButton(
padding: const EdgeInsets.only(right: 8.0),
onPressed: this.onPress,
icon: Stack(children: <Widget>[
Opacity(
opacity: animation.value,
child: const ImageIcon(AssetImage('assets/slanted_menu.png')),
),
FractionalTranslation(
translation: Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.0, 0.0),
).evaluate(animation),
child: const ImageIcon(AssetImage('assets/diamond.png')),
)]),
),
),
// Here, we do a custom cross fade between backTitle and frontTitle.
// This makes a smooth animation between the two texts.
Stack(
children: <Widget>[
Opacity(
opacity: CurvedAnimation(
parent: ReverseAnimation(animation),
curve: const Interval(0.5, 1.0),
).value,
child: FractionalTranslation(
translation: Tween<Offset>(
begin: Offset.zero,
end: const Offset(0.5, 0.0),
).evaluate(animation),
child: backTitle,
),
),
Opacity(
opacity: CurvedAnimation(
parent: animation,
curve: const Interval(0.5, 1.0),
).value,
child: FractionalTranslation(
translation: Tween<Offset>(
begin: const Offset(-0.25, 0.0),
end: Offset.zero,
).evaluate(animation),
child: frontTitle,
),
),
],
)
]),
);
}
}
_BackdropTitle হল একটি কাস্টম উইজেট যা AppBar উইজেটের title প্যারামিটারের জন্য সাধারণ Text উইজেটকে প্রতিস্থাপন করবে। এতে অ্যানিমেটেড মেনু আইকন এবং ফ্রন্ট ও ব্যাক টাইটেলের মধ্যে অ্যানিমেটেড ট্রানজিশন আছে। অ্যানিমেটেড মেনু আইকন একটি নতুন অ্যাসেট ব্যবহার করবে। নতুন slanted_menu.png-এর রেফারেন্স অবশ্যই pubspec.yaml-এ যোগ করতে হবে।
assets:
- assets/diamond.png
# TODO: Add slanted menu asset (104)
- assets/slanted_menu.png
- packages/shrine_images/0-0.jpg
AppBar বিল্ডারে leading প্রপার্টি সরিয়ে দিন। কাস্টম ব্র্যান্ডেড আইকনকে আসল leading উইজেটের জায়গায় রেন্ডার করার জন্য এটি সরিয়ে দেওয়া প্রয়োজন। ব্র্যান্ডেড আইকনের জন্য অ্যানিমেশন listenable ও onPress হ্যান্ডলার _BackdropTitle-এ পাস করা হয়। frontTitle এবং backTitle-ও পাস করা হয় যাতে সেগুলি ব্যাকড্রপ টাইটেলে রেন্ডার করা যায়। AppBar-এর title প্যারামিটারটি এই ধরনের হতে হবে:
// TODO: Create title with _BackdropTitle parameter (104)
title: _BackdropTitle(
listenable: _controller.view,
onPress: _toggleBackdropLayerVisibility,
frontTitle: widget.frontTitle,
backTitle: widget.backTitle,
),
ব্র্যান্ডেড আইকনটি _BackdropTitle.-এ তৈরি করা হয়েছে। এতে অ্যানিমেটেড আইকনের একটি Stack রয়েছে: একটি বাঁকা মেনু এবং একটি ডায়মন্ড, যা একটি IconButton-এ মোড়ানো থাকে যাতে এটি প্রেস করা যায়। তারপর IconButton-কে SizedBox-এ র্যাপ করা হয় যাতে অনুভূমিক আইকন মোশনের জন্য জায়গা তৈরি করা যায়।
Flutter-এর "সবকিছুই একটি উইজেট" আর্কিটেকচার সম্পূর্ণ নতুন কাস্টম AppBar উইজেট তৈরি না করেই ডিফল্ট AppBar-এর লেআউট পরিবর্তন করার অনুমতি দেয়। title প্যারামিটার, যা আসলে একটি Text উইজেট, আরও জটিল _BackdropTitle দিয়ে প্রতিস্থাপন করা যেতে পারে। যেহেতু _BackdropTitle-এ কাস্টম আইকনও অন্তর্ভুক্ত থাকে, তাই এটি leading প্রপার্টির জায়গা নেয়, যা এখন বাদ দেওয়া যেতে পারে। এই সহজ উইজেট প্রতিস্থাপনটি অন্য কোনও প্যারামিটার পরিবর্তন না করেই সম্পন্ন হয়, যেমন অ্যাকশন আইকন, যা তাদের নিজস্ব কাজ চালিয়ে যায়।
লগ-ইন স্ক্রিনে ফিরে যাওয়ার শর্টকাট যোগ করুন
backdrop.dart,অ্যাপ বারে থাকা দুটি ট্রেলিং আইকন থেকে লগ-ইন স্ক্রিনে ফিরে যাওয়ার শর্টকাট যোগ করুন: আইকনগুলির নতুন উদ্দেশ্য প্রতিফলিত করতে সেম্যান্টিক লেবেল পরিবর্তন করুন।
// TODO: Add shortcut to login screen from trailing icons (104)
IconButton(
icon: const Icon(
Icons.search,
semanticLabel: 'login', // New code
),
onPressed: () {
// TODO: Add open login (104)
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => LoginPage()),
);
},
),
IconButton(
icon: const Icon(
Icons.tune,
semanticLabel: 'login', // New code
),
onPressed: () {
// TODO: Add open login (104)
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => LoginPage()),
);
},
),
রিলোড করার চেষ্টা করলে আপনি একটি সমস্যা দেখতে পাবেন। সমস্যার সমাধান করতে login.dart ইমপোর্ট করুন:
import 'login.dart';
অ্যাপ আবার লোড করুন এবং লগ-ইন স্ক্রিনে ফিরে যেতে 'সার্চ করুন' বা 'টিউন করুন' বোতামে ট্যাপ করুন।
৯. অভিনন্দন!
এই চারটি কোডল্যাবের মাধ্যমে আপনি ব্র্যান্ডের ব্যক্তিত্ব ও স্টাইল প্রকাশ করে এমন অনন্য ও মার্জিত ব্যবহারকারীর অভিজ্ঞতা তৈরি করতে Material Components কীভাবে ব্যবহার করতে হয় তা শিখেছেন।
পরবর্তী ধাপ
এই কোডল্যাব, MDC-104, কোডল্যাবের এই ক্রম সম্পূর্ণ করে। আপনি Material Components উইজেট ক্যাটালগ লিঙ্কে গিয়ে Material Flutter-এ আরও কম্পোনেন্ট এক্সপ্লোর করতে পারবেন।
স্ট্রেচ গোলের জন্য, ব্র্যান্ডেড আইকনটি একটি AnimatedIcon দিয়ে প্রতিস্থাপন করার চেষ্টা করুন যা ব্যাকড্রপ দৃশ্যমান হলে দুটি আইকনের মধ্যে অ্যানিমেট করে।
আপনার আগ্রহের উপর ভিত্তি করে, আপনার চেষ্টা করার জন্য আরও অনেক Flutter codelabs আছে। আমাদের কাছে আরেকটি Material-নির্দিষ্ট কোডল্যাব আছে যা আপনার আগ্রহের বিষয় হতে পারে: Flutter-এর জন্য Material Motion-এর মাধ্যমে সুন্দর ট্রানজিশন তৈরি করা।






















