FromSunNews / DongNaiTravelApp

Application about introducing tourist places or hanging out in Dong Nai Province (Viet Nam)
4 stars 3 forks source link

Change: New GroupBottomTab #17

Open NguyenAnhTuan1912 opened 1 year ago

NguyenAnhTuan1912 commented 1 year ago

Change

Original version by Phuong, change by Tuan

Lời mở đầu

Trước khi đến phần giải thích thì nói sơ qua một số thứ

  1. Bài thảo luận này được viết với mục đích là giải thích những thay đổi quan trọng trong project, bởi vì đây là component chung.
  2. Đây chỉ là những thay đổi mang tính tham khảo, bàn luận không chính thức.
  3. Component này là phiên bản trước của component trong master.
  4. Có vấn đề gì sai sót thì hãy comment phía dưới.

Component này dùng để làm gì?

Theo như React Navigation, tụi mình có 3 option để navigate screen:

  1. Stack (Sub Screen sẽ được pop vào Stack và hiển thị đè lên Root Screen hoặc các Sub Screen khác).
  2. Tab (Cái này thì dùng cho việc chuyển màn hình thay phiên, dùng cho các màn hình chính).
  3. Drawer (Cái này thì nó là một cái ngăn kéo, cho người dùng chọn màn hình muốn chuyển đến).

Trong bài này thì chỉ nói chủ yếu về Tab. Sự khác biệt của cả ba là ở behaviour và appearance.

Giống với routing ở Browser, thì mỗi screen khi được nhấn thì nó sẽ được render ra màn hình mà sẽ không có sự xuất hiện của screen khác dưới bất kì hình thức nào. Khi screen mới được navigate, thì các screen khác sẽ được unmount ra khỏi hệ thống view.

Tab được tạo thông qua một function được cung cấp mới React Navigation. Đúng như tên gọi thì nó sẽ tập chung vào phần Bottom và hành vi của Tab.

const Tab = createBottomTabNavigator()

Vì cái tên thì có thể gây hiểu nhầm, function này trả về một component cho phép dev quản lý việc navigate các screen thông qua Navigation Bar nằm ở dưới. Như thế thì có thể hiểu, component này cho phép mình có thể custom BottomBar để làm Navigation Bar (Quan trọng).

Một số sửa đổi

  1. Đổi inline style thành external style.
  2. Tab.Screen sẽ không còn 2 props là tabBarIconlisteners nữa, thay vào vào đó là BottomBar (Component).
  3. Tính toán lại khoảng cách di chuyển của index dot.
  4. Thêm một object chứa các thông tin để build Tab Button.

Từ Inline thành External

Làm gọn lại Component, file JSX chứa component đó. Code cũ image

Code mới image

Thay đổi Tab.Screen

Bời vì cần phài có một custom BottomBar mới cho nên phải thay đổi. Code cũ: image

Code mới: image

Thêm custom BottomBar

Mục đích: tạo ra một bottom bar, có thể tương tác và chuyển screen được. Vì component này khá là nhiều, cho nên t sẽ phân tách nó ra như sau:

Ok, đó là ý tưởng, giờ thì đi đến từng phần: GroupBottomTab gồm một Container bọc bên ngoài một dot index container và Tab buttons container. Trong đó, dùng state.route.map để lấy thông tin các route như đã nói ở trên, tổng hợp lại để tạo ra Tab button. image

Bên trong {state.route.map((route, index) => {})}

const { options } = descriptors[route.key];
const isFocused = state.index === index;

Object options sẽ được lấy từ trong descriptors theo route.key (các key này đã được tạo từ trrước, nó giống như id để mình lấy thông tin thôi). Check xem button này có focus hay không khá đơn giản, chỉ cần check index của nó với state.index. Nếu như state.index đang là 1, thì có nghĩa là người đùng đang ấn vào nút thứ 2 (bắt đầu từ 0), cho khi render lại nó sẽ biết được là button thứ 2 đang focus.

Tổng hợp thông tin và tạo ra function xử lý sự kiện navigate.

const handlePressTabButton = () => {
    const event = navigation.emit({
        type: 'tabPress',
        target: route.key,
        canPreventDefault: true,
    });

    if (!isFocused && !event.defaultPrevented) {
    // The `merge: true` option makes sure that the params inside the tab screen are preserved
        navigation.navigate({ name: route.name, merge: true });
    }

    Animated.spring(tabOffsetValue, {
        toValue: dotMoveDistance * index + centerDotDistance,
        useNativeDriver: true
    }).start()
};

Trước tiên nó sẽ tổng hợp thông tin của event thông qua biến event được khai bảo ở bước đầu tiên. Với những nút chưa focus thì mới có thể navigate với route mà nó đại diện thôi đúng không, cho nên ở đây chúng ta sẽ check thêm là nếu như nút này đang focus và defaultPrevented hay chưa? Nếu chả 2 đều chưa thì chúng ta mới có thể navigate. Lúc này khi xử lý navigate, default behaviour mới được prevent. Cuối cùng là sau khi chuyển qua screen mới thì dot index sẽ di chuyển nhờ hàm Animate.spring(). Hàm này nhận về vị trí cũ của dot và vị trí mới của dot, sau đó bắt đầu quá trình transform.

Phần còn lại là tổng hợp các thông tin và tạo component thôi, nên cũng không có gì khó image

Tại đây, tabOffsetValue sẽ dùng để set value mới cho translateX(), vì thế dot index mới di chuyển. image

Ok chỉ có vậy thôi, có gì bàn thêm ở dưới.

NguyenAnhTuan1912 commented 1 year ago

Giải thích cách tính "bước di chuyển" cho Dot index. Trước khi vào phần này thì nói sơ qua một số thứ đi.

Chắc cái này ai cũng biết rồi nhưng vẫn nói lại, trong một view, thì sẽ có gốc toạ độ là từ góc trên cùng bên trái gọi là O (0; 0). Nên khi một view (box) nào đó di chuyển theo chiều dương, thì sẽ có xu hướng đi sang bên phải đối với trục Y và đi xuống dưới đối với trục X.

Và nó sẽ không di chuyển từ tâm của box mà sẽ đi từ đi trên cùng bên trái của box.

image

Ok, quay lại với bài của mình. Dot index có vai trò chỉ cho người dùng là Tab button nào đã được bấm (cho biết họ đang ở screen nào), thì dot index đó phải di chuyển qua lại giữa các button và phải nằm ở giữa button đó.

Trước hết thì xem ngữ cảnh của mình là như nào đã. Đầu tiên, tụi mình có một bottom bar với một chiều dài bất kì gọi là BottomBarWidth. BottomBar này có padding bên trong được gọi là BottomBarVPadding.

image

Tiếp theo, tụi mình sẽ có n icon, với mỗi icon được bọc trong một là Icon container, mỗi container này có độ rộng là IconContainerWidth. Các Icon container này được bọc trong button (width, height của button này bằng với _Icon container, cho nên tính width, height của icon container luôn), và container bọc bên ngoài các button này là Tab buttons container có độ rộng là TabButtonsContainerWidth.

image

Cuối cùng, cứ cách mỗi một Tab button thì có một khoảng trống ở giữa, nên với n số Tab button thì sẽ có n - 1 Khoảng trống, mỗi khoảng trống như thế có độ rộng là TabButtonSpaceWidth.

image

Ok phần mở đầu đã xong, bây giờ tới phần chính, tính bước di chuyển cho Dot index. Như lý thuyết đã nói ở trên thì khi di chuyển, box sẽ lấy góc trên cùng bên trái làm gốc và tương tự với hình tròn, nó cũng chọn điểm như vậy làm gốc.

image

Và bây giờ tụi mình cần Dot index di chuyển từ ở giữa Tab button này sang chính giữa của Tab button khác. Trước tiên, để Dot index nằm ngay giữa Tab button thì chúng ta cần phải lấy IconContainerWidth / 2 - DotWidth / 2. Nên chúng ta có

CenterDotDistance = IconContainerWidth / 2 - DotWidth / 2

image

Tiếp theo, để Dot index có thể đến được center của Tab button tiếp theo, thì chúng ta sẽ lấy TabButtonSpaceWidth + CenterDotDistance + (IconContainerWidth - CenterDotDistance). Lúc này, gốc của Dot index không nằm ở chính giữa Tab button, mà nó nằm cách center của Tab button một khoảng là DotWidth / 2 về bên trái, cho nên góc của Dot index cần phải chạm đến width của Tab button (hay là của Icon container), cho nên mình phải lấy chiều rộng của Tab button trừ đi khoảng cách để center Dot index là ra phần khoảng cách còn lại cần phải di chuyển.

image

Ok, khi đó phần còn lại là việc Dot index sẽ di chuyển một khoảng cách là TabButtonSpaceWidth để đi qua tới Tab button t iếp theo và tiếp tục di chuyển với khoảng cách là CenterDotDistance nữa là Dot index đã được center. Như vậy thì chúng ta sẽ có công thức như sau:

DotMoveDistance = (IconContainerWidth - CenterDotDistance) + TabButtonSpaceWidth + CenterDotDistance.

Mọi thứ bây giờ đã khá là dễ dàng hơn, khi mà Dot index muốn chuyển tới vị trí của Tab button nào thì chỉ cần nhân DotMoveDistance với index của Tab button đó. Ví dụ như Dot index đang ở Home (index = 0) và Dot index cần di chuyển tới Blogs (index = 3) thì chỉ việc lấy DotMoveDistance * 3 là được.

Tuy nhiên nhiêu đó vẫn chưa đủ, còn khoảng CenterDotDistance ban đầu của Tab button đầu tiên nữa (Bời vì Dot index bắt đầu từ 0 và kết thúc ở TabButtonsContainerWidth), cho nên trong quá trính tính toán vị trí cho Dot index, thì phải cộng thêm CenterDotDistance vào. Nên ta có

ToValue = DotMoveDistance * index + CenterDotDistance;

image