Hầu hết các ứng dụng mà chúng ta thường xuyên sử dụng đều có cách thông báo tin nhắn mới một cách trực quantai ban ca, thường là dưới dạng số hoặc biểu tượng chấm đỏ. Ví dụ như trên ứng dụng WeChat, khi bạn bè gửi tin nhắn mới cho bạn, ở góc trên của cuộc trò chuyện sẽ xuất hiện một con số để báo hiệu số lượng tin chưa đọc. Hay khi có người đăng bài mới trên trang Moments (bạn bè), biểu tượng vào phần Moments sẽ hiện lên một dấu chấm đỏ để thu hút sự chú ý. Đặc biệt hơn, nếu có ai đó thích bài viết của bạn hoặc bình luận dưới nội dung bạn đã đăng tải, thay vì chỉ đơn giản là một dấu chấm đỏ, Moments sẽ hiển thị một con số cụ thể để bạn dễ dàng nhận biết số lượng tương tác mà bài viết của mình nhận được. Điều này không chỉ giúp người dùng nhanh chóng nắm bắt thông tin mà còn tạo ra một trải nghiệm thú vị và tiện lợi trong quá trình sử dụng ứng dụng. Những chi tiết nhỏ này đôi khi lại đóng vai trò quan trọng trong việc giữ chân người dùng và khuyến khích họ tương tác nhiều hơn với nền tảng.
Tuy nhiênsv 88, khi thử nghiệm một số sản phẩm ứng dụng (App) mới, chúng ta thường gặp phải nhiều vấn đề khác nhau liên quan đến việc hiển thị số và dấu chấm đỏ. Chẳng hạn, có những dấu chấm đỏ dù đã nhấn đi nhấn lại vẫn không thể xóa được; hoặc, khi phát hiện ra một con số xuất hiện nhưng khi vào bên trong thì chẳng có gì cả; thậm chí đôi khi con số hiển thị bên ngoài và bên trong lại hoàn toàn không khớp với nhau. Những điều này khiến trải nghiệm của người dùng trở nên phiền phức và khó chịu.
Vậy những vấn đề này thực chất xuất phát từ đâu?
Tôi cho rằng nguyên nhân gốc rễ của vấn đề nằm ở chỗ: chưa có một cách tiếp cận thống nhất để khái quát và quản lý logic hiển thị giữa các con số và chấm đỏ. Kết quả làtai ban ca, mối quan hệ giữa các con số và chấm đỏ trở nên phức tạp, một thay đổi nhỏ cũng có thể ảnh hưởng đến toàn bộ hệ thống. Điều này dẫn đến việc trong quá trình duy trì ứng dụng, chỉ cần thực hiện một thay đổi nhỏ (ví dụ như thêm một số lượng hoặc loại chấm đỏ mới), nguy cơ gặp lỗi sẽ rất cao. Thậm chí, mỗi khi có sự điều chỉnh nhỏ, các lập trình viên phải đối mặt với hàng loạt các vấn đề phát sinh từ sự thiếu nhất quán trong thiết kế ban đầu, khiến công việc trở nên mệt mỏi và tốn thời gian hơn nhiều so với dự kiến. Điều này không chỉ làm chậm tiến độ mà còn tăng nguy cơ tạo ra các lỗ hổng bảo mật hoặc trải nghiệm người dùng kém chất lượng.
Bài viết này sẽ giới thiệu một mô hình cấu trúc cây để quản lý đồng nhất cấp bậc của các số và chấm đỏtai ban ca, đồng thời ở phần cuối bài, chúng tôi sẽ trình bày một ứng dụng demo chạy được trên nền tảng Android để bạn tham khảo. Ngoài ra, chúng tôi cũng sẽ thảo luận thêm về cách triển khai và tối ưu hóa hiệu suất của mô hình này trong thực tế.
Nếu bạn hiện đang có một chiếc điện thoại Android trong taytai ban ca, bạn có thể quét mã QR bên dưới (hoặc nhấp vào liên kết tải xuống dưới mã QR) để tải về và cài đặt phiên bản demo này. Chỉ cần dành ra vài phút để xem liệu nó có thực sự phù hợp với nhu cầu của bạn hay không.
Hoặc nhấp vào Liên kết tải xuống 。
Để thuận tiện cho việc thảo luậnsv 88, chúng ta hãy bắt đầu bằng cách sắp xếp nhanh các yêu cầu chung về việc hiển thị số và chấm đỏ, sau đó cùng tìm hiểu xem phương án thực hiện trực quan nhất có thể là gì dựa trên những yêu cầu này. Trước hết, điều quan trọng là phải xác định rõ vai trò của các con số và chấm đỏ trong giao diện. Chúng thường được sử dụng để thu hút sự chú ý hoặc nhấn mạnh một thông tin cụ thể nào đó. Chẳng hạn, một chấm đỏ có thể tượng trưng cho trạng thái "khẩn cấp" hoặc mức độ ưu tiên cao, trong khi các con số lại giúp người dùng dễ dàng theo dõi hoặc đánh giá tình hình một cách chính xác hơn. Tiếp theo, chúng ta cần suy nghĩ về cách bố trí và thiết kế sao cho người dùng có thể nhận ra ngay lập tức những yếu tố quan trọng này. Một gợi ý đơn giản nhưng hiệu quả là đặt chúng ở vị trí dễ nhìn thấy nhất trên màn hình, chẳng hạn như góc trên bên trái hoặc giữa trung tâm. Điều này sẽ đảm bảo rằng người dùng không bỏ lỡ bất kỳ thông tin quan trọng nào. Cuối cùng, để tăng tính tương tác, có thể bổ sung thêm hiệu ứng động nhẹ khi người dùng di chuột qua hoặc nhấn vào các biểu tượng này. Điều này không chỉ làm cho giao diện trở nên sống động mà còn giúp cải thiện trải nghiệm người dùng tổng thể.
Bạn có thể nhận thấy rằng những điểm tóm tắt trên đây tương đối giống với logic hiển thị của hầu hết các ứng dụng. Ngay cả khi có một số khác biệt99win club, điều đó cũng không ảnh hưởng nhiều đến phần thảo luận tiếp theo của chúng ta. Những khác biệt nhỏ ấy thường chỉ là sự điều chỉnh để phù hợp với mục đích riêng của từng ứng dụng mà thôi.
Bình luận nhận được
Khi chúng ta tập trung vào việc phân tích logic hiển thị chấm đỏ trên tab "Tin nhắn"tai ban ca, việc viết mã giả (pseudo-code) cho chức năng này sẽ không quá phức tạp. Dưới đây là một ví dụ: ``` Hàm capNhatChamDo(tongSoTinNhanMoi): Nếu tongSoTinNhanMoi > 0: Hiển thịChamDo() Nếu tongSoTinNhanMoi >= 99: HienThiText("99+") Else: HienThiText(tongSoTinNhanMoi) Else: AnChamDo() Hàm xuLyTinNhanMoi(): GiaiMaDuLieuTuServer() Cập nhật biến tongSoTinNhanMoi capNhatChamDo(tongSoTinNhanMoi) Khi moTabTinNhan: = "Tin Nhan": Neu tongSoTinNhanMoi > 0: PlayAmThanhCanhBao() XuLyTinNhanMoi() Else: KhongCanXuLyThem() ``` Trong đoạn mã giả trên, chúng ta thấy rằng logic chính xoay quanh việc kiểm tra số lượng tin nhắn mới và cập nhật giao diện tương ứng. Chấm đỏ sẽ xuất hiện khi có tin nhắn chưa đọc và biến đổi dựa trên số lượng cụ thể.
1
2
3
4
5
6
7
8
9
10
int
count
=
Số bình luận +
Số lượt thích;
if
(
count
>
0
)
{
Hiển thị số lượng count
}
else
if
(
Có tin nhắn hệ thống)
{
Hiển thị điểm đỏ
}
else
{
Ẩn số và điểm đỏ
}
Mã nguồn này chắc chắn có thể đáp ứng yêu cầusv 88, nhưng nhược điểm cũng khá rõ ràng. Điểm quan trọng nhất là nó yêu cầu logic hiển thị trong tab "Tin nhắn" phải liệt kê toàn bộ các loại tin nhắn con bên dưới (như bình luận, thích, thông báo hệ thống), đồng thời xác định từng loại nên sử dụng số hay chấm đỏ để biểu thị. Phần trên chỉ đề cập đến trường hợp hai cấp của giao diện, nhưng nếu xuất hiện ba cấp hoặc nhiều cấp hơn thì sao? Khi đó, những thông tin này sẽ phải lặp đi lặp lại ở từng cấp của giao diện, dẫn đến việc code trở nên dài dòng và khó bảo trì.
Điều này sẽ khiến việc bảo trì và chỉnh sửa trở nên phức tạp hơn bao giờ hết. Hãy tưởng tượng99win club, nếu bạn thêm một loại tin nhắn mới dưới mục "tin nhắn", hoặc một loại tin nhắn nào đó chuyển từ cách hiển thị bằng số thành biểu tượng chấm đỏ, thậm chí là di chuyển một loại tin nhắn từ ngăn chứa của một trang sang một trang khác. Tất cả những tình huống này đều yêu cầu mọi trang ở cấp cao hơn phải thực hiện thay đổi tương ứng. Khi một ứng dụng có số lượng loại tin nhắn ngày càng tăng, lên đến vài chục loại, có thể tưởng tượng rằng việc sửa đổi như vậy rất dễ dẫn đến sai sót. Thêm vào đó, khi số lượng tin nhắn tăng lên, việc quản lý logic liên quan cũng trở nên khó khăn hơn. Bạn không chỉ phải theo dõi cách hiển thị mà còn phải đảm bảo rằng các chức năng khác, chẳng hạn như thông báo đẩy hay cập nhật trạng thái, vẫn hoạt động ổn định. Điều này đặt ra áp lực lớn cho đội ngũ phát triển, đặc biệt là khi thời gian phát hành gần kề. Vì vậy, việc thiết kế hệ thống cần được tối ưu hóa ngay từ đầu để giảm thiểu sự phức tạp khi mở rộng tính năng. Một giải pháp hiệu quả có thể là sử dụng các mô hình thiết kế linh hoạt, chẳng hạn như Pattern Matching hoặc Observer Pattern, giúp ứng dụng có khả năng thích nghi tốt hơn với sự thay đổi mà không cần phải chỉnh sửa quá nhiều phần khác nhau. Điều này không chỉ giúp giảm thiểu rủi ro lỗi mà còn cải thiện chất lượng tổng thể của sản phẩm.
Những vấn đề trênsv 88, chúng tôi đã xử lý ở MicroLove Trong giai đoạn đầu phát triển ứng dụng99win club, chúng tôi cũng đã gặp phải một số vấn đề. Sau đó, chúng tôi quyết định xem xét lại cấu trúc của các biểu tượng đỏ và số liệu hiển thị trong ứng dụng, và bắt đầu tiếp cận nó dưới dạng cấu trúc cây. Cách làm này đã giúp công tác bảo trì trở nên dễ dàng hơn rất nhiều. Đồng thời, việc áp dụng phương pháp mới cũng giúp đội ngũ kỹ sư có cái nhìn toàn diện hơn về hệ thống, từ đó tối ưu hóa hiệu suất và cải thiện trải nghiệm người dùng một cách đáng kể.
Một trang của ứng dụng vốn dĩ đã được phân cấptai ban ca, vì vậy đường dẫn truy cập trang về bản chất là một cấu trúc cây.
Như đã thể hiện trong hình trêntai ban ca, nút 1 đại diện cho trang cấp 1, trang này bao gồm ba cổng truy cập vào các trang cấp 2 sâu hơn, tương ứng với các nút 2, 3 và 4. Tiếp tục đi sâu hơn nữa sẽ đến các trang cuối cùng, được biểu thị bằng các nút hình vuông màu xanh lá cây. Mỗi bước chuyển tiếp không chỉ mở ra thêm nhiều lựa chọn mà còn giúp người dùng dễ dàng định hướng trong cấu trúc hệ thống ngày càng phức tạp này.
Mô hình cây này có thể được diễn đạt như sau:
Để biểu diễn Badge Number của một nhóm lớn bằng cách sử dụng một khoảng giá trị loại99win club, khi gán giá trị cho loại, chúng ta có thể áp dụng phương pháp tương tự như sau: sử dụng một số nguyên (int) để biểu thị loại Badge Number, trong đó 16 bit cao sẽ được dùng để chỉ định nhóm lớn. Ví dụ, nếu nhóm lớn "Tin nhắn" có giá trị 16 bit cao là 0x2, thì ba loại Badge Number (type) thuộc nhóm này có thể được phân bổ như sau:
Bằng cách nàysv 88, nhóm "tin nhắn" có thể được biểu diễn bằng một khoảng loại dữ liệu [(0x2 << 16) + 0x1, (0x2 << 16) + 0x3]. Khoảng giá trị này cho phép phân loại các loại tin nhắn một cách rõ ràng và hiệu quả trong hệ thống. Với cách thiết lập này, việc quản lý và phân tích các loại dữ liệu sẽ trở nên dễ dàng hơn bao giờ hết, đồng thời giúp tránh được xung đột giữa các loại thông tin khác nhau.
Sau khi có khoảng cách kiểu dữ liệusv 88, chúng ta hãy cùng xem lại các nút trung gian trong mô hình cây. Tất cả chúng đều có thể được biểu diễn bằng một hoặc nhiều khoảng cách kiểu dữ liệu. Cách chúng hiển thị logic (hiển thị dưới dạng số, biểu tượng chấm đỏ hay ẩn đi), sẽ phụ thuộc vào việc tính tổng tất cả các khoảng cách kiểu dữ liệu của cây con tương ứng. Quy trình tính tổng cụ thể như sau:
Việc triển khai mô hình cây có thể được gọi là Bài viết này giới thiệu một phiên bản demo dành cho Android99win club, mã nguồn có thể được tải xuống từ kho lưu trữ GitHub: https://github.com/tielei/BadgeNumberTree 。
Dưới đây chúng ta sẽ phân tích phần quan trọng.
Trong phiên bản Android99win club, lớp chính được sử dụng để triển khai là Dưới đây là đoạn mã quan trọng của nó (một số đoạn code không liên quan đến logic chính đã bị lược bỏ để dễ hiểu hơn. Nếu bạn muốn xem toàn bộ mã nguồn, vui lòng truy cập GitHub để tải về).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/**
public
interface
AsyncResult
<
ResultType
>
{
void
returnResult
(
ResultType
result
);
}
/**
public
class
BadgeNumberTreeManager
{
/** * Cài đặt số badge * @param badgeNumber Số badge cần được thiết lập * @param asyncResult Kết quả trả về theo phương thức bất đồng bộ99win club, sẽ trả về một giá trị Boolean cho biết liệu việc cài đặt có thành công hay không. */
public
void
setBadgeNumber
(
final
BadgeNumber
badgeNumber
,
final
AsyncResult
<
Boolean
>
asyncResult
)
{
...
}
/** * Tính tổng số badge * @param badgeNumber Số badge cần được tính toán * @param asyncResult Kết quả trả về từ hoạt động bất đồng bộsv 88, sẽ trả về một giá trị Boolean cho biết liệu thao tác cộng dồn có thành công hay không. */
public
void
addBadgeNumber
(
final
BadgeNumber
badgeNumber
,
final
AsyncResult
<
Boolean
>
asyncResult
)
{
...
}
/** * Xóa badge number theo loại được chỉ định * @param type Loại badge number mà bạn muốn xóa. * @param asyncResult Kết quả trả về từ hoạt động bất đồng bộ. Kết quả này sẽ là một giá trị Boolean99win club, cho biết việc xóa có thành công hay không. */
public
void
clearBadgeNumber
(
final
int
type
,
final
AsyncResult
<
Boolean
>
asyncResult
)
{
...
}
/** * Lấy số hiệu huy hiệu theo loại được chỉ định * @param type Loại. Khi muốn lấy số hiệu huy hiệu từ cuộc trò chuyện99win club, chỉ cần truyền giá trị 0. * @param asyncResult Kết quả trả về bất đồng bộ, sẽ trả về số lượng của số hiệu huy hiệu theo loại được chỉ định. */
public
void
getBadgeNumber
(
final
int
type
,
final
AsyncResult
<
Integer
>
asyncResult
)
{
...
}
/** * Dựa trên danh sách khoảng các loại badge number để tính toán tổng số badge number của nút cha trong cấu trúc cây. * Ưu tiên tính toán các con số trướctai ban ca, tiếp đến là biểu tượng chấm đỏ. * Một danh sách khoảng các loại badge number trong thực tế tương ứng với một nút cha trong cấu trúc cây. * @param typeIntervalList Danh sách khoảng các loại badge number được chỉ định, chắc chắn chứa ít nhất một khoảng. * @param asyncResult Kết quả trả về từ quy trình xử lý bất đồng bộ, sẽ cung cấp tình trạng badge number theo loại (bao gồm cách hiển thị và tổng số). */ Mỗi phần tử trong danh sách khoảng các loại badge number sẽ đại diện cho một phạm vi cụ thể mà chúng ta cần tính toán. Trong khi đó, kết quả bất đồng bộ sẽ giúp chúng ta xác định chi tiết từng loại badge number, từ đó có thể dễ dàng tổng hợp lại giá trị cuối cùng cho toàn bộ nút cha trong cấu trúc cây.
public
void
getTotalBadgeNumberOnParent
(
final
List
<
BadgeNumberTypeInterval
>
typeIntervalList
,
final
AsyncResult
<
BadgeNumberCountResult
>
asyncResult
)
{
// Đầu tiên tính toán loại badge number để hiển thị số
getTotalBadgeNumberOnParent
(
typeIntervalList
,
BadgeNumber
.
DISPLAY_MODE_ON_PARENT_NUMBER
,
new
AsyncResult
<
BadgeNumberCountResult
>()
{
@Override
public
void
returnResult
(
BadgeNumberCountResult
result
)
{
if
(
result
.
getTotalCount
()
>
0
)
{
// Tổng số loại số lớn hơn 099win club, có thể trả về rồi.
if
(
asyncResult
!=
null
)
{
asyncResult
.
returnResult
(
result
);
}
}
else
{
// Tổng số loại số không lớn hơn 0sv 88, tiếp tục tính toán loại điểm đỏ
getTotalBadgeNumberOnParent
(
typeIntervalList
,
BadgeNumber
.
DISPLAY_MODE_ON_PARENT_DOT
,
new
AsyncResult
<
BadgeNumberCountResult
>()
{
@Override
public
void
returnResult
(
BadgeNumberCountResult
result
)
{
if
(
asyncResult
!=
null
)
{
asyncResult
.
returnResult
(
result
);
}
}
});
}
}
});
}
private
void
getTotalBadgeNumberOnParent
(
final
List
<
BadgeNumberTypeInterval
>
typeIntervalList
,
final
int
displayMode
,
final
AsyncResult
<
BadgeNumberCountResult
>
asyncResult
)
{
final
List
<
Integer
>
countsList
=
new
ArrayList
<
Integer
>(
typeIntervalList
.
size
());
for
(
BadgeNumberTypeInterval
typeInterval
:
typeIntervalList
)
{
getBadgeNumber
(
typeInterval
.
getTypeMin
(),
typeInterval
.
getTypeMax
(),
displayMode
,
new
AsyncResult
<
Integer
>()
{
@Override
public
void
returnResult
(
Integer
result
)
{
countsList
.
add
(
result
);
if
(
countsList
.
size
()
==
typeIntervalList
.
size
())
{
// Count của tất cả các loại đã có
int
totalCount
=
0
;
for
(
Integer
count
:
countsList
)
{
if
(
count
!=
null
)
{
totalCount
+=
count
;
}
}
// Trả về tổng
if
(
asyncResult
!=
null
)
{
BadgeNumberCountResult
badgeNumberCountResult
=
new
BadgeNumberCountResult
();
badgeNumberCountResult
.
setDisplayMode
(
displayMode
);
badgeNumberCountResult
.
setTotalCount
(
totalCount
);
asyncResult
.
returnResult
(
badgeNumberCountResult
);
}
}
}
});
}
}
private
void
getBadgeNumber
(
final
int
typeMin
,
final
int
typeMax
,
final
int
displayMode
,
final
AsyncResult
<
Integer
>
asyncResult
)
{
...
}
/**
public
static
class
BadgeNumberTypeInterval
{
private
int
typeMin
;
private
int
typeMax
;
public
int
getTypeMin
()
{
return
typeMin
;
}
public
void
setTypeMin
(
int
typeMin
)
{
this
.
typeMin
=
typeMin
;
}
public
int
getTypeMax
()
{
return
typeMax
;
}
public
void
setTypeMax
(
int
typeMax
)
{
this
.
typeMax
=
typeMax
;
}
}
/** * Số hiệu huy hiệu được đếm dựa trên một khoảng loại hình cụ thể và cho ra kết quả tương ứng. */
public
static
class
BadgeNumberCountResult
{
private
int
displayMode
;
private
int
totalCount
;
public
int
getDisplayMode
()
{
return
displayMode
;
}
public
void
setDisplayMode
(
int
displayMode
)
{
this
.
displayMode
=
displayMode
;
}
public
int
getTotalCount
()
{
return
totalCount
;
}
public
void
setTotalCount
(
int
totalCount
)
{
this
.
totalCount
=
totalCount
;
}
}
}
Điểm cần chú ý trong đoạn mã này bao gồm:
Ví dụ mã để gọi getTotalBadgeNumberOnParent:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
BadgeNumberTypeInterval
typeInterval
=
new
BadgeNumberTypeInterval
();
typeInterval
.
setTypeMin
(
BadgeNumber
.
CATEGORY_NEWS_MIN
);
typeInterval
.
setTypeMax
(
BadgeNumber
.
CATEGORY_NEWS_MAX
);
List
<
BadgeNumberTypeInterval
>
typeIntervalList
=
new
ArrayList
<
BadgeNumberTypeInterval
>(
1
);
typeIntervalList
.
add
(
typeInterval
);
BadgeNumberTreeManager
.
getInstance
().
getTotalBadgeNumberOnParent
(
typeIntervalList
,
new
AsyncResult
<
BadgeNumberCountResult
>()
{
@Override
public
void
returnResult
(
BadgeNumberCountResult
result
)
{
if
(
result
.
getDisplayMode
()
==
BadgeNumber
.
DISPLAY_MODE_ON_PARENT_NUMBER
&&
result
.
getTotalCount
()
>
0
)
{
// Hiển thị số
showTabBadgeCount
(
tabIndex
,
result
.
getTotalCount
());
}
else
if
(
result
.
getDisplayMode
()
==
BadgeNumber
.
DISPLAY_MODE_ON_PARENT_DOT
&&
result
.
getTotalCount
()
>
0
)
{
// Hiển thị điểm đỏ
showTabBadgeDot
(
tabIndex
);
}
else
{
// Ẩn số và điểm đỏ
hideTabBadgeNumber
(
tabIndex
);
}
}
});