Khi các kiểu dáng mới vượt ra ngoài màn hình cảm ứng, hệ thống đầu vào của Compose đã phát triển để đáp ứng thách thức này. Kiến trúc phân lớp của Compose cung cấp nền tảng để hỗ trợ liền mạch mọi thứ, từ cử chỉ nâng cao trên bàn di chuột đến các phương thức nhập phần cứng mới.
Thông qua các phân tích rõ ràng về cấu trúc và ví dụ về mã thực tế, hãy theo dõi vòng đời của một sự kiện đầu vào từ lớp nền tảng hệ điều hành cho đến trình phát hiện cử chỉ và thành phần giao diện người dùng của Compose. Bạn sẽ khám phá cách theo dõi sự kiện trên toàn bộ ngăn xếp, áp dụng các mẫu kiến trúc chính cho các loại dữ liệu đầu vào mới nổi và tự tin gỡ lỗi các hoạt động tương tác bằng cử chỉ phức tạp trong ứng dụng của riêng mình.
performTrackpadInput. Các API này cung cấp các nền tảng kiểm thử quen thuộc để xác thực rằng ứng dụng xử lý các thiết bị không cảm ứng một cách chính xác.Bản chép lời
Xin chào mọi người!
Hy vọng bạn có khoảng thời gian vui vẻ tại Hội nghị Nhà phát triển Android và cảm ơn bạn đã tham dự buổi trò chuyện của chúng tôi.
Tôi là Alex Vanyo, kỹ sư phần mềm trong nhóm Compose, và cùng tham gia với tôi là Jeremy, cũng là kỹ sư phần mềm trong nhóm Compose.
Cả hai chúng tôi đều đang nỗ lực cải thiện hệ thống nhập của Jetpack Compose. Hôm nay, chúng tôi sẽ nói về cách chúng tôi mở rộng hệ thống này ra ngoài màn hình cảm ứng để hỗ trợ nhập không chạm dễ dàng hơn.
Nói chung, dữ liệu đầu vào ghi lại mọi cách người dùng tương tác với ứng dụng của bạn.
Màn hình cảm ứng có lẽ là điều đầu tiên bạn nghĩ đến khi nói về ứng dụng di động, nhưng chắc chắn đây không phải là loại phương thức nhập duy nhất.
Các loại phương thức nhập khác bao gồm bàn phím thực, chuột, bàn di chuột, bút cảm ứng, tay điều khiển và thiết bị xoay.
Chúng ta cũng đang chứng kiến những loại phương thức nhập mới, chẳng hạn như bàn di chuột trên kính thông minh, tính năng theo dõi mắt và tay trong XR, cũng như cử chỉ ngón tay và cổ tay trên Wear.
Và nếu bạn muốn tìm hiểu chung hơn nữa, thì còn có các loại dữ liệu đầu vào khác, chẳng hạn như IME phần mềm để chỉnh sửa văn bản, micrô, máy ảnh và các cảm biến khác của thiết bị.
Bạn cần dự kiến rằng ứng dụng của mình sẽ được dùng với nhiều loại phương thức nhập.
Một lý do cho việc này là sự đa dạng ngày càng tăng của các thiết bị mà ứng dụng đang chạy và khả năng thích ứng ngày càng tăng của các thiết bị đó.
Hệ số hình dạng của thiết bị có thể có một phương thức nhập phổ biến nhất, nhưng điều quan trọng là không được giả định rằng một hệ số hình dạng cụ thể của thiết bị ngụ ý việc sử dụng một loại phương thức nhập cụ thể.
Mặc dù Chromebook có bàn di chuột và bàn phím tích hợp, nhưng điều đó không có nghĩa là các loại phương thức nhập này chỉ dành riêng cho kiểu dáng đó.
Bạn cũng nên dự kiến rằng bàn phím vật lý, bàn di chuột và chuột sẽ được dùng trên điện thoại, máy tính bảng và TV, thậm chí một số TV còn có màn hình cảm ứng.
Lý do quan trọng thứ hai để hỗ trợ nhiều loại phương thức nhập là khả năng hỗ trợ tiếp cận.
Việc cho phép người dùng sử dụng thiết bị đầu vào mà họ chọn và cho phép họ chuyển đổi thiết bị đầu vào khi thấy phù hợp là điều nên làm để giúp ứng dụng dễ tiếp cận và dễ sử dụng hơn.
Tuỳ thuộc vào khu vực tài phán, có thể có yêu cầu pháp lý về việc ứng dụng của bạn phải có hành vi cụ thể, tương thích với các loại phương thức nhập này, đặc biệt là những phương thức như bàn phím và chuột.
Nói tóm lại, bạn nên hỗ trợ nhiều loại phương thức nhập bất kể kiểu dáng thiết bị.
Vậy hãy cùng xem cách Jetpack Compose giúp ứng dụng của bạn đáp ứng những nhu cầu này của người dùng.
Nhìn chung, triết lý của Compose là các loại dữ liệu đầu vào khác nhau này sẽ hoạt động phù hợp ngay từ đầu với các thành phần tiêu chuẩn mà bạn không cần phải làm gì đặc biệt.
Sau đó, bạn sẽ có các công cụ cần thiết để tuỳ chỉnh và điều chỉnh hành vi cho phù hợp.
Vì vậy, ngoài các loại phương thức nhập ban đầu đó, khi nền tảng cơ bản hỗ trợ thêm nhiều loại phương thức nhập, chúng tôi sẽ xây dựng tính năng hỗ trợ cho các loại phương thức nhập đó trong các thành phần của Compose, cải thiện hành vi cho ứng dụng của bạn chỉ bằng cách cập nhật phiên bản Compose.
Trong bài nói chuyện này, chúng ta sẽ xem xét hai ví dụ như vậy, cả hai đều là những bổ sung gần đây cho Compose.
Thật trùng hợp, cả hai đều là thiết bị giống như bàn di chuột, nhưng như bạn sẽ thấy, chúng được sử dụng theo những cách rất khác nhau.
Vì vậy, hãy bắt đầu với một số công việc gần đây trên bàn di chuột của loại máy tính xách tay.
Bàn di chuột là một thiết bị đầu vào dựa trên con trỏ.
Cụ thể hơn, khi sử dụng bàn di chuột, bạn sẽ nhận được toạ độ hai chiều.
Nhưng không giống như màn hình cảm ứng, toạ độ của ngón tay trên bàn di chuột không nằm trực tiếp trong không gian toạ độ của giao diện người dùng.
Thay vào đó, đối với bàn di chuột giống như máy tính xách tay, có một bản dịch để điều khiển con trỏ.
Trong quá trình dịch này, bạn có thể gặp phải một loạt các chuyển đổi nền tảng phức tạp: những thứ như tăng tốc con trỏ và từ chối lòng bàn tay.
Vì vậy, những gì bạn nhận được ở phía Compose là một con trỏ tương ứng với vị trí con trỏ thường hoạt động như một thao tác đầu vào bằng chuột.
Từ nền tảng Android, chúng ta nhận được thông tin di chuột, lượt nhấp bao gồm cả lượt nhấp phụ hoặc lượt nhấp thứ ba và tất cả thông tin này được gói gọn trong MotionEvents.
Để hiểu cách chúng tôi cải thiện hành vi trong các phiên bản mới nhất của Compose, trước tiên, hãy xem thông tin tổng quan về cách Compose xử lý các sự kiện con trỏ này một cách chặt chẽ hơn.
Tôi sẽ chia quy trình này thành 6 giai đoạn và chúng ta sẽ lần lượt tìm hiểu từng giai đoạn.
Giai đoạn đầu tiên là MotionEvents chuyển sang Compose.
Vì vậy, về bản chất, Compose thực sự chỉ là một khung hiển thị: một khung hiển thị thực sự phức tạp và làm được nhiều việc, nhưng nó chỉ là một khung hiển thị, nghĩa là nó cần hoạt động trong toàn bộ hệ thống Android để nhập con trỏ.
Vì vậy, trong Compose, điều đó có nghĩa là Compose, vì nằm trong một cửa sổ thông thường bên trong một hoạt động thông thường, nên sẽ nhận được MotionEvents: MotionEvents của nền tảng Android.
Sau đó, Compose cần xử lý các sự kiện này và thực hiện mọi việc bạn muốn làm trong Compose liên quan đến hoạt động nhập bằng con trỏ.
Giai đoạn đầu tiên ở đây là dịch các MotionEvent đó thành một bản trình bày nội bộ, không phụ thuộc vào nền tảng trong Compose mà chúng tôi gọi là PointerInputEvent.
Đó là một loại nội bộ, vì vậy bạn sẽ không thấy trực tiếp.
Và nếu muốn xem tất cả thông tin chi tiết về những gì đang diễn ra ở đây, bạn có thể xem MotionEventAdapter, nhưng chúng ta sẽ bỏ qua những thông tin chi tiết đó tại đây.
Giờ đây, chúng ta đã có một bản trình bày độc lập với nền tảng của MotionEvent trong Compose.
Tiếp theo, dữ liệu đầu vào thường được cung cấp theo thời gian.
Bạn sẽ nhận được một loạt sự kiện đầu vào liên tiếp, vì vậy, việc biết được những gì đang xảy ra theo thời gian sẽ rất hữu ích.
Những thứ như cử chỉ, theo dõi vận tốc, phát hiện độ dốc, tất cả những thứ đó cần phải hiểu rõ những gì đang xảy ra tại mỗi thời điểm.
Vì vậy, giai đoạn tiếp theo trong quy trình xử lý này là thêm thông tin thời gian này và sẽ thực hiện một số bước xử lý trước dựa trên các sự kiện con trỏ liên tiếp.
Trong mã, bạn có thể xem đây là việc lấy PointerInputEvent hiện tại, kết hợp với những gì đã xảy ra với PointerInputEvent trước đó, sau đó chúng ta tạo một InternalPointerEvent nhận biết thời gian.
Đây là một loại nội bộ khác, như tên gọi của nó, nhưng bạn có thể coi đây là bản đồ gồm nhiều mã con trỏ khác nhau cho các thay đổi về dữ liệu đầu vào của con trỏ và PointerInputChanges là một loại công khai.
Đó là câu hỏi đầu tiên.
Và một điều mà bạn có thể nhận thấy khi nghĩ về các mã con trỏ khác nhau là giờ đây, khi đã biết về việc theo dõi thời gian, chúng ta cũng có thể theo dõi thời điểm mã con trỏ xuất hiện và biến mất.
Không chỉ có một.
Nếu bạn dùng nhiều ngón tay trên màn hình cảm ứng, thì có thể bạn sẽ có nhiều con trỏ cùng lúc và chúng ta cần xử lý việc đó.
Xin nhắc lại, nếu muốn tìm hiểu chi tiết, bạn có thể xem PointerInputChangeEventProducer, nơi thực hiện tất cả những việc này, nhưng chúng ta sẽ bỏ qua phần đó.
Chúng ta còn nhiều nội dung cần tìm hiểu.
Vì vậy, giờ đây chúng ta có một bản trình bày Compose của dữ liệu đầu vào và bản trình bày này có thông tin về thời gian.
Bước tiếp theo là xác định việc cần làm với sự kiện đó.
Chúng tôi nên gửi đến đâu?
Và điều đó dẫn đến việc thử nghiệm Hit Path.
Vì vậy, hãy tưởng tượng một tình huống trong đó chúng ta có một MotionEvent được gửi qua và ComposeView này đang lấp đầy toàn bộ cửa sổ, có thể là toàn bộ màn hình.
Chúng ta cần tìm ra nơi để gửi nó.
Và vì Compose chỉ là một thành phần hiển thị, nhưng chúng ta có rất nhiều thứ bên trong Compose, một loạt các nút, nút, những thứ mà bạn tương tác, chúng ta cần thực hiện trong Compose tất cả công việc tìm ra nơi gửi sự kiện xảy ra tại một vị trí cụ thể và tìm ra nút nào thực sự quan tâm đến sự kiện đó.
Các nút bố cục này có toạ độ, có ranh giới và có hình dạng, vì vậy, chúng ta cần tìm ra nút nào (nếu có) được nhấn.
Nhưng đó cũng không phải là chế độ xem chung nhất.
Hoá ra chúng ta thích đặt các thành phần tương tác bên trong các thành phần tương tác khác, chẳng hạn như đặt một nút bên trong một danh sách.
Hoá ra chúng ta làm việc đó rất nhiều.
Vì vậy, trường hợp chung đầy đủ ở đây là một sự kiện đầu vào cụ thể sẽ tạo ra một Đường dẫn nhấn đầy đủ đi qua mọi nút có thể tương tác được nhấn theo toạ độ của sự kiện đầu vào.
Vì vậy, trong trường hợp này, có 3 giá trị khi đi qua hệ phân cấp lồng nhau.
Vì vậy, chúng ta có một Hit Path, nhưng vẫn chưa đủ.
Bạn có nhớ tôi đã nói rằng chúng ta có nhiều sự kiện nhập bằng con trỏ, nhiều con trỏ có thể đang nhấn xuống không?
Chúng ta thực sự cần có một Cây đường dẫn lượt truy cập đầy đủ.
Không chỉ có một Đường dẫn nhấn khi bạn có nhiều con trỏ.
Vì vậy, đó là giai đoạn tiếp theo, tạo và cập nhật Cây đường dẫn lượt truy cập này.
Vì vậy, đây là thông tin chi tiết hơn về những gì đang diễn ra.
Ở bên trái, chúng ta có các nút được bố trí trực quan, sau đó ở dưới cùng bên trái, bạn có thể thấy biểu diễn phân cấp, nút nào nằm trong nút khác, nút nào là nút cấp trên hoặc cấp dưới của nhau.
Khi con trỏ di chuyển xuống, chúng ta sẽ theo dõi Đường dẫn nhấn, đường dẫn đầy đủ qua cây, sau đó chúng ta sẽ nhận được Đường dẫn nhấn cho con trỏ cụ thể đó.
Sau đó, Hit Path này sẽ trở thành một cây khi chúng ta nhận được con trỏ thứ hai.
Nó có thể giao nhau với một số nút giống như nút kia, nhưng cũng có thể tương tác với các nút khác.
Vì vậy, ở bên phải, đó là Cây đường dẫn lượt truy cập mà chúng tôi hiện đang duy trì.
Và con trỏ có thể xuất hiện rồi biến mất, vì vậy, chúng ta cần tự duy trì con trỏ này theo thời gian để đảm bảo rằng chúng ta có được danh sách đầy đủ các nút quan tâm đến mọi sự kiện đang diễn ra tại thời điểm này.
Được rồi.
Vì vậy, hiện tại chúng ta có một Cây đường dẫn lượt truy cập mà chúng ta đang duy trì cho các sự kiện đầu vào này, tức là chúng ta đang chuyển sang giai đoạn tiếp theo.
Bây giờ, chúng ta thực sự biết nơi sẽ gửi các sự kiện đầu vào con trỏ này, nút nào quan tâm đến từng sự kiện cụ thể.
Bây giờ, chúng ta đã biết điều đó, chúng ta có thể thực hiện phép biến đổi toạ độ.
Hoá ra sẽ rất tuyệt nếu nút của bạn nhận được các sự kiện đầu vào trong không gian toạ độ cục bộ của riêng nút đó, nhờ vậy bạn không cần phải tự mình thực hiện một loạt phép toán toạ độ.
Vì vậy, khi biết điều đó, chúng ta có thể lấy sự kiện nhập bằng con trỏ, kết hợp sự kiện đó với toạ độ của nút quan tâm, rồi cuối cùng tạo ra PointerEvent công khai sẽ được gửi đến tất cả các bạn và đối tượng sửa đổi hoạt động nhập bằng con trỏ.
Và đó là bước cuối cùng trong giai đoạn 6, tức là gửi sự kiện.
Đây là một sự kiện đầu vào con trỏ đã xảy ra và gửi đến nút để thực hiện bất kỳ thao tác nào mà nút muốn.
Chúng tôi thực hiện việc này theo ba lượt khác nhau.
Chúng tôi có một lượt kiểm tra ban đầu, một lượt kiểm tra chính và một lượt kiểm tra cuối cùng.
Lý do có ba lượt truyền khác nhau là để cho phép các nút giao tiếp với nhau và truyền đạt mối quan tâm đến các phần cụ thể của sự kiện nhập bằng con trỏ cụ thể, sau đó sử dụng các phần khác nhau của sự kiện đó trong quá trình gửi.
Vì vậy, lượt đầu tiên, lượt khởi đầu, sẽ đi từ thành phần gốc đến thành phần con.
Ví dụ: khi một nút bố cục bên ngoài có thể là vùng chứa cuộn và biết rằng vùng chứa đó đã bắt đầu cuộn, đồng thời muốn ngăn tất cả sự kiện con trỏ tiếp cận bất kỳ phần tử con nào của vùng chứa đó.
Trong lượt truyền đầu tiên, thao tác này cho phép thành phần hiển thị cha sử dụng sự kiện, sau đó thành phần hiển thị con sẽ không nhận được thêm sự kiện nào nữa.
Lượt thứ hai là lượt chính.
Và đây là mối quan hệ duy nhất đi từ phần tử con đến phần tử mẹ.
Đúng như tên gọi, đây là lượt chính để thực hiện các thao tác.
Ví dụ: đây là nơi thành phần con sẽ sử dụng sự kiện nhấn xuống, bắt đầu xử lý một lượt nhấp và sau đó có cơ hội thực hiện việc đó trước bất kỳ thành phần mẹ nào nếu bạn có các thành phần có thể nhấp được lồng nhau.
Sau đó, lượt cuối cùng, lượt cuối cùng, trông giống như lượt ban đầu, nơi nó đi từ tổ tiên đến hậu duệ.
Và đây là nơi thành phần con có thể cho thành phần mẹ biết rằng thành phần mẹ đã sử dụng một phần của sự kiện.
Vì vậy, có thể nếu một thành phần con bắt đầu xử lý một lượt nhấp, nhưng sau đó thành phần mẹ bắt đầu cuộn, thì thành phần con có thể thấy thành phần mẹ đã làm điều đó, và sau đó thành phần con có thể huỷ lượt nhấp mà nó đã bắt đầu xử lý.
Vậy là chúng ta đã tìm hiểu xong 3 lượt và 6 giai đoạn.
Đây là nền tảng cho mọi thứ trên hệ thống nhập bằng con trỏ.
Tất cả thao tác xử lý cử chỉ, thao tác cuộn, kéo, nhấp và mọi hoạt động nhập bằng con trỏ tuỳ chỉnh đều được xây dựng dựa trên nền tảng này.
Giờ thì chúng ta có thể nói một chút về những điểm cụ thể mà chúng tôi đã cải thiện để hỗ trợ hành vi của bàn di chuột trong Compose.
Vì vậy, trước Compose 1.12, các sự kiện đầu vào của bàn di chuột sẽ được báo cáo dưới dạng loại con trỏ chạm, vì đó là ngón tay tương tác vật lý với bàn di chuột để điều khiển con trỏ.
Tuy nhiên, người dùng mong đợi loại dữ liệu đầu vào này hoạt động giống như chuột thực tế hơn.
Vì vậy, chúng tôi đã thay đổi loại con trỏ từ PointerType.Touch thành PointerType.Mouse.
Nhờ thay đổi này, người dùng sẽ thấy trải nghiệm chuột và bàn di chuột nhất quán hơn nhiều đối với mọi thành phần đang xem thông tin này.
Ví dụ: hành vi của chú thích: bạn có thể muốn có cùng một hành vi cho bàn di chuột và chuột đối với chú thích.
Thay đổi lớn thứ hai mà chúng tôi thực hiện đối với bàn di chuột liên quan đến các cử chỉ nhiều ngón tay mà bạn sẽ thực hiện trên bàn di chuột.
Trước đây, Compose đã truyền qua khái niệm thông minh nhưng có phần kỳ lạ của nền tảng Android về ngón tay giả.
Vậy hãy cùng tìm hiểu xem điều đó có nghĩa là gì.
Khi bạn dùng hai ngón tay trên bàn di chuột để kéo, Android sẽ tạo một ngón tay giả trên màn hình cảm ứng, ngón tay này sẽ di chuyển xuống vị trí của con trỏ.
Sau đó, khi bạn di chuyển ngón tay trên bàn di chuột, ngón tay giả trên màn hình cảm ứng sẽ di chuyển đúng khoảng cách và hướng để bắt chước hành vi mong muốn, rồi thả ra.
Vì vậy, thao tác di chuyển hai ngón tay lên trên bàn di chuột sẽ được chuyển thành một ngón tay giả trên màn hình cảm ứng, cũng di chuyển lên trên, từ đó kích hoạt thao tác cuộn xuống.
Một điều thú vị ở đây là nếu bạn sử dụng tính năng cuộn ngược bàn di chuột giống như tôi, thì bạn sẽ muốn thao tác cuộn thực sự diễn ra theo hướng ngược lại.
Và cách để đạt được điều đó là lật hướng của ngón tay giả.
Vì vậy, giờ đây, thao tác di chuyển hai ngón tay lên trên bàn di chuột sẽ được chuyển thành thao tác di chuyển một ngón tay giả xuống dưới trên màn hình cảm ứng, từ đó khiến màn hình cuộn lên, ngược lại với những gì chúng ta đã có trước đây.
Việc điều chỉnh tỷ lệ cũng hoạt động theo cách tương tự.
Khi bạn dùng hai ngón tay trên bàn di chuột để điều chỉnh tỷ lệ, Android sẽ tạo ra hai ngón tay giả trên màn hình cảm ứng, sau đó di chuyển lại gần nhau hoặc xa nhau hơn (tuỳ thuộc vào cử chỉ) để mô phỏng hành vi phóng to hoặc thu nhỏ mong muốn.
Vì vậy, kể từ Compose 1.12 cho thao tác kéo và Compose 1.13 cho thao tác điều chỉnh tỷ lệ, chúng tôi hiện chặn các ngón tay giả đó.
Chúng tôi không gửi các sự kiện này theo cách nền tảng cung cấp mà diễn giải lại thành các loại sự kiện con trỏ mới: PanStart, PanMove và PanEnd để kéo và di chuyển, còn ScaleStart, ScaleChange và ScaleEnd để điều chỉnh tỷ lệ.
Chúng ta thực hiện việc này trong giai đoạn chuyển đổi sự kiện từ Android sang Compose mà tôi đã đề cập trước đó.
Vì vậy, chúng tôi chuyển đổi các sự kiện Ngón tay giả thành các loại sự kiện thích hợp, sau đó gửi các cử chỉ do nền tảng xác định này đến Đường dẫn nhấn tương ứng với con trỏ.
Hãy cùng tìm hiểu lý do điều này quan trọng.
Tại sao chúng ta lại làm việc này?
Giả sử hộp này là một hình ảnh đẹp mà chúng ta muốn phóng to.
Khi bạn dùng hai ngón tay trên bàn di chuột để điều chỉnh tỷ lệ, có thể một trong hai ngón tay giả sẽ bỏ lỡ phần tử.
Đường dẫn nhấn của nó sẽ không giao nhau với hình ảnh và sẽ bắt đầu bên ngoài giới hạn.
Vì vậy, thay vì điều chỉnh tỷ lệ, hình ảnh có thể sẽ cuộn.
Sau đó, nếu có một phần tử có thể cuộn khác bên ngoài hình ảnh, thì có thể phần tử đó cũng sẽ bắt đầu cuộn cùng nhau.
Đây không phải là điều người dùng muốn.
Họ muốn mở rộng quy mô.
Vì vậy, thay vì gửi thông qua các ngón tay giả đó, giờ đây chúng ta sẽ gửi cử chỉ điều chỉnh tỷ lệ chỉ đến phần tử bên dưới vị trí con trỏ, từ đó dẫn đến hành vi chính xác.
Bất kể con trỏ ở đâu hoặc gần mép của phần tử mà bạn muốn điều chỉnh tỷ lệ đến mức nào, thì giờ đây, con trỏ sẽ điều chỉnh tỷ lệ một cách chính xác.
Vì vậy, tin vui cho tất cả các bạn là những sự kiện đầu vào được diễn giải lại này được dùng theo mặc định trong các thành phần như có thể cuộn và có thể biến đổi để mang lại hành vi có độ chính xác cao hơn cho dữ liệu đầu vào của bàn di chuột nếu bạn đang sử dụng các khối bản dựng cơ bản đó.
Nếu đang sử dụng bất kỳ thành phần nào được xây dựng dựa trên các thành phần đó, bạn chỉ cần cập nhật phiên bản Compose lên phiên bản mới nhất để có được hành vi cải tiến đó.
Và nếu bạn đang viết các đối tượng sửa đổi dữ liệu đầu vào con trỏ tuỳ chỉnh, tôi khuyên bạn nên xem các API mới này và đảm bảo rằng bạn đang hỗ trợ chúng để mang đến cho người dùng trải nghiệm tốt nhất có thể trên tất cả các thiết bị đầu vào khác nhau này.
Và tất nhiên, chúng tôi cũng thêm các API kiểm thử để bạn có thể kiểm tra xem ứng dụng của mình có đang xử lý chính xác tất cả các loại dữ liệu đầu vào khác nhau này hay không.
Vì vậy, performTrackpadInput trông rất giống với các API kiểm thử hiện có cho chuột và thao tác chạm.
onNodeWithContentDescription("Photo")
.performTrackpadInput {
click(center)
pan(Offset(0f, 100f))
scale(0.9f)
}Ngoài ra, khi sử dụng tất cả các công cụ đó, bạn có thể xác thực rằng ứng dụng của mình đang hoạt động chính xác bất kể người dùng đang sử dụng thiết bị đầu vào nào trên thiết bị của bạn.
Vì vậy, hy vọng điều này sẽ giúp bạn hiểu rõ hơn về cách một sự kiện con trỏ đi từ ngón tay trên bàn di chuột đến ứng dụng của bạn.
Bây giờ, tôi sẽ chuyển sang Jeremy để nói về một cách rất khác mà bàn di chuột đang được sử dụng.
Cảm ơn bạn.
Được rồi.
Xin chào, tôi là Jeremy Walker.
Tôi làm việc rất nhiều về phương thức nhập và tôi nghĩ sẽ rất thú vị nếu tôi chia sẻ với bạn một số điều mới mà chúng tôi đang làm với phương thức nhập của Compose và tác động của những điều đó đối với bạn.
Và cụ thể là với loại dữ liệu đầu vào gián tiếp này, xuất hiện do một dữ liệu đầu vào mới dựa trên kiểu dáng mới: kính thông minh.
Vì vậy, khi nhập dữ liệu vào kính, bạn có thể thực hiện theo một số cách: bạn có thể nhìn vào một thứ gì đó, bạn có thể sử dụng giọng nói, bạn có thể sử dụng cử chỉ, nhưng tôi sẽ nói cụ thể về bàn di chuột.
Và những chiếc vây này hơi khác so với những chiếc vây mà Alex đã nói, ở chỗ chúng nằm ở bên đầu, thẳng đứng, rất rộng và cực kỳ ngắn.
Vì vậy, khi chúng tôi nghĩ về cách hoạt động của tính năng này trong Compose, nếu bạn tưởng tượng mình đang nhìn vào một nút nhỏ trước mặt, nếu bạn di chuyển con trỏ để cố gắng chạm vào nút đó, thì đó sẽ không phải là một trải nghiệm tuyệt vời, phải không?
Vậy chúng tôi sẽ hỗ trợ như thế nào?
Đây là giao diện người dùng của Kính.
Kính được thiết kế độc đáo để hiển thị nội dung một cách liền mạch trong trường nhìn của bạn.
Nhưng nó rất đơn giản, chúng tôi cố gắng giữ cho nó đơn giản.
Và nếu xem ví dụ ở đây, bạn có thể thấy đây là một danh sách.
Tập trung vào một phần tử nhất định ở giữa.
Chúng ta thực sự muốn làm gì?
Chúng ta muốn chọn, hoặc có thể chuyển đến mục trước hoặc mục tiếp theo.
Chúng ta không nhất thiết phải có vị trí trên màn hình.
Và phần này phức tạp hơn một chút, nhưng bạn có thể thấy về cơ bản có ba lựa chọn: bạn có thể gác máy, bạn có thể bật tiếng hoặc bạn có thể tắt video.
Nhưng thực ra, bạn muốn chọn mục đó hoặc chuyển sang một mục khác rồi chọn mục đó.
Vì vậy, chúng tôi cho rằng chúng ta không thực sự cần ánh xạ trực tiếp đến một vị trí trên màn hình, hoặc trong trường hợp này là mắt của bạn.
Vậy nên, đây là giao diện thực tế của tính năng này.
Ví dụ: bạn có thể thấy ở đây, tôi chỉ đang di chuyển giữa các phần tử và đây là trên giao diện người dùng của Kính, và điều này phù hợp với những gì chúng ta muốn.
Bạn có thể nhấn vào đó.
Giờ thì chúng ta đã xem xét Android để nghĩ xem có những gì có thể dùng để hỗ trợ việc này.
Và nếu các bạn từng, nếu các bạn, ồ, xin lỗi.
Đây là bước chuyển tiếp.
Để tôi quay lại.
Chúng ta hãy bắt đầu.
Được rồi.
Vì vậy, nếu đã từng sử dụng một trong những thiết bị này và liên kết thiết bị đó với một thiết bị Android, thì bạn biết rằng bạn có thể dễ dàng di chuyển các phần tử trên giao diện người dùng từ thiết bị này sang thiết bị khác.
Ví dụ: với điều khiển từ xa, bạn có thể thấy bạn có thể Nếu bạn tưởng tượng hoặc bạn đã liên kết điều khiển từ xa với Google TV và bạn có một lưới phim, bạn có thể nhấn các nút mũi tên để chuyển đến bộ phim bạn muốn và bạn có thể nhấn nút để chọn bộ phim đó, nút OK.
Nếu có trình đơn trò chơi và bạn có tay điều khiển này, bạn có thể sử dụng D-pad hoặc cần điều khiển để di chuyển đến một phần tử trên giao diện người dùng, giả sử như trong trình đơn, rồi chọn phần tử đó.
Tương tự với bàn phím.
Vì vậy, điều này phù hợp với những gì chúng ta muốn trong Compose.
Và một lần nữa, đây là những thứ, vì vậy bạn có thể hiểu được nó trông như thế nào.
Và nếu chúng ta quay lại xa hơn một chút, thì thực ra có loại bàn di chuột này được ghép nối với TV và nó thực sự sử dụng một thứ gọi là cảm ứng điều hướng.
Và ý tưởng ở đây là toạ độ x và y hoàn toàn không liên quan đến màn hình.
Dữ liệu này chỉ được dùng để phát hiện cử chỉ.
Vậy nên, nếu bạn vuốt sang phải, thì thao tác đó giống như nhấn vào mũi tên để chuyển sang trang tiếp theo.
Nếu vuốt sang trái, bạn sẽ di chuyển sang trái, lên và xuống.
Vì vậy, điều này mang lại cho chúng ta hầu hết những gì chúng ta muốn.
Bạn chỉ sử dụng x và y để theo dõi vận tốc nhằm tìm ra các thao tác kéo và vuốt, v.v. Vì vậy, điều đó cung cấp cho chúng ta hầu hết những gì chúng ta muốn để bắt đầu trên Compose.
Nó không chính xác vì nó được liên kết với TV, vì vậy chúng ta cần đảm bảo rằng chúng ta không đi theo lộ trình của TV, chúng ta không làm hỏng TV sau này, vì vậy chúng ta cần đảm bảo rằng chúng ta phân chia chúng, nhưng đó là điều chúng ta sẽ làm trong một chút nữa.
Nhưng đây là điểm xuất phát.
Để chúng ta có thể sử dụng loại thao tác chạm này.
Vậy thì được rồi.
Thiết kế API sẽ trông như thế nào?
Trong Compose, thao tác nhập phổ biến nhất là chạm, đúng không?
Vì vậy, chúng tôi có rất nhiều việc phải làm để giải quyết vấn đề đó.
Vì vậy, chúng tôi phải tìm hiểu xem điều đó có liên quan như thế nào.
Xin nhắc lại, đây là giao diện của PointerEvent.
Bạn có thể thấy ở đây có loại là loại di chuyển xuống trong phần di chuyển lên, sau đó bạn có PointerInputChange này chỉ là một danh sách. Bạn có thể coi đó là các ngón tay.
Nếu bạn có 3 ngón tay, bạn sẽ có 3 mục trong danh sách.
Vì vậy, chúng tôi muốn nó trông giống như thế này.
Đây không phải là mô-đun giao diện người dùng, mà là một mô-đun cấp thấp hơn nhiều so với mô-đun mà hầu hết các nhà phát triển sử dụng khi xử lý dữ liệu đầu vào.
Các lớp này sẽ nằm trên lớp nền tảng.
Và đó là nơi chứa tất cả trình phát hiện cử chỉ.
Vì là nhà phát triển, bạn không quan tâm đến tất cả những thông tin đầu vào nhỏ nhặt này.
Bạn chỉ muốn biết liệu có thao tác kéo, nhấp hay vuốt hay không, rồi thực hiện hành động dựa trên thao tác đó.
Vì vậy, chúng tôi có một loạt mã thực hiện việc đó cho bạn và mã này được hỗ trợ bởi các mã này.
Vì vậy, chúng tôi bắt đầu nghĩ rằng, được rồi, chúng ta phải làm sao cho mã hiện có trong nền tảng có thể dễ dàng hỗ trợ các loại mới này.
Vì vậy, chúng tôi đã tạo một loại mới trông khá giống.
Nó chỉ được gọi là gián tiếp.
Và ý nghĩa gián tiếp là không chạm trực tiếp vào màn hình.
Vì vậy, nó cho bạn biết rằng, này, điều này sắp xảy ra Đừng nghĩ về điều này từ quan điểm chạm.
Hãy nghĩ về việc chúng ta chỉ đang xử lý cử chỉ ở đây và điều hướng giữa các phần tử trên giao diện người dùng.
Loại giống nhau.
Nó có một danh sách các ngón tay.
Trong trường hợp này, đó là IndirectPointerInputChange.
Nhưng chúng tôi đã làm những việc đó và đạt được kết quả tuyệt vời.
Nhưng sau đó, chúng tôi còn tìm thấy một mục nữa có tên là Trục chuyển động có hướng chính.
Nghe có vẻ phức tạp nhưng bạn sẽ hiểu khi tôi giải thích, vì quá trình hình thành nên nó khá thú vị.
Vậy, làm thế nào để có được điều này?
Một lần nữa, bạn có bàn di chuột này ở bên cạnh đầu, nó rộng và ngắn.
Và đến thời điểm này, chúng tôi đã có ý tưởng về việc điều hướng bằng thao tác chạm.
Vì vậy, chúng ta có thể vuốt sang trái và vuốt sang phải.
Và cách này hoạt động rất tốt.
Nhưng nếu bạn nghĩ về cách bàn di chuột tồn tại hiện nay, đúng không?
Khi di chuyển sang trái hoặc phải, bạn sẽ di chuyển sang trái hoặc phải trên bàn di chuột bằng nội dung giao diện người dùng.
Nếu di chuyển lên hoặc xuống, bạn sẽ cuộn lên hoặc xuống trong danh sách.
Nếu bạn thử làm điều đó với kính, thì như thế này cũng không tệ lắm, đúng không?
Nhưng nếu bạn làm việc này, thì trải nghiệm sẽ không được tốt.
Vì vậy, chúng tôi nhận thấy thao tác tự nhiên nhất rõ ràng là nhấn, nhưng là vuốt dọc theo cạnh kính.
Vì vậy, chúng tôi đã thực hiện một loạt nghiên cứu với người dùng và họ thực sự mong đợi rằng khi bạn vuốt theo chiều ngang, bạn không thực sự di chuyển nội dung theo chiều ngang mà di chuyển nội dung theo chiều dọc.
Bạn có thể hình dung điều này nếu thử trong đầu, chẳng hạn như khi vuốt ở đây, nếu có một danh sách trước mặt, như tiến lên, bạn có thể tiến lên trong danh sách hoặc có thể quay lại trong danh sách.
Vì vậy, chúng ta đang lấy một đầu vào hai chiều và chúng ta đang cố gắng nghĩ về nó theo cách một chiều.
Và đó là lý do chúng tôi đưa ra ý tưởng về Trục chuyển động chính, tức là bạn có thể di chuyển theo chiều ngang và nó sẽ di chuyển theo chiều dọc.
Vì vậy, bạn có thể chỉ định trên chính thiết bị.
Bạn có thể nói rằng bàn di chuột này không giống như bàn di chuột thông thường.
Tôi muốn di chuyển Tôi muốn hướng chính là hướng ngang và đó là trục chính, trục duy nhất mà tôi muốn di chuyển theo.
Và đó là lý do tôi làm vậy.
Được rồi.
Vì vậy, chúng tôi cảm thấy khá hài lòng về điều đó.
Compose sẽ xử lý mọi thứ.
Và bạn có cách triển khai theo chiều dọc đẹp mắt này.
Bạn có thể cân nhắc điều này.
Vì vậy, cách chúng tôi liên kết là giờ đây, bạn có định nghĩa theo chiều ngang, chúng ta chỉ có tiến và lùi.
Chúng ta không có trên, dưới, trái, phải.
Chúng ta chỉ có tiến và lùi.
Sau đó, chúng ta có thể liên kết nếu bạn đã quen với tiêu điểm tiếp theo và trước đó.
Giờ thì chúng ta chỉ cần chuyển sang mục tiếp theo và mục trước đó.
Và cách này rất hiệu quả cho video dọc.
Và thậm chí còn đẹp hơn nữa.
Nó hoạt động với chiều ngang ngay từ đầu, phải không?
Vì bạn chỉ có một loạt các mục được lấy tiêu điểm.
Bạn chỉ cần chuyển đến trang tiếp theo hoặc trang trước, và thao tác này sẽ hoạt động với cả hai.
Vì vậy, đây là trường hợp mà chúng tôi đã xác định và tìm ra tất cả trong Compose, nhưng sẽ rất tuyệt nếu thiết bị có thể tự nói, vì thiết bị biết liệu nó có hình dạng kỳ lạ hay không và biết liệu nó chỉ muốn trục chính này hay không, nhưng không có cách nào để xác định điều đó.
Vì vậy, chúng tôi phải đưa khái niệm này trở lại nền tảng và nói rằng, được rồi, chúng tôi cần xác định khái niệm này trong nền tảng để có thể chuẩn hoá và các thiết bị này có thể tự chỉ định, sau đó Compose có thể hỗ trợ đầy đủ cho đến hết.
Vì vậy, có trường hợp chúng tôi làm việc trong Compose và tiến lên trong Compose rồi lùi lại, và mang theo nó.
Đó chính xác là những gì chúng tôi đã làm.
Chúng tôi đã tạo API mới này để bạn hoặc thiết bị có thể chỉ định điều đó.
InputDevice.ViewBehavior
.getPrimaryDirectionalMotionAxis()Và nếu bạn nhớ lại phần trước, khi tôi nói về thao tác nhập bằng cách chạm, tôi đã, hoặc, thao tác điều hướng bằng cách chạm, tôi đã nói về việc thao tác này chủ yếu dành cho TV.
Chúng tôi không muốn làm hỏng những cách triển khai đó.
Vì vậy, chúng tôi đã thêm một trường khác để chỉ định loại dữ liệu đầu vào mới này cho kính.
Được rồi.
Vậy rốt cuộc, điều này có ý nghĩa gì đối với bạn?
Đây là phiên bản cụ thể hơn của những gì bạn nhận được dưới dạng trình phát hiện cử chỉ.
// Glimmer Library
Box(
modifier =
Modifier.fillMaxSize()
.onIndirectPointerGesture(
enabled = true,
onSwipeForward = { /* onSwipeForward */ },
onSwipeBackward = { /* onSwipeBackward */ },
onClick = { /* onClick */ },
)
.focusTarget()
) {
// App()Nếu bạn đang sử dụng Foundation, thì tính năng này sẽ hoạt động ngay với trình phát hiện cử chỉ.
Chúng tôi đã làm việc này cho bạn nên bạn không cần phải làm gì cả.
Nhưng nếu muốn tạo một ứng dụng cho kính, bạn có thể sử dụng thư viện Glimmer của chúng tôi. Sau đó, chúng tôi có một trình phát hiện cử chỉ đặc biệt, chỉ cung cấp cho bạn, bạn biết đấy, vì vậy bạn không cần phải kết hợp chúng, bạn có thể chỉ sử dụng trình phát hiện này, nhận lượt nhấp, khung hình tiến và lùi.
Được rồi.
Vậy là chúng ta đã tìm hiểu về cách hoạt động của API trong nền tảng và trong Compose.
Còn việc kiểm thử thì sao?
Đây là giao diện của tính năng cảm ứng khi bạn muốn kiểm tra cảm ứng.
@Test
clickTest {
rule.onNodeWithTag(defaultTag)
.performTouchInput
{ click(center) }
}Bạn chỉ cần nói performTouchInput rồi ra hiệu.
Có rất nhiều cử chỉ khác nhau và chúng tôi xây dựng tất cả các sự kiện nhỏ để cử chỉ đó xảy ra.
Quá trình này diễn ra như thế nào?
Bạn có mô-đun Giao diện người dùng Compose, mô-đun Kiểm thử giao diện người dùng và chúng giao tiếp với nhau, ít nhất là về mặt đầu vào, bằng MotionEvents.
Ngoài ra, mô-đun Kiểm thử giao diện người dùng cũng sử dụng cơ chế xếp hàng, tất cả đều dựa trên MotionEvent.
Tại sao tôi lại nói với bạn điều này?
Vì bạn không thể tạo MotionEvent theo phương thức lập trình giống như một sự kiện đầu vào gián tiếp, điều này rất tốt cho tôi vì điều đó có nghĩa là tôi phải viết lại tất cả hoạt động giao tiếp giữa các mô-đun và hệ thống xếp hàng.
Nhưng tôi đã làm được.
Vậy điều này có ý nghĩa gì đối với bạn?
Nếu bạn đã quen với việc kiểm thử nhập bằng cách chạm, thì đây là những gì bạn nhận được khi nhập gián tiếp.
Bạn chỉ cần thêm từ "gián tiếp" vào tên.
@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput (
IndirectPointerEventPrimaryDirectionalMotionAxis.X,
inputDeviceSize,
)
{ click(center) }
}Bây giờ, tôi đã chuyển một số tham số bổ sung.
Có thể bạn đã nhận ra thuộc tính đầu tiên, đó là nơi bạn chỉ định chiều ngang.
Và sau đó, kích thước đầu vào rõ ràng sẽ là kích thước của bàn di chuột.
Giờ thì điều này không thay đổi nhiều.
Vì vậy, nếu xem mã nguồn, bạn có thể thấy tôi vừa viết một hàm mở rộng và hàm này trông như thế này.
@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput
{ click(center) }
}Được rồi.
Như vậy, bạn có thể hình dung được tất cả những việc chúng tôi đã làm chỉ để hỗ trợ bàn di chuột trên gọng kính hoặc trên kính thông minh.
Nhưng chúng tôi đang thực hiện, tôi cũng đang thực hiện các bài tập này để theo dõi mắt và theo dõi cử động của tay, vì vậy còn nhiều điều sắp tới.
Tóm lại, tôi muốn bạn nhớ điều gì từ phần trình bày của tôi?
Vì vậy, cho dù bạn đang sử dụng bàn di chuột với điện thoại, kính hay bất kỳ thiết bị đầu vào nào khác, chúng tôi đều cân nhắc đến điều đó và cố gắng thực hiện trước.
Vì vậy, nếu bạn đang sử dụng các trình phát hiện cử chỉ Foundation này, thì tính năng này sẽ hoạt động ngay lập tức.
Đó là điều đầu tiên.
Thứ hai, Compose là bộ công cụ giao diện người dùng chính và chúng tôi sẽ tiếp tục khám phá cũng như hỗ trợ các loại phương thức nhập mới, cả trong nền tảng và trong bộ công cụ, như bạn có thể thấy những gì tôi đã làm ở đây.
Và sau đó, rõ ràng là để thử nghiệm API, tôi muốn duy trì một giao diện và mô hình quen thuộc để bạn có thể dễ dàng di chuyển và thử nghiệm các loại mới này.
Cảm ơn bạn. Tôi hy vọng thông tin này hữu ích cho bạn.
onNodeWithContentDescription("Photo")
.performTrackpadInput {
click(center)
pan(Offset(0f, 100f))
scale(0.9f)
}InputDevice.ViewBehavior
.getPrimaryDirectionalMotionAxis()// Glimmer Library
Box(
modifier =
Modifier.fillMaxSize()
.onIndirectPointerGesture(
enabled = true,
onSwipeForward = { /* onSwipeForward */ },
onSwipeBackward = { /* onSwipeBackward */ },
onClick = { /* onClick */ },
)
.focusTarget()
) {
// App()@Test
clickTest {
rule.onNodeWithTag(defaultTag)
.performTouchInput
{ click(center) }
}@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput (
IndirectPointerEventPrimaryDirectionalMotionAxis.X,
inputDeviceSize,
)
{ click(center) }
}@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput
{ click(center) }
}