چطور یک پروژه فلاتر سازگار با تمام دستگاه ها بسازیم؟(ریسپانسیو کردن اپ فلاتر)
سلام
تو این پست میخوایم در مورد ریسپانسیو و انطباقی کردن پروژه های فلاتر صحبت کنیم؛ که این کار چطور انجام میشه.
۲ تا پکیج خفنم آموزش میدیم واقعا خفنن.
خب شروع میکنیم.
طراحی انطباقی(Adaptive) چیه؟
تعریف روشنی براش وجود نداره ولی میشه گفت: طراحی واکنشگرا. یه نوع طراحی که بر حسب نوع دستگاه تغییر پیدا کنه. تنها بر حسب نوع دستگاه هم نه. عامل های مختلفی داره. عکس زیر رو ببینید تا کامل متوجه شید:
همونجور که تو عکس میبینید. طراحی انطباقی یعنی یه جور طراحی برای تمام افراد با هر نوع دستگاه.
عامل های مختلفی که توی عکس میبینید:
- پلتفرم(اندروید، آیاواس، مکاواس، ویندوز، وب و ....)
- صفحهنمایش(سایز، تراکمپیکسل، padding، دارک/لایت مود)
- ورودی(صفحهلمسی، کیبورد و موس، صفحهخوان)
- دسترسی(مقیاسگذاری متن، کنتراست)
- کارایی(شبکه، پویا نمایی ها)
پلتفرم
دونستن اینکه سیستمعامل کاربر ما چیه باعث میشه ما از قابلیت های خاص اون سیستمعامل استفاده کنیم و این کار میتونه رو تجربه کاربر ما تاثیر زیادی داشته باشه.
برای فهمیدن سیستمعامل از کلاس Platform استفاده میکنیم. چون تو این کلاس وب نیست مجبوریم که از ثابت kIsWeb استفاده کنیم.
import 'dart:io';
import 'package:flutter/foundation.dart';
@override
Widget build(BuildContext context) {
if(kIsWeb || Platform.isMacOS || Platform.isLinux ||Platform.isWindows){
return DesktopLayout();
}
return MobileLayout();
}
صفحهنمایش
MediaQuery
فکر کنم قبلا هنگام توسعه با Flutter از MediaQuery استفاده کردید.حتما به خاطر اینکه به اکثر خصوصیات توصیف کننده دستگاه کاربر دسترسی دارین.
تنها یه کار نیازه، اونم تعریف یه متغیر:
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
// Rebuilt every time a property of media query changes
// ...
}
سایز صفحهنمایش
سیستمعامل هرچی که باشه اون صفحه نمایشه که به کاربر داده هارو نشون میده. پس ما باید به بهترین شکل ممکن از سایزش استفاده کنیم.
برای فهمیدن سایز بهترین راه استفاده از size از کلاس MediaQuery هست:
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
if (mediaQuery.size.width > 1024) {
return LargeLayout();
}
if (mediaQuery.size.width > 332) {
return MediumLayout();
}
return SmallLayout();
}
تراکمپیکسل
برای استفاده از devicePixelRatio در MediaQuery کمک بگیرید:
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
if (mediaQuery.devicePixelRatio >= 2) {
return HighDefinitionVideo();
}
return LowDefinitionVideo();
}
به عنوان مثال مورد استفاده: می توانید با نشون دادن ویدئویی از شبکه در کیفیت کمتری برای دستگاه هایی با تراکم کمتر، پهنای باند شبکه را ذخیره کنید چون تفاوت اون رو با یک فیلم HD نمی بینند.
Padding
برای استفاده از Padding در MediaQuery کمک بگیرید:
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
return Padding(
padding: EdgeInsets.only(
top: mediaQuery.padding.top,
),
child: Child(),
);
}
فلاتر همچنین به ما یک ابزارک میده تا در انجام این کار به ما کمک کنه: SafeArea.
@override
Widget build(BuildContext context) {
return SafeArea(
left: false,
right: false,
bottom: false,
child: Child(),
);
}
خود Scaffold این رو برای ما مدیریت میکنه ولی بعضی مواقع نیازه به طور مستقیم کنترلش کنیم:
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
return Padding(
padding: EdgeInsets.only(
bottom: mediaQuery.viewInsets.bottom,
),
child: Child(),
);
}
تنظیمات دسترسی
قشر خاصی از کاربرا اندازه فونتشون رو عوض میکنن به همین خاطر ماهم باید اندازه فونت اپ مون رو همینجور که کاربر میخواد تنظیم کنیم.
بازم باید از MediaQuery عزیز کمک بگیریم:
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
return Text(
'Hello!',
textScaleFactor: mediaQuery.textScaleFactor.clamp(1.0, 1.5),
style: TextStyle(
fontFamily: mediaQuery.boldText ? 'LightFont' : 'BoldFont',
),
);
}
نکته: شاید ندونید که ? : یعنی چی؟ این دو علامت یه جور خلاصه if else هست یعنی:
اگر شرط قبل از ? برقرار بود(true بود) این کار رو انجام بده درغیر این صورت هم بعد از : هست.
مثال:
final name = "Hadi"; print(name == "Hadi" ? "Hello Hadi" : "Hello User")
یعنی اگر متغیر name برابر با Hadi بود پرینت کن Hello Hadi درغیر این صورت پرینت کن Hello User
دارک مود
از اونجایی که اندروید و آیاواس اجازه فعال کردن دارک مود رو میدن برنامه های زیادی قابلیت دارک مود رو دارن، پس ماهم باید استفاده کنیم:
برای اینکه بفهمیم مود صفحه نمایش کاربر چیه بازم از MediaQuery استفاده میکنیم:
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
return Image.asset(
mediaQuery.platformBrightness == Brightness.dark
? 'assets/background_dark.png'
: 'assets/background_light.png',
);
}
اگر از MaterialApp استفاده می کنید می توانید یک lightTheme و darkTheme برای سازگاری بیشتر برای خودتون تعریف کنید.
بین المللی شدن
اگر اپ شما بینالمللی هست باید از زبانها و ساعتهای مختلفی پشتیبانی کنه ولی چجوری؟
برای دسترسی به محل کاربر باید از کلاس Localizations استفاده کنیم:
@override
Widget build(BuildContext context) {
final locale = Localizations.localeOf(context);
return Image.asset('flag_${locale.countryCode}.png');
}
برای اینکه نوع ساعت رو بفهمیم و تغییر بدیم هم:
@override
Widget build(BuildContext context) {
final locale = Localizations.localeOf(context);
final formattedDate = DateFormat.yMMMMEEEEd(locale.toString());
return Text(formattedDate);
}
و در آخر برای اینکه بفهمیم rtl هست یا ltr از Directionality استفاده میکنیم:
@override
Widget build(BuildContext context) {
final direction = Directionality.of(context);
return Image.asset('horizontal_illustration_${direction.value}.png');
}
بخش صفحه نمایش تموم شد:)
ورودی کاربر
برنامه یک رابط دو طرفه است. صرفا انطباق با اطلاعات ارائه شده به کاربر کافی نیست، بلکه باید نحوه تعامل کاربر با برنامه ما هم خوب باشه تا کاربر بتونه با اپ ما ارتباط بگیره.
خواننده صدا
با پشتیبانی از خواننده های صدا (که از طریق تنظیمات سیستم فعال می شوند)، به کاربران معلول اجازه می دهید تا با صدای خودشون با برنامه ما تعامل داشته باشند.
برای پشتیبانی از تعاملات صوتی فلاتر ویجت Semantics رو ارائه داده:
@override
Widget build(BuildContext context) {
return Semantics(
button: true,
label: 'My awesome button',
child: Child(),
);
}
کیبورد و موس
اکنون که پشتیبانی دسکتاپ با فل استیبل شده و آیاواس از trackpad پشتیبانی می کند، ترکیب صفحه کلید و موس برای کاربران بسیار مهم است. این اغلب روشی برای بهبود بهره وری با افزودن میانبرها یا داشتن دقت اشاره گر بهتر است.
فلاتر هم که برا همه چی ویجت داره برای اینم داره ویجت Shortcuts برای کیبورد و MouseRegion برای موس.
Shortcuts:
return Shortcuts(
shortcuts: {
LogicalKeySet(
LogicalKeyboardKey.control,
LogicalKeyboardKey.keyC,
): CopyIntent(),
},
child: Actions(
actions: {
CopyIntent: CallbackAction<Intent>(
onInvoke: (intent) => copy(),
),
},
child: Child(),
),
);
return Shortcuts(
shortcuts: {
LogicalKeySet(
LogicalKeyboardKey.control,
LogicalKeyboardKey.keyC,
): CopyIntent(),
},
child: Actions(
actions: {
CopyIntent: CallbackAction<Intent>(
onInvoke: (intent) => copy(),
),
},
child: Child(),
),
);
MouseRegion:
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (event) => print('Enter'),
onExit: (event) => print('Exit'),
onHover: (event) => print('Hover'),
child: Child(),
);
}
کارایی
درآخر، چندین چیز به خود کاربر بستگی ندارد، بلکه به دستگاه و زمینه آن بستگی دارد.
Connectivity
برای اینکه بفهمیم کاربر ما به اینترنت دسترسی داره یا نه از پکیج بالا استفاده میکنیم.
اینگونه:
import 'package:connectivity/connectivity.dart';
var connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.mobile) {
//کابر به داده موبایل وصله
} else if (connectivityResult == ConnectivityResult.wifi) {
// کاربر به وایفای وصله
} else if (connectivityResult == ConnectivityResult.none){
//کاربر به اینترنت وصل نیست
}
انیمیشنها
برای اینکه بفهمید کاربر انیمیشن هارو فعال گذاشته یا نه از پراپرتی disableAnimation در MediaQuery استفاده کنید:
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
return mediaQuery.disableAnimations ? StaticChild() : AnimatedChild();
}
پکیج هایی برای بهبود سازگاری:
1. FlutterScreenUtil:
پکیج FlutterScreenUtil واقعا برای طراحی واکنش گرا عالیه.
اول اونو به pubspec.yaml اضافه کنید:
dependencies:
flutter:
sdk: flutter
# add flutter_screenutil
flutter_screenutil: ^4.0.4+1
بعد اونو import کنید:
import 'package:flutterscreenutil/flutterscreenutil.dart';
در مرحله بعد ،باید یه خط قبل return Scaffold این کد را وارد کنید:
ScreenUtil.init(BoxConstraints(maxHeight: MediaQuery.of(context).size.height,
maxWidth: MediaQuery.of(context).size.width));
حالا کار باهاش:
Container(
width: 50.w,
height:200.h
)
انواع مقادیر موجود در این پلاگین:
۵۰.w این واحد براساس عرض صفحه مقداری تولید میکنه
۲۰۰.h این واحد براساس طول صفحه مقداری تولید میکنه
۰.۴.sw که مساوی با ۴۰ درصد است
۱۶.sp که برای سایز فونت استفاده می شه، که باعث میشه تو صفحهنمایش های بزرگ، کمی سایزش بزرگ شه و تو صفحهنمایش های کوچیک، سایزش کمی کوچیک میشه که در کل سایز متن رو ریسپانسیو می کنه.
مقادیر های دیگه هم داره که برین بخونین:)
۲. Sizer:
یه جورایی این پکیج مکمل پکیج بالاست و من فقط برای یه چیزی ازش استفاده میکنم؛ DeviceType
شما میتونید استفاده نکنید ولی خوبه.
import 'package:sizer/sizer.dart';
return SizerUtil.deviceType == DeviceScreenType.Tablet
? Container(
width: 100.w,
height: 20.h, // استفاده از پکیج بالا
) //for Tablet
: Container(
width: 100.w,
height: 12.h,
); //for Mobile
۳. DevicePreview:
این پکیج عالیه عالی!
باهاش میشه اپلیکیشن تون رو تو سایز های مختلف تست کنید.
پیاده سازیشم خیلی سخت نیست:
import 'package:device_preview/device_preview.dart';
void main() => runApp(
DevicePreview(
enabled: !kReleaseMode,
builder: (context) => MyApp(),
),
);
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
builder: DevicePreview.appBuilder,
home: HomePage(),
);
}
}
برای درک بیشتر این فیلم رو ببینید:
این هم اپ بنده است با ترکیب هرسه پکیج بالا:
مشاهده نظرات بیشتر...