১. ভূমিকা
| ম্যাটেরিয়াল কম্পোনেন্টস (MDC) ডেভেলপারদের ম্যাটেরিয়াল ডিজাইন বাস্তবায়ন করতে সাহায্য করে। গুগলের একদল ইঞ্জিনিয়ার এবং ইউএক্স ডিজাইনার দ্বারা তৈরি, MDC-তে কয়েক ডজন সুন্দর এবং কার্যকরী UI কম্পোনেন্ট রয়েছে এবং এটি অ্যান্ড্রয়েড, আইওএস, ওয়েব এবং ফ্লাটারের জন্য উপলব্ধ। material.io/develop |
কোডল্যাব MDC-103- এ, আপনি আপনার অ্যাপকে স্টাইল করার জন্য ম্যাটেরিয়াল কম্পোনেন্ট (MDC)-এর রঙ, এলিভেশন, টাইপোগ্রাফি এবং আকৃতি কাস্টমাইজ করেছিলেন।
ম্যাটেরিয়াল ডিজাইন সিস্টেমের একটি কম্পোনেন্ট কিছু পূর্বনির্ধারিত কাজ সম্পাদন করে এবং এর কিছু নির্দিষ্ট বৈশিষ্ট্য থাকে, যেমন একটি বাটন। তবে, একটি বাটন ব্যবহারকারীর কোনো কাজ করার মাধ্যম মাত্র নয়; এটি এর আকৃতি, আকার এবং রঙের একটি চাক্ষুষ প্রকাশও বটে, যা ব্যবহারকারীকে জানিয়ে দেয় যে এটি ইন্টারেক্টিভ এবং স্পর্শ বা ক্লিক করলে কিছু একটা ঘটবে।
ম্যাটেরিয়াল ডিজাইন নির্দেশিকা একজন ডিজাইনারের দৃষ্টিকোণ থেকে কম্পোনেন্টগুলোর বর্ণনা দেয়। এতে বিভিন্ন প্ল্যাটফর্মে উপলব্ধ নানা ধরনের মৌলিক ফাংশন এবং প্রতিটি কম্পোনেন্ট গঠনকারী কাঠামোগত উপাদানগুলোর বর্ণনা দেওয়া হয়েছে। উদাহরণস্বরূপ, একটি ব্যাকড্রপে একটি ব্যাক লেয়ার ও তার কন্টেন্ট, ফ্রন্ট লেয়ার ও তার কন্টেন্ট, মোশন রুলস এবং ডিসপ্লে অপশন থাকে। এই কম্পোনেন্টগুলোর প্রত্যেকটিকে প্রতিটি অ্যাপের প্রয়োজন, ব্যবহারের ক্ষেত্র এবং কন্টেন্ট অনুযায়ী কাস্টমাইজ করা যায়।
আপনি যা তৈরি করবেন
এই কোডল্যাবে, আপনি Shrine অ্যাপের UI পরিবর্তন করে একটি দুই-স্তরের প্রেজেন্টেশন তৈরি করবেন, যাকে "ব্যাকড্রপ" বলা হয়। এই ব্যাকড্রপে একটি মেনু রয়েছে, যেখানে নির্বাচনযোগ্য ক্যাটাগরিগুলোর তালিকা থাকে, যা অ্যাসিমেট্রিক্যাল গ্রিডে দেখানো প্রোডাক্টগুলো ফিল্টার করতে ব্যবহৃত হয়। এই কোডল্যাবে, আপনি নিম্নলিখিত বিষয়গুলো ব্যবহার করবেন:
- আকৃতি
- গতি
- ফ্লাটার উইজেট (যা আপনি আগের কোডল্যাবগুলোতে ব্যবহার করেছেন)
অ্যান্ড্রয়েড | আইওএস |
|
|
|
|
এই কোডল্যাবে ম্যাটেরিয়াল ফ্লাটার কম্পোনেন্ট এবং সাবসিস্টেম
- আকৃতি
ফ্লাটার ডেভেলপমেন্টে আপনার অভিজ্ঞতার স্তরকে আপনি কীভাবে মূল্যায়ন করবেন?
২. আপনার ফ্লাটার ডেভেলপমেন্ট পরিবেশ সেট আপ করুন।
এই ল্যাবটি সম্পন্ন করার জন্য আপনার দুটি সফটওয়্যার প্রয়োজন— ফ্লাটার এসডিকে এবং একটি এডিটর ।
আপনি এই ডিভাইসগুলোর যেকোনো একটি ব্যবহার করে কোডল্যাবটি চালাতে পারেন:
- আপনার কম্পিউটারের সাথে সংযুক্ত এবং ডেভেলপার মোডে সেট করা একটি ফিজিক্যাল অ্যান্ড্রয়েড বা আইওএস ডিভাইস।
- iOS সিমুলেটর (এর জন্য Xcode টুলস ইনস্টল করা প্রয়োজন)।
- অ্যান্ড্রয়েড এমুলেটর (অ্যান্ড্রয়েড স্টুডিওতে সেটআপ করা প্রয়োজন)।
- একটি ব্রাউজার (ডিবাগিংয়ের জন্য ক্রোম আবশ্যক)।
- একটি 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 ডিরেক্টরিতে অবস্থিত।
...অথবা গিটহাব থেকে এটি ক্লোন করুন
গিটহাব থেকে এই কোডল্যাবটি ক্লোন করতে, নিম্নলিখিত কমান্ডগুলো চালান:
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
প্রজেক্টটি খুলুন এবং অ্যাপটি চালান।
- আপনার পছন্দের এডিটরে প্রজেক্টটি খুলুন।
- আপনার নির্বাচিত এডিটরের জন্য " Get Started: Test drive "-এ থাকা "Run the app" নির্দেশাবলী অনুসরণ করুন।
সফল! আপনি আপনার ডিভাইসে আগের কোডল্যাবগুলো থেকে শ্রাইন লগইন পেজটি দেখতে পাবেন।
অ্যান্ড্রয়েড | আইওএস |
|
|
৪. ব্যাকড্রপ মেনু যোগ করুন
অন্য সমস্ত কন্টেন্ট এবং কম্পোনেন্টের পিছনে একটি ব্যাকড্রপ দেখা যায়। এটি দুটি লেয়ার দিয়ে গঠিত: একটি ব্যাক লেয়ার (যা অ্যাকশন এবং ফিল্টার প্রদর্শন করে) এবং একটি ফ্রন্ট লেয়ার (যা কন্টেন্ট প্রদর্শন করে)। আপনি নেভিগেশন বা কন্টেন্ট ফিল্টারের মতো ইন্টারেক্টিভ তথ্য এবং অ্যাকশন দেখানোর জন্য একটি ব্যাকড্রপ ব্যবহার করতে পারেন।
হোম অ্যাপ বারটি সরিয়ে ফেলুন
হোমপেজ উইজেটটি আমাদের ফ্রন্ট লেয়ারের কন্টেন্ট হবে। এখন এতে একটি অ্যাপ বার আছে। আমরা অ্যাপ বারটিকে ব্যাক লেয়ারে সরিয়ে দেব এবং হোমপেজে শুধুমাত্র অ্যাসিমেট্রিকভিউ থাকবে।
home.dart এ, build() ফাংশনটি পরিবর্তন করে শুধু একটি AsymmetricView রিটার্ন করুন:
// TODO: Return an AsymmetricView (104)
return AsymmetricView(products: ProductsRepository.loadProducts(Category.all));
ব্যাকড্রপ উইজেট যোগ করুন
Backdrop নামে একটি উইজেট তৈরি করুন যাতে frontLayer এবং backLayer অন্তর্ভুক্ত থাকে।
backLayer একটি মেনু রয়েছে যা আপনাকে তালিকাটি ফিল্টার করার জন্য একটি ক্যাটাগরি ( currentCategory ) নির্বাচন করতে দেয়। যেহেতু আমরা চাই মেনুর নির্বাচনটি স্থায়ী হোক, তাই আমরা ব্যাকড্রপকে একটি স্টেটফুল উইজেট বানাবো।
/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() ` ফাংশনটি একটি অ্যাপ বার সহ একটি `Scaffold` রিটার্ন করে, ঠিক যেমনটি আগে `HomePage`-এ থাকতো। কিন্তু `Scaffold`-এর `body` হলো একটি `Stack` । একটি `Stack`-এর `child`-গুলো একে অপরের উপর ওভারল্যাপ করতে পারে। প্রতিটি `child`-এর আকার এবং অবস্থান `Stack`-এর `parent`-এর সাপেক্ষে নির্দিষ্ট করা হয়।
এখন 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'),
),
আপনার প্রজেক্টটি সেভ করুন, আপনি দেখবেন যে আমাদের হোম পেজ এবং অ্যাপ বারটি দেখা যাচ্ছে:
অ্যান্ড্রয়েড | আইওএস |
|
|
ব্যাকলেয়ারটি ফ্রন্টলেয়ার হোম পেজের পিছনে একটি নতুন লেয়ারে গোলাপী এলাকাটি দেখায়।
আপনি ফ্লাটার ইন্সপেক্টর ব্যবহার করে যাচাই করতে পারেন যে স্ট্যাকটিতে হোমপেজের পিছনে সত্যিই একটি কন্টেইনার আছে কিনা। এটি দেখতে এইরকম হবে:

এখন আপনি উভয় লেয়ারের ডিজাইন ও বিষয়বস্তু পরিবর্তন করতে পারবেন।
৫. একটি আকৃতি যোগ করুন
এই ধাপে, আপনি সামনের লেয়ারটিকে এমনভাবে স্টাইল করবেন যাতে উপরের বাম কোণায় একটি কাট যোগ হয়।
ম্যাটেরিয়াল ডিজাইন বলতে এই ধরনের কাস্টমাইজেশনকে একটি আকৃতি হিসেবে বোঝায়। ম্যাটেরিয়াল সারফেসের যেকোনো আকৃতি থাকতে পারে। আকৃতিগুলো সারফেসে জোর ও শৈলী যোগ করে এবং ব্র্যান্ডিং প্রকাশ করতে ব্যবহৃত হতে পারে। সাধারণ আয়তক্ষেত্রাকার আকৃতিকে বাঁকা বা কৌণিক কোণ ও প্রান্ত এবং যেকোনো সংখ্যক বাহু দিয়ে কাস্টমাইজ করা যায়। এগুলো প্রতিসম বা অনিয়মিত হতে পারে।
সামনের স্তরে একটি আকৃতি যোগ করুন
শ্রাইন অ্যাপের শেপ স্টোরির অনুপ্রেরণা ছিল অ্যাঙ্গেলযুক্ত শ্রাইন লোগোটি। শেপ স্টোরি হলো একটি অ্যাপ জুড়ে বিভিন্ন আকারের সাধারণ ব্যবহার। উদাহরণস্বরূপ, লোগোর আকৃতিটি লগইন পেজের সেইসব এলিমেন্টে প্রতিধ্বনিত হয়, যেগুলোতে শেপ প্রয়োগ করা হয়েছে। এই ধাপে, আপনি ফ্রন্ট লেয়ারটিকে উপরের-বাম কোণে একটি অ্যাঙ্গেলযুক্ত কাট দিয়ে স্টাইল করবেন।
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-এর _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),
],
);
}
পুনরায় লোড করুন।
অ্যান্ড্রয়েড | আইওএস |
|
|
আমরা 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,
),
),
);
}
হট রিস্টার্ট করুন। এখন নতুন রঙিন অ্যাপ বারটি দেখা যাবে।
অ্যান্ড্রয়েড | আইওএস |
|
|
এই পরিবর্তনের কারণে, ব্যবহারকারীরা দেখতে পাচ্ছেন যে সামনের সাদা স্তরটির ঠিক পিছনে কিছু একটা আছে। চলুন এতে মোশন যোগ করি যাতে ব্যবহারকারীরা ব্যাকড্রপের পেছনের স্তরটি দেখতে পান।
৬. গতি যোগ করুন
মোশন হলো আপনার অ্যাপকে প্রাণবন্ত করে তোলার একটি উপায়। এটি বড় ও নাটকীয়, সূক্ষ্ম ও সংক্ষিপ্ত, অথবা এর মাঝামাঝি যেকোনো ধরনের হতে পারে। কিন্তু মনে রাখবেন যে, আপনি যে ধরনের মোশন ব্যবহার করছেন তা পরিস্থিতির জন্য উপযুক্ত হওয়া উচিত। বারবার করা হয় এমন নিয়মিত কাজের জন্য মোশন ছোট এবং সূক্ষ্ম হওয়া উচিত, যাতে কাজগুলো ব্যবহারকারীর মনোযোগ নষ্ট না করে বা নিয়মিতভাবে খুব বেশি সময় না নেয়। তবে কিছু উপযুক্ত পরিস্থিতি রয়েছে, যেমন যখন কোনো ব্যবহারকারী প্রথমবারের মতো একটি অ্যাপ খোলে, তখন এটি আরও বেশি দৃষ্টি আকর্ষণকারী হতে পারে এবং কিছু অ্যানিমেশন ব্যবহারকারীকে আপনার অ্যাপটি কীভাবে ব্যবহার করতে হয় সে সম্পর্কে শেখাতেও সাহায্য করতে পারে।
মেনু বাটনে রিভিল মোশন যোগ করুন
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 উইজেটের মধ্যে রাখুন। যখন ব্যাকলেয়ারটি দৃশ্যমান থাকবে না, তখন এই উইজেটটি সেম্যান্টিকস ট্রি থেকে ব্যাকলেয়ারের মেনু আইটেমগুলোকে বাদ দেবে।
return Stack(
key: _backdropKey,
children: <Widget>[
// TODO: Wrap backLayer in an ExcludeSemantics widget (104)
ExcludeSemantics(
child: widget.backLayer,
excluding: _frontLayerVisible,
),
...
_buildStack() ফাংশনটিকে পরিবর্তন করে এমনভাবে তৈরি করুন যাতে এটি একটি BuildContext এবং BoxConstraints গ্রহণ করে। এছাড়াও, একটি PositionedTransition অন্তর্ভুক্ত করুন যা একটি RelativeRectTween Animation গ্রহণ করে:
// 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,
),
রিলোড করার পর সিমুলেটরের মেনু বাটনটি ট্যাপ করুন।
অ্যান্ড্রয়েড | আইওএস |
|
|
সামনের লেয়ারটি অ্যানিমেট হয়ে (স্লাইড করে) নিচে নেমে আসে। কিন্তু নিচে তাকালে একটি লাল এরর এবং একটি ওভারফ্লো এরর দেখা যায়। এর কারণ হলো, এই অ্যানিমেশনের ফলে AsymmetricView-টি সংকুচিত হয়ে ছোট হয়ে যায়, যার ফলে কলামগুলোর জন্য জায়গা কমে যায়। অবশেষে, কলামগুলো প্রদত্ত জায়গায় নিজেদের বিন্যস্ত করতে পারে না এবং এর ফলে একটি এরর দেখা দেয়। যদি আমরা কলামগুলোকে ListView দিয়ে প্রতিস্থাপন করি, তাহলে অ্যানিমেট হওয়ার সময়ও কলামের আকার একই থাকবে।
পণ্যের কলামগুলোকে একটি ListView-এর মধ্যে রাখুন।
supplemental/product_columns.dart ফাইলে, OneProductCardColumn ভেতরের Column-টিকে একটি 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 চিহ্নিত করুন। এই পরিবর্তনের সাথে সামঞ্জস্য রাখতে চাইল্ডগুলোর ক্রম উল্টে যায়।
পুনরায় লোড করুন এবং মেনু বোতামটি ট্যাপ করুন।
অ্যান্ড্রয়েড | আইওএস |
|
|
OneProductCardColumn-এর ধূসর ওভারফ্লো সতর্কতাটি চলে গেছে! এবার অন্যটি ঠিক করা যাক।
supplemental/product_columns.dart ফাইলে, imageAspectRatio গণনা করার পদ্ধতি পরিবর্তন করুন, এবং TwoProductCardColumn ভেতরের Column-টিকে একটি 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 তেও কিছু সুরক্ষা ব্যবস্থা যুক্ত করেছি।
পুনরায় লোড করুন। তারপর মেনু বোতামে ট্যাপ করুন।
অ্যান্ড্রয়েড | আইওএস |
|
|
আর উপচে পড়বে না।
৭. পেছনের লেয়ারে একটি মেনু যোগ করুন
মেনু হলো ট্যাপযোগ্য টেক্সট আইটেমের একটি তালিকা, যা আইটেমগুলোতে স্পর্শ করা হলে লিসেনারদের অবহিত করে। এই ধাপে, আপনি একটি ক্যাটাগরি ফিল্টারিং মেনু যোগ করবেন।
মেনু যোগ করুন
মেনুটি সামনের লেয়ারে এবং ইন্টারেক্টিভ বাটনগুলো পেছনের লেয়ারে যোগ করুন।
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 যা একটি Column-কে ঘিরে রেখেছে, এবং এই Column-টির চাইল্ডগুলো হলো ক্যাটাগরির নাম। নির্বাচিত ক্যাটাগরি বোঝাতে একটি আন্ডারলাইন ব্যবহার করা হয়।
app.dart ফাইলে ShrineApp উইজেটটিকে স্টেটলেস থেকে স্টেটফুল-এ রূপান্তর করুন।
-
ShrineApp. - আপনার IDE অনুযায়ী কোড অ্যাকশনগুলো দেখান:
- অ্যান্ড্রয়েড স্টুডিও: ⌥Enter (macOS) অথবা alt + enter চাপুন।
- ভিএস কোড: ⌘ চাপুন। (ম্যাকওএস) অথবা Ctrl+।
- 'Convert to StatefulWidget' নির্বাচন করুন।
- ShrineAppState ক্লাসটিকে private (_ShrineAppState) এ পরিবর্তন করুন। ShrineAppState-এর উপর রাইট-ক্লিক করুন, এবং
- অ্যান্ড্রয়েড স্টুডিও: রিফ্যাক্টর > রিনেম নির্বাচন করুন
- 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'),
),
পুনরায় লোড করুন এবং মেনু বোতামে ট্যাপ করুন।
অ্যান্ড্রয়েড | আইওএস |
|
|
আপনি মেনুর কোনো অপশনে ট্যাপ করলে কিছুই হয় না... এখনও। চলুন, এটা ঠিক করা যাক।
home.dart ফাইলে Category-এর জন্য একটি ভেরিয়েবল যোগ করুন এবং সেটি 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),
পুনরায় লোড করুন। সিমুলেটরের মেনু বোতামে ট্যাপ করুন এবং একটি বিভাগ নির্বাচন করুন।
অ্যান্ড্রয়েড | আইওএস |
|
|
এগুলো ফিল্টার করা!
মেনু নির্বাচনের পর সামনের স্তরটি বন্ধ করুন ।
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-এর child: Column-এর children-গুলোতে একটি 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,
),
),
রিলোড করুন এবং সামনের লেয়ারের উপরের অংশে ট্যাপ করুন। প্রতিবার সামনের লেয়ারের উপরের অংশে ট্যাপ করলে লেয়ারটি খুলবে এবং বন্ধ হবে।
৮. একটি ব্র্যান্ডেড আইকন যোগ করুন
ব্র্যান্ডেড আইকনোগ্রাফি পরিচিত আইকনগুলোর ক্ষেত্রেও প্রযোজ্য। চলুন, রিভিল আইকনটিকে কাস্টম তৈরি করে আমাদের টাইটেলের সাথে জুড়ে দিই, যা একটি অনন্য ও ব্র্যান্ডেড লুক দেবে।
মেনু বোতামের আইকন পরিবর্তন করুন
অ্যান্ড্রয়েড | আইওএস |
|
|
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 মধ্যে মোড়ানো হয়।
ফ্লাটারের "সবকিছুই একটি উইজেট" আর্কিটেকচারের কারণে, সম্পূর্ণ নতুন একটি কাস্টম 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';
অ্যাপটি রিলোড করুন এবং লগইন স্ক্রিনে ফিরে যেতে সার্চ বা টিউন বাটনগুলোতে ট্যাপ করুন।
৯. অভিনন্দন!
এই চারটি কোডল্যাবের মাধ্যমে, আপনি শিখেছেন কীভাবে ম্যাটেরিয়াল কম্পোনেন্ট ব্যবহার করে ব্র্যান্ডের ব্যক্তিত্ব ও শৈলী প্রকাশ করে এমন অনন্য ও মার্জিত ইউজার এক্সপেরিয়েন্স তৈরি করতে হয়।
পরবর্তী পদক্ষেপ
এই কোডল্যাব, MDC-104, এই কোডল্যাবগুলোর ক্রমটি সম্পূর্ণ করে। আপনি Material Components উইজেট ক্যাটালগ পরিদর্শন করে Material Flutter-এর আরও অনেক কম্পোনেন্ট সম্পর্কে জানতে পারবেন।
একটি স্ট্রেচ গোল হিসেবে, ব্র্যান্ডেড আইকনটিকে এমন একটি অ্যানিমেটেড আইকন দিয়ে প্রতিস্থাপন করার চেষ্টা করুন, যা ব্যাকড্রপ দৃশ্যমান হলে দুটি আইকনের মধ্যে অ্যানিমেট হতে থাকে।
আপনার আগ্রহ অনুযায়ী চেষ্টা করার জন্য আরও অনেক ফ্লাটার কোডল্যাব রয়েছে। আমাদের আরও একটি ম্যাটেরিয়াল-কেন্দ্রিক কোডল্যাব আছে, যেটিতে আপনি আগ্রহী হতে পারেন: ফ্লাটারের জন্য ম্যাটেরিয়াল মোশন ব্যবহার করে সুন্দর ট্রানজিশন তৈরি করা ।






















