Xin chào các bạn, ở bài trước mình có giới thiệu cho các bạn về Animation Listener và Custom Animator với Lottie.
Trong bài này mình sẽ giới thiệu cho các bạn về cách điều chỉnh các thuộc tính động. Bạn có thể điều chỉnh các thuộc tính động trong thời gian đang chạy của nó. Một số mục đích như:
Thay đổi chủ đề.
Thay đổi kích thước và thời gian.
Đáp ứng với những sự kiện lỗi hay thành công.
Nội dung bài bao gồm:
Hiểu về After Effects để có thể vận dụng vào trong bài này.
Cần có những gì để thay đổi?
Cách thực hiện nó như thế nào?
Hiểu về After Effects
Để điều chỉnh các thuộc tính trong Lottie thì mình cần hiểu các thuộc tính đó.
Các thuộc tính này được kế thừa từ các thuộc tính trong After Effects. Trong After Effects, nó là tập hợp các Layer ứng với mỗi một thời gian. Đối tượng trong Layer bao gồm: tên, màu sắc, kích thước … Lottie có thể tìm thấy các đối tượng và thuộc tính bằng KeyPath.
Cần có những gì để thay đổi?
Để thay đổi thuộc tính trong thời gian chạy, bạn cần có:
KeyPath
LottieProperty
LottieValueCallback
KeyPath
KeyPath được sử dụng với một nội dung cụ thể hoặc toàn bộ nội dung cần thay đổi. Nó được xác định bởi một danh sách các chuỗi tương ứng trong cấu trúc phân cấp của After Effects.
KeyPath bao gồm tên cụ thể của nội dung hoặc ký tự đại diện:
Wildcard *: sử dụng để phù hợp với nội dung duy nhất ở vị trí của nó trong KeyPath.
Globstar **: sử dụng để phù hợp với không hoặc nhiều layer.
KeyPath resolution
KeyPath có khả năng lưu trữ một tham chiếu nội bộ đến nội dung mà họ quyết định. Khi bạn tạo một đối tượng KeyPath mới, nó sẽ không được quyết định. LottieDrawable và LottieAnimationView có một phương thức notifyKeyPath() lấy KeyPath và trả về một danh sách bằng 0 hoặc nhiều quyết định mà mỗi quyết định thành một phần nội dung bên trong. Điều này có thể được sử dụng để khám phá cấu trúc animation của bạn. Để làm như vậy, trong môi trường phát triển, new KeyPath("**") và ghi lại danh sách được trả về. Tuy nhiên, bạn không nên sử dụng ** với ValueCallback vì nó sẽ được áp dụng cho mọi phần nội dung trong animation của bạn. Nếu bạn quyết định KeyPath của mình và muốn thêm một giá trị callback, hãy sử dụng KeyPath được trả về từ phương thức đó vì chúng sẽ được giải quyết nội bộ và sẽ không phải tìm lại nội dung.
LottieProperty
LottieProperty là các thuộc tính có thể được set. Chúng tương ứng với giá trị trong After Effects.
Bạn có thể tham khảo các thuộc tính ở đây.
ValueCallback
ValueCallback được gọi mỗi khi animation hoạt động. Nó cung cấp:
Khung bắt đầu của khung hình hiện tại.
Khung kết thúc của khung hình hiện tại.
Giá trị bắt đầu của khung hình hiện tại.
Giá trị kết thúc của khung hình hiện tại.
Giá trị progress từ 0 tới 1 của khung hình hiện tại với ngoài thời gian interpolation.
Giá trị progress của khung hình hiện tại với thời gian interpolator.
Progress trong tổng thể animation từ 0 tới 1.
Ngoài ra, cũng có một số lớp con ValueCallback như LottieStaticValueCallback, nó nhận đúng một giá trị trong constructor và sẽ luôn trả về giá trị đó.
ValueCallback classes
LottieValueCallback: đặt giá trị tĩnh trong contructor or override getValue().
LottieRelativeTYPEValueCallback: đặt giá trị trong constructor or override getOffset().
LottieInterpolatedTYPEValue: cung cấp giá trị bắt đầu, giá trị kết thúc và tuỳ chọn interpolator để có thời gian.
Cách thực hiện nó như thế nào?
Mình vẫn sử dụng trail-loading.json ở bài trước để điều chỉnh.
Sau đó, bạn hãy xem các thuộc tính và phân cấp của nó:
Hoặc, có thể sử dụng công cụ Lottie JSON Editor.
(Với file mình sử dụng bên trên thì sẽ có 5 layer, còn các bạn…). Dưới đây sẽ là một vài ví dụ thay đổi.
Thay đổi trong một layer
Mình sẽ thay đổi màu Shape Layer 1("nm": "Shape Layer 1"), trong Ellipse 1("nm": "Ellipse 1") sang màu đỏ như sau:
Các bạn có thể tham khảo các thuộc tính có thể thay đổi dưới đây:
Transform:
TRANSFORM_ANCHOR_POINT
TRANSFORM_POSITION
TRANSFORM_OPACITY
TRANSFORM_SCALE
TRANSFORM_ROTATION
Fill:
COLOR (non-gradient)
OPACITY
COLOR_FILTER
Stroke:
COLOR (non-gradient)
STROKE_WIDTH
OPACITY
COLOR_FILTER
Ellipse:
POSITION
ELLIPSE_SIZE
Polystar:
POLYSTAR_POINTS
POLYSTAR_ROTATION
POSITION
POLYSTAR_OUTER_RADIUS
POLYSTAR_OUTER_ROUNDEDNESS
POLYSTAR_INNER_RADIUS (star)
POLYSTAR_INNER_ROUNDEDNESS (star)
Repeater:
All transform properties
REPEATER_COPIES
REPEATER_OFFSET
TRANSFORM_ROTATION
TRANSFORM_START_OPACITY
TRANSFORM_END_OPACITY
Layers:
All transform properties
TIME_REMAP (composition layers only)
Bài này đến đây là kết thúc rồi, hãy đón đọc bài viết tiếp theo của mình bạn nhé. Cảm ơn các bạn đã dành thời gian đọc bài của mình. Rất mong nhận được sự góp ý từ các bạn ^^
Xin chào các bạn, ở bài trước mình có giới thiệu cho các bạn về Lottie và cách sử dụng Lottie cho Android.
Trong bài này mình sẽ giới thiệu cho các bạn về Animation Listener và Custom Animator với Lottie.
Animation Listener
Có rất nhiều trường hợp khi sử dụng Lottie mà chúng ta cần phải xử lý các công việc khác nữa. Dưới đây là một vài trường hợp cụ thể:
Mở một màn hình mới sau khi kết thúc chạy animation với Lottie.
Cập nhật giá trị trong khi đang chạy animation với Lottie.
Điều chỉnh tốc độ hay thời gian chạy của animation.
Tham số valueAnimator bên trên thực chất nó là tham số số trong ValueAnimator Class ở Android SDK. Nó cung cấp cho bạn biết về trạng thái hiện tại và thời gian hiện tại của animation.
Bây giờ, tôi sẽ có 1 bài toán nho nhỏ như sau: ở bài trước tôi sử dụng loading với Lottie, bài này tôi sẽ thực hiện khi loading thì sẽ thực hiện cùng ProgressBar và set giá trị của progress đó hiển thị trên màn hình.
Để kết hợp Lottie vào ứng dụng để có những hình ảnh mượt mà, sinh động thì thật đơn giản phải không nào? 😉 Nhưng sẽ có nhiều trường hợp việc kết hợp cũng trở nên quan ngại phần nào: ví dụ như kết hợp Lottie khi download, khi scroll position hay cử chỉ… Vì vậy chúng ta phải Custom Animator để cho phù hợp với từng bài toán. Ví dụ sau sẽ giúp các bạn hình dung dễ hơn:
//Custom animation speed or duration.
val animator = ValueAnimator.ofFloat(0f, 2f)
animator.addUpdateListener { valueAnimator: ValueAnimator ->
loading_animation.speed = valueAnimator.animatedValue as Float
}
animator.start()
Ở đây, mình đã thực hiện điều chỉnh tốc độ của loading chạy từ 0 -> 2 bằng việc sử dụng Animator.
Custom Animator
Bài tiếp theo mình sẽ giới thiệu làm cách nào để điều chỉnh các thuộc tính động, sẽ có nhiều cái thú vị đấy, hãy đón đọc bài viết của mình bạn nhé.
Cảm ơn các bạn đã dành thời gian đọc bài của mình. Rất mong nhận được sự góp ý từ các bạn ^^
Xin chào các bạn hi hi, lại là mình đây. 🙂
Để thay đổi không khí sau loạt bài về animation, hôm nay mình sẽ giới thiệu với các bạn về Lottie cho Android.
Sau những loạt bài về animation thì các bạn có thấy ứng dụng của chúng ta đã trở nên đẹp và sinh động hơn chưa?. Tôi nghĩ chắc chắn là rồi phải không? 🙂
Nhưng sẽ có nhiều vị khách khó tính thì vẫn có chút xíu chưa hài lòng về độ mượt mà của animation. Bạn đừng lo, Lottie sẽ giải quyết vấn đề đó cho bạn ngay.
Ứng dụng của bạn sẽ trở nên mượt mà, sinh động và đẹp hơn rất nhiều nữa đấy. Nghe đến đây thì bạn đã hào hứng để tìm hiểu nó rồi chứ. Let’s go…
Trong bài này mình sẽ giới thiệu đến các bạn những mục sau:
Giới thiệu về Lottie
Cách sử dụng Lottie cho Android
Giới thiệu về Lottie
Lottie là một mã nguồn mở về animation được xây dựng bởi Airbnb. Nó có thể dùng được ở Android(hỗ trợ android từ phiên bản JellyBean API 16), iOS, React Native hay cả Web. Về bản chất hoạt động thì nó sẽ parse animation từ Adobe After Effects, thông qua Bodymovin và được xuất ra định dạng json. Sau đó, các nhà phát triển của các platform sẽ sử dụng công cụ thư viện Lottie để các animation sẽ được hiển thị tương ứng trên các platform.
Cách sử dụng Lottie cho Android
Đầu tiên, bạn chuẩn bị cho mình file có định json (được xuất ra từ Adobe After Effects, thông qua Bodymovin như bên trên mình đã chia sẻ).
Thường thì cái này được design team của các dự án cung cấp cho bạn hoặc bạn tự tạo đều được.
Bạn cũng có thể tham khảo ở đây rất nhiều.
Tôi đã chuẩn bị cho mình trail-loading.json cho bài viết này.
Sau khi có được file định dang json bên trên thì tiếp đến bạn sẽ tạo project và thêm vào trong build.gradle một dependencies như dưới đây:
Bạn sẽ tạo assets folder (app/src/main/assets) và copy file có định dạng json bên trên vào nhé. Còn mình sẽ copy file trail-loading.json của mình vào.
Tiếp theo bạn sẽ thêm animation vào xml với layout tương ứng để bạn hiển thị. Ở đây, mình sẽ thêm vào xml trên activity của mình.
Thật đơn giản phải không các bạn? Giờ thì xem thành quả của bạn vừa làm nhé.
Trail-Loading
Đến đây, các bạn sẽ đặt câu hỏi rằng: tôi có thể điều khiển và lắng nghe nó không? (think) Oh, Tất nhiên là được rồi. Bài tiếp theo mình sẽ giới thiệu, hãy đón đọc bài viết của mình bạn nhé.
Cảm ơn các bạn đã dành thời gian đọc bài của mình. Rất mong nhận được sự góp ý từ các bạn ^^
Mặc định thì Oracle JDK sẽ được chọn cài đặt trên MacOS. Do đó nếu muốn sử dụng Oracle JDK thì bạn cần phải cài đặt lại. Trong bài viết này tôi sẽ hướng dẫn các bạn cài đặt Oracle JDK.
Homebrew
Nếu bạn chưa cài đặt brew thì có thể sử dụng lệnh sau để tiến hành cài đặt
Nếu đã cài đặt rồi bạn sẽ nhận được thông tin về phiên bản java đã được cài đặt
hieunv@HieuNV ~ % brew cask info java
java: 13.0.2,8:d4173c853231432d94f001e99d882ca7
https://openjdk.java.net/
Not installed
From: https://github.com/Homebrew/homebrew-cask/blob/master/Casks/java.rb
==> Name
OpenJDK Java Development Kit
==> Artifacts
jdk-13.0.2.jdk -> /Library/Java/JavaVirtualMachines/openjdk-13.0.2.jdk (Generic Artifact)
Tiến hành cài đặt Oracle JDK sử dụng brew cask
hieunv@HieuNV ~ % brew cask install oracle-jdk
==> Caveats
Installing oracle-jdk means you have AGREED to the license at:
https://www.oracle.com/technetwork/java/javase/terms/license/javase-license.html
==> Downloading https://download.oracle.com/otn-pub/java/jdk/13.0.2+8/d4173c8532
==> Downloading from https://download.oracle.com/otn-pub/java/jdk/13.0.2+8/d4173
######################################################################## 100.0%
==> Verifying SHA-256 checksum for Cask 'oracle-jdk'.
==> Installing Cask oracle-jdk
==> Running installer for oracle-jdk; your password may be necessary.
==> Package installers may write to any location; options such as --appdir are i
Password:
installer: Package name is JDK 13.0.2
installer: Installing at base path /
installer: The install was successful.
? oracle-jdk was successfully installed!
Kiểm tra phiên bản java sau khi cài đặt
hieunv@HieuNV ~ % java --version
java 13.0.2 2020-01-14
Java(TM) SE Runtime Environment (build 13.0.2+8)
Java HotSpot(TM) 64-Bit Server VM (build 13.0.2+8, mixed mode, sharing)
hieunv@HieuNV ~ % javac --version
javac 13.0.2
setting JAVA_HOME
Thêm export JAVA_HOME=$(/usr/libexec/java_home) vào ~/.zshrc
Xin chào các bạn, bài tiếp theo trong series về animation của mình đó là giới thiệu cho các bạn về AnimatedVectorDrawable.
VectorDrawable
Thông thường để có những hình ảnh tương tích với các màn hình khác nhau trong thiết bị Android thì bạn sẽ tạo ra các hình ảnh như hdpi, mdpi, xhdpi, xxhdpi, xxxhdpi.
Để tối ưu cho phần này thì trong API 21, Android đã phát hành VectorDrawable giúp thay thế nhiều ảnh .png thành một đồ hoạ vector được tạo bằng xml.
VectorDrawable bao gồm các điểm, đường thằng, đường cong, màu sắc…khi co giãn không làm ảnh hưởng tới chất lượng của ảnh. Đó thực sự là một điểm mạnh của VectorDrawable.
Ví dụ VectorDrawable cho numeric_0 ở bài trước như dưới đây:
cú pháp: <vector>: xác định một vector drawable cần vẽ. android:width & android:height: kích thước chiều rộng, chiều cao của hình dạng vector. android:viewportWidth & android:viewportHeight: khung cửa sổ để vẽ hình dạng vector. <path>: bên trong thẻ <vector>, xác định đường dẫn để vẽ. android:fillColor: màu bạn sử dụng android:pathData: các thuộc tính để vẽ và theo bộ quy tắc dưới đây:
M: di chuyển điểm vẽ đến tọa độ x, y (M x y).
L: vẽ từ điểm hiện tại đến điểm x, y (L x y).
H: vẽ đường ngang từ điểm hiện tại đến điểm có tọa độ x (H x).
V: vẽ đường thẳng đứng đến điểm có tọa độ y (V y).
C: vẽ đường cong cubic-bezier từ điểm hiện tại x0, y0 đến điểm x, y. Điểm đầu đường cong tiếp tuyến với đường thẳng x0,y0, x1, y1. Điểm thứ 2 của đường cong tiếp tuyến với tường x,y, x2, y2 C x1 y1, x2 y2, x, y.
S: vẽ đường cong trơn từ điểm hiện tại x0, y0 đến điểm x, y trong đó điểm đầu tiếp tuyến với đường x0,y0, x2, y2 S x2 y2, x y.
Q: vẽ đường cong cubic-bezier từ điểm hiện tại x0, y0 đến điểm x, y. điểm đầu đường cong tiếp tuyến với đường thẳng x0,y0, x1, y1 điểm thứ 2 của đường cong tiếp tuyến với tường x,y, x1, y1 C x1 y1, x y.
T: vẽ đường cong cubic-bezier, từ điểm hiện tại đến điểm x,y (T x y).
A: vẽ cung tròn.
Z: đóng đường vẽ.
Ngoài ra, chúng ta còn các cú pháp khác nữa. Bạn có thể tham khảo ở đây
AnimatedVectorDrawable
Để giới thiệu cho các bạn về AnimatedVectorDrawable thì tôi sẽ làm giống animation của bài trước, nhưng sẽ sử dụng AnimatedVectorDrawable để cho các bạn hình dung về nó dễ hơn.
Như bài trước thì chúng ta cần 3 ảnh png: ic_numeric_0.png, ic_numeric_1.png, ic_numeric_2.png và các ảnh cho các màn hình khác nhau trong Android: hdpi, mhdpi, xhdpi, xxhdpi, xxxhdpi là có tới khoảng 15 hình ảnh tất cả phải không?.
Ở bài này thì các bạn cần chuẩn bị 3 ảnh VectorDrawable cho mình nhé.
Bạn có thể tìm kiếm ở đây .
Mình sẽ sử dụng 3 ảnh VectorDrawable của bài trước.
Chú ý: AnimatedVectorDrawable yêu cầu bạn sử dụng các hình ảnh tương thích với nhau (bạn hiểu đơn giản là android:pathData của các hình ảnh phải có cùng các lệnh, theo cùng một thứ tự và có cùng số lượng tham số cho mỗi lệnh…)
Các ảnh bên trên sẽ không tương thích nên bài viết này mình sẽ sử dụng ShapeShifter là một ứng dụng web được tạo bởi Alex Lockwood, giúp cho các ảnh .svg tương thích với nhau.
Sau khi mình sử dụng ShapeShifter thì sẽ được kết qủa như dưới đây animated_vector.xml:
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="96dp"
android:height="96dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:name="path"
android:fillColor="#000"
android:pathData="M 19 3 C 19.53 3 20.039 3.211 20.414 3.586 C 20.789 3.961 21 4.47 21 5 L 21 19 C 21 19.53 20.789 20.039 20.414 20.414 C 20.039 20.789 19.53 21 19 21 L 5 21 C 4.47 21 3.961 20.789 3.586 20.414 C 3.211 20.039 3 19.53 3 19 L 3 5 C 3 4.47 3.211 3.961 3.586 3.586 C 3.961 3.211 4.47 3 5 3 L 19 3 M 11 7 C 10.47 7 9.961 7.211 9.586 7.586 C 9.211 7.961 9 8.47 9 9 L 9 15 C 9 15.53 9.211 16.039 9.586 16.414 C 9.961 16.789 10.47 17 11 17 L 13 17 C 13.53 17 14.039 16.789 14.414 16.414 C 14.789 16.039 15 15.53 15 15 L 15 9 C 15 8.47 14.789 7.961 14.414 7.586 C 14.039 7.211 13.53 7 13 7 L 11 7 M 11 9 L 13 9 L 13 15 L 11 15 L 11 9 Z" />
</vector>
</aapt:attr>
<target android:name="path">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:duration="1000"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="pathData"
android:valueFrom="M 19 3 C 19.53 3 20.039 3.211 20.414 3.586 C 20.789 3.961 21 4.47 21 5 L 21 19 C 21 19.53 20.789 20.039 20.414 20.414 C 20.039 20.789 19.53 21 19 21 L 5 21 C 4.47 21 3.961 20.789 3.586 20.414 C 3.211 20.039 3 19.53 3 19 L 3 5 C 3 4.47 3.211 3.961 3.586 3.586 C 3.961 3.211 4.47 3 5 3 L 19 3 M 11 7 C 10.47 7 9.961 7.211 9.586 7.586 C 9.211 7.961 9 8.47 9 9 L 9 15 C 9 15.53 9.211 16.039 9.586 16.414 C 9.961 16.789 10.47 17 11 17 L 13 17 C 13.53 17 14.039 16.789 14.414 16.414 C 14.789 16.039 15 15.53 15 15 L 15 9 C 15 8.47 14.789 7.961 14.414 7.586 C 14.039 7.211 13.53 7 13 7 L 11 7 M 11 9 L 13 9 L 13 15 L 11 15 L 11 9 Z"
android:valueTo="M 19 3 C 19.53 3 20.039 3.211 20.414 3.586 C 20.789 3.961 21 4.47 21 5 L 21 19 C 21 19.53 20.789 20.039 20.414 20.414 C 20.039 20.789 19.53 21 19 21 L 5 21 C 4.47 21 3.961 20.789 3.586 20.414 C 3.211 20.039 3 19.53 3 19 L 3 5 C 3 4.47 3.211 3.961 3.586 3.586 C 3.961 3.211 4.47 3 5 3 L 19 3 M 11 7 C 10.47 7 9.961 7.211 9.586 7.586 C 9.211 7.961 9 8.47 9 9 L 9 15 C 9 15.53 9.211 16.039 9.586 16.414 C 9.961 16.789 10.47 17 11 17 L 13 17 C 13.53 17 14.039 16.789 14.414 16.414 C 14.789 16.039 15 15.53 15 15 L 15 9 C 15 8.47 14.789 7.961 14.414 7.586 C 14.039 7.211 13.53 7 13 7 L 11 7 M 11 9 L 13 9 L 13 15 L 11 15 L 11 9 Z"
android:valueType="pathType" />
<objectAnimator
android:duration="1000"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="pathData"
android:startOffset="1000"
android:valueFrom="M 14 17 L 12 17 L 12 9 L 10 9 L 10 7 L 14 7 M 19 3 L 5 3 C 4.47 3 3.961 3.211 3.586 3.586 C 3.211 3.961 3 4.47 3 5 L 3 19 C 3 19.53 3.211 20.039 3.586 20.414 C 3.961 20.789 4.47 21 5 21 L 19 21 C 19.53 21 20.039 20.789 20.414 20.414 C 20.789 20.039 21 19.53 21 19 L 21 5 C 21 4.47 20.789 3.961 20.414 3.586 C 20.039 3.211 19.53 3 19 3 Z"
android:valueTo="M 14 17 L 12 17 L 12 9 L 10 9 L 10 7 L 14 7 M 19 3 L 5 3 C 4.47 3 3.961 3.211 3.586 3.586 C 3.211 3.961 3 4.47 3 5 L 3 19 C 3 19.53 3.211 20.039 3.586 20.414 C 3.961 20.789 4.47 21 5 21 L 19 21 C 19.53 21 20.039 20.789 20.414 20.414 C 20.789 20.039 21 19.53 21 19 L 21 5 C 21 4.47 20.789 3.961 20.414 3.586 C 20.039 3.211 19.53 3 19 3 Z"
android:valueType="pathType" />
<objectAnimator
android:duration="1000"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="pathData"
android:startOffset="2000"
android:valueFrom="M 15 11 C 15 12.11 14.1 13 13 13 L 11 13 L 11 15 L 15 15 L 15 17 L 9 17 L 9 13 C 9 11.89 9.9 11 11 11 L 13 11 L 13 9 L 9 9 L 9 7 L 13 7 C 13.53 7 14.039 7.211 14.414 7.586 C 14.789 7.961 15 8.47 15 9 M 19 3 L 5 3 C 4.47 3 3.961 3.211 3.586 3.586 C 3.211 3.961 3 4.47 3 5 L 3 19 C 3 19.53 3.211 20.039 3.586 20.414 C 3.961 20.789 4.47 21 5 21 L 19 21 C 19.53 21 20.039 20.789 20.414 20.414 C 20.789 20.039 21 19.53 21 19 L 21 5 C 21 4.47 20.789 3.961 20.414 3.586 C 20.039 3.211 19.53 3 19 3 Z"
android:valueTo="M 15 11 C 15 12.11 14.1 13 13 13 L 11 13 L 11 15 L 15 15 L 15 17 L 9 17 L 9 13 C 9 11.89 9.9 11 11 11 L 13 11 L 13 9 L 9 9 L 9 7 L 13 7 C 13.53 7 14.039 7.211 14.414 7.586 C 14.789 7.961 15 8.47 15 9 M 19 3 L 5 3 C 4.47 3 3.961 3.211 3.586 3.586 C 3.211 3.961 3 4.47 3 5 L 3 19 C 3 19.53 3.211 20.039 3.586 20.414 C 3.961 20.789 4.47 21 5 21 L 19 21 C 19.53 21 20.039 20.789 20.414 20.414 C 20.789 20.039 21 19.53 21 19 L 21 5 C 21 4.47 20.789 3.961 20.414 3.586 C 20.039 3.211 19.53 3 19 3 Z"
android:valueType="pathType" />
</set>
</aapt:attr>
</target>
</animated-vector>
Bài viết tiếp theo mình sẽ giới thiệu kỹ hơn đến các bạn về ShapeShifter.
Còn giờ thì hãy chạy để xem kết quả của bạn vừa làm với những dòng code dưới đây:
val animatedVectorDrawableCompat =
AnimatedVectorDrawableCompat.create(this, R.drawable.animated_vector)
val imageView = (findViewById<ImageView>(R.id.numeric_image)).apply {
setImageDrawable(animatedVectorDrawableCompat)
}
animatedVectorDrawableCompat?.registerAnimationCallback(object :
Animatable2Compat.AnimationCallback() {
override fun onAnimationEnd(drawable: Drawable?) {
imageView.post { animatedVectorDrawableCompat.start() }
}
})
animatedVectorDrawableCompat?.start()
AnimatedVectorDrawable
Mình rất mong được các bạn đón đọc và để lại lời bình luận để mình cải thiện hơn nữa <3
Bạn có thể hiểu Jetpack như 1 hệ sinh thái của android vậy. Jetpack là một tập hợp của Foundation, Architecture, Behavior, UI để giúp bạn tạo các ứng dụng Android tuyệt vời một cách nhanh chóng và dễ dàng.
Theo đánh giá cá nhân của mình thì đây sẽ là tương lai của Android. Khi nó đã tích hợp mọi thứ mà một lập trình viên Android cần. Và điều mình thích nhất ở Jetpack là bộ Architecture. Chưa hề có 1 chuẩn nào về architecture cho đến khi Google chính thức đưa ra 1 chuẩn architecture cho lập trình viên Android.
Các bạn có thể xem về Android Architecture Components tại đây
Trong bài viết này thì mình sẽ viết về WorkManager.
WorkManager
Define
WorkManager là 1 thành viên trong bộ Jetpack Architecture.
WorkManager manage your Android background jobs.
Tức là sao?. Mọi task vụ thực hiện dưới background như download, upload file, network…
Các bạn đều có thể dùng WorkManager để thực thi.
Overview
Schedule tasks with WorkManager
Là sao? Là thế này. Với WorkManager bạn có thể sắp xếp các task vụ của mình.
Bạn có thể quyết định khi nào thực hiện task. Và giả sử như bạn có nhiều task cần
thực hiện, bạn có thể quyết định thằng B chạy trước A rồi sau đó sẽ chạy thằng C hoặc ở
1 màn hình khác bạn lại cần chạy task theo thứ tự A > B > C. Nếu như task sau của bạn có input
là output của task trước thì bạn cũng đừng lo lắng. Với WorkManager bạn có thể làm được.
Chú ý nhé :
WorkManager dành cho các task vụ mà ngay cả khi bạn thoát ứng dụng thì task vẫn thực hiện
ví dụ như việc bạn upload data lên server. Còn các task vụ mà sẽ tắt khi thoát app ra
thì nên dùng ThreadPools nhé các bạn.
Xem qua cách hoạt động nào:
WorkManager chọn cách thích hợp để chạy tác vụ của bạn dựa trên các yếu tố như level API
thiết bị và trạng thái ứng dụng. Nếu WorkManager thực thi một trong các nhiệm vụ của bạn
trong khi ứng dụng đang chạy, WorkManager có thể chạy tác vụ của bạn trong một luồng mới.
Nếu ứng dụng của bạn không chạy, WorkManager chọn một cách thích hợp để schedule
một backgorund task – tùy thuộc vào mức API của thiết bị và các phụ thuộc kèm theo,
WorkManager có thể sử dụng JobScheduler, Firebase JobDispatcher hoặc AlarmManager.
Bạn không cần viết logic để tìm ra khả năng của thiết bị và chọn một API thích hợp,
thay vào đó bạn chỉ có thể giao nhiệm vụ của mình cho WorkManager và để cho nó chọn
tùy chọn tốt nhất. (Đoạn này hiểu đơn giản là bạn chỉ cần sử dụng WorkManager thôi, đừng
lăn tăn về việc nó sẽ sử dụng JobScheduler hay Firebase JobDispatcher, cũng như bạn k cần
viết code check API này kia làm gì. WorkManager đã xử lý điều đó).
WorkManager cung cấp một số tính năng nâng cao. Ví dụ, bạn có thể thiết lập một chuỗi các nhiệm vụ; khi một tác vụ kết thúc, WorkManager sẽ xếp hàng nhiệm vụ tiếp theo trong chuỗi. Bạn cũng có thể kiểm tra trạng thái của nhiệm vụ và các giá trị trả về của nó bằng cách quan sát LiveData của nó. (LiveData cũng là 1 component mới trong bộ Android Architecture Component nhé)
Classes and concepts
API WorkManager sử dụng một số lớp khác nhau. Tuy nhiên mình thấy có các class cần chú ý sau:
Worker: class xác định task cần thực hiện. API WorkManager bao gồm 1 class abstract Worker. Bạn cần extend class này và thực hiện công việc tại đây.
WorkRequest: đại diện cho một nhiệm vụ riêng lẻ. Ở mức tối thiểu, một đối tượng WorkRequest xác định lớp Worker nào sẽ thực hiện nhiệm vụ. Tuy nhiên, bạn cũng có thể thêm các chi tiết vào đối tượng WorkRequest, chỉ định những thứ như các trường hợp mà tác vụ sẽ chạy. Mỗi WorkRequest có một ID duy nhất được tạo tự động; bạn có thể sử dụng ID để thực hiện những việc như hủy một công việc xếp hàng đợi hoặc nhận trạng thái của tác vụ. WorkRequest là một lớp trừu tượng; trong mã của bạn, bạn sẽ sử dụng một trong các lớp con trực tiếp, OneTimeWorkRequest hoặc PeriodicWorkRequest.
WorkRequest.Builder: một lớp trợ giúp để tạo các đối tượng WorkRequest. Bạn sẽ sử dụng một trong các lớp con, OneTimeWorkRequest.Builder hoặc PeriodicWorkRequest.Builder.
Ràng buộc: chỉ định các hạn chế về thời điểm tác vụ sẽ chạy (ví dụ: “chỉ khi được kết nối với mạng”). Bạn tạo đối tượng Constraints với Constraints.Builder và chuyển các ràng buộc tới WorkRequest.Builder trước khi tạo WorkRequest.
WorkManager: enqueues và quản lý các yêu cầu công việc. Bạn chuyển đối tượng WorkRequest của bạn tới WorkManager để enqueue nhiệm vụ. WorkManager lên lịch nhiệm vụ theo cách như vậy để trải rộng tải trên tài nguyên hệ thống, tất nhiên nó sẽ thực hiện với các ràng buộc mà bạn đã chỉ định.
WorkStatus: chứa thông tin về một tác vụ cụ thể. WorkManager cung cấp một LiveData cho mỗi đối tượng WorkRequest. LiveData chứa đối tượng WorkStatus; bằng cách quan sát LiveData, bạn có thể xác định trạng thái hiện tại của tác vụ và nhận bất kỳ giá trị trả về nào sau khi tác vụ kết thúc.
Typical workflow
Giả sử bạn đang viết một ứng dụng thư viện ảnh và ứng dụng đó cần nén định kỳ hình ảnh được lưu trữ của nó. Bạn muốn sử dụng các API WorkManager để lên lịch nén ảnh. Trong trường hợp này, bạn không đặc biệt quan tâm khi nén xảy ra; bạn muốn thiết lập nhiệm vụ và quên nó đi.
Đầu tiên, bạn sẽ định nghĩa lớp Worker của mình và ghi đè phương thức doWork () của nó. Lớp Worker của bạn chỉ định cách thực hiện thao tác, nhưng không có bất kỳ thông tin nào về thời điểm tác vụ sẽ chạy.
public class CompressWorker extends Worker {
@Override
public Worker.WorkerResult doWork() {
// Thực hiện task ở đây.
//Trong case này task là nén ảnh và không có params
myCompress();
// Cho biết kết quả: Thành công hay thất bại
return WorkerResult.SUCCESS;
// trả về RETRY nếu thấy bại. Task sẽ được thực thi lại
// trả về FAILURE nếu muốn kết thúc, không thực thi lại.)
}
}
Tiếp theo, bạn tạo một đối tượng OneTimeWorkRequest dựa trên Worker đó, sau đó enqueue nhiệm vụ với WorkManager:
OneTimeWorkRequest compressionWork =
new OneTimeWorkRequest.Builder(CompressWorker.class)
.build();
WorkManager.getInstance().enqueue(compressionWork);
WorkManager chọn một thời điểm thích hợp để chạy tác vụ.Trong hầu hết các trường hợp, nếu bạn không chỉ định bất kỳ ràng buộc nào, WorkManager sẽ chạy tác vụ của bạn ngay lập tức. Nếu bạn cần kiểm tra trạng thái tác vụ, bạn có thể lấy đối tượng WorkStatus bằng cách xử lý LiveData. Ví dụ: nếu bạn muốn kiểm tra xem tác vụ đã hoàn tất chưa, bạn có thể sử dụng mã như sau:
WorkManager.getInstance().getStatusById(compressionWork.getId())
.observe(lifecycleOwner, workStatus -> {
// Do something with the status
if (workStatus != null && workStatus.getState().isFinished())
{ ... }
});
Nếu muốn, bạn có thể chỉ định các ràng buộc khi nhiệm vụ được chạy. Ví dụ: bạn có thể muốn chỉ định rằng tác vụ chỉ nên chạy khi thiết bị ở chế độ chờ và kết nối với nguồn. Trong trường hợp này, bạn cần tạo một đối tượng OneTimeWorkRequest.Builder và sử dụng trình tạo đó để tạo OneTimeWorkRequest:
// Tạo sự ràng buộc, khi nào thì task được thực thi
Constraints myConstraints = new Constraints.Builder()
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
// Có rất nhiều ràng buộc có sẵn.
// Tham khảo tại Constraints.Builder
.build();
// ...sau đó tạo một OneTimeWorkRequest sử dụng các ràng buộc đó
OneTimeWorkRequest compressionWork =
new OneTimeWorkRequest.Builder(CompressWorker.class)
.setConstraints(myConstraints)
.build();
Canceling a Task
Bạn có thể hủy một task sau khi bạn enqueue nó. Để hủy tác vụ, bạn cần ID task đó, mà bạn có thể nhận được từ đối tượng WorkRequest. Ví dụ, đoạn mã sau hủy bỏ yêu cầu compressionWork từ phần trước:
WorkManager cố gắng hết sức để hủy tác vụ, nhưng điều này vốn dĩ không chắc chắn – nhiệm vụ có thể đã chạy hoặc kết thúc khi bạn cố hủy nó. WorkManager cũng cung cấp các phương thức để hủy bỏ tất cả các nhiệm vụ trong một chuỗi công việc duy nhất, hoặc tất cả các nhiệm vụ với một thẻ (TAG) được chỉ định.
Advanced functionality (Nâng cao)
API WorkManager cung cấp các tính năng nâng cao cho phép bạn thiết lập các yêu cầu phức tạp.
Recurring tasks(Nhiệm vụ định kỳ)
Bạn có thể có một nhiệm vụ mà bạn cần phải thực hiện nhiều lần. Ví dụ: ứng dụng trình quản lý ảnh sẽ không muốn nén ảnh một lần. Nhiều khả năng, nó sẽ muốn kiểm tra hình ảnh được chia sẻ của nó thường xuyên như vậy, và xem nếu có bất kỳ hình ảnh mới hoặc thay đổi cần phải được nén. Tác vụ lặp lại này có thể nén hình ảnh mà nó tìm thấy, hoặc cách khác, nó có thể kích hoạt 1 tác vụ mới : “nén hình ảnh này”.
Để tạo một nhiệm vụ định kỳ, sử dụng lớp PeriodicWorkRequest.Builder để tạo một đối tượng PeriodicWorkRequest, sau đó enqueue PeriodicWorkRequest giống như cách bạn sẽ làm đối tượng OneTimeWorkRequest. Ví dụ, giả sử chúng ta định nghĩa một lớp PhotoCheckWorker để xác định các hình ảnh cần được nén. Nếu chúng ta muốn chạy tác vụ kiểm kê mỗi 12 giờ, chúng ta sẽ tạo một đối tượng PeriodicWorkRequest như sau:
new PeriodicWorkRequest.Builder photoWorkBuilder =
new PeriodicWorkRequest.Builder(PhotoCheckWorker.class, 12,
TimeUnit.HOURS);
// ...nếu bạn muốn, bạn có thể áp dụng các ràng buộc ở đây ...
PeriodicWorkRequest photoWork = photoWorkBuilder.build();
// enqueue task:
WorkManager.getInstance().enqueue(photoWork );
WorkManager cố gắng chạy nhiệm vụ của bạn tại khoảng thời gian bạn yêu cầu, tùy thuộc vào các ràng buộc mà bạn áp đặt và các yêu cầu khác của nó.
Chained tasks ( Chuỗi công việc)
Ứng dụng của bạn có thể cần chạy một số tác vụ theo một thứ tự cụ thể. WorkManager cho phép bạn tạo và enqueue một chuỗi công việc xác định nhiều nhiệm vụ và thứ tự chúng sẽ chạy.
Ví dụ: giả sử ứng dụng của bạn có ba đối tượng OneTimeWorkRequest: workA, workB và workC. Các nhiệm vụ phải được chạy theo thứ tự đó. Để enqueue chúng, tạo một chuỗi với phương thức WorkManager.beginWith (), truyền đối tượng OneTimeWorkRequest đầu tiên; phương thức đó trả về một đối tượng WorkContinuation, nó định nghĩa một chuỗi các nhiệm vụ. Sau đó, thêm các đối tượng OneTimeWorkRequest còn lại, theo thứ tự, với WorkContinuation.then (), và cuối cùng, enqueue toàn bộ chuỗi với WorkContinuation.enqueue ():
Hơi khó hiểu đúng không? Xem ví dụ nhé:
WorkManager.getInstance()
.beginWith(workA)
// Note: WorkManager.beginWith() trả về 1 đối tượng WorkContinuation
.then(workB) // FYI, then() cũng trả về 1 thể hiện của WorkContinuation
.then(workC)
.enqueue();
WorkManager chạy các tác vụ theo thứ tự được yêu cầu, theo các ràng buộc cụ thể của mỗi tác vụ. Nếu bất kỳ tác vụ nào trả về Worker.WorkerResult.FAILURE, toàn bộ chuỗi sẽ kết thúc.
Bạn cũng có thể truyền nhiều đối tượng OneTimeWorkRequest cho bất kỳ lệnh gọi startsWith () và .then () nào. Nếu bạn truyền một số đối tượng OneTimeWorkRequest cho một cuộc gọi phương thức duy nhất, WorkManager sẽ chạy tất cả các tác vụ đó (song song) trước khi nó chạy phần còn lại của chuỗi. Ví dụ:
WorkManager.getInstance()
// First, run all the A tasks (in parallel):
.beginWith(workA1, workA2, workA3)
// ...when all A tasks are finished, run the single B task:
.then(workB)
// ...then run the C tasks (in any order):
.then(workC1, workC2)
.enqueue();
Bạn có thể tạo các chuỗi phức tạp hơn bằng cách nối nhiều chuỗi với các phương thức WorkContinuation.combine (). Ví dụ: giả sử bạn muốn chạy một chuỗi như sau:
Để thiết lập trình tự này, hãy tạo hai chuỗi riêng biệt, sau đó ghép chúng lại với nhau thành một chuỗi thứ ba:
Trong trường hợp này, WorkManager chạy workA trước khi làm việc. Nó cũng hoạt động trước khi làm việc. Sau khi cả hai công việc và workD đã hoàn thành, WorkManager chạy workE.
Có một số biến thể của phương thức WorkContinuation cung cấp viết tắt cho các tình huống cụ thể. Ví dụ, có một phương thức WorkContinuation.combine (OneTimeWorkRequest, WorkContinuation…), hướng dẫn WorkManager hoàn thành tất cả các chuỗi WorkContinuation đã chỉ định, sau đó kết thúc với OneTimeWorkRequest được chỉ định. Để biết chi tiết, xem WorkContinuation nhé.
Unique work sequences (một chuỗi công việc duy nhất)
Bạn có thể tạo một chuỗi công việc duy nhất, bằng cách bắt đầu chuỗi với một cuộc gọi đến beginUniqueWork () thay vì beginWith (). Mỗi chuỗi công việc duy nhất có một tên; WorkManager chỉ cho phép một chuỗi công việc với tên đó tại một thời điểm. Khi bạn tạo một chuỗi công việc duy nhất mới, bạn chỉ định những gì WorkManager sẽ làm nếu có một chuỗi chưa hoàn thành có cùng tên:
Hủy chuỗi hiện tại và thay thế bằng trình tự mới
Giữ chuỗi hiện tại và bỏ qua yêu cầu mới của bạn
Nối chuỗi mới của bạn vào chuỗi hiện tại, chạy tác vụ đầu tiên của chuỗi mới sau khi tác vụ cuối cùng của chuỗi hiện tại kết thúc.
Một chuỗi công việc duy nhất có thể hữu ích nếu bạn có một nhiệm vụ không nên được enqueued nhiều lần. Ví dụ: nếu ứng dụng của bạn cần đồng bộ hóa dữ liệu với mạng, bạn có thể enqueue một chuỗi có tên là “sync” và chỉ định rằng tác vụ mới của bạn sẽ bị bỏ qua nếu đã có một chuỗi có tên đó. Một chuỗi công việc duy nhất cũng có thể hữu ích nếu bạn cần dần dần xây dựng một chuỗi nhiệm vụ dài. Ví dụ: ứng dụng chỉnh sửa ảnh có thể cho phép người dùng hoàn tác một chuỗi hành động dài. Mỗi hoạt động hoàn tác có thể mất một thời gian, nhưng chúng phải được thực hiện đúng thứ tự. Trong trường hợp này, ứng dụng có thể tạo chuỗi “hoàn tác” và nối thêm từng hoạt động hoàn tác vào chuỗi khi cần.
Tagged work ( Task được gắn thẻ)
Bạn có thể nhóm các nhiệm vụ của bạn một cách hợp lý bằng cách gán một chuỗi thẻ cho bất kỳ đối tượng WorkRequest nào. Để đặt thẻ, hãy gọi WorkRequest.Builder.addTag (), ví dụ:
OneTimeWorkRequest cacheCleanupTask =
new OneTimeWorkRequest.Builder(MyCacheCleanupWorker.class)
.setConstraints(myConstraints)
.addTag("cleanup")
.build();
Các lớp WorkManager cung cấp một số phương thức tiện ích cho phép bạn thao tác trên tất cả các nhiệm vụ với một thẻ cụ thể. Ví dụ, WorkManager.cancelAllWorkByTag () hủy bỏ tất cả các nhiệm vụ với một thẻ cụ thể, và WorkManager.getStatusesByTag () trả về một danh sách tất cả các WorkStatus cho tất cả các tác vụ với thẻ đó.
Input parameters and returned values
Để linh hoạt hơn, bạn có thể chuyển đối số cho công việc của mình và có nhiệm vụ trả về kết quả. Các giá trị được trả về và trả về là các cặp khóa-giá trị. Để chuyển một đối số cho một nhiệm vụ, hãy gọi phương thức WorkRequest.Builder.setInputData () trước khi bạn tạo đối tượng WorkRequest. Phương thức đó lấy một đối tượng Data, mà bạn tạo ra với Data.Builder. Lớp Worker có thể truy cập các đối số đó bằng cách gọi hàm Worker.getInputData (). Để xuất ra một giá trị trả về bạn sử dụng Worker.setOutputData (), lấy một đối tượng Data, bạn có thể lấy kết quả bằng cách quan sát LiveData .
Ví dụ, giả sử bạn có một lớp Worker thực hiện một phép tính tốn thời gian. Đoạn mã sau cho thấy lớp Worker sẽ trông như thế nào:
// Define the Worker class:
public class MathWorker extends Worker {
// Define the parameter keys:
public static final String KEY_X_ARG = "X";
public static final String KEY_Y_ARG = "Y";
public static final String KEY_Z_ARG = "Z";
// ...and the result key:
public static final String KEY_RESULT = "result";
@Override
public Worker.WorkerResult doWork() {
// Fetch the arguments (and specify default values):
int x = getInputData().getInt(KEY_X_ARG, 0);
int y = getInputData().getInt(KEY_Y_ARG, 0);
int z = getInputData().getInt(KEY_Z_ARG, 0);
// ...do the math...
int result = myCrazyMathFunction(x, y, z);
//...set the output, and we're done!
Data output = new Data.Builder()
.putInt(KEY_RESULT, result)
.build();
setOutputData(output);
return WorkerResult.SUCCESS;
}
}
Để tạo công việc và chuyển các đối số, bạn sẽ sử dụng mã như sau:
// Create the Data object:
Data myData = new Data.Builder()
// We need to pass three integers: X, Y, and Z
.putInt(KEY_X_ARG, 42)
.putInt(KEY_Y_ARG, 421)
.putInt(KEY_Z_ARG, 8675309)
// ... and build the actual Data object:
.build();
// ...then create and enqueue a OneTimeWorkRequest that uses those arguments
OneTimeWorkRequest.Builder argsWorkBuilder =
new OneTimeWorkRequest.Builder(MathWorker.class)
.setInputData(myData);
OneTimeWorkRequest mathWork = argsWorkBuilder.build();
WorkManager.getInstance().enqueue(mathWork);
Giá trị trả về sẽ có sẵn trong WorkStatus của tác vụ:
WorkManager.getInstance().getStatusById(mathWork.getId())
.observe(lifecycleOwner, status -> {
if (status != null) {
int myResult =
status.getOutputData().getInt(KEY_RESULT,
myDefaultValue));
// ... do something with the result ...
}
});
Nếu bạn có một chuỗi nhiệm vụ, kết quả đầu ra từ một nhiệm vụ có sẵn như là đầu vào cho nhiệm vụ tiếp theo trong chuỗi. Nếu đó là một chuỗi đơn giản, với một OneTimeWorkRequest duy nhất được theo sau bởi một OneTimeWorkRequest khác, tác vụ đầu tiên trả về kết quả của nó bằng cách gọi hàm setOutputData () và nhiệm vụ tiếp theo sẽ lấy kết quả đó bằng cách gọi phương thức getInputData (). Nếu chuỗi phức tạp hơn – ví dụ, bởi vì một số tác vụ gửi đầu ra tới một tác vụ duy nhất sau đây, bạn có thể định nghĩa một InputMerger trên OneTimeWorkRequest.Builder để xác định điều gì sẽ xảy ra nếu các tác vụ khác nhau trả về đầu ra có cùng khóa.
Lời kết
Đây là lần đầu tiên mình viết bài nên nếu có sai xót gì nhờ anh em đóng góp. Bài viết này mình tham khảo và dịch từ trang developer android, anh em có thể xem bản gốc tại đây
Nếu như bạn đã dành thời gian ra tìm hiểu về Architectute thì chắc chắn đã nghe qua “The Clean Architecture” rồi đúng không?
Việc định hình ra architecture cho một project chưa bao giờ là đơn giản, nó phụ thuộc vào rất nhiều yếu tố như yêu cầu của khách hàng, UI , kĩ năng của member, chức năng của app… Dựa vào các yếu tố đó mà Project Technical Lead hay TeamLead sẽ quyết định dùng architecture, mô hình nào vào project.
Trong bài viết này mình sẽ viết về The Clean Architecture – “Kiến trúc sạch”
Getting Started
Để viết ra một sản phẩm tốt là điều rất khó, tốt ở đây là sao? Ý kiến cá nhân của mình tốt có nghĩa là * easy to maintain * easy to debug * easy to develop * easy to understand * code clear
Sau một hồi tìm kiếm, mình đã tìm ra The Clean Architecture.
Bộ nguyên tắc của Clean Architecture như sau:
* Độc lập ( tách biệt) với Framework. * Dễ dàng cho việc test code. * Tách biệt giữa bussiness và UI * Tách biệt với cơ sở dữ liệu * Independent of any external agency
Nguyên tắc cuối mình để tiếng anh vì dịch ra tiếng việt nó tối nghĩa. khó hiểu. Nó được giải thích như sau: In fact your business rules simply don’t know anything at all about the outside world. Nghĩa là phần code logic của bạn nó là tách biệt, độc lập, nó không quan tâm đến cái cách mà nó được dùng như nào.
Tóm lại cái quan trọng nhất mình thấy ở architecture này chính là ĐỘC LẬP (TÁCH BIỆT). tách biệt mọi thứ, càng tách biệt rõ càng tốt, càng clear. Mà vì lẽ đó nên nếu apply architecture này thì số lượng class của bạn sẽ rất lớn đó nhé
Cấu trúc của The Clean Architecture
Mình sẽ giải thích về 4 vòng tròn thông qua 1 ứng dụng Movie nhé.
* Entities: Là các Object phục vụ cho Bussiness của bạn (là các model đó. Ví dụ MovieObject :P) * Use Cases: Mình thấy khá giống với khái niệm Usecase bên UML, nó đại diện cho các nghiệp vụ trong ứng dụng. ( Ví dụ Usecase: Show list HotMovies, show list favorite movies…) * Interface Adapters: hay còn gọi là tầng Presentation. Nó là cầu nối giữa tầng bussiness với tầng UI của bạn. Nếu bạn đã làm mô hình MVC hay MVP thì đây là nơi chứa Controller or Presenter. * Frameworks and Drivers: là tầng chứa UI, tools, frameworks, etc (ví dụ trong android thì tầng này là chứa Activity/Fragment đấy)
Android Architecture
Mục đích là tách ứng dụng thành các tầng tách biệt, không phụ thuộc vào nhau, như vậy mới dễ dàng cho việc test, cũng như maintain, phát triển.
Để đạt được điều này, chúng ta sẽ chia nhỏ dự án thành 3 lớp khác nhau, trong đó mỗi lớp có mục đích riêng và hoạt động riêng biệt với các mục đích khác nhau.
Mỗi lớp có thể thực hiện theo các mô hình, pattern khác nhau để đạt được mục đích của lớp đó.
Presentation Layer Đây là cầu nối giữa logic với UI (animation…) bạn có thể apply MVP hay MVC, MVVM tại đây, mình sẽ không đi chi tiết vào mô hình. Chú ý rằng tại tầng này chỉ có views (Fragment, Activity), sẽ không viết bất kì logic nào trừ logic liên quan đến UI.
Tầng Presenter sẽ sử dụng các interactors (uses case) để thực hiện logic dưới background thread (không thực hiện trên Mainthead(UI thread), sau đó trả về kết quả thông qua callback để view hiển thị.
Domain Layer
Tầng này chỉ viết logic mà thôi. Tại đây chúng ta sẽ định nghĩa các interactors (usecase, UserRepository).
chú ý rằng lớp này là 1 module thuần java (kotlin) không chứa bất kì phụ thuộc Android nào. Các thành phần bên ngoài khác muốn trỏ đến sẽ dùng interface.
Data Layer
Tất cả data của ứng dụng sẽ xử lý tại đây (hãy hiểu như này cho đơn giản, tầng này là tầng implement lại các interface repository ở tầng Domain cũng như định nghĩa ra các data model) .Chúng ta sẽ sử dụng Repository Pattern cho tầng này. Ví dụ khi chúng ta muốn lấy ra 1 bộ phim theo ID, repository sẽ quyết định lấy ra từ local nếu đã lưu bộ phim đó vào cache từ lần load trước, nếu chưa có sẽ request API để lấy bộ phim từ Server.
Lời kết
Như vậy là mình đã giới thiệu qua về The Clear Architecture.
Bài viết mang yếu tố chủ quan, đánh giá cá nhân nên nếu sai xót nhờ anh em bổ sung giúp mình nhé.
Xin chào các bạn, bài tiếp theo trong series về animation của mình đó là FrameAnimation.
Như ở bài trước mình đã nói thì FrameAnimation là khởi tạo một animation bằng cách sử dụng một chuỗi các hình ảnh được hiển thị theo một thứ tự nhất định với AnimationDrawable. Chúng ta cùng đi tìm hiểu về nó. Les’t go…
Với định nghĩa về FrameAnimation bên trên thì chúng ta sẽ hình dung là trước tiên chúng ta cần tạo file xml để định nghĩa các frame.
Bạn có thể tham khảo file numeric_animation.xml của tôi dưới đây:
<!-- Animation frames are ic_numeric_0.png through ic_numeric_2.png
files inside the res/drawable/ folder -->
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/selected"
android:oneshot="false">
<item
android:drawable="@drawable/ic_numeric_0"
android:duration="1000" />
<item
android:drawable="@drawable/ic_numeric_1"
android:duration="1000" />
<item
android:drawable="@drawable/ic_numeric_2"
android:duration="1000" />
</animation-list>
element: <animation-list>: là thẻ bắt buộc, nó chứa một hoặc nhiều thẻ <item>. android:oneshot: kiểu giá trị kiểu Boolean. “true” là khi bạn muốn thực hiện 1 lần duy nhất. “false” là khi bạn muốn lặp lại. <item>: là 1 item trong danh sách, bạn hiểu nó là 1 khung hình khi chạy. android:drawable: Drawable của bạn. android:duration: kiểu giá trị Integer, nó là thời lượng hiển thị được tính bằng milliseconds. Chạy nào, chạy nào…
private lateinit var numericAnimation: AnimationDrawable
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Load the ImageView that will host the animation and
// set its background to our AnimationDrawable XML resource.
findViewById<ImageView>(R.id.numeric_image).apply {
setBackgroundResource(R.drawable.numeric_animation)
// Get the background, which has been compiled to an AnimationDrawable object.
numericAnimation = background as AnimationDrawable
// Start the animation (looped playback by default).
numericAnimation.start()
}
}
Ở đây thì bạn cũng thấy rằng trên màn hình main của tôi sẽ có 1 ImageView. Sau đó tôi sẽ chạy animation với AnimationDrawable đã được định nghĩa trong numeric_animation.xml.
FrameAnimation
Thật đơn giản phải không các bạn? 😀 Nhưng nó có nhược điểm khi bạn sử dụng nhiều ảnh với animation có độ phức tạp cao sẽ làm tăng kích thước của ứng dụng, hay là bạn sẽ mất khá nhiều thời gian để custom lại animation đó cho phù hợp với bài toán của bạn. Để giải quyết vấn đề này, bài tiếp theo tôi sẽ giới thiệu cho các bạn về AnimatedVectorDrawable, các bạn hãy đón chờ nhé 😀
Với một vài dự án khách hàng đã code sẵn cho 1 cái library android rồi, và họ sẽ gửi cho bạn sử dụng cái lib đó. Hoặc là bạn có 1 cái lib rất ngon trên android. Nhưng muốn dùng trong project Xamarin của bạn mà không muốn code lại logic của nó. Như vậy bạn cần binding nó vào project của bạn và chỉ việc dùng không cần code lại 1 tí logic nào.
I. Input: Project dự án code bằng ngôn ngữ Xamarin Các file android libraries .aar (Android Archive Library) Bản chất của file này là file .zip gồm – Compiled Java code – Resource Ids (File R) – Resources – Meta-data (for example, Activity declarations, permissions)
II. Output Project Xamarin có thể gọi và sử dụng được class chứa trong file .aar
III.Guideline 1. Tạo 1 project binding với 1 file .aar cơ bản 1) Add new project Binding library(Android) 2) Add file .aar vào folder Jar 3) Set build action cho .AAR file là LibraryProjectZip 4) Chọn target framework cho project lib nếu cần 5) Build Bindings Library. Sau khi tạo xong và build thành công chúng ta sẽ add file đó vào project Xamarin của chúng ta muốn sử dụng. Vậy là chúng ta có thể call tới class chứa trong file .aar. Mình chỉ nói các bước thực hiện thôi, chi tiết các bạn nên tham khảo tại : https://docs.microsoft.com/en-us/xamarin/android/platform/binding-java-library/binding-an-aar
Như ở trên là cách tạo và add 1 project binding library với file source code trong file aar là đơn giản. Với những file .aar phức tạp và chúng có link đến nhau thì sao ?
2. Tạo project binding nâng cao 1. Đầu tiên các bạn vẫn phải thực hiện các bước như trên. Vì xamarin có 1 nhược điểm là 1 file aar bắt buộc bạn cần phải tạo ra 1 project binding 2. Cần phải xác định file aar nào sử dụng file aar nào. Ví dụ chúng ta có 2 file aar, file thứ nhất call đến 1 class trong file thứ 2 Như vậy làm thế nào để biết được file nào dùng đến file nào – Bạn cần 1 tool để decode java trong file aar (Android studio chẳng hạn) – Sau đó bạn mở những class trong file aar ra. Xem phần import của chúng sử dụng những class nào có chứa trong file aar khác không – Đôi khi trong file aar nó sẽ sử dụng dependence đến 1 lib nào mà k chứa trong tất cả file aar của bạn. Thì bạn cần xem nó là những lib nào. Click chuột phải vào Packages của Project đó chọn Add packages. Rồi tìm xem tên lib đó có trong nuget không. Nếu không có, thì bạn có thể tìm và tải file jar của lib ấy về rồi copy vào folder Jar. Build action bạn để là EmbeddedJar Bạn thử build xem còn lỗi gì không. Nếu còn lỗi. Bạn cần phải customizing binding. 3.Customizing Binding Với android chúng ta chỉ cần customize lại file Metadata.xml Transform File. Trong file Metadata.xml thì sẽ có 3 element : – add-node – attr – remove-node
Mình cũng làm 1 vài project liên quan đến binding library. Và cũng customize file metadata.xml này, mình thấy như sau
khi thấy error bạn chọn vào 1 item error
Bạn có thể nhìn thấy dòng // Metadata.xml Xpath classs …. hãy coppy từ chỗ path=”…” Bạn có thể sử dụng remove-node trong file metadata.xml với lỗi ”do not exist in the namespace …’ Thực ra bạn remove node này nó không ảnh hưởng gì đến code trong project cả. Việc generate class java sang c# ở đây chỉ là Android.Runtime.Register thôi. chứ không phải là nó generate all source code của cả class java sang c# .
Một số trường hợp bạn không nhìn thấy class mình dùng được generate ra Trường hợp này chúng ta sẽ add bằng tay dùng add-node Ví dụ :
<add-node path="/api/package[@name='your package name']">
<class abstract="false" deprecated="not deprecated" extends="java.lang.Object"
extends-generic-aware="java.lang.Object" final="false" name=" your class name" static="true" visibility="public">
</class>
</add-node>
Bài viết này mình muốn chia sẽ về Animation trong Android.
Tổng quan
Như các bạn đã biết thì Animation trong Android sẽ có loại View Animation (Android 2.3 và các bản trước đó), loại Property Animation (Android 3.0 và các bản sau này).
Property Animation
Là Animation được tạo bằng cách thay đổi các giá trị của thuộc tính của các đối tượng trong một khoảng thời gian đã được định sẵn bằng Animator.
View Animation
View Animation có Tween Animation, Frame Animation.
Tween Animation: Khởi tạo một animation bằng cách thực hiện một loạt các thay đổi trên một hình ảnh duy nhất với Animation.
Frame Animation: Khởi tạo một animation bằng cách sử dụng một chuỗi các hình ảnh được hiển thị theo một thứ tự nhất định với AnimationDrawable.
Bài viết này mình sẽ giới thiệu cho các bạn về Tween Animation.
Tween Animation
Để thực hiện thì chúng tôi sẽ gọi method loadAnimation() của AnimationUtils.
R.anim.my_animation : các bạn sẽ tạo 1 thư mục anim trong thư mục res và tạo 1 file xml với tên là my_animation.
Để áp dụng animation có đối tượng cần chạy animation thì các bạn gọi method startAnimation().
Ở đây tôi có 1 TextView đã được đặt tên là animation_fade_in và 1 file xml fade_in.xml để thực hiện animation fade in cho TextView.
Chúng ta có một số animation thông dụng dưới đây:
Các thuộc tính bạn cần phải biết: android:duration – Thời gian hoàn thành. android:startOffset – Thời gian chờ trước khi một animaiton bắt đầu và thường được sử dụng khi có nhiều animation. android:repeatMode – Thiết lập lặp lại animation. android:repeatCount – Xác định số lần lặp lại animation. Nếu bạn thiết lập giá trị này là infinite thì animation sẽ lặp lại lần vô hạn. android:interpolator – Tỷ lệ thay đổi animation. android:fillAfter – Xác định liệu có áp dụng việc chuyển đổi đối tượng về trạng thái ban đầu sau khi một animation đã hoàn thành hay không.
Các thành phần chính trong interpolator để các bạn dùng cho phù hợp:
accelerateDecelerateInterpolator: Tốc độ thay đổi bắt đầu và kết thúc chậm nhưng tăng tốc qua giữa.
accelerateInterpolator: Tốc độ thay đổi bắt đầu chậm, sau đó tăng tốc.
anticipateInterpolator: Bắt đầu một khoảng lùi lại sau đó bay về phía trước.
anticipateOvershootInterpolator: Bắt đầu lùi lại, bay về phía trước và vượt qua giá trị đích, sau đó lùi lại giá trị đích.
bounceInterpolator: Sau khi đến vị trí giá trị cuối thì quay lại giá trị ban đầu
cycleInterpolator: Lặp lại hoạt ảnh động theo một số chu kỳ xác định. Tốc độ thay đổi theo mô hình hình sin.
decelerateInterpolator: Hoạt ảnh có tốc độ thay đổi bắt đầu nhanh chóng, sau đó giảm tốc.
linearInterpolator: Tốc độ thay đổi hoạt ảnh là không đổi(tuyến tính).