Có rất nhiều cách áp dụng dynamic theme trong Flutter. Vì Provider đang được khá nhiều người sử dụng nên chúng ta hãy cùng thử tạo ứng dụng và thiết lập dynamic theme sử dụng Provider nhé.
Cài đặt
Tạo ứng dụng counter mặc định của Flutter: flutter create counter
Mình có tách MyHomePage
và _MyHomePageState
ra một file riêng là lib/home.dart
để rõ ràng hơn và tránh việc khó theo dõi.
Sau đó chúng ta thêm thư viện provider vào pubspec.yaml
1 2 3 4 5 6 7 8 9 10 | <span class="token key atrule">dependencies</span><span class="token punctuation">:</span> <span class="token key atrule">flutter</span><span class="token punctuation">:</span> <span class="token key atrule">sdk</span><span class="token punctuation">:</span> flutter <span class="token key atrule">cupertino_icons</span><span class="token punctuation">:</span> ^0.1.3 <span class="token key atrule">provider</span><span class="token punctuation">:</span> ^4.0.5 <span class="token comment"># Thêm thư viện provider</span> <span class="token key atrule">dev_dependencies</span><span class="token punctuation">:</span> <span class="token key atrule">flutter_test</span><span class="token punctuation">:</span> <span class="token key atrule">sdk</span><span class="token punctuation">:</span> flutter |
Khởi tạo
Tạo file tên themes.dart
, file này sẽ chứa các theme và các class liên quan đến theme.
Đầu tiên ta sẽ tạo ra 1 enum chứa tên của các theme trong class thay vì sử dụng string để tránh các lỗi lặt vặt do sai chính tả hoặc không báo lỗi nếu như chúng ta đổi tên theme nhưng lại quên không đổi ở đâu đó trong app.
Ở đây mình muốn app có 2 theme là light và dark
1 2 | <span class="token keyword">enum</span> AppThemeKeys <span class="token punctuation">{</span> light<span class="token punctuation">,</span> dark <span class="token punctuation">}</span> |
Sau khi xong, chúng ta sẽ tạo một Map
chứa các thuộc tính của các theme có trong app. Mình muốn các AppBar, Button trong light theme sẽ có màu xanh nước biển, còn dark theme sẽ là màu xanh lá cây. Thuộc tính brightness sẽ giúp mình tự động chuyển một số màu từ light sang dark để phù hợp với theme (ví dụ như ở light theme thì màu chữ là màu đen xám, màu background là màu trắng, khi qua dark theme thì màu chữ sẽ là màu trắng, màu background là màu đen xám). Bạn có thể đọc thêm về brightness tại đây
1 2 3 4 5 6 7 8 9 10 11 12 13 | <span class="token keyword">final</span> Map<span class="token operator"><</span>AppThemeKeys<span class="token punctuation">,</span> ThemeData<span class="token operator">></span> _themes <span class="token operator">=</span> <span class="token punctuation">{</span> AppThemeKeys<span class="token punctuation">.</span>light<span class="token punctuation">:</span> <span class="token function">ThemeData</span><span class="token punctuation">(</span> primaryColor<span class="token punctuation">:</span> Colors<span class="token punctuation">.</span>blue<span class="token punctuation">,</span> accentColor<span class="token punctuation">:</span> Colors<span class="token punctuation">.</span>blue<span class="token punctuation">,</span> brightness<span class="token punctuation">:</span> Brightness<span class="token punctuation">.</span>light<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> AppThemeKeys<span class="token punctuation">.</span>dark<span class="token punctuation">:</span> <span class="token function">ThemeData</span><span class="token punctuation">(</span> primaryColor<span class="token punctuation">:</span> Colors<span class="token punctuation">.</span>green<span class="token punctuation">,</span> accentColor<span class="token punctuation">:</span> Colors<span class="token punctuation">.</span>green<span class="token punctuation">,</span> brightness<span class="token punctuation">:</span> Brightness<span class="token punctuation">.</span>dark<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> |
Tiếp theo đó chúng ta sẽ tạo class AppTheme
kế thừa ChangeNotifier
, class này sẽ chính là class điều khiển trạng thái theme của app chúng ta. Bạn có thể đọc thêm về ChangeNotifier để nắm rõ hơn cách nó hoạt động
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | <span class="token keyword">class</span> <span class="token class-name">AppTheme</span> <span class="token keyword">extends</span> <span class="token class-name">ChangeNotifier</span> <span class="token punctuation">{</span> <span class="token comment">// Method này sẽ giúp chúng ta lấy AppTheme ra một cách dễ dàng hơn, thay vì</span> <span class="token comment">// Provider.of<AppTheme>(context)</span> <span class="token comment">// chúng ta chỉ cần</span> <span class="token comment">// AppTheme.of(context)</span> <span class="token keyword">static</span> AppTheme <span class="token function">of</span><span class="token punctuation">(</span>BuildContext context<span class="token punctuation">,</span> <span class="token punctuation">{</span>bool listen <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">></span> Provider<span class="token punctuation">.</span>of<span class="token operator"><</span>AppTheme<span class="token operator">></span><span class="token punctuation">(</span>context<span class="token punctuation">,</span> listen<span class="token punctuation">:</span> listen<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Đây sẽ là state chính của class, chứa tên của theme, mặc định mình set là light</span> AppThemeKeys _themeKey <span class="token operator">=</span> AppThemeKeys<span class="token punctuation">.</span>light<span class="token punctuation">;</span> <span class="token comment">// Mình tạo thêm 2 cái getter này để dễ sử dụng hơn</span> <span class="token comment">// currentTheme sẽ là theme hiện tại (là ThemeData không phải tên theme nữa)</span> <span class="token comment">// currentThemeKey là tên theme hiện tại (mình không public _themeKey bởi vì </span> <span class="token comment">// mình không muốn chỉnh sửa trực tiếp vào biến mà qua các setter vì còn cần notifyListeners() nữa)</span> ThemeData <span class="token keyword">get</span> currentTheme <span class="token operator">=</span><span class="token operator">></span> _themes<span class="token punctuation">[</span>_themeKey<span class="token punctuation">]</span><span class="token punctuation">;</span> AppThemeKeys <span class="token keyword">get</span> currentThemeKey <span class="token operator">=</span><span class="token operator">></span> _themeKey<span class="token punctuation">;</span> <span class="token comment">// Đổi theme sang một theme khác </span> <span class="token keyword">void</span> <span class="token function">setTheme</span><span class="token punctuation">(</span>AppThemeKeys themeKey<span class="token punctuation">)</span> <span class="token punctuation">{</span> _themeKey <span class="token operator">=</span> themeKey<span class="token punctuation">;</span> <span class="token function">notifyListeners</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// Ở đây mình chỉ có 2 theme nên mình sẽ tạo thêm 1 method để demo dễ hơn. Method này sẽ switch giữa</span> <span class="token comment">// light theme và dark theme</span> <span class="token keyword">void</span> <span class="token function">switchTheme</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>_themeKey <span class="token operator">==</span> AppThemeKeys<span class="token punctuation">.</span>dark<span class="token punctuation">)</span> <span class="token punctuation">{</span> _themeKey <span class="token operator">=</span> AppThemeKeys<span class="token punctuation">.</span>light<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> _themeKey <span class="token operator">=</span> AppThemeKeys<span class="token punctuation">.</span>dark<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">notifyListeners</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Vậy là xong phần chính rồi. Tiếp sau đây sẽ là sử dụng vào app counter mặc định của Flutter
Áp dụng
Mở file main.dart
, tại hàm main()
chúng ta sẽ cần bọc cả App trong ChangeNotifierProvider
và truyền một instance của AppTheme
mình vừa tạo vào thuộc tính create
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token comment">// Trước</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">runApp</span><span class="token punctuation">(</span><span class="token function">MyApp</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// Sau</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">runApp</span><span class="token punctuation">(</span> <span class="token function">ChangeNotifierProvider</span><span class="token punctuation">(</span> create<span class="token punctuation">:</span> <span class="token punctuation">(</span>_<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token function">AppTheme</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> child<span class="token punctuation">:</span> <span class="token function">MyApp</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> |
Giờ đây các widget con sẽ có thể lấy được instance kia bằng cách sử dụng Provider.of<AppTheme>(context)
hoặc AppTheme.of(context)
Vậy nên chúng ta sẽ sử dụng luôn tại class MyApp
vì trong đó có sử dụng MaterialApp
, nơi chúng ta có thể đổi theme.
Chúng ta sẽ đổi thuộc tính theme
từ giá trị hiện tại qua AppTheme.of(context, listen: true).currentTheme
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <span class="token comment">// Trước</span> <span class="token keyword">return</span> <span class="token function">MaterialApp</span><span class="token punctuation">(</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> theme<span class="token punctuation">:</span> <span class="token function">ThemeData</span><span class="token punctuation">(</span> primarySwatch<span class="token punctuation">:</span> Colors<span class="token punctuation">.</span>blue<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Sau</span> <span class="token keyword">return</span> <span class="token function">MaterialApp</span><span class="token punctuation">(</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> theme<span class="token punctuation">:</span> AppTheme<span class="token punctuation">.</span><span class="token function">of</span><span class="token punctuation">(</span>context<span class="token punctuation">,</span> listen<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">.</span>currentTheme<span class="token punctuation">,</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> |
Chúng ta thêm thuộc tính listen: true vì chúng ta muốn update lại view mỗi khi state của AppTheme
thay đổi.
Vậy là khi state của AppTheme
thay đổi, theme của chúng ta cũng thay đổi theo.
Chúng ta cần đổi theme khi bấm vào một button nào đó trong view, vậy trước tiên chúng ta cần lấy instance của AppTheme
tương tự như trên.
Tại _MyHomePageState
tạo một biến để chứa AppTheme và khởi tạo nó trong didChangeDependencies
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token keyword">class</span> <span class="token class-name">_MyHomePageState</span> <span class="token keyword">extends</span> <span class="token class-name">State</span><span class="token operator"><</span>MyHomePage<span class="token operator">></span> <span class="token punctuation">{</span> AppTheme _theme<span class="token punctuation">;</span> <span class="token metadata symbol">@override</span> <span class="token keyword">void</span> <span class="token function">didChangeDependencies</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>_theme <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> _theme <span class="token operator">=</span> AppTheme<span class="token punctuation">.</span><span class="token function">of</span><span class="token punctuation">(</span>context<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">super</span><span class="token punctuation">.</span><span class="token function">didChangeDependencies</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token punctuation">}</span> |
Sau đó mình kéo xuống đoạn khai báo AppBar và thêm một button bên phải của AppBar để đổi theme. Tại hàm onPressed của button mình gọi đến hàm switchTheme()
của AppTheme
để đổi qua lại giữa 2 theme. Mình sử dụng ?.
để đề phòng trường hợp _theme
null
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> appBar<span class="token punctuation">:</span> <span class="token function">AppBar</span><span class="token punctuation">(</span> title<span class="token punctuation">:</span> <span class="token function">Text</span><span class="token punctuation">(</span>widget<span class="token punctuation">.</span>title<span class="token punctuation">)</span><span class="token punctuation">,</span> actions<span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token function">IconButton</span><span class="token punctuation">(</span> icon<span class="token punctuation">:</span> <span class="token function">Icon</span><span class="token punctuation">(</span>Icons<span class="token punctuation">.</span>flip<span class="token punctuation">,</span> color<span class="token punctuation">:</span> Colors<span class="token punctuation">.</span>white<span class="token punctuation">)</span><span class="token punctuation">,</span> onPressed<span class="token punctuation">:</span> _theme<span class="token operator">?</span><span class="token punctuation">.</span>switchTheme<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> |
Vậy là xong. Và đây là thành quả: