ConstraintLayout – Phần 2: Các Chức Năng Nâng Cao
Chào các bạn. Mời các bạn đến với phần 2 của bài viết về ConstraintLayout của mình. Như mình có nói ở phần 1, ConstraintLayout tuy dài (bị tách ra làm hai phần lận), nhưng nó không khó. Qua bài viết đó bạn đã nắm được ý nghĩa và cách sử dụng các công cụ cơ bản nhất mà Android Studio cung cấp để ...
Chào các bạn.
Mời các bạn đến với phần 2 của bài viết về ConstraintLayout của mình.
Như mình có nói ở phần 1, ConstraintLayout tuy dài (bị tách ra làm hai phần lận), nhưng nó không khó. Qua bài viết đó bạn đã nắm được ý nghĩa và cách sử dụng các công cụ cơ bản nhất mà Android Studio cung cấp để thiết kế kéo-thả cho layout mới mẻ này. Phần tiếp theo này bạn sẽ biết thêm các chức năng hay ho khác của ConstraintLayout, giúp bạn có thể thiết kế được tất cả các layout mà bạn muốn.
Và chắc chắn chúng ta sẽ cùng nhau thử thực hành xây dựng một layout hoàn thiện ở cuối bài hôm nay.
Nếu như ở phần trước, bạn phải kéo kéo, neo neo thì mới có được các constraint ưng ý. Thì với một cú click chuột ở phần này, bạn đã có thể nói cho hệ thống biết nên tự tạo các constraint ngay khi bạn thả một view từ palette vào design view hoặc blueprint view.
Đó là bạn hãy nhấn vào icon toolbar được khoanh đỏ như hình trên để kích hoạt nó. Ở phần trước bạn đã thấy chức năng này bị tắt, khi đó bạn chú ý icon này sẽ có một đường gạch chéo. Khi được kích hoạt thì đường gạch chéo này sẽ biến mất.
Việc tự tạo các constraint một cách tự động như thế này theo mình thấy vẫn chưa hoàn hảo lắm, một số điểm neo chưa tạo được đúng theo ý mình. Nhưng nếu bật nó lên, thì mình không phủ nhận một điều rằng nó sẽ giúp thiết kế nhanh hơn trong một số trường hợp. Sử dụng nó hay không là tùy bạn nhé,
Bạn hãy xem sự so sánh giữa việc tắt và bật chức năng Autoconnect này như sau. (Nếu bạn không xem được ảnh gif thì có thể xem bài viết ở link gốc này) (Nếu bạn không xem được ảnh gif thì có thể xem bài viết ở link gốc này)
Nghĩa tiếng Việt là do tự mình đặt. Mà nghĩ cũng đúng thôi. Vì chức năng này khi được kích hoạt sẽ tự nó tính toán, suy luận ra các constraint để khớp với bố cục hiện thời của layout. Để sử dụng tính năng này, bạn hãy tìm đến icon toolbar được khoanh đỏ như trên.
Sự khác nhau giữa tính năng Infer Constraints và Autoconnect trên kia là, Infer Constraints sẽ suy luận ra tất cả các constraint cho tất cả các view trong layout, để đạt được một bố cục như ý muốn của lập trình viên, còn Autoconnect thì sẽ tạo các constraint cho bản thân view đang được tương tác. (Nếu bạn không xem được ảnh gif thì có thể xem bài viết ở link gốc này)
Ý nghĩa của chức năng này là Đóng gói. Nhưng không hẳn như nghĩa của nó. Theo mình thấy thì chức năng này thực chất giúp bạn kéo dãn hết không gian của view theo chiều ngang hay dọc, sự kéo dãn này không đẩy các view khác đi khỏi vị trí của chúng. Bạn sẽ dễ dàng kiểm chứng chức năng này dựa theo minh họa sau. (Nếu bạn không xem được ảnh gif thì có thể xem bài viết ở link gốc này)
Theo lẽ mình đã có thể nói đến chức năng này ở mục Attribute của phần trước. Nhưng ratio quá xuất sắc khiến mình tách nó ra làm một phần riêng để bạn dễ nhớ.
Chức năng này cho phép bạn tạo các view bên trong ConstraintLayout với kích thước theo tỉ lệ ngang-dọc. Chẳng hạn 16:9, hay 4:3. Khi bạn đã chỉ định ratio cho view, thì bạn sẽ phải phó mặc sự tính toán về kích thước của view cho hệ thống.
Để có thể thiết lập ratio cho view, bạn buộc phải chỉ định một trong hai giá trị (hoặc cả hai) layout_awidth và layout_height của view thành match_constraint (hoặc là 0dp), bạn có thể xem lại mục này ở phần trước để nhớ lại thiết lập match_constraint cho view là như thế nào.
Sau khi bạn thiết lập một trong hai hướng (hoặc cả hai) của view là match_constraint, thì ở view inspector, chỗ hình vuông đại diện cho view trong cửa sổ này, sẽ xuất hiện một hình tam giác báo hiệu cho bạn biết bạn có thể click vào đó để thiết lập ratio cho view.
Bạn có thể xem minh họa bên dưới (minh họa này có thực hiện việc kéo thả ImageView vào blueprint view, sau khi kéo các view liên quan đến image bạn phải chỉ định ngay cho nó một resource image ở cửa sổ xuất hiện sau đó, lát nữa vào phần thực hành bạn sẽ được kiểm chứng). (Nếu bạn không xem được ảnh gif thì có thể xem bài viết ở link gốc này)
Có một lưu ý cho tính năng này là. Nếu như ví dụ trên, bạn chỉ định match_constraint theo một phương ngang, thì ratio chỉ giúp bạn điều chỉnh chiều ngang của view, chiều cao của view vẫn được giữ cho cố định. Tức là nếu bạn chỉ định tỷ lệ 16:9, thì hệ thống sẽ lấy chiều cao hiện tại và xem giá trị ứng với tỷ lệ 9 là bao nhiêu, sau đó sẽ tính toán cho ra chiều ngang tương ứng với tỷ lệ 16, chính vì vậy, nếu nhìn kỹ bạn sẽ thấy chiều ngang của image ở trên dường như vượt ra khỏi chiều ngang của màn hình.
Nếu chưa rõ bạn hãy xem ví dụ dưới đây, khi mà layout_awidth và layout_height đều được thiết lập match_constraint, thì nếu bạn nhấn nhiều lần lên hình tam giác ở góc, bạn có thể chọn được phương nào (ngang hay dọc) được hệ thống tinh chỉnh lại so với phương còn lại. Bạn chú ý phương được vẽ bằng đường thẳng đậm bên trong hình vuông của view inspector chính là phương bị tinh chỉnh so với phương gốc nhé. Nếu không có đường thẳng đậm thì hoặc là tính năng ratio đã tắt, hoặc là cả hai phương đều bị điều chỉnh. Chà nhức đầu nhỉ. (Nếu bạn không xem được ảnh gif thì có thể xem bài viết ở link gốc này)
Nếu bạn có nhiều view được canh chỉnh theo một trật tự thẳng hàng nào đó (thẳng hàng ngang hay thẳng hàng dọc), bạn có thể cân nhắc sử dụng guideline. Các guideline này là các đường thẳng không hề hiển thị lên giao diện khi bạn thực thi ứng dụng, nó chỉ được nhìn thấy khi thiết kế mà thôi, và nhờ vào các đường guideline ẩn này, bạn có thể neo các view vào nó, để tạo ra một trật tự thẳng hàng nhất định.
Chi tiết guideline hoạt động như thế nào thì bạn có thể nhìn vào ví dụ sau. Ở ví dụ dưới đây mình chỉ dùng các đường guideline theo chiều dọc (Vetical Guideline), nhưng việc sử dụng với guideline theo chiều ngang (Horizontal Guideline) cũng sẽ tương tự. (Nếu bạn không xem được ảnh gif thì có thể xem bài viết ở link gốc này)
Ngoài việc canh chỉnh guideline theo biên trái như trên, bạn còn có thể nhấn vào hình tròn ở đầu guideline để thay đổi cách canh chỉnh theo biên phải, hay theo phần trăm. (Nếu bạn không xem được ảnh gif thì có thể xem bài viết ở link gốc này)
Nếu như guideline giúp chúng ta tạo ra các đường biên, và canh chỉnh view dựa trên các đường biên đó. Mặc dù bạn có thể di chuyển được guideline như ví dụ trên. Nhưng guideline vẫn mang một công dụng “cứng”, tức là bạn chỉ có thể chỉnh sửa các guideline khi nó còn đang ở dạng thiết kế, khi thực thi ứng dụng rồi thì guideline ở đâu view sẽ ở đó, mặc cho có sự chồng lấp giữa các view với nhau, vì ai mà biết được các kích cỡ màn hình thực tế như thế nào để mà thiết lập các guideline hợp lý.
Để giải quyết vấn đề cần phải có sự dịch chuyển động giữa các guideline với nhau, bạn nên dùng chức năng hay hơn, đó là barrier.
Bạn có thể thêm bất kỳ barrier theo hướng ngang hay dọc vào màn hình bằng cách chọn trên toolbar theo hình ảnh trên, rồi thực hiện như minh họa dưới đây. (Nếu bạn không xem được ảnh gif thì có thể xem bài viết ở link gốc này)
Do ảnh động trên khá là dài nên có thể gây khó khăn cho bạn khi muốn xem lại quy trình sử dụng barrier, nên mình viết lại các bước sau.
- Như đã nói, đầu tiên bạn thêm barrier (dọc hay ngang) bằng cách chọn trên toolbar như hình trên kia. Hoặc click chuột phải vào blueprint view thì bạn cũng sẽ thấy tùy chọn tương tự.
- Sau đó bạn phải kéo các view muốn “chặn” lại một cách cơ động vào barrier vừa tạo ở component tree.
- Bạn nhớ chỉ định thuộc tính barrierDirection là right, đây chính là phía mà barrier sẽ “chặn động” các view.
- Cuối cùng, bạn có thể neo các view khác, như các EditText ở ví dụ trên, vào barrier này,.
- Xong. Bạn có thể thử nghiệm tính hiệu quả của barrier bằng cách thay đổi text của các TextView. Bạn sẽ thấy barrier chạy động theo biên phải của các thành phần con của nó như thế nào. Và các view khác khi neo vào barrier cũng sẽ tự dịch chuyển như thế nào.
Chức năng này mình cũng khá thích. Nó giúp bạn canh chỉnh vị trí của nhiều view một cách tự động. Việc canh chỉnh này giống như hệ thống đã dùng dây xích để cột chúng lại vậy, và sau đó hệ thống sẽ dàn các view đó ra hoặc co chúng lại tùy thuộc vào kích cỡ màn hình và các thiết lập của bạn. Hệ thống gọi đây là chain.
Để gom các view vào trong một chain, bạn chỉ cần chọn hết các view muốn gom bằng cách nhấn giữ phím Shift trong lúc click chọn từng view. Rồi click phải lên bất kỳ view nào trong số chúng, và chọn Chain > Create Horizontal Chain (hay Create Vertical Chain). Bạn hãy xem minh họa sau. (Nếu bạn không xem được ảnh gif thì có thể xem bài viết ở link gốc này)
Bạn cũng thấy đó, sau khi gia nhập vào một chain, sẽ có một sợi xích ràng buộc các view lại với nhau, và khi đó vị trí các view sẽ dàn đều không gian của chúng.
Công dụng của chain chưa dừng lại ở đó. Bạn có thể chỉ định nhiều thể loại chain khác nhau. Các loại chain đó là.
- Spread: Đây là kiểu dàn đều các view dựa vào không gian của chúng theo phương ngang hoặc dọc. Đây là kiểu sắp xếp mặc định khi bạn tạo mới một chain. Bạn có thể xem lại minh họa kiểu chain này ở hình động trên kia.
- Spread inside: Kiểu này cũng sẽ dàn đều các view, nhưng nó sẽ tôn trọng constraint của view đầu và cuối trong một chain. Như bạn thấy trên hình, nếu các view A và C đều set margin ở các biên là 0dp thì chúng sẽ dính chặt vào biên như vậy.
- Weight: Cách này tương tự như bạn chỉ định trọng số layout_weight trong LinearLayout vậy. Để sử dụng được weight trong ConstraintLayout thì bạn phải chỉ định các view trong chain về match_constraint, rồi tìm đến thuộc tính horizontal_weight hoặc vertical_weight để thiết lập trọng số này cho từng view. Bạn sẽ hiểu rõ hơn ở minh họa phía dưới đây.
- Packed: Kiểu “đóng gói” các view lại thành một “cục” sát vào nhau. Sau khi đóng gói các view lại xong, bạn có thể sử dụng bias để thay đổi độ lệch theo chiều ngang hoặc dọc cho các gói này (bạn nhớ để ý xem minh họa cho việc thay đổi bias với kiểu packed này ở dưới đây).
Để thay đổi từng loại chain đã nói đến trên đây, rất đơn giản, bạn hãy đưa trỏ chuột vào bất kỳ một view nào trong chain, khi thấy xuất hiện một nút giống như hình mắt xích ở dưới view, bạn hãy nhấn vào nó để lần lượt thay đổi qua các loại chain.
Minh họa dưới đây cho thấy chuyển đổi qua lại giữa các loại chain thứ 1 (Spread), 2 (Spread inside) và 4 (Packed). Vì loại số 3 (Weight) cần phải thay đổi chút giá trị match_constraint và weight nên mình sẽ minh họa ở sau. (Nếu bạn không xem được ảnh gif thì có thể xem bài viết ở link gốc này)
Còn đây là minh họa cho chain ở loại thứ 3 (weight). Với chain này mình sẽ set trọng số cho các view lần lượt là 1-2-1. Bạn xem. (Nếu bạn không xem được ảnh gif thì có thể xem bài viết ở link gốc này)
Chức năng cuối cùng mà mình nói đến hơi kém quan trọng xíu. Nó không phải ViewGroup mà chúng ta từng biết. Bởi vì nó chẳng chứa đựng view nào bên trong cả. Group ở ConstraintLayout chỉ chứa địa chỉ các view, để bạn dễ dàng thiết lập một số tham số chung cho các view mà nó đang chứa đựng mà thôi, như tham số ẩn hiện như ví dụ này. (Nếu bạn không xem được ảnh gif thì có thể xem bài viết ở link gốc này)
Đến đây thì các bạn đã nắm được tất cả các công cụ để tạo nên một giao diện hoàn chỉnh bằng ConstraintLayout rồi đó. Giờ thì chúng ta cùng vận dụng những điều đã biết ở hai phần của bài viết để xây dựng một giao diện hoàn chỉnh nhé.
Bài thực hành này của mình sẽ xây dựng lại giao diện theo như hướng dẫn này.
Để có thể bắt tay vào xây dựng một giao diện hoàn chỉnh bằng ConstraintLayout, thì tốt nhất bạn phải có đầy đủ các resource cho nó, đặc biệt là resource ảnh.
Bạn có thể làm theo hướng dẫn ở link thực hành mình để ở trên mà không cần phải đọc phần này của mình. Hoặc bạn cũng có thể vào link đó lấy resource về rồi đi qua các bước của mình bên dưới. Nếu không thích vào link trên thì bạn có thể click vào link này để download resource về.
Nào, sau khi chuẩn bị sẵn resource cho bài thực hành, để bắt đầu, bạn hãy tạo project với Empty Activity như mình có nói ở phần 1.
Đầu tiên, chúng ta sẽ kéo một ImageView, đó chính là header ở trên cùng của màn hình. Header này sẽ có chiều dài chiếm trọn không gian dài của màn hình. Chiều cao của nó mình sẽ không set tỷ lệ, mà mình sẽ cho nó neo vào icon favorite sẽ xây dựng ở bước sau.
Bạn nhớ chỉnh margin cho header về 0dp hết. Định nghĩa text cho thuộc tính contentDescription, thuộc tính này sẽ giúp hiển thị text cho ImageView khi hệ thống không load được ảnh. Và nhớ chỉ định scaleTyle là centerCrop nhé.
(Nếu bạn không xem được ảnh gif thì có thể xem bài viết ở link gốc này)
Tiếp theo bạn hãy kéo một ImageView nữa, đây chính là icon favorite. Bạn hãy chỉ định kích thước cố định cho nó là 36dp. Cũng định nghĩa text cho contentDescription và thiết lập background cho nó. Sau đó bạn nhớ neo đáy của header trên kia vào đáy của favorite. Khi này nếu bạn thay đổi bias của icon favorite, bạn sẽ thấy chiều cao của header thay đổi theo. Thiệt là vi diệu! (Nếu bạn không xem được ảnh gif thì có thể xem bài viết ở link gốc này)
Chúng ta sẽ thêm một TextView làm title. TextView này không có gì đặc biệt cả. Chỉ có lưu ý là ở bước này bạn nên thay đổi giá trị default của margin về 16dp. Vì hầu như các view sau đều dùng đến giá trị margin này. (Nếu bạn không xem được ảnh gif thì có thể xem bài viết ở link gốc này)
Tiếp theo chúng ta sẽ thêm hai TextView. Hai TextView mà bạn sẽ thêm vào chỉ neo về bên trái thôi. Bạn chấp nhận báo lỗi từ Component Tree đi, vì hai TextView này chưa được neo theo chiều dọc nào. Chúng ta sẽ hoàn thiện constraint cho chúng khi có EdiText ở bước dưới nữa. Có một lưu ý là chúng ta nên tạo barrier cho hai TextView ở bước này luôn. (Nếu bạn không xem được ảnh gif thì có thể xem bài viết ở link gốc này)
Giờ thì chúng ta tạo EditText rồi neo nó vào barrier của TextView, để nó có thể “lạc trôi” theo chiều rộng của TextView.
Sau khi có EditText rồi thì bạn tạo baseline constraint cho TextView tương ứng với EditText của nó. Minh họa sau chỉ thực hiện cho một EditText, bạn hãy tạo một EditText nữa bên dưới EditText đã tạo nhé. (Nếu bạn không xem được ảnh gif thì có thể xem bài viết ở link gốc này)
Bước tiếp theo sẽ tạo hai Button rồi neo chúng về phía phải và dưới màn hình. Bước này không có gì khó khăn cả. (Nếu bạn không xem được ảnh gif thì có thể xem bài viết ở link gốc này)
Bước cuối cùng bạn hãy đặt một TextView vào khoảng không gian giữa màn hình. Đây chính là description của giao diện. Bạn có thể dùng chức năng pack để mở rộng view hết cỡ trong khoảng không gian còn lại đó. Vậy là bạn đã xong một màn hình hoàn chỉnh bằng ConstraintLayout rồi đó. (Nếu bạn không xem được ảnh gif thì có thể xem bài viết ở link gốc này)
Qua hai phần của ConstrainLayout mà mình tự thiết kế lại (dĩ nhiên cũng có tham khảo khá nhiều nguồn). Hi vọng các bạn đủ tự tin để bắt đầu thiết kế các giao diện ứng dụng trên Android bằng ConstrainLayout rồi nhé. Hoặc nếu các bạn có các project đang được xây dựng, hãy thử chuyển các layout kiểu cũ sang ConstraintLayout xem sao.
Để biết nhiều hơn cách thức xây dựng một ứng dụng Android, bạn có thể đọc series bài viết này.
Bạn cũng có thể vào link gốc này để xem lại vài viết của ngày hôm nay: ConstraintLayout – Phần 2: Các Chức Năng Nâng Cao.