Hầu hết các ứng dụng mà chúng ta thường sử dụng hàng ngày đều có cách thông báo khác nhau khi nhận được tin nhắn mới. Ví dụcá cược bóng đá, trong ứng dụng WeChat, khi một người bạn gửi tin nhắn trò chuyện mới cho bạn, biểu tượng cuộc trò chuyện tương ứng sẽ hiển thị số lượng tin nhắn chưa đọc. Tương tự, nếu có người đăng bài mới trên trang (Moment), biểu tượng vào mục sẽ xuất hiện dấu chấm màu đỏ để 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 bài đăng của bạn, biểu tượng này sẽ không chỉ xuất hiện một dấu chấm đỏ mà còn kèm theo số lượng cụ thể để cho bạn biết chính xác có bao nhiêu lượt thích hoặc bình luận. Ngoài ra, một số ứng dụng khác cũng áp dụng cách thức tương tự để giúp người dùng dễ dàng nhận diện những thông báo quan trọng. Điều này không chỉ tạo điều kiện thuận lợi cho việc quản lý thông tin mà còn giúp người dùng không bỏ lỡ bất kỳ cập nhật nào từ bạn bè hay đồng nghiệp. Chúng ta có thể thấy rằng, dù là một tính năng đơn giản nhưng nó đóng vai trò vô cùng quan trọng trong việc cải thiện trải nghiệm người dùng và tăng cường sự kết nối giữa mọi người.
Tuy nhiênmua thẻ trực tuyến, khi thử nghiệm các sản phẩm ứng dụng (App) mới, chúng ta thường nhận thấy nhiều vấn đề liên quan đến việc hiển thị số và chấm đỏ. Chẳng hạn, có những trường hợp chấm đỏ cứ hiện mãi dù đã nhấn đi nhấn lại nhiều lần mà không thể xóa được; hoặc khi nhìn thấy số xuất hiện bên ngoài, nhưng khi nhấn vào thì chẳng có gì cả; thậm chí, đôi khi số lượng được hiển thị bên trong lại hoàn toàn khác với số lượng được thể hiện ở ngoài. Những điều này không chỉ gây phiền phức mà còn làm giảm trải nghiệm người dùng một cách đáng kể.
Vậy những vấn đề này thực chất được tạo ra như thế nào?
Tôi cho rằngmua thẻ trực tuyến, nguyên nhân cốt lõi 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ị số và chấm đỏ. Điều này dẫn đến việc mối quan hệ giữa các số và chấm đỏ trở nên phức tạp, mỗi thay đổi dù nhỏ đều có thể gây ra tác động dây chuyền. Kết quả là, trong quá trình bảo trì ứng dụng, chỉ cần có một sự điều chỉnh nhỏ (như thêm một vài loại số hoặc chấm đỏ), khả năng xảy ra lỗi sẽ tăng lên đáng kể.
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ý thống nhất thứ tự ưu tiên giữa số và chấm đỏmua thẻ trực tuyến, đồng thời ở phần cuối cùng, chúng tôi sẽ cung cấp một ứng dụng demo chạy được trên nền tảng Android để bạn tham khảo thêm.
Nếu bạn hiện đang có trong tay một chiếc điện thoại Androidbầu cua, bạn có thể quét mã QR bên dưới (hoặc nhấp vào đường dẫn tải xuống dưới mã QR) để tải về và cài đặt phiên bản thử nghiệm này. Chỉ cần dành ra vài phút để khám phá xem nó có 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ậnmua thẻ trực tuyến, chúng ta hãy bắt đầu bằng cách sắp xếp một cách đơn giản các yêu cầu về việc hiển thị số và chấm đỏ trong trường hợp chung. Sau đó, chúng ta sẽ xem xét cách thức thực hiện trực quan nhất có thể dựa trên những yêu cầu này. Việc hiểu rõ các yêu cầu cơ bản là rất quan trọng, bởi vì nó giúp chúng ta xác định chính xác cách nào để tạo ra một giao diện vừa dễ sử dụng vừa hiệu quả. Hãy tưởng tượng rằng bạn đang thiết kế một hệ thống quản lý thông tin mà ở đó, số và dấu chấm đỏ đóng vai trò như những tín hiệu cảnh báo hoặc chỉ báo quan trọng. Việc sắp xếp các thành phần này một cách logic sẽ đảm bảo rằng người dùng có thể nắm bắt mọi thông tin cần thiết một cách nhanh chóng và chính xác. Vì vậy, trước tiên, hãy cùng liệt kê và phân tích các yếu tố cốt lõi liên quan đến việc hiển thị số và chấm đỏ. Điều này không chỉ giúp chúng ta hiểu rõ hơn về nhu cầu thực tế mà còn tạo nền tảng cho việc phát triển giải pháp tối ưu nhất.
Bạn có thể thấy rằng những điểm tóm tắt trên đây khá tương đồng với logic hiển thị của hầu hết các ứng dụng. Dù có một số khác biệt nhỏmua thẻ trực tuyến, điều đó chắc chắn sẽ không làm ảnh hưởng đến phần thảo luận tiếp theo của chúng ta. Thêm vào đó, dù mỗi ứng dụng có cách trình bày riêng, nhìn chung chúng đều hướng tới mục tiêu tạo ra trải nghiệm người dùng tốt nhất. Điều này cho phép các nhà phát triển linh hoạt trong việc thiết kế giao diện và chức năng mà vẫn đảm bảo sự nhất quán trong cách thức hoạt động.
các lượt thích nhận được
Khi chúng ta tập trung vào việc xử lý logic hiển thị chấm đỏ trên tab "Tin nhắn"bầu cua, việc viết một đoạn mã giả (pseudo-code) tương tự như dưới đây không phải là điều quá khó: ```python # Hàm kiểm tra trạng thái tab tin nhắn def kiem_tra_cham_do(tong_so_tin_nhan_moi): neu tong_so_tin_nhan_moi > 0: hien_thi_cham_do = True nguoc_lai: hien_thi_cham_do = False return hien_thi_cham_do # Ví dụ sử dụng hàm so_tin_nhan_moi = lay_so_tin_nhan_moi() # Hàm lấy số lượng tin nhắn chưa đọc neu kiem_tra_cham_do(so_tin_nhan_moi): hien_thi_cham_do_trong_giao_dien() ``` Đoạn mã này đơn giản chỉ là một ví dụ cơ bản về cách xác định khi nào nên hiển thị chấm đỏ để thông báo có tin nhắn mới trong tab "Tin nhắn". Tùy thuộc vào yêu cầu cụ thể của dự án, chúng ta có thể thêm nhiều điều kiện khác hoặc cải thiện cách xử lý.
1
2
3
4
5
6
7
8
9
10
int
count
=
Số lượng bình luận +
Số lượt thích;
if
(
count
>
0
)
{
Hiển thị số đếm
}
else
if
(
Có tin nhắn hệ thống)
{
Hiển thị thông báo đỏ
}
else
{
Ẩn số và thông báo đỏ
}
Mã nguồn này tất nhiên có thể đáp ứng được yêu cầubầu cua, nhưng nhược điểm cũng khá rõ ràng. Điểm quan trọng nhất là nó đòi hỏi phải liệt kê toàn bộ loại tin nhắn con (như bình luận, thích, thông báo hệ thống) trong phần hiển thị của tab "Tin nhắn" và phải biết chính xác từng loại sẽ được biểu thị bằng số hay chấm đỏ. Những gì đã được trình bày ở trên chỉ áp dụng cho trường hợp hai cấp trang, còn nếu xuất hiện ba cấp hoặc nhiều cấp hơn thì sao? Lúc đó, toàn bộ thông tin này cần phải được lặp lại trên từng cấp trang, dẫn đến việc quản lý ngày càng phức tạp và dễ gây lỗi.
Điều này sẽ làm cho việc bảo trì và chỉnh sửa trở nên phức tạp hơn. Hãy tưởng tượngbầu cua, nếu bạn thêm một loại tin nhắn mới dưới phần "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ố sang chỉ xuất hiện biểu tượng chấm đỏ, thậm chí là một loại tin nhắn được di chuyển từ ngăn xếp trang này sang ngăn xếp trang khác. Tất cả những tình huống như vậy đều yêu cầu tất cả các trang ở mức cao hơn phải thực hiện thay đổi tương ứng. Khi một ứng dụng có ngày càng nhiều loại tin nhắn, lên đến vài chục loại, có thể hình dung rằng việc sửa đổi như vậy rất dễ xảy ra sai sót. Hơn nữa, mỗi lần cần điều chỉnh, đội ngũ phát triển không chỉ phải kiểm tra lại toàn bộ luồng dữ liệu mà còn phải đảm bảo tính nhất quán giữa các thành phần khác nhau trong hệ thống. Điều này không chỉ mất thời gian mà còn gia tăng rủi ro về mặt chất lượng sản phẩm. Vì vậy, việc xây dựng một hệ thống linh hoạt và dễ mở rộng ngay từ đầu là vô cùng quan trọng để giảm thiểu những vấn đề tiềm ẩn này.
Những vấn đề trêncá cược bóng đá, chúng tôi ở MicroLove Trong giai đoạn đầu phát triển ứng dụngbầu cua, chúng tôi cũng đã gặp phải một số khó khăn. Sau đó, chúng tôi quyết định xem xét lại cấu trúc hiển thị các biểu tượng đỏ và số liệu trong ứng dụng, thay vì tiếp cận theo cách thông thường, chúng tôi đã thử sử dụng cấu trúc cây (tree structure) để tổ chức mọi thứ. Điều này không chỉ giúp cải thiện khả năng quản lý mà còn làm cho công tác bảo trì trở nên dễ dàng hơn rất nhiều. Bằng cách sắp xếp logic và có hệ thống, toàn bộ quá trình phát triển đã đạt được hiệu quả tốt hơn đáng kể.
Một trang ứng dụng chính là phân cấpcá cược bóng đá, đường dẫn truy cập của trang web về bản chất là một cấu trúc cây.
Như đã thể hiện trong hình trênmua thẻ trực tuyến, nút 1 đại diện cho trang cấp 1, trang này bao gồm ba lối vào trang cấp 2 sâu hơn, tương ứng với các nút 2, 3 và 4. Khi đi sâu thêm một cấp nữa, chúng ta 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ô hình cây này có thể được trình bày 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ạibầu cua, khi chúng ta gán giá trị cho các loại, có thể áp dụng cách làm 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ó phần 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àycá cược bóng đá, nhóm "tin nhắn" có thể được biểu thị 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 chúng ta dễ dàng xác định và phân loại các loại tin nhắn khác nhau trong hệ thống. Với việc sử dụng toán tử dịch bit (<<), chúng ta có thể tạo ra các giá trị duy nhất để đại diện cho từng loại tin nhắn mà không sợ bị trùng lặp hoặc nhầm lẫn. Điều này làm tăng tính chính xác và hiệu quả trong việc quản lý thông tin trong hệ thống.
Khi đã có các khoảng loạicá cược bóng đá, hãy cùng xem lại các nút trung gian trong mô hình cây. Những nút này đều có thể được biểu diễn bằng một hoặc nhiều khoảng loại. Cách chúng hiển thị (số, chấm đỏ hay ẩn đi) sẽ phụ thuộc vào việc tính tổng tất cả các khoảng loại của cây con liên quan. Quy trình cụ thể để tính tổng như sau: Trước hết, hãy xác định tất cả các cây con trực thuộc từng nú Mỗi cây con sẽ có một tập hợp khoảng loại riêng biệt. Tiếp theo, tiến hành so sánh và cộng dồn các khoảng loại này theo quy tắc: nếu hai khoảng loại trùng nhau hoặc có sự chồng lấn, chúng sẽ được kết hợp thành một khoảng lớn hơn; còn nếu không có sự chồng lấn, chúng vẫn giữ nguyên trạng thái riêng biệt. Sau khi đã hoàn thành việc tổng hợp tất cả các khoảng loại từ các cây con, hãy kiểm tra lại tổng giá trị của nó. Nếu tổng này nằm trong khoảng nào, thì nút trung gian đó sẽ tuân theo logic hiển thị của khoảng đó. Ví dụ, nếu tổng nằm trong khoảng cho phép hiển thị số, thì nút sẽ hiện ra dưới dạng số; nếu nằm trong khoảng yêu cầu hiển thị chấm đỏ, thì sẽ xuất hiện một dấu chấm đỏ; còn nếu nằm ngoài mọi khoảng cho phép hiển thị, nút sẽ bị ẩn đi. Hãy tưởng tượng mỗi khoảng loại như một dải màu trên mộ Khi các cây con kết nối với nhau, các dải màu này sẽ chồng lên nhau hoặc tách rời. Việc tính toán tổng là quá trình giúp xác định chính xác dải màu cuối cùng mà nút trung gian cần áp dụng. Điều này đảm bảo rằng mọi quyết định hiển thị đều dựa trên sự hợp lý và thống nhất.
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 Androidbầu cua, mã nguồn có thể được tải xuống từ GitHub: https://github.com/tielei/BadgeNumberTree 。
Bây giờ chúng ta hãy phân tích phần quan trọng.
Lớp chính thực hiện cho phiên bản Android là BadgeNumberTreeManagermua thẻ trực tuyến, và mã quan trọng của nó như sau (để không làm gián đoạn việc hiểu logic chính, các đoạn mã không quan trọng đã bị lược bỏ, chưa được hiển thị ở đây. Nếu bạn muốn xem chi tiết, vui lòng tải mã nguồn từ GitHub).
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ố lượng badge * @param badgeNumber Số lượng badge cần thiết lập * @param asyncResult Kết quả trả về theo phương thức bất đồng bộbầu cua, sẽ nhận được một tham số kiểu Boolean cho biết 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 number * @param badgeNumber Số badge cần được cộng dồn * @param asyncResult Kết quả trả về từ tác vụ bất đồng bộcá cược bóng đá, chứa một tham số kiểu Boolean để xác định xem 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ừ tác vụ bất đồng bộ. Kết quả này sẽ là một giá trị Booleancá cược bóng đá, 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ố badge theo loại được chỉ định * @param type Loại. Khi muốn lấy số badge từ cuộc trò chuyệnmua thẻ trực tuyến, bạn 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 count của số badge theo loại đã chỉ định. */
public
void
getBadgeNumber
(
final
int
type
,
final
AsyncResult
<
Integer
>
asyncResult
)
{
...
}
kết quả trả về bất đồng bộ
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 0cá cược bóng đá, 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 0cá cược bóng đá, tiếp tục tính toán loại thông báo đỏ
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
())
{
// Đã có tất cả các giá trị đếm trong khoảng loại
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ả như sau. */
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
;
}
}
}
Trong đoạn mã nàycá cược bóng đá, các điểm cần chú ý bao gồm:
Ví dụ mã gọi getTotalBadgeNumberOnParent như sau:
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ị thông báo đỏ
showTabBadgeDot
(
tabIndex
);
}
else
{
// Ẩn số và thông báo đỏ
hideTabBadgeNumber
(
tabIndex
);
}
}
});