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 mới dựa trên số hoặc biểu tượng chấm đỏ. Ví dụ như trong ứng dụng WeChatkết quả bóng đá ngoại hạng anh, khi bạn bè gửi tin nhắn trò chuyện mới cho bạn, một con số sẽ xuất hiện trên cuộc trò chuyện tương ứng để chỉ ra số lượng tin nhắn chưa đọc. Hoặc nếu có người đăng bài mới trên trang (), một dấu chấm đỏ sẽ xuất hiện tại lối vào của phần này. Đặc biệt hơn, khi có ai đó thích bài viết của bạn hoặc để lại bình luận trên nội dung bạn đăng tải, lối vào sẽ hiển thị thêm một con số để thông báo chính xác số lượng hoạt động mới mà bạn nhận được từ bạn bè. Ứng dụng không chỉ đơn thuần là thông báo qua số lượng hay dấu chấm đỏ mà còn giúp người dùng dễ dàng theo dõi mọi tương tác mà họ nhận được, từ đó tạo nên sự kết nối nhanh chóng và tiện lợi giữa các cá nhân. Chính những chi tiết nhỏ nhặt như vậy đã góp phần làm cho trải nghiệm của người dùng trở nên thú vị và hữu ích hơn mỗi khi mở ứng dụng.
Tuy nhiênboi tu vi, khi thử nghiệm một số sản phẩm ứng dụng mới, chúng ta thường gặp phải nhiều vấn đề liên quan đến việc hiển thị số và chấm đỏ. Có những trường hợp, dù đã cố gắng nhấn mãi nhưng chấm đỏ vẫn không thể biến mất; hoặc, khi phát hiện có số hiển thị, nhưng khi nhấp vào bên trong thì lại chẳng thấy gì cả; thậm chí, đôi khi con số bên trong khác hoàn toàn so với con số được nhìn thấy từ bên ngoài. Những điều này thực sự gây khó chịu và làm giảm trải nghiệm người dùng.
Vậy những vấn đề này thực sự được tạo ra như thế nào?
Tôi cho rằngVSBET, 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 hóa và quản lý logic hiển thị của các con số và chấm đỏ. Điều này dẫn đến việc mối quan hệ giữa các con số và chấm đỏ trở nên phức tạp, mỗi thay đổi nhỏ đều có thể ảnh hưởng đến toàn bộ hệ thống. Do đó, trong quá trình duy trì ứng dụng (App), chỉ cần thực hiện một số điều chỉnh nhỏ (như thêm vài loại con số hoặc chấm đỏ), nguy cơ phát sinh lỗi sẽ rất cao. Thêm vào đó, khi không có cấu trúc rõ ràng, việc mở rộng hoặc bảo trì sẽ trở nên khó khăn hơn nhiều. Các lập trình viên phải dành thời gian tìm hiểu toàn bộ hệ thống thay vì tập trung vào cải tiến tính năng mới, làm giảm hiệu quả công việc và tăng nguy cơ sai sót trong quá trình sửa lỗi.
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 bộ hóa cơ cấu cấp bậc giữa các số và chấm đỏ. Cuối bàiVSBET, chúng tôi cũng sẽ cung cấp một ứng dụng demo chạy được phiên bản Android làm ví dụ tham khảo cho người đọc.
Nếu bạn hiện đang có trong tay một điện thoại thông minh chạy hệ điều hành AndroidVSBET, bạn có thể quét mã QR bên dưới (hoặc nhấp vào đường link 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 để khám phá xem nó có phù hợp với nhu cầu của bạn hay không.
Hoặc nhấn vào Liên kết tải xuống 。
Để thuận tiện cho việc thảo luậnboi tu vi, 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 thông thường. 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. Trước tiên, điều quan trọng là phải hiểu rõ hơn về mục đích của việc sử dụng số và chấm đỏ. Điều này không chỉ giúp chúng ta xác định cách bố trí mà còn giúp tăng cường hiệu quả giao tiếp qua hình ảnh. Ví dụ, nếu số cần được hiển thị ở giữa và chấm đỏ phải nằm ngay bên cạnh hoặc phía dưới để tạo sự chú ý, thì chúng ta cần thiết kế một cấu trúc logic để đảm bảo rằng cả hai yếu tố đều dễ dàng nhận biết và phân biệt. Tiếp theo, khi đã hiểu rõ các yêu cầu cơ bản, chúng ta có thể tìm ra phương án thực hiện nhanh chóng và hiệu quả nhất. Có thể sử dụng các công cụ đồ họa hiện đại hoặc lập trình với ngôn ngữ phù hợp để đạt được kết quả mong muốn. Điều này không chỉ tiết kiệm thời gian mà còn tối ưu hóa trải nghiệm người dùng. Tóm lại, việc sắp xếp nhu cầu một cách có hệ thống và chọn giải pháp trực quan sẽ giúp chúng ta đạt được mục tiêu nhanh chóng và hiệu quả.
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ỏkết quả bóng đá ngoại hạng anh, nhưng điều đó không làm ảnh hưởng nhiều đến phần thảo luận tiếp theo của chúng ta. Những điểm khác biệt ấy thường chỉ là những chi tiết kỹ thuật mà người dùng cuối cùng ít khi để ý đến. Điều quan trọng là chúng ta vẫn có thể hiểu rõ được cấu trúc cơ bản và cách hoạt động chung của ứng dụng.
Bình luận đã nhận
Khi chỉ tập trung phân tích logic hiển thị chấm đỏ trên tab "Tin nhắn"boi tu vi, chúng ta có thể dễ dàng viết ra mã nguồn giả (pseudo-code) giống như bên dưới: ```plaintext function capNhatChamDo(tongSoTinNhanMoi) { if (tongSoTinNhanMoi > 0) { hienThiChamDo(); neu (tongSoTinNhanMoi >= 99) { hienThiSo(99); } else { hienThiSo(tongSoTinNhanMoi); } } else { anChamDo(); } } function xuLyClickTabTinNhan() { neu ( dangHienThi) { resetSoTinNhanMoi(); capNhatChamDo(0); } else { // Xử lý khi người dùng chuyển sang tab tin nhắn } } ``` Trên đây là cách thiết kế logic cơ bản để quản lý việc hiển thị chấm đỏ trên tab "Tin nhắn". Code này giúp cập nhật giao diện dựa trên số lượng tin nhắn chưa đọc và sẽ ẩn chấm đỏ khi người dùng chuyển đến tab này.
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ố lượng count
}
else
if
(
Có thông báo hệ thống)
{
Hiển thị thông báo đỏ
}
else
{
Ẩn số và thông báo đỏ
}
Mã nguồn này chắc chắn có thể đáp ứng được yêu cầuVSBET, nhưng nhược điểm của nó cũng khá rõ ràng. Điểm quan trọng nhất là nó đòi hỏi logic hiển thị trong tab "Tin nhắn" phải liệt kê tất cả các loại tin nhắn con (như bình luận, thích, tin hệ thống) và phải phân biệt rõ ràng từng loại là số hay dấu 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 thêm các cấp độ thứ ba hoặc nhiều hơn nữa thì làm sao? Những thông tin này sẽ cần phải lặp đi lặp lại ở mọi cấp độ trang khác nhau, điều đó vừa phức tạp vừa dễ gây lỗi.
Việc này sẽ khiến việc bảo trì và chỉnh sửa trở nên phức tạp hơn. Hãy tưởng tượng rằngboi tu vi, dưới mục "tin nhắn" lại thêm một loại tin nhắn mới, hoặc một loại tin nhắn nào đó thay đổi cách hiển thị từ dạng số thành biểu tượng chấm đỏ, thậm chí là một loại tin nhắn nào đó đượ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 trường hợp như vậy đều yêu cầu tất cả các trang ở cấp cao hơn phải thực hiện điều chỉnh tương ứng. Khi ứ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ể tưởng tượng rằng việc chỉnh sửa nà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ý chúng không chỉ khó khăn mà còn có thể gây ra sự chồng chéo trong logic của ứng dụng. Điều này có thể dẫn đến tình trạng một số tính năng bị lỗi hoặc hoạt động không ổn định nếu không được kiểm tra kỹ lưỡng trước khi triển khai. Chính vì thế, việc thiết kế hệ thống cần có chiến lược rõ ràng để tránh những rủi ro không đáng có trong quá trình phát triển.
Những vấn đề ở trênboi tu vi, chúng tôi đã xử lý trong MicroLove Trong giai đoạn đầu phát triển ứng dụngkết quả bóng đá ngoại hạng anh, chúng tôi cũng đã gặp phải một số vấn đề. Sau đó, chúng tôi quyết định tái đánh giá cấu trúc của các biểu tượng đỏ và số liệu hiển thị trong ứng dụng, chuyển sang cách nhìn nhận theo dạng cây (tree structure). Điều này đã giúp cho công tác bảo trì trở nên dễ dàng hơn rất nhiều. Không chỉ vậy, việc áp dụng phương pháp mới còn giúp đội ngũ phát triển có cái nhìn toàn diện hơn về cách hoạt động của từng thành phần trong ứng dụng.
Một trang ứng dụng vốn dĩ đã có thứ bậckết quả bóng đá ngoại hạng anh, về bản chất đường dẫn truy cập trang chính là cấu trúc cây.
Như đã trình bày trong hình trênkết quả bóng đá ngoại hạng anh, nút 1 đại diện cho trang cấp 1, trang này có chứa ba lối vào trang cấp 2, tương ứng với các nút 2, 3 và 4. Nếu đi sâu thêm một cấp nữa, chúng ta sẽ đến các trang đích cuối cùng, được biểu thị bằng các nút hình vuông màu xanh lá cây. Những trang đích này là điểm kết thúc của hệ thống phân cấp, nơi người dùng có thể thực hiện các hành động cụ thể hoặc thu thập thông tin chi tiết từ cấu trúc nà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 một khoảng giá trị loạiboi tu vi, khi gán giá trị cho các loại, chúng ta có thể áp dụng cách tiếp cận như sau: sử dụng một số nguyên (int) để đại diện cho loại Badge Number, trong đó 16 bit cao sẽ biểu thị nhóm lớn. Ví dụ, nếu nhóm "Tin nhắn" có giá trị 16 bit cao là 0x2, thì ba loại Badge Number thuộc nhóm này có thể được phân bổ như sau: - Loại thứ nhất: 0x20001 - Loại thứ hai: 0x20002 - Loại thứ ba: 0x20003 Cách làm này không chỉ giúp quản lý dễ dàng hơn mà còn tạo ra sự linh hoạt và hiệu quả trong việc phân loại cá
Thế làkết quả bóng đá ngoại hạng anh, nhóm "tin nhắn" có thể được biểu thị bằng một khoảng kiểu dữ liệu [(0x2 << 16) + 0x1, (0x2 << 16) + 0x3]. Đây là cách gọn gàng để phân loại và quản lý các thông tin quan trọng trong hệ thống. Khoảng giá trị này giúp xác định rõ ràng ranh giới cho các loại tin nhắn, từ đó dễ dàng theo dõi và xử lý hơn khi triển khai các ứng dụng phức tạp.
Sau khi có các khoảng loại trừboi tu vi, 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 loại trừ. Cách chúng được hiển thị (số, chấm đỏ hay ẩn đi) phụ thuộc vào việc tính tổng tất cả các khoảng loại trừ của cây con tương ứng. Quy trình tính toán cụ thể như sau: Đầu tiên, ta cần duyệt qua từng cây con liên kết với nút trung gian đó. Mỗi khoảng loại trừ trong cây con sẽ được thêm vào một danh sách tạm thời. Nếu có sự trùng lặp giữa các khoảng loại trừ, chúng sẽ được hợp nhất thành một khoảng lớn hơn. Tiếp theo, sau khi đã tổng hợp toàn bộ khoảng loại trừ từ tất cả các cây con, chúng ta kiểm tra điều kiện để xác định cách thức hiển thị nú Nếu tổng khoảng loại trừ bao phủ toàn bộ phạm vi giá trị, nút sẽ được ẩn đi. Nếu chỉ một phần nào đó của phạm vi bị che khuất, nút sẽ được hiển thị dưới dạng chấm đỏ. Cuối cùng, nếu không có khoảng nào bị che lấp, nó sẽ hiển thị dưới dạng số. Quy trình này giúp đảm bảo rằng cấu trúc cây luôn phản ánh logic chính xác và nhất quán đối với tất cả các loại dữ liệu.
Thực hiện mô hình câykết quả bóng đá ngoại hạng anh, chúng tôi gọi là Bài viết này cung cấp một phiên bản Demo cho Android, mã nguồn có thể được tải xuống từ GitHub: https://github.com/tielei/BadgeNumberTree 。
Bây giờ chúng ta cùng phân tích phần quan trọng.
Trong phiên bản Androidkết quả bóng đá ngoại hạng anh, lớp thực hiện chính được gọi là Dưới đây là đoạn mã quan trọng của nó (để không làm gián đoạn việc hiểu logic chính, các đoạn mã không cần thiết đã bị lược bỏ và không được hiển thị ở đây. 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
{
/** * Thiết lập số badge * @param badgeNumber Số badge cần được thiết lập * @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ị booleanVSBET, cho biết liệu việc thiết lập 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ừ hoạt động bất đồng bộ. Kết quả này sẽ là một giá trị Booleanboi tu vi, 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 đã chỉ định * @param type Loại badge number cần được xóa. * @param asyncResult Kết quả trả về từ hoạt động xử lý bất đồng bộ. Kết quả này sẽ là một giá trị Booleankết quả bóng đá ngoại hạng anh, 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 badge theo loại được chỉ định * @param type Loại. Khi muốn lấy số hiệu badge từ cuộc trò chuyệnboi tu vi, chỉ cần truyền giá trị 0. * @param asyncResult Kết quả trả về bất đồng bộ, sẽ cung cấp số lượng count của số hiệu badge 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ách loại badge để tính toán tổng số badge của một nút cha trong cấu trúc cây. * Thứ tự ưu tiên sẽ là các con số trướcVSBET, tiếp theo mới đến các dấu chấm đỏ. * Trong thực tế, mỗi danh sách khoảng cách loại badge tương ứng với một nút cha trong cây cấu trúc. * @param danhSachKhoangCachLoaiBadge Danh sách khoảng cách loại badge được chỉ định, chắc chắn chứa ít nhất một khoảng. * @param ketQuaAsyc Kết quả trả về từ xử lý bất đồng bộ, nó sẽ cung cấp thông tin tình trạng badge theo loại (bao gồm cả cách hiển thị và tổng số lượng). */
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 0kết quả bóng đá ngoại hạng anh, 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 0boi tu vi, 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
())
{
// Tất cả các loại khoảng count đã 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 tính toán dựa trên một khoảng loại 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
;
}
}
}
Trong đoạn mã nàyboi tu vi, điểm cần chú ý bao gồm:
Ví dụ về 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ị thông báo đỏ
showTabBadgeDot
(
tabIndex
);
}
else
{
// Ẩn số và thông báo đỏ
hideTabBadgeNumber
(
tabIndex
);
}
}
});