Category: Android

  • Lottie on Android (Part 3)

    Lottie on Android (Part 3)

    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.jsonbà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ó:

    Có thể sử dụng log sau:

    loading_animation.resolveKeyPath(KeyPath("**")).forEach {
                Log.i("KeyPath", it.toString())
            }

    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:

    loading_animation.addValueCallback(
                KeyPath("Shape Layer 1", "Ellipse 1", "Fill 1"),
                LottieProperty.COLOR, { Color.RED }
            )
    Shape Layer 1

    Hoặc bạn có thể thay đổi màu sắc và kích thước của stroke như sau:

    loading_animation.addValueCallback(
                KeyPath("Shape Layer 1", "Ellipse 1", "Stroke 1"),
                LottieProperty.STROKE_COLOR, { Color.GREEN }
            )
    
            loading_animation.addValueCallback(
                KeyPath("Shape Layer 1", "Ellipse 1", "Stroke 1"),
                LottieProperty.STROKE_WIDTH, { 20f }
            )
    Stroke

    Sử dụng Wildcards

    Mình sử dụng Wildcards để thay đổi toàn bộ layer có Ellipse 1("nm": "Ellipse 1") sang màu GREEN như sau:

    loading_animation.addValueCallback(
                KeyPath("*", "Ellipse 1", "Fill 1"),
                LottieProperty.COLOR, { Color.GREEN }
            )
    Wildcards

    Sử dụng Globstars

    Mình sử dụng Globstars để thay đổi toàn bộ layer kết thúc thuộc tính Fill 1 sang màu BLUE như sau:

    loading_animation.addValueCallback(
                KeyPath("**", "Fill 1"),
                LottieProperty.COLOR, { Color.BLUE }
            )
    Globstars

    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 ^^

  • Lottie on Android (Part 2)

    Lottie on Android (Part 2)

    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.

    Listener được mô tả như sau:

    loading_animation.addAnimatorUpdateListener { valueAnimator ->
                //do something
            }

    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.

    Tôi sẽ thiết kế layout như sau:

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <ProgressBar
            android:id="@+id/progress_horizontal"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:max="100"
            android:progress="0"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintWidth_percent="0.9" />
    
        <TextView
            android:id="@+id/progress_number"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/progress_horizontal"
            app:layout_constraintWidth_percent="0.9"
            tools:text="0/100" />
    
        <com.airbnb.lottie.LottieAnimationView
            android:id="@+id/loading_animation"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>

    Sau đó, tôi sẽ thực hiện lấy giá trị và set cho ProgressBar như dưới đây:

    package techover.lottie
    
    import android.os.Bundle
    import androidx.appcompat.app.AppCompatActivity
    import kotlinx.android.synthetic.main.activity_main.*
    
    class MainActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            loading_animation.setAnimation("trail-loading.json")
            loading_animation.loop(true)
            loading_animation.speed = 0.5f
            loading_animation.addAnimatorUpdateListener { valueAnimator ->
                val progress = (valueAnimator.animatedValue as Float * 100).toInt()
                progress_horizontal.progress = progress
                progress_number.text = "$progress / 100"
            }
            loading_animation.playAnimation()
    
        }
    }
    Animation Listener

    Custom Animator

    Để 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 ^^

  • Lottie on Android (Part 1)

    Lottie on Android (Part 1)

    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:

    dependencies {
        implementation 'com.airbnb.android:lottie:3.4.0'
    }

    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.

    <?xml version="1.0" encoding="utf-8"?>
    <com.airbnb.lottie.LottieAnimationView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/loading_animation"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:lottie_autoPlay="true"
        app:lottie_fileName="trail-loading.json"
        app:lottie_loop="true"
        app:lottie_speed="1" />

    Các thuộc tính mà chúng ta sử dụng bên trên:

    • app:lottie_autoPlay="true": bắt đầu chạy animation.
    • app:lottie_fileName="trail-loading.json": sử dụng file json ở trong thư mục assets mà bạn đã thêm.
    • app:lottie_loop="true": cho phép animation được lặp.
    • app:lottie_speed="1": tốc độ chạy animation.
      Ngoài ra cũng có các thuộc tính sau nữa: lottie_scale, lottie_repeatCount, lottie_repeatMode

    Bên trên là bạn đã thêm animation vào trong xml, nhưng nó cũng có thể được thực hiện, được set các thuộc tính ở onCreate() / onCreateView() bạn nhé.

    package techover.lottie
    
    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import kotlinx.android.synthetic.main.activity_main.*
    
    class MainActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            loading_animation.setAnimation("trail-loading.json")
            loading_animation.loop(true)
            loading_animation.speed = 1f
            loading_animation.playAnimation()
        }
    }

    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 ^^

  • [MacOS] Hướng dẫn cài đặt Oracle JDK

    [MacOS] Hướng dẫn cài đặt Oracle JDK

    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
    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
    
    • Nếu đã cài đặt rồi thì tiến hành update lastest brew như sau:
    hieunv@HieuNV ~ % brew update && brew upgrade
    Updated 1 tap (homebrew/core).
    ==> New Formulae
    swift-sh
    ==> Updated Formulae
    apache-spark               jetty                      xcodegen
    docker-slim                vim                        zsh-syntax-highlighting
    Updating Homebrew...
    

    Kiểm tra caskjava

    brew cask info java
    

    Nếu homebrew/cask chưa được cài đặt thì nó sẽ tự động cài đặt luôn

    hieunv@HieuNV ~ % brew cask info java
    ==> Tapping homebrew/cask
    Cloning into '/usr/local/Homebrew/Library/Taps/homebrew/homebrew-cask'...
    remote: Enumerating objects: 3655, done.
    remote: Counting objects: 100% (3655/3655), done.
    remote: Compressing objects: 100% (3648/3648), done.
    remote: Total 3655 (delta 26), reused 510 (delta 5), pack-reused 0
    Receiving objects: 100% (3655/3655), 1.23 MiB | 215.00 KiB/s, done.
    Resolving deltas: 100% (26/26), done.
    Tapped 1 command and 3543 casks (3,660 files, 4.0MB).
    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)
    
    • 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

    echo 'export JAVA_HOME=$(/usr/libexec/java_home)' >> ~/.zshrc
    

    Kiểm tra biến JAVA_HOME

    Đóng Termial sau đó bật lại và kiểm tra biến JAVA_HOME

    hieunv@HieuNV libexec % echo $JAVA_HOME
    /Library/Java/JavaVirtualMachines/jdk-13.0.2.jdk/Contents/Home
    

    Như vậy là bạn đã tiến hành cài đặt thành công Oracle Java rồi.
    Tài liệu tham khảo
    https://emcorrales.com/blog/install-oracle-jdk-macos-homebrew

  • Android Animation (Part 3)

    Android Animation (Part 3)

    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:

    <!-- drawable/numeric_0_vector.xml -->
    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24"
        android:viewportHeight="24">
        <path
            android:fillColor="#000"
            android:pathData="M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3H19M11,7A2,2 0 0,0 9,9V15A2,2 0 0,0 11,17H13A2,2 0 0,0 15,15V9A2,2 0 0,0 13,7H11M11,9H13V15H11V9Z" />
    </vector>

    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.

    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:height="24dp"
        android:width="24dp"
        android:viewportWidth="24"
        android:viewportHeight="24">
        <path android:fillColor="#000" android:pathData="M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3H19M11,7A2,2 0 0,0 9,9V15A2,2 0 0,0 11,17H13A2,2 0 0,0 15,15V9A2,2 0 0,0 13,7H11M11,9H13V15H11V9Z" />
    </vector>
    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:height="24dp"
        android:width="24dp"
        android:viewportWidth="24"
        android:viewportHeight="24">
        <path android:fillColor="#000" android:pathData="M14,17H12V9H10V7H14M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" />
    </vector>
    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:height="24dp"
        android:width="24dp"
        android:viewportWidth="24"
        android:viewportHeight="24">
        <path android:fillColor="#000" android:pathData="M15,11C15,12.11 14.1,13 13,13H11V15H15V17H9V13C9,11.89 9.9,11 11,11H13V9H9V7H13A2,2 0 0,1 15,9M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" />
    </vector>

    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

    Link tham khảo:

    • https://developer.android.com/reference/android/graphics/drawable/VectorDrawable
    • https://developer.android.com/guide/topics/graphics/vector-drawable-resources
  • [Android Jetpack] Schedule tasks with WorkManager

    [Android Jetpack] Schedule tasks with WorkManager

    Giới thiệu Android Jetpack

    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())
        { ... }
    });

    Để hiểu thêm về LiveData, các bạn xem tại đây

    Task constraints ( Sự ràng buộc)

    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:

    UUID compressionWorkId = compressionWork.getId();
    WorkManager.getInstance().cancelByWorkId(compressionWorkId);

    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:

    WorkContinuation chain1 = WorkManager.getInstance()
    .beginWith(workA)
    .then(workB);
    WorkContinuation chain2 = WorkManager.getInstance()
    .beginWith(workC)
    .then(workD);
    WorkContinuation chain3 = WorkContinuation
    .combine(chain1, chain2)
    .then(workE);
    chain3.enqueue();

    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

  • Architecting Android…The clean way?

    Architecting Android…The clean way?

    Lời Mở Đầu

    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é.

  • Android Animation (Part 2)

    Android Animation (Part 2)

    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>

    syntax:

    <?xml version="1.0" encoding="utf-8"?>
    <animation-list xmlns:android="http://schemas.android.com/apk/res/android"
        android:oneshot=["true" | "false"] >
        <item
            android:drawable="@[package:]drawable/drawable_resource_name"
            android:duration="integer" />
    </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é 😀

    Link tham khảo:

    • https://developer.android.com/guide/topics/graphics/drawable-animation
  • Binding library android -Xamarin Android

    Kết quả hình ảnh cho binding android xamarin

    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>	

    Một số trường hợp khác như tên classs trùng nhau, rồi đổi visibility của class … thì bạn dùng thằng att để thay đổi attribute của nó.
    Về chi tiết các bạn có thể tham khảo tại : https://docs.microsoft.com/en-us/xamarin/android/platform/binding-java-library/customizing-bindings/java-bindings-metadata
    Một vài lưu ý nữa là kể cả việc file aar bị mã hoá cũng không ảnh hưởng gì đến việc binding nhé. Vì những class a, b, c trong đó bạn trả bao giờ dùng đến cả.

    Bài viết của mình xin hết, nếu bạn nào dùng mà cảm thấy không hiểu thì contact với mình.

    Nếu các bạn cần binding với xamarin ios xem tại: https://magz.techover.io/2019/12/30/binding-objective-c-libraries-xamarin-ios/

    Thanks you!

  • Android Animation (Part 1)

    Android Animation (Part 1)

    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.

    AnimationUtils.loadAnimation(applicationContext, R.anim.my_animation)

    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().

    animation_fade_in.startAnimation(AnimationUtils.loadAnimation(applicationContext, R.anim.fade_in))

    Ở đâ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:

    • Fade In
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true">
        <alpha
            android:duration="1000"
            android:fromAlpha="0.0"
            android:interpolator="@android:anim/accelerate_interpolator"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toAlpha="1.0" />
    </set>
    • Fade Out
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true">
        <alpha
            android:duration="1000"
            android:fromAlpha="1.0"
            android:interpolator="@android:anim/accelerate_interpolator"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toAlpha="0.0" />
    </set>
    • Blink
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android">
        <alpha
            android:duration="1000"
            android:fromAlpha="0.0"
            android:interpolator="@android:anim/accelerate_interpolator"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toAlpha="1.0" />
    </set>
    • Zoom In
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true">
        <scale
            android:duration="1000"
            android:fromXScale="1"
            android:fromYScale="1"
            android:pivotX="50%"
            android:pivotY="50%"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toXScale="2"
            android:toYScale="2" />
    </set>
    • Zoom Out
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true">
        <scale
            android:duration="1000"
            android:fromXScale="2.0"
            android:fromYScale="2.0"
            android:pivotX="50%"
            android:pivotY="50%"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toXScale="1"
            android:toYScale="1" />
    </set>
    • Rotate
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android">
        <rotate
            android:duration="1000"
            android:fromDegrees="0"
            android:interpolator="@android:anim/cycle_interpolator"
            android:pivotX="50%"
            android:pivotY="50%"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toDegrees="360" />
    </set>
    • Move
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true"
        android:interpolator="@android:anim/linear_interpolator">
        <translate
            android:duration="1000"
            android:fromXDelta="0%p"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toXDelta="50%p" />
    </set>
    • Slide Up
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true">
        <scale
            android:duration="1000"
            android:fromXScale="1.0"
            android:fromYScale="1.0"
            android:interpolator="@android:anim/linear_interpolator"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toXScale="1.0"
            android:toYScale="0.0" />
    </set>
    • Slide Down
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true">
        <scale
            android:duration="1000"
            android:fromXScale="1.0"
            android:fromYScale="0.0"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toXScale="1.0"
            android:toYScale="1.0" />
    </set>
    • Sequential
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true"
        android:interpolator="@android:anim/linear_interpolator"
        android:repeatCount="infinite"
        android:repeatMode="restart">
        <!-- Move -->
        <translate
            android:duration="1000"
            android:fillAfter="true"
            android:fromXDelta="0%p"
            android:startOffset="300"
            android:toXDelta="75%p" />
        <translate
            android:duration="1000"
            android:fillAfter="true"
            android:fromYDelta="0%p"
            android:startOffset="1100"
            android:toYDelta="70%p" />
        <translate
            android:duration="1000"
            android:fillAfter="true"
            android:fromXDelta="0%p"
            android:startOffset="1900"
            android:toXDelta="-75%p" />
        <translate
            android:duration="1000"
            android:fillAfter="true"
            android:fromYDelta="0%p"
            android:startOffset="2700"
            android:toYDelta="-70%p" />
    
        <!-- Rotate 360 degrees -->
        <rotate
            android:duration="1200"
            android:fromDegrees="0"
            android:interpolator="@android:anim/cycle_interpolator"
            android:pivotX="50%"
            android:pivotY="50%"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:startOffset="3800"
            android:toDegrees="360" />
    
    </set>
    • Together
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:fillAfter="true"
        android:interpolator="@android:anim/linear_interpolator">
    
        <!-- Move -->
        <scale
            android:duration="4000"
            android:fromXScale="1"
            android:fromYScale="1"
            android:pivotX="50%"
            android:pivotY="50%"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toXScale="3"
            android:toYScale="3" />
    
        <!-- Rotate 180 degrees -->
        <rotate
            android:duration="1000"
            android:fromDegrees="0"
            android:pivotX="50%"
            android:pivotY="50%"
            android:repeatCount="infinite"
            android:repeatMode="restart"
            android:toDegrees="360" />
    
    </set>

    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).

    Các cú pháp cho các bạn tham khảo:

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@[package:]anim/interpolator_resource"
        android:shareInterpolator=["true" | "false"] >
        <alpha
            android:fromAlpha="float"
            android:toAlpha="float" />
        <scale
            android:fromXScale="float"
            android:toXScale="float"
            android:fromYScale="float"
            android:toYScale="float"
            android:pivotX="float"
            android:pivotY="float" />
        <translate
            android:fromXDelta="float"
            android:toXDelta="float"
            android:fromYDelta="float"
            android:toYDelta="float" />
        <rotate
            android:fromDegrees="float"
            android:toDegrees="float"
            android:pivotX="float"
            android:pivotY="float" />
        <set>
            ...
        </set>
    </set>
    • Fade In
    • Fade Out
    • Blink
    • Zoom In
    • Zoom Out
    • Rotate
    • Move
    • Slide Up
    • Slide Down
    • Sequential
    • Together

    Link tham khảo:

    • https://developer.android.com/training/animation