1. Giriş
Flutter, Google'ın tek bir kod tabanından mobil, web ve masaüstü için yerel olarak derlenmiş, güzel uygulamalar geliştirmeye yönelik kullanıcı arayüzü araç setidir. Ücretsiz ve açık kaynak olan Flutter, mevcut kodlarla çalışır, dünyanın dört bir yanındaki geliştiriciler ve kuruluşlar tarafından kullanılır.
Bu codelab'de bir Flutter müzik uygulamasını geliştirerek sıkıcı olmaktan güzel görünen bir uygulamaya dönüştüreceksiniz. Bu codelab'de, bunun için Material 3'te kullanıma sunulan araçlar ve API'ler kullanılıyor.
Neler öğreneceksiniz?
- Farklı platformlarda kullanışlı ve güzel bir Flutter uygulaması yazma.
- Uygulamanızda metinlerin kullanıcı deneyimine katkıda bulunduğundan emin olmak için nasıl tasarlanacağı.
- Doğru renkleri seçme, widget'ları özelleştirme, kendi temanızı oluşturma ve koyu modu hızlı ve kolay bir şekilde uygulama.
- Platformlar arası uyarlanabilir uygulamalar geliştirme.
- Her ekranda iyi görünen uygulamalar nasıl geliştirilir?
- Flutter uygulamanızı çarpıcı hale getirmek için ona nasıl hareket ekleyebilirsiniz?
Ön koşullar:
Bu codelab'de Flutter deneyiminiz olduğu varsayılır. Yoksa öncelikle temel bilgileri öğrenmeniz iyi olabilir. Aşağıdaki bağlantılar yararlı olabilir:
- Flutter Widget Çerçevesi Turu'na katılın
- Write Your First Flutter App, Part 1 (İlk Flutter Uygulamanızı Yazma, 1. bölüm) codelab'ini deneyin
Neler oluşturacaksınız?
Bu codelab'de, hayranların en sevdikleri sanatçılarla ilgili gelişmeleri takip edebilecekleri bir müzik çalma uygulaması olan MyArtist adlı uygulamanın ana ekranını oluşturma konusunda yardım alabilirsiniz. Farklı platformlarda güzel görünmek için uygulama tasarımınızı nasıl değiştirebileceğiniz açıklanmaktadır.
Aşağıdaki videolarda, bu codelab'in tamamlanmasının ardından uygulamanın işleyiş şekli gösterilmektedir:
Bu codelab'den ne öğrenmek istersiniz?
2. Flutter geliştirme ortamınızı kurma
Bu laboratuvarı tamamlamak için iki yazılıma ihtiyacınız vardır: Flutter SDK'sı ve düzenleyici.
Codelab'i aşağıdaki cihazlardan birini kullanarak çalıştırabilirsiniz:
- Bilgisayarınıza bağlı ve Geliştirici moduna ayarlanmış fiziksel bir Android veya iOS cihaz.
- iOS simülatörü (Xcode araçlarının yüklenmesini gerektirir).
- Android Emülatör (Android Studio'da kurulum gerektirir).
- Tarayıcı (hata ayıklama için Chrome gereklidir).
- Windows, Linux veya macOS masaüstü uygulaması olarak Uygulamayı dağıtmayı planladığınız platformda gerçekleştirmeniz gerekir. Bu nedenle, bir Windows masaüstü uygulaması geliştirmek istiyorsanız uygun derleme zincirine erişmek için Windows'da geliştirme yapmanız gerekir. İşletim sistemine özgü gereksinimler docs.flutter.dev/desktop sayfasında ayrıntılı olarak açıklanmıştır.
3. codelab başlangıç uygulamasını edinme
GitHub'dan klonlama
Bu codelab'i GitHub'dan klonlamak için şu komutları çalıştırın:
git clone https://github.com/flutter/codelabs.git cd codelabs/boring_to_beautiful/step_01/
Her şeyin çalıştığından emin olmak için Flutter uygulamasını aşağıda gösterildiği gibi bir masaüstü uygulaması olarak çalıştırın. Alternatif olarak, bu projeyi IDE'nizde açın ve uygulamayı çalıştırmak için projedeki araçları kullanın.
Başarıyla gerçekleştirildi. MyArtist'in ana ekranının başlangıç kodu çalışıyor olmalıdır. MyArtist ana ekranı görünür. Masaüstünde güzel görünüyor, ancak mobilde... Çok iyi değil. Birincisi, kaliteye itibar etmiyor. Endişelenmeyin, bu sorunu düzelteceksiniz.
Kodda gezin
Ardından, kod turuna katılın.
Aşağıdakileri içeren lib/src/features/home/view/home_screen.dart
dosyasını açın:
lib/src/features/home/view/home_screen.dart
import 'package:adaptive_components/adaptive_components.dart';
import 'package:flutter/material.dart';
import '../../../shared/classes/classes.dart';
import '../../../shared/extensions.dart';
import '../../../shared/providers/providers.dart';
import '../../../shared/views/views.dart';
import '../../playlists/view/playlist_songs.dart';
import 'view.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
final PlaylistsProvider playlistProvider = PlaylistsProvider();
final List<Playlist> playlists = playlistProvider.playlists;
final Playlist topSongs = playlistProvider.topSongs;
final Playlist newReleases = playlistProvider.newReleases;
final ArtistsProvider artistsProvider = ArtistsProvider();
final List<Artist> artists = artistsProvider.artists;
return LayoutBuilder(
builder: (context, constraints) {
// Add conditional mobile layout
return Scaffold(
body: SingleChildScrollView(
child: AdaptiveColumn(
children: [
AdaptiveContainer(
columnSpan: 12,
child: Padding(
padding: const EdgeInsets.all(2), // Modify this line
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
'Good morning',
style: context.displaySmall,
),
),
const SizedBox(width: 20),
const BrightnessToggle(),
],
),
),
),
AdaptiveContainer(
columnSpan: 12,
child: Column(
children: [
const HomeHighlight(),
LayoutBuilder(
builder: (context, constraints) => HomeArtists(
artists: artists,
constraints: constraints,
),
),
],
),
),
AdaptiveContainer(
columnSpan: 12,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(2), // Modify this line
child: Text(
'Recently played',
style: context.headlineSmall,
),
),
HomeRecent(
playlists: playlists,
),
],
),
),
AdaptiveContainer(
columnSpan: 12,
child: Padding(
padding: const EdgeInsets.all(2), // Modify this line
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.all(2), // Modify this line
child: Text(
'Top Songs Today',
style: context.titleLarge,
),
),
LayoutBuilder(
builder: (context, constraints) =>
PlaylistSongs(
playlist: topSongs,
constraints: constraints,
),
),
],
),
),
// Add spacer between tables
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.all(2), // Modify this line
child: Text(
'New Releases',
style: context.titleLarge,
),
),
LayoutBuilder(
builder: (context, constraints) =>
PlaylistSongs(
playlist: newReleases,
constraints: constraints,
),
),
],
),
),
],
),
),
),
],
),
),
);
},
);
}
}
Bu dosya, material.dart
öğesini içe aktarır ve iki sınıf kullanarak durum bilgili bir widget uygular:
import
ifadesi, Malzeme Bileşenlerini kullanıma sunar.HomeScreen
sınıfı, görüntülenen sayfanın tamamını temsil eder._HomeScreenState
sınıfınınbuild()
yöntemi, widget ağacının kökünü oluşturur. Bu da kullanıcı arayüzündeki tüm widget'ların oluşturulma şeklini etkiler.
4. Tipografiden yararlanın
Metin her yerde. Metin, kullanıcıyla iletişim kurmak için kullanışlı bir yöntemdir. Uygulamanızın amacı arkadaş canlısı ve eğlenceli mi ya da güvenilir ve profesyonel mi? En sevdiğiniz bankacılık uygulamasının Comic Sans'ı kullanmamasının bir nedeni var. Metnin sunulma şekli, kullanıcının uygulamanızla ilgili ilk izlenimini şekillendirir. Metinleri daha dikkatli şekilde kullanmanın bazı yollarını burada bulabilirsiniz.
Anlatmak yerine gösterin
Mümkün olduğunda, "göster" yerine "tell" yazın. Örneğin, başlangıç uygulamasındaki NavigationRail
her ana rota için sekmelere sahiptir, ancak baştaki simgeler aynıdır:
Kullanıcının her sekmedeki metni okuması gerektiği için bu yararlı değildir. Kullanıcıların önde gelen simgelere hızlıca göz atması için görsel ipuçları ekleyerek başlayın. Bu işlem yerelleştirme ve erişilebilirlik açısından da faydalıdır.
lib/src/shared/router.dart
uygulamasında her gezinme hedefi (ev, oynatma listesi ve kişiler) için başında ayrı simgeler ekleyin:
lib/src/shared/router.dart
const List<NavigationDestination> destinations = [
NavigationDestination(
label: 'Home',
icon: Icon(Icons.home), // Modify this line
route: '/',
),
NavigationDestination(
label: 'Playlists',
icon: Icon(Icons.playlist_add_check), // Modify this line
route: '/playlists',
),
NavigationDestination(
label: 'Artists',
icon: Icon(Icons.people), // Modify this line
route: '/artists',
),
];
Bir sorun mu var?
Uygulamanız düzgün bir şekilde çalışmıyorsa yazım hataları olup olmadığını kontrol edin. Gerekirse bu sorunu çözmek için aşağıdaki bağlantılarda yer alan kodu kullanın.
Yazı tiplerini dikkatlice seçin
Yazı tipleri uygulamanızın kişiliğini belirler. Bu nedenle, doğru yazı tipini seçmek çok önemlidir. Yazı tipi seçerken dikkate almanız gereken birkaç nokta vardır:
- Sans-serif veya serif: Serif yazı tiplerinde dekoratif hatlar veya "kuyruklar" vardır harflerin sonuna eklenmiş ve daha resmi olduğu düşünülür. Sans-serif yazı tiplerinde dekoratif darbeler yoktur ve bu yazı tipleri daha samimi olarak algılanır. A sans serif büyük harf T, serif büyük harf T
- Tümü büyük harf yazı tipleri: Az miktarda metne dikkat çekmek için (başlık gibi) yazıların tamamında büyük harf kullanılması uygundur, ancak aşırı kullanıldığında kullanıcının bunu tamamen göz ardı etmesine neden olacak şekilde bağırarak algılanabilir.
- İlk harfler büyük veya tümce düzeni: Başlık ya da etiket eklerken büyük harfleri nasıl kullandığınızı göz önünde bulundurun: Başlık harfinde, her kelimenin ilk harfi büyük yazılır ("Bu, İlk Harfler Büyüktür Başlıktır") daha resmidir. Yalnızca özel isimlerin ve metindeki ilk kelimenin büyük harfle yazıldığı Cümle düzeni, daha konuşmaya dayalı ve resmi olmayan bir yaklaşımdır.
- Kısıtlama (her bir harf arasındaki boşluk), satır uzunluğu (ekrandaki tam metnin genişliği) ve satır yüksekliği (her bir metin satırının yüksekliği): Bunların çok fazla veya çok az olması uygulamanızı daha az okunabilir hale getirir. Örneğin, büyük ve kesintisiz bir metin bloğunu okurken yerinizi kolayca kaybedebilirsiniz.
Bu doğrultuda, Google Fonts'a gidin ve müzik uygulamasının amacı eğlenceli ve eğlenceli olması için tasarlanmış olan Montserrat gibi bir sans-serif yazı tipi seçin.
Komut satırından, google_fonts
paketini alın. Bu işlem, yazı tiplerini uygulama bağımlılığı olarak eklemek için pubspec dosyasını da günceller.
$ flutter pub add google_fonts
macos/Runner/DebugProfile.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<!-- Make sure these lines are present from here... -->
<key>com.apple.security.network.client</key>
<true/>
<!-- To here. -->
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>
lib/src/shared/extensions.dart
'te yeni paketi içe aktarın:
lib/src/shared/extensions.dart
import 'package:google_fonts/google_fonts.dart'; // Add this line.
Montserrat'ı kurun TextTheme:
TextTheme get textTheme => GoogleFonts.montserratTextTheme(theme.textTheme); // Modify this line
Değişiklikleri etkinleştirmek için çalışırken yeniden yükleyin. (IDE'nizdeki düğmeyi kullanın veya çalışır durumda yeniden yüklemek için komut satırında r
yazın.):
Yeni NavigationRail
simgelerini, Montserrat yazı tipiyle yazılmış metinle birlikte görürsünüz.
Bir sorun mu var?
Uygulamanız düzgün bir şekilde çalışmıyorsa yazım hataları olup olmadığını kontrol edin. Gerekirse bu sorunu çözmek için aşağıdaki bağlantılarda yer alan kodu kullanın.
5. Temayı belirleme
Temalar, renk ve metin stillerinden oluşan bir sistem belirleyerek uygulamanın yapılandırılmış bir tasarıma ve tek tip ortama sahip olmasını sağlar. Temalar, her widget için tam rengi belirtmek gibi küçük ayrıntıları strese sokmadan kullanıcı arayüzünü hızla uygulamanıza olanak tanır.
Flutter geliştiricileri, özel temalı bileşenleri genellikle şu iki yöntemden biriyle oluşturur:
- Her biri kendi temasına sahip, bağımsız özel widget'lar oluşturun.
- Varsayılan widget'lar için kapsamlı temalar oluşturun.
Bu örnekte, uygulama genelinde tutarlı bir şekilde temalı widget'lar ve renkler oluşturmak için lib/src/shared/providers/theme.dart
'te bulunan bir tema sağlayıcı kullanılmaktadır:
lib/src/shared/providers/theme.dart
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:material_color_utilities/material_color_utilities.dart';
class NoAnimationPageTransitionsBuilder extends PageTransitionsBuilder {
const NoAnimationPageTransitionsBuilder();
@override
Widget buildTransitions<T>(
PageRoute<T> route,
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return child;
}
}
class ThemeSettingChange extends Notification {
ThemeSettingChange({required this.settings});
final ThemeSettings settings;
}
class ThemeProvider extends InheritedWidget {
const ThemeProvider(
{super.key,
required this.settings,
required this.lightDynamic,
required this.darkDynamic,
required super.child});
final ValueNotifier<ThemeSettings> settings;
final ColorScheme? lightDynamic;
final ColorScheme? darkDynamic;
final pageTransitionsTheme = const PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.linux: NoAnimationPageTransitionsBuilder(),
TargetPlatform.macOS: NoAnimationPageTransitionsBuilder(),
TargetPlatform.windows: NoAnimationPageTransitionsBuilder(),
},
);
Color custom(CustomColor custom) {
if (custom.blend) {
return blend(custom.color);
} else {
return custom.color;
}
}
Color blend(Color targetColor) {
return Color(
Blend.harmonize(targetColor.value, settings.value.sourceColor.value));
}
Color source(Color? target) {
Color source = settings.value.sourceColor;
if (target != null) {
source = blend(target);
}
return source;
}
ColorScheme colors(Brightness brightness, Color? targetColor) {
final dynamicPrimary = brightness == Brightness.light
? lightDynamic?.primary
: darkDynamic?.primary;
return ColorScheme.fromSeed(
seedColor: dynamicPrimary ?? source(targetColor),
brightness: brightness,
);
}
ShapeBorder get shapeMedium => RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
);
CardTheme cardTheme() {
return CardTheme(
elevation: 0,
shape: shapeMedium,
clipBehavior: Clip.antiAlias,
);
}
ListTileThemeData listTileTheme(ColorScheme colors) {
return ListTileThemeData(
shape: shapeMedium,
selectedColor: colors.secondary,
);
}
AppBarTheme appBarTheme(ColorScheme colors) {
return AppBarTheme(
elevation: 0,
backgroundColor: colors.surface,
foregroundColor: colors.onSurface,
);
}
TabBarTheme tabBarTheme(ColorScheme colors) {
return TabBarTheme(
labelColor: colors.secondary,
unselectedLabelColor: colors.onSurfaceVariant,
indicator: BoxDecoration(
border: Border(
bottom: BorderSide(
color: colors.secondary,
width: 2,
),
),
),
);
}
BottomAppBarTheme bottomAppBarTheme(ColorScheme colors) {
return BottomAppBarTheme(
color: colors.surface,
elevation: 0,
);
}
BottomNavigationBarThemeData bottomNavigationBarTheme(ColorScheme colors) {
return BottomNavigationBarThemeData(
type: BottomNavigationBarType.fixed,
backgroundColor: colors.surfaceContainerHighest,
selectedItemColor: colors.onSurface,
unselectedItemColor: colors.onSurfaceVariant,
elevation: 0,
landscapeLayout: BottomNavigationBarLandscapeLayout.centered,
);
}
NavigationRailThemeData navigationRailTheme(ColorScheme colors) {
return const NavigationRailThemeData();
}
DrawerThemeData drawerTheme(ColorScheme colors) {
return DrawerThemeData(
backgroundColor: colors.surface,
);
}
ThemeData light([Color? targetColor]) {
final _colors = colors(Brightness.light, targetColor);
return ThemeData.light().copyWith(
pageTransitionsTheme: pageTransitionsTheme,
colorScheme: _colors,
appBarTheme: appBarTheme(_colors),
cardTheme: cardTheme(),
listTileTheme: listTileTheme(_colors),
bottomAppBarTheme: bottomAppBarTheme(_colors),
bottomNavigationBarTheme: bottomNavigationBarTheme(_colors),
navigationRailTheme: navigationRailTheme(_colors),
tabBarTheme: tabBarTheme(_colors),
drawerTheme: drawerTheme(_colors),
scaffoldBackgroundColor: _colors.background,
useMaterial3: true,
);
}
ThemeData dark([Color? targetColor]) {
final _colors = colors(Brightness.dark, targetColor);
return ThemeData.dark().copyWith(
pageTransitionsTheme: pageTransitionsTheme,
colorScheme: _colors,
appBarTheme: appBarTheme(_colors),
cardTheme: cardTheme(),
listTileTheme: listTileTheme(_colors),
bottomAppBarTheme: bottomAppBarTheme(_colors),
bottomNavigationBarTheme: bottomNavigationBarTheme(_colors),
navigationRailTheme: navigationRailTheme(_colors),
tabBarTheme: tabBarTheme(_colors),
drawerTheme: drawerTheme(_colors),
scaffoldBackgroundColor: _colors.background,
useMaterial3: true,
);
}
ThemeMode themeMode() {
return settings.value.themeMode;
}
ThemeData theme(BuildContext context, [Color? targetColor]) {
final brightness = MediaQuery.of(context).platformBrightness;
return brightness == Brightness.light
? light(targetColor)
: dark(targetColor);
}
static ThemeProvider of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<ThemeProvider>()!;
}
@override
bool updateShouldNotify(covariant ThemeProvider oldWidget) {
return oldWidget.settings != settings;
}
}
class ThemeSettings {
ThemeSettings({
required this.sourceColor,
required this.themeMode,
});
final Color sourceColor;
final ThemeMode themeMode;
}
Color randomColor() {
return Color(Random().nextInt(0xFFFFFFFF));
}
// Custom Colors
const linkColor = CustomColor(
name: 'Link Color',
color: Color(0xFF00B0FF),
);
class CustomColor {
const CustomColor({
required this.name,
required this.color,
this.blend = true,
});
final String name;
final Color color;
final bool blend;
Color value(ThemeProvider provider) {
return provider.custom(this);
}
}
Sağlayıcıyı kullanmak için bir örnek oluşturup bunu lib/src/shared/app.dart
konumunda bulunan MaterialApp
bölgesindeki kapsamlı tema nesnesine iletin. İç içe yerleştirilmiş tüm Theme
nesneleri tarafından devralınır:
lib/src/shared/app.dart
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'playback/bloc/bloc.dart';
import 'providers/theme.dart';
import 'router.dart';
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final settings = ValueNotifier(ThemeSettings(
sourceColor: Colors.pink,
themeMode: ThemeMode.system,
));
@override
Widget build(BuildContext context) {
return BlocProvider<PlaybackBloc>(
create: (context) => PlaybackBloc(),
child: DynamicColorBuilder(
builder: (lightDynamic, darkDynamic) => ThemeProvider(
lightDynamic: lightDynamic,
darkDynamic: darkDynamic,
settings: settings,
child: NotificationListener<ThemeSettingChange>(
onNotification: (notification) {
settings.value = notification.settings;
return true;
},
child: ValueListenableBuilder<ThemeSettings>(
valueListenable: settings,
builder: (context, value, _) {
final theme = ThemeProvider.of(context); // Add this line
return MaterialApp.router(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: theme.light(settings.value.sourceColor), // Add this line
routeInformationParser: appRouter.routeInformationParser,
routerDelegate: appRouter.routerDelegate,
);
},
),
)),
),
);
}
}
Tema ayarlandıktan sonra uygulama için renk seçebilirsiniz.
Doğru renk grubunu seçmek her zaman kolay değildir. Birincil renk hakkında bir fikre sahip olabilirsiniz, ancak büyük olasılıkla uygulamanızın birden fazla rengine sahip olmasını istersiniz. Metin ne renk olmalı? Başlık mı? İçerik mi? Bağlantılar mı? Peki arka plan rengi nedir? Material Theme Builder, uygulamanız için bir dizi tamamlayıcı renkler seçmenize yardımcı olan web tabanlı bir araçtır (Materyal 3'te kullanıma sunulmuştur).
Uygulamanın kaynak rengini seçmek için Material Theme Builder'ı açın ve kullanıcı arayüzünün farklı renklerini keşfedin. Markanın estetiğine ve/veya kişisel tercihinize uygun bir renk seçmek önemlidir.
Tema oluşturduktan sonra, Birincil renk balonunu sağ tıklayın. Bu işlem, birincil rengin onaltılık değerini içeren bir iletişim kutusu açar. Bu değeri kopyalayın. (Bu iletişim kutusunu kullanarak rengi de ayarlayabilirsiniz.)
Birincil rengin onaltılık değerini tema sağlayıcıya iletin. Örneğin, #00cbe6
onaltılık rengi Color(0xff00cbe6)
olarak belirtilir. ThemeProvider
, Materyal Tema Oluşturucu'da önizlediğiniz tamamlayıcı renkler grubunu içeren bir ThemeData
oluşturur:
final settings = ValueNotifier(ThemeSettings(
sourceColor: Color(0xff00cbe6), // Replace this color
themeMode: ThemeMode.system,
));
Uygulamayı çalışırken yeniden başlatın. Birincil renk yerine koyduğunda uygulama daha etkileyici görünmeye başlar. Temaya bağlam içinde başvurarak ve ColorScheme
öğesini alarak yeni renklerin tümüne erişin:
final colors = Theme.of(context).colorScheme;
Belirli bir rengi kullanmak için colorScheme
üzerinde renk rolüne erişin. lib/src/shared/views/outlined_card.dart
bölümüne gidin ve OutlinedCard
öğesine kenarlık ekleyin:
lib/src/shared/views/outlined_card.dart
class _OutlinedCardState extends State<OutlinedCard> {
@override
Widget build(BuildContext context) {
return MouseRegion(
cursor: widget.clickable
? SystemMouseCursors.click
: SystemMouseCursors.basic,
child: Container(
child: widget.child,
// Add from here...
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.outline,
width: 1,
),
),
// ... To here.
),
);
}
}
Materyal 3, birbirini tamamlayan ve yeni ifade katmanları eklemek için kullanıcı arayüzü genelinde kullanılabilen incelikli renk rolleri sunar. Bu yeni renk rolleri şunları içerir:
Primary
,OnPrimary
,PrimaryContainer
,OnPrimaryContainer
Secondary
,OnSecondary
,SecondaryContainer
,OnSecondaryContainer
Tertiary
,OnTertiary
,TertiaryContainer
,OnTertiaryContainer
Error
,OnError
,ErrorContainer
,OnErrorContainer
Background
,OnBackground
Surface
,OnSurface
,SurfaceVariant
,OnSurfaceVariant
Shadow
,Outline
InversePrimary
Ayrıca, yeni tasarım jetonları hem açık hem de koyu temaları destekler:
Bu renk rolleri, kullanıcı arayüzünün farklı bölümlerine anlam ve vurgu atamak için kullanılabilir. Bir bileşen belirgin olmasa bile dinamik renkten yararlanabilir.
Kullanıcı, uygulamanın parlaklığını cihazın sistem ayarlarından ayarlayabilir. lib/src/shared/app.dart
'da cihaz koyu moda ayarlandığında MaterialApp
öğesine koyu tema ve tema modu geri döndürür.
lib/src/shared/app.dart
return MaterialApp.router(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: theme.light(settings.value.sourceColor),
darkTheme: theme.dark(settings.value.sourceColor), // Add this line
themeMode: theme.themeMode(), // Add this line
routeInformationParser: appRouter.routeInformationParser,
routerDelegate: appRouter.routerDelegate,
);
Koyu modu etkinleştirmek için sağ üst köşedeki ay simgesini tıklayın.
Bir sorun mu var?
Uygulamanız düzgün çalışmıyorsa sorunu tekrar çözmek için aşağıdaki bağlantıda bulunan kodu kullanın.
6. Uyarlanabilir tasarım ekleme
Flutter ile hemen hemen her yerde çalışan uygulamalar oluşturabilirsiniz. Ancak bu, her uygulamanın her yerde aynı davranması beklendiği anlamına gelmez. Kullanıcılar, farklı platformlardan farklı davranış ve özellikler bekler.
Material, uyarlanabilir düzenlerle çalışmayı kolaylaştıran paketler sunar. Bu Flutter paketlerini GitHub'da bulabilirsiniz.
Platformlar arası, uyarlanabilir uygulama oluştururken aşağıdaki platform farklılıklarını göz önünde bulundurun:
- Giriş yöntemi: fare, dokunma veya oyun kumandası
- Yazı tipi boyutu, cihaz yönü ve görüntüleme mesafesi
- Ekran boyutu ve form faktörü: telefon, tablet, katlanabilir, masaüstü, web
lib/src/shared/views/adaptive_navigation.dart
dosyası, gövdeyi oluşturmak için hedeflerin ve içeriklerin listesini sağlayabileceğiniz bir gezinme sınıfı içerir. Bu düzeni birden fazla ekranda kullandığınızdan, her alt öğeye aktarılacak ortak bir temel düzen olur. Gezinme rayları, masaüstü ve büyük ekranlar için uygundur. Ancak bunun yerine mobil cihazlarda alt gezinme çubuğu göstererek düzeni mobil uyumlu hale getirin.
lib/src/shared/views/adaptive_navigation.dart
import 'package:flutter/material.dart';
class AdaptiveNavigation extends StatelessWidget {
const AdaptiveNavigation({
super.key,
required this.destinations,
required this.selectedIndex,
required this.onDestinationSelected,
required super.child,
});
final List<NavigationDestination> destinations;
final int selectedIndex;
final void Function(int index) onDestinationSelected;
final Widget child;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, dimens) {
// Tablet Layout
if (dimens.maxWidth >= 600) { // Add this line
return Scaffold(
body: Row(
children: [
NavigationRail(
extended: dimens.maxWidth >= 800,
minExtendedWidth: 180,
destinations: destinations
.map((e) => NavigationRailDestination(
icon: e.icon,
label: Text(e.label),
))
.toList(),
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
),
Expanded(child: child),
],
),
);
} // Add this line
// Mobile Layout
// Add from here...
return Scaffold(
body: child,
bottomNavigationBar: NavigationBar(
destinations: destinations,
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
),
);
// ... To here.
},
);
}
}
Tüm ekranlar aynı boyutta değildir. Telefonunuzda uygulamanızın masaüstü sürümünü görüntülemeyi denediyseniz her şeyi görmek için gözlerinizi kısma ve yakınlaştırmayı bir arada yapmanız gerekir. Uygulamanızın, gösterildiği ekrana bağlı olarak görünümünü değiştirmesini istersiniz. Duyarlı tasarım sayesinde uygulamanızın tüm ekran boyutlarında mükemmel görünmesini sağlayabilirsiniz.
Uygulamanızı duyarlı hale getirmek için birkaç uyarlanabilir ayrılma noktası ekleyin (hata ayıklama ayrılma noktalarıyla karıştırmayın). Bu ayrılma noktaları, uygulamanızın düzenini değiştirmesi gereken ekran boyutlarını belirtir.
Küçük ekranlar, içeriği küçültmeden büyük ekranlar kadar büyük ekranlar kadar gösterilemez. Uygulamanın küçültülmüş bir masaüstü uygulaması gibi görünmesini önlemek amacıyla, mobil cihazlarda içeriği bölmek için sekmelerin kullanıldığı ayrı bir düzen oluşturun. Bu sayede uygulama, mobil cihazlarda daha yerel bir görünüme sahip olur.
lib/src/shared/extensions.dart
projesindeki MyArtist projesinde tanımlanan aşağıdaki uzantı yöntemleri, farklı hedefler için optimize edilmiş düzenler tasarlarken iyi bir başlangıç noktası olabilir.
lib/src/shared/extensions.dart
extension BreakpointUtils on BoxConstraints {
bool get isTablet => maxWidth > 730;
bool get isDesktop => maxWidth > 1200;
bool get isMobile => !isTablet && !isDesktop;
}
730 pikselden büyük (en uzun yönde) ancak 1200 pikselden küçük bir ekran tablet olarak kabul edilir. 1.200 pikselden büyük her şey masaüstü olarak kabul edilir. Bir cihaz tablet veya masaüstü değilse mobil olarak kabul edilir. Uyarlanabilir ayrılma noktaları hakkında daha fazla bilgiyi material.io adresinde bulabilirsiniz. adaptive_breakpoints paketini kullanmayı düşünebilirsiniz.
Ana ekranın duyarlı düzeni, Materyal Tasarım'da duyarlı bir ızgara düzeni uygulamak için adaptive_components ve adaptive_breakpoints paketlerini kullanan 12 sütunlu ızgaraya göre AdaptiveContainer
ve AdaptiveColumn
kullanır.
return LayoutBuilder(
builder: (context, constraints) {
return Scaffold(
body: SingleChildScrollView(
child: AdaptiveColumn(
children: [
AdaptiveContainer(
columnSpan: 12,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 40,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
'Good morning',
style: context.displaySmall,
),
),
const SizedBox(width: 20),
const BrightnessToggle(),
],
),
),
),
AdaptiveContainer(
columnSpan: 12,
child: Column(
children: [
const HomeHighlight(),
LayoutBuilder(
builder: (context, constraints) => HomeArtists(
artists: artists,
constraints: constraints,
),
),
],
),
),
AdaptiveContainer(
columnSpan: 12,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 15,
vertical: 20,
),
child: Text(
'Recently played',
style: context.headlineSmall,
),
),
HomeRecent(
playlists: playlists,
),
],
),
),
AdaptiveContainer(
columnSpan: 12,
child: Padding(
padding: const EdgeInsets.all(15),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.only(left: 8, bottom: 8),
child: Text(
'Top Songs Today',
style: context.titleLarge,
),
),
LayoutBuilder(
builder: (context, constraints) =>
PlaylistSongs(
playlist: topSongs,
constraints: constraints,
),
),
],
),
),
const SizedBox(width: 25),
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.only(left: 8, bottom: 8),
child: Text(
'New Releases',
style: context.titleLarge,
),
),
LayoutBuilder(
builder: (context, constraints) =>
PlaylistSongs(
playlist: newReleases,
constraints: constraints,
),
),
],
),
),
],
),
),
),
],
),
),
);
},
);
Uyarlanabilir düzende, biri mobil, diğeri büyük ekranlar için duyarlı düzen olmak üzere iki düzen gerekir. LayoutBuilder
şu anda yalnızca masaüstü düzeni döndürüyor. lib/src/features/home/view/home_screen.dart
ürününde mobil düzeni 4 sekmeli TabBar
ve TabBarView
olarak oluşturun.
lib/src/features/home/view/home_screen.dart
import 'package:adaptive_components/adaptive_components.dart';
import 'package:flutter/material.dart';
import '../../../shared/classes/classes.dart';
import '../../../shared/extensions.dart';
import '../../../shared/providers/providers.dart';
import '../../../shared/views/views.dart';
import '../../playlists/view/playlist_songs.dart';
import 'view.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
final PlaylistsProvider playlistProvider = PlaylistsProvider();
final List<Playlist> playlists = playlistProvider.playlists;
final Playlist topSongs = playlistProvider.topSongs;
final Playlist newReleases = playlistProvider.newReleases;
final ArtistsProvider artistsProvider = ArtistsProvider();
final List<Artist> artists = artistsProvider.artists;
return LayoutBuilder(
builder: (context, constraints) {
// Add from here...
if (constraints.isMobile) {
return DefaultTabController(
length: 4,
child: Scaffold(
appBar: AppBar(
centerTitle: false,
title: const Text('Good morning'),
actions: const [BrightnessToggle()],
bottom: const TabBar(
isScrollable: true,
tabs: [
Tab(text: 'Home'),
Tab(text: 'Recently Played'),
Tab(text: 'New Releases'),
Tab(text: 'Top Songs'),
],
),
),
body: LayoutBuilder(
builder: (context, constraints) => TabBarView(
children: [
SingleChildScrollView(
child: Column(
children: [
const HomeHighlight(),
HomeArtists(
artists: artists,
constraints: constraints,
),
],
),
),
HomeRecent(
playlists: playlists,
axis: Axis.vertical,
),
PlaylistSongs(
playlist: topSongs,
constraints: constraints,
),
PlaylistSongs(
playlist: newReleases,
constraints: constraints,
),
],
),
),
),
);
}
// ... To here.
return Scaffold(
body: SingleChildScrollView(
child: AdaptiveColumn(
children: [
AdaptiveContainer(
columnSpan: 12,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 40,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
'Good morning',
style: context.displaySmall,
),
),
const SizedBox(width: 20),
const BrightnessToggle(),
],
),
),
),
AdaptiveContainer(
columnSpan: 12,
child: Column(
children: [
const HomeHighlight(),
LayoutBuilder(
builder: (context, constraints) => HomeArtists(
artists: artists,
constraints: constraints,
),
),
],
),
),
AdaptiveContainer(
columnSpan: 12,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 15,
vertical: 20,
),
child: Text(
'Recently played',
style: context.headlineSmall,
),
),
HomeRecent(
playlists: playlists,
),
],
),
),
AdaptiveContainer(
columnSpan: 12,
child: Padding(
padding: const EdgeInsets.all(15),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.only(left: 8, bottom: 8),
child: Text(
'Top Songs Today',
style: context.titleLarge,
),
),
LayoutBuilder(
builder: (context, constraints) =>
PlaylistSongs(
playlist: topSongs,
constraints: constraints,
),
),
],
),
),
const SizedBox(width: 25),
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.only(left: 8, bottom: 8),
child: Text(
'New Releases',
style: context.titleLarge,
),
),
LayoutBuilder(
builder: (context, constraints) =>
PlaylistSongs(
playlist: newReleases,
constraints: constraints,
),
),
],
),
),
],
),
),
),
],
),
),
);
},
);
}
}
Bir sorun mu var?
Uygulamanız düzgün çalışmıyorsa sorunu tekrar çözmek için aşağıdaki bağlantıda bulunan kodu kullanın.
Boşluk kullan
Boşluklar, uygulamanız için önemli bir görsel araçtır. Bu araç, bölümler arasında düzenli bir ara oluşturur.
Yeterli boşluk bırakmamaktansa çok fazla boşluk olması daha iyidir. Alana daha fazla sığması için yazı tipinizin veya görsel öğelerinizin boyutunu küçültmek yerine daha fazla boşluk eklemeniz tercih edilir.
Boşluk olmaması, görme sorunları olanlar için zor olabilir. Çok fazla boşluk olması durumunda tutarlılık engellenebilir ve kullanıcı arayüzü kötü düzenlenmiş görünebilir. Örneğin, aşağıdaki ekran görüntülerine bakın:
Ardından, ana ekrana daha fazla alan açmak için boşluk eklersiniz. Ardından, boşlukların ince ayarını yapmak için düzende daha fazla ince ayar yaparsınız.
Bir widget'ın etrafına boşluk eklemek için widget'ı Padding
nesnesiyle sarmalayın. Şu anda lib/src/features/home/view/home_screen.dart
alanında bulunan tüm dolgu değerlerini 35'e yükseltin:
lib/src/features/home/view/home_screen.dart
Scaffold(
body: SingleChildScrollView(
child: AdaptiveColumn(
children: [
AdaptiveContainer(
columnSpan: 12,
child: Padding(
padding: const EdgeInsets.all(35), // Modify this line
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
'Good morning',
style: context.displaySmall,
),
),
const SizedBox(width: 20),
const BrightnessToggle(),
],
),
),
),
AdaptiveContainer(
columnSpan: 12,
child: Column(
children: [
const HomeHighlight(),
LayoutBuilder(
builder: (context, constraints) => HomeArtists(
artists: artists,
constraints: constraints,
),
),
],
),
),
AdaptiveContainer(
columnSpan: 12,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(35), // Modify this line
child: Text(
'Recently played',
style: context.headlineSmall,
),
),
HomeRecent(
playlists: playlists,
),
],
),
),
AdaptiveContainer(
columnSpan: 12,
child: Padding(
padding: const EdgeInsets.all(35), // Modify this line
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.all(35), // Modify this line
child: Text(
'Top Songs Today',
style: context.titleLarge,
),
),
LayoutBuilder(
builder: (context, constraints) => PlaylistSongs(
playlist: topSongs,
constraints: constraints,
),
),
],
),
),
// Add spacer between tables
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.all(35), // Modify this line
child: Text(
'New Releases',
style: context.titleLarge,
),
),
LayoutBuilder(
builder: (context, constraints) => PlaylistSongs(
playlist: newReleases,
constraints: constraints,
),
),
],
),
),
],
),
),
),
],
),
),
);
Uygulamayı çalışırken yeniden yükleyin. Önceki gibi görünmesi gerekir. Ancak widget'lar arasında daha fazla boşluk vardır. Ek dolgu daha iyi görünüyor ancak üst kısımdaki vurgu banner'ı kenarlara hâlâ çok yakın.
lib/src/features/home/view/home_highlight.dart
içinde banner'ın dolgusunu 35 olarak değiştirin:
lib/src/features/home/view/home_highlight.dart
class HomeHighlight extends StatelessWidget {
const HomeHighlight({super.key});
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(35), // Modify this line
child: Clickable(
child: SizedBox(
height: 275,
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.asset(
'assets/images/news/concert.jpeg',
fit: BoxFit.cover,
),
),
),
onTap: () => launch('https://docs.flutter.dev'),
),
),
),
],
);
}
}
Uygulamayı çalışırken yeniden yükleyin. Alt kısımdaki iki oynatma listesinin arasında boşluk olmadığından aynı tabloya ait gibi görünürler. Durum bu değil. Bir sonraki adımda bunu düzelteceksiniz.
Oynatma listelerini içeren Row
öğesine bir boyut widget'ı ekleyerek oynatma listeleri arasına boşluk ekleyin. lib/src/features/home/view/home_screen.dart
'da, 35 genişliğinde bir SizedBox
ekleyin:
lib/src/features/home/view/home_screen.dart
Padding(
padding: const EdgeInsets.all(35),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(35),
child: Text(
'Top Songs Today',
style: context.titleLarge,
),
),
PlaylistSongs(
playlist: topSongs,
constraints: constraints,
),
],
),
),
const SizedBox(width: 35), // Add this line
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(35),
child: Text(
'New Releases',
style: context.titleLarge,
),
),
PlaylistSongs(
playlist: newReleases,
constraints: constraints,
),
],
),
),
],
),
),
Uygulamayı çalışırken yeniden yükleyin. Uygulama şu şekilde görünmelidir:
Artık ana ekran içerikleri için bolca yer var ancak her şey çok ayrı görünüyor ve bölümler arasında uyuşmazlık yok.
Şu ana kadar EdgeInsets.all(35)
kullanarak ana ekrandaki widget'lara ilişkin tüm dolguyu (yatay ve dikey) 35 olarak ayarladınız, ancak kenarların her biri için dolguyu ayrı ayrı da ayarlayabilirsiniz. Dolguyu alana daha iyi sığacak şekilde özelleştirin.
EdgeInsets.LTRB()
sol, üst, sağ ve alt öğelerini ayrı ayrı ayarlarEdgeInsets.symmetric()
, dikey (üst ve alt) dolguyu eşdeğer, yatay (sol ve sağ) dolguyu eşdeğer olacak şekilde ayarlarEdgeInsets.only()
yalnızca belirtilen kenarları ayarlar.
Scaffold(
body: SingleChildScrollView(
child: AdaptiveColumn(
children: [
AdaptiveContainer(
columnSpan: 12,
child: Padding(
padding: const EdgeInsets.fromLTRB(20, 25, 20, 10), // Modify this line
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
'Good morning',
style: context.displaySmall,
),
),
const SizedBox(width: 20),
const BrightnessToggle(),
],
),
),
),
AdaptiveContainer(
columnSpan: 12,
child: Column(
children: [
const HomeHighlight(),
LayoutBuilder(
builder: (context, constraints) => HomeArtists(
artists: artists,
constraints: constraints,
),
),
],
),
),
AdaptiveContainer(
columnSpan: 12,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 15,
vertical: 10,
), // Modify this line
child: Text(
'Recently played',
style: context.headlineSmall,
),
),
HomeRecent(
playlists: playlists,
),
],
),
),
AdaptiveContainer(
columnSpan: 12,
child: Padding(
padding: const EdgeInsets.all(15), // Modify this line
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 8, bottom: 8), // Modify this line
child: Text(
'Top Songs Today',
style: context.titleLarge,
),
),
LayoutBuilder(
builder: (context, constraints) =>
PlaylistSongs(
playlist: topSongs,
constraints: constraints,
),
),
],
),
),
const SizedBox(width: 25),
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 8, bottom: 8), // Modify this line
child: Text(
'New Releases',
style: context.titleLarge,
),
),
LayoutBuilder(
builder: (context, constraints) =>
PlaylistSongs(
playlist: newReleases,
constraints: constraints,
),
),
],
),
),
],
),
),
),
],
),
),
);
lib/src/features/home/view/home_highlight.dart
özelliğinde banner'ın sol ve sağ dolgusunu 35, üst ve alt dolgusunu 5 değerine ayarlayın:
lib/src/features/home/view/home_highlight.dart
class HomeHighlight extends StatelessWidget {
const HomeHighlight({super.key});
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
// Modify this line
padding: const EdgeInsets.symmetric(horizontal: 35, vertical: 5),
child: Clickable(
child: SizedBox(
height: 275,
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.asset(
'assets/images/news/concert.jpeg',
fit: BoxFit.cover,
),
),
),
onTap: () => launch('https://docs.flutter.dev'),
),
),
),
],
);
}
}
Uygulamayı çalışırken yeniden yükleyin. Düzen ve boşluk çok daha iyi görünüyor. Son rötuş için biraz hareket ve animasyon ekleyin.
Bir sorun mu var?
Uygulamanız düzgün çalışmıyorsa sorunu tekrar çözmek için aşağıdaki bağlantıda bulunan kodu kullanın.
7. Hareket ve animasyon ekleme
Hareket ve animasyon, hareket ve enerji vermenin yanı sıra kullanıcı uygulamayla etkileşimde bulunduğunda geri bildirim sağlamak için harika yöntemlerdir.
Ekranlar arasında animasyon uygulama
ThemeProvider
, mobil platformlar (iOS, Android) için ekran geçişi animasyonlarının bulunduğu bir PageTransitionsTheme
tanımlar. Masaüstü kullanıcıları fare veya dokunmatik yüzey tıklamasından zaten geri bildirim aldığından, sayfa geçiş animasyonuna gerek yoktur.
Flutter, lib/src/shared/providers/theme.dart
'te gösterildiği gibi hedef platforma göre uygulamanız için yapılandırabileceğiniz ekran geçişi animasyonları sağlar:
lib/src/shared/providers/theme.dart
final pageTransitionsTheme = const PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.linux: NoAnimationPageTransitionsBuilder(),
TargetPlatform.macOS: NoAnimationPageTransitionsBuilder(),
TargetPlatform.windows: NoAnimationPageTransitionsBuilder(),
},
);
lib/src/shared/providers/theme.dart içindeki PageTransitionsTheme
öğesini hem açık hem de koyu temaya geçirin.
lib/src/shared/providers/theme.dart
ThemeData light([Color? targetColor]) {
final _colors = colors(Brightness.light, targetColor);
return ThemeData.light().copyWith(
pageTransitionsTheme: pageTransitionsTheme, // Add this line
colorScheme: ColorScheme.fromSeed(
seedColor: source(targetColor),
brightness: Brightness.light,
),
appBarTheme: appBarTheme(_colors),
cardTheme: cardTheme(),
listTileTheme: listTileTheme(),
tabBarTheme: tabBarTheme(_colors),
scaffoldBackgroundColor: _colors.background,
);
}
ThemeData dark([Color? targetColor]) {
final _colors = colors(Brightness.dark, targetColor);
return ThemeData.dark().copyWith(
pageTransitionsTheme: pageTransitionsTheme, // Add this line
colorScheme: ColorScheme.fromSeed(
seedColor: source(targetColor),
brightness: Brightness.dark,
),
appBarTheme: appBarTheme(_colors),
cardTheme: cardTheme(),
listTileTheme: listTileTheme(),
tabBarTheme: tabBarTheme(_colors),
scaffoldBackgroundColor: _colors.background,
);
}
iOS'te animasyonsuz
iOS'te animasyonlu
Bir sorun mu var?
Uygulamanız düzgün çalışmıyorsa sorunu tekrar çözmek için aşağıdaki bağlantıda bulunan kodu kullanın.
Fareyle öğelerin üzerine gelerek durumu ekle
Masaüstü uygulamasına hareket eklemenin bir yolu, kullanıcı imleci üzerine getirdiğinde widget'ın durumunu (renk, şekil veya içerik gibi) değiştirdiği fareyle üzerine gelme durumlarıdır.
Varsayılan olarak _OutlinedCardState
sınıfı ("son oynatılan" oynatma listesi kutuları için kullanılır), fareyle üzerine gelindiğinde imleç okunu işaretçiye dönüştüren bir MouseRegion
döndürür. Ancak daha fazla görsel geri bildirim ekleyebilirsiniz.
lib/src/shared/views/outlined_card.dart dosyasını açın ve içeriğini aşağıdaki uygulamayla değiştirerek _hovered
durumunu kullanıma sunun.
lib/src/shared/views/outlined_card.dart
import 'package:flutter/material.dart';
class OutlinedCard extends StatefulWidget {
const OutlinedCard({
super.key,
required this.child,
this.clickable = true,
});
final Widget child;
final bool clickable;
@override
State<OutlinedCard> createState() => _OutlinedCardState();
}
class _OutlinedCardState extends State<OutlinedCard> {
bool _hovered = false;
@override
Widget build(BuildContext context) {
final borderRadius = BorderRadius.circular(_hovered ? 20 : 8);
const animationCurve = Curves.easeInOut;
return MouseRegion(
onEnter: (_) {
if (!widget.clickable) return;
setState(() {
_hovered = true;
});
},
onExit: (_) {
if (!widget.clickable) return;
setState(() {
_hovered = false;
});
},
cursor: widget.clickable ? SystemMouseCursors.click : SystemMouseCursors.basic,
child: AnimatedContainer(
duration: kThemeAnimationDuration,
curve: animationCurve,
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.outline,
width: 1,
),
borderRadius: borderRadius,
),
foregroundDecoration: BoxDecoration(
color: Theme.of(context).colorScheme.onSurface.withOpacity(
_hovered ? 0.12 : 0,
),
borderRadius: borderRadius,
),
child: TweenAnimationBuilder<BorderRadius>(
duration: kThemeAnimationDuration,
curve: animationCurve,
tween: Tween(begin: BorderRadius.zero, end: borderRadius),
builder: (context, borderRadius, child) => ClipRRect(
clipBehavior: Clip.antiAlias,
borderRadius: borderRadius,
child: child,
),
child: widget.child,
),
),
);
}
}
Uygulamayı yeniden yükleyin ve ardından fareyle, son oynatılan oynatma listesi karolarından birinin üzerine gelin.
OutlinedCard
, opaklığı değiştirir ve köşeleri yuvarlar.
Son olarak, lib/src/shared/views/hoverable_song_play_button.dart
içinde tanımlanan HoverableSongPlayButton
widget'ını kullanarak bir oynatma listesindeki şarkı numarasına animasyon ekleyin. lib/src/features/playlists/view/playlist_songs.dart
'da Center
widget'ını (şarkı numarasını içerir) bir HoverableSongPlayButton
ile sarmalayın:
lib/src/features/playlists/view/playlist_songs.dart
HoverableSongPlayButton( // Add this line
hoverMode: HoverMode.overlay, // Add this line
song: playlist.songs[index], // Add this line
child: Center( // Modify this line
child: Text(
(index + 1).toString(),
textAlign: TextAlign.center,
),
),
), // Add this line
Uygulamayı yeniden yükleyin ve imleci Günün En Popüler Şarkıları veya Yeni Çıkanlar şarkı listesindeki şarkı numarasının üzerine getirin.
Sayı, tıkladığınızda şarkıyı çalan bir oynat düğmesine dönüşür.
Projenin son kodunu GitHub'da görün.
8. Tebrikler!
Bu codelab'i tamamladınız. Bir uygulamayı daha güzel, aynı zamanda daha erişilebilir, yerelleştirilebilir ve çeşitli platformlar için daha uygun hale getirmek için uygulamaya entegre edebileceğiniz pek çok küçük değişiklik olduğunu öğrendiniz. Bu teknikler aşağıdakileri kapsar, ancak bunlarla sınırlı değildir:
- Tipografi: Metin bir iletişim aracından daha fazlasıdır. Kullanıcılar üzerinde olumlu bir etki yaratmak için metnin görüntülenme şeklini kullanın. ve uygulamanızla ilgili algıyı güçlendirir.
- Tema: Her widget için tasarım kararları vermek zorunda kalmadan, güvenilir bir şekilde kullanabileceğiniz bir tasarım sistemi oluşturun.
- Uyarlanabilirlik: Kullanıcının uygulamanızı çalıştırdığı cihaz ile platformu ve uygulamanın özelliklerini göz önünde bulundurun. Ekran boyutunu ve uygulamanızın nasıl görüntülendiğini göz önünde bulundurun.
- Hareket ve animasyon: Uygulamanıza hareket eklemek, kullanıcı deneyimine enerji katar ve hatta kullanıcılara geri bildirim verir.
Birkaç ufak değişiklikle uygulamanızı sıkıcı olmaktan güzelliğe taşıyabilirsiniz:
Önce
Sonra
Sonraki adımlar
Flutter'da güzel uygulamalar geliştirme hakkında daha fazla bilgi edindiğinizi umuyoruz.
Burada belirtilen ipuçları veya püf noktalarından herhangi birini uygularsanız (ya da paylaşmak istediğiniz bir ipucu varsa) görüşlerinizi almak isteriz. Twitter'da @rodydavis ve @khanhnwin üzerinden bize ulaşabilirsiniz.
Aşağıdaki kaynaklardan da yararlanabilirsiniz.
Tema oluşturma
- Material Theme Builder (araç)
Uyarlanabilir ve duyarlı kaynaklar:
- Uyarlanabilir ve Duyarlı Sürümde Flutter'ın Kodunu Çözme (video)
- Uyarlanabilir düzenler (The Boring Flutter Development Show'un videosu)
- Duyarlı ve uyarlanabilir uygulamalar oluşturma (flutter.dev)
- Flutter için Uyarlanabilir Materyal bileşenleri (GitHub'daki kitaplık)
- Uygulamanızı büyük ekranlara hazırlamak için yapabileceğiniz 5 şey (Google I/O 2021 videosu)
Genel tasarım kaynakları:
- Küçük şeyler: Efsanevi tasarımcı geliştirici olmak (Flutter Engage'in videosu)
- Katlanabilir Cihazlar için Materyal Tasarım 3 (material.io)
Ayrıca Flutter topluluğuyla bağlantı kurabilirsiniz.
İlerleyin ve uygulamayı güzelleştirin.