主题:优化整体样式+动态样式修改
This commit is contained in:
parent
7994b66ea1
commit
ae28ad17e5
|
|
@ -530,6 +530,7 @@ html, body {
|
||||||
.sheet-content {
|
.sheet-content {
|
||||||
display: none;
|
display: none;
|
||||||
padding: 0 16px 24px;
|
padding: 0 16px 24px;
|
||||||
|
padding-bottom: 140px; /* 底部导航栏高度 + 额外空间 */
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
max-height: calc(var(--sheet-expanded-height) - 50px);
|
max-height: calc(var(--sheet-expanded-height) - 50px);
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
|
|
|
||||||
|
|
@ -76,37 +76,37 @@
|
||||||
|
|
||||||
<!-- 心愿信笺卡片 -->
|
<!-- 心愿信笺卡片 -->
|
||||||
<div class="wish-notes" id="wishCards">
|
<div class="wish-notes" id="wishCards">
|
||||||
<div class="wish-note glass floating" data-index="0" style="--x: 12%; --y: 15%; --delay: 0s;">
|
<div class="wish-note glass floating" data-index="0" style="--x: 2%; --y: 55%; --delay: 0s;">
|
||||||
<div class="note-pin"></div>
|
<div class="note-pin"></div>
|
||||||
<div class="note-content"></div>
|
<div class="note-content"></div>
|
||||||
<div class="note-footer"><span class="heart">♥</span><span class="count">0</span></div>
|
<div class="note-footer"><span class="heart">♥</span><span class="count">0</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="wish-note glass floating" data-index="1" style="--x: 72%; --y: 12%; --delay: 0.4s;">
|
<div class="wish-note glass floating" data-index="1" style="--x: 15%; --y: 42%; --delay: 0.4s;">
|
||||||
<div class="note-pin"></div>
|
<div class="note-pin"></div>
|
||||||
<div class="note-content"></div>
|
<div class="note-content"></div>
|
||||||
<div class="note-footer"><span class="heart">♥</span><span class="count">0</span></div>
|
<div class="note-footer"><span class="heart">♥</span><span class="count">0</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="wish-note glass floating" data-index="2" style="--x: 5%; --y: 40%; --delay: 0.8s;">
|
<div class="wish-note glass floating" data-index="2" style="--x: 28%; --y: 52%; --delay: 0.8s;">
|
||||||
<div class="note-pin"></div>
|
<div class="note-pin"></div>
|
||||||
<div class="note-content"></div>
|
<div class="note-content"></div>
|
||||||
<div class="note-footer"><span class="heart">♥</span><span class="count">0</span></div>
|
<div class="note-footer"><span class="heart">♥</span><span class="count">0</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="wish-note glass floating" data-index="3" style="--x: 78%; --y: 38%; --delay: 1.2s;">
|
<div class="wish-note glass floating" data-index="3" style="--x: 55%; --y: 48%; --delay: 1.2s;">
|
||||||
<div class="note-pin"></div>
|
<div class="note-pin"></div>
|
||||||
<div class="note-content"></div>
|
<div class="note-content"></div>
|
||||||
<div class="note-footer"><span class="heart">♥</span><span class="count">0</span></div>
|
<div class="note-footer"><span class="heart">♥</span><span class="count">0</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="wish-note glass floating" data-index="4" style="--x: 18%; --y: 62%; --delay: 0.2s;">
|
<div class="wish-note glass floating" data-index="4" style="--x: 70%; --y: 55%; --delay: 0.2s;">
|
||||||
<div class="note-pin"></div>
|
<div class="note-pin"></div>
|
||||||
<div class="note-content"></div>
|
<div class="note-content"></div>
|
||||||
<div class="note-footer"><span class="heart">♥</span><span class="count">0</span></div>
|
<div class="note-footer"><span class="heart">♥</span><span class="count">0</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="wish-note glass floating" data-index="5" style="--x: 68%; --y: 60%; --delay: 0.6s;">
|
<div class="wish-note glass floating" data-index="5" style="--x: 82%; --y: 45%; --delay: 0.6s;">
|
||||||
<div class="note-pin"></div>
|
<div class="note-pin"></div>
|
||||||
<div class="note-content"></div>
|
<div class="note-content"></div>
|
||||||
<div class="note-footer"><span class="heart">♥</span><span class="count">0</span></div>
|
<div class="note-footer"><span class="heart">♥</span><span class="count">0</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="wish-note glass floating" data-index="6" style="--x: 42%; --y: 8%; --delay: 1s;">
|
<div class="wish-note glass floating" data-index="6" style="--x: 5%; --y: 70%; --delay: 1s;">
|
||||||
<div class="note-pin"></div>
|
<div class="note-pin"></div>
|
||||||
<div class="note-content"></div>
|
<div class="note-content"></div>
|
||||||
<div class="note-footer"><span class="heart">♥</span><span class="count">0</span></div>
|
<div class="note-footer"><span class="heart">♥</span><span class="count">0</span></div>
|
||||||
|
|
|
||||||
|
|
@ -194,7 +194,8 @@ public class FeedAdapter extends ListAdapter<FeedItem, RecyclerView.ViewHolder>
|
||||||
"title=" + work.getTitle() +
|
"title=" + work.getTitle() +
|
||||||
", userName=" + work.getUserName() +
|
", userName=" + work.getUserName() +
|
||||||
", authorName=" + work.getAuthorName() +
|
", authorName=" + work.getAuthorName() +
|
||||||
", likeCount=" + work.getLikeCount());
|
", likeCount=" + work.getLikeCount() +
|
||||||
|
", content=" + (work.getContent() != null ? work.getContent().substring(0, Math.min(50, work.getContent().length())) : "null"));
|
||||||
|
|
||||||
// 设置标题
|
// 设置标题
|
||||||
String title = work.getTitle();
|
String title = work.getTitle();
|
||||||
|
|
@ -255,27 +256,83 @@ public class FeedAdapter extends ListAdapter<FeedItem, RecyclerView.ViewHolder>
|
||||||
toggleWorkLike(work, !currentLiked, binding);
|
toggleWorkLike(work, !currentLiked, binding);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 加载封面图片
|
// 判断是否为纯文字动态
|
||||||
String coverUrl = work.getCoverUrl();
|
String coverUrl = work.getCoverUrl();
|
||||||
if (coverUrl == null || coverUrl.trim().isEmpty()) {
|
String videoUrl = work.getVideoUrl();
|
||||||
// 如果没有封面,尝试使用第一张图片
|
boolean hasVideo = videoUrl != null && !videoUrl.trim().isEmpty();
|
||||||
if (work.getImageUrls() != null && !work.getImageUrls().isEmpty()) {
|
|
||||||
coverUrl = work.getImageUrls().get(0);
|
// 检查是否有有效的图片列表
|
||||||
|
boolean hasValidImageList = false;
|
||||||
|
if (work.getImageUrls() != null && !work.getImageUrls().isEmpty()) {
|
||||||
|
for (String imgUrl : work.getImageUrls()) {
|
||||||
|
if (imgUrl != null && !imgUrl.trim().isEmpty() && !isTextOnlyPlaceholder(imgUrl)) {
|
||||||
|
hasValidImageList = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文字内容
|
||||||
|
String content = work.getContent();
|
||||||
|
if (content == null || content.trim().isEmpty()) {
|
||||||
|
content = work.getDescription();
|
||||||
|
}
|
||||||
|
boolean hasContent = content != null && !content.trim().isEmpty();
|
||||||
|
|
||||||
|
// 判断是否为纯文字动态:
|
||||||
|
// 核心逻辑:如果没有图片列表、没有视频,且有文字内容,就是纯文字动态
|
||||||
|
// 不管coverUrl是什么,因为后端可能给纯文字动态设置了一个无效的封面URL
|
||||||
|
boolean isTextOnly = !hasValidImageList && !hasVideo && hasContent;
|
||||||
|
|
||||||
|
// 检查封面是否为占位图(用于有图片的作品)
|
||||||
|
boolean isPlaceholderCover = isTextOnlyPlaceholder(coverUrl);
|
||||||
|
boolean hasCover = coverUrl != null && !coverUrl.trim().isEmpty() && !isPlaceholderCover;
|
||||||
|
|
||||||
|
// 如果没有封面,尝试使用第一张有效图片作为封面
|
||||||
|
String finalCoverUrl = coverUrl;
|
||||||
|
if (!hasCover && work.getImageUrls() != null) {
|
||||||
|
for (String imgUrl : work.getImageUrls()) {
|
||||||
|
if (imgUrl != null && !imgUrl.trim().isEmpty() && !isTextOnlyPlaceholder(imgUrl)) {
|
||||||
|
finalCoverUrl = imgUrl;
|
||||||
|
hasCover = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
android.util.Log.d("FeedAdapter", "作品类型判断: id=" + work.getId() +
|
||||||
|
", coverUrl=" + coverUrl +
|
||||||
|
", hasValidImageList=" + hasValidImageList +
|
||||||
|
", hasVideo=" + hasVideo +
|
||||||
|
", hasContent=" + hasContent +
|
||||||
|
", isTextOnly=" + isTextOnly);
|
||||||
|
|
||||||
|
final String finalContent = content;
|
||||||
|
if (isTextOnly) {
|
||||||
|
// 纯文字动态:隐藏封面区域,显示文字内容区域
|
||||||
|
binding.coverContainer.setVisibility(View.GONE);
|
||||||
|
binding.textContentContainer.setVisibility(View.VISIBLE);
|
||||||
|
binding.worksContentText.setText(finalContent);
|
||||||
|
|
||||||
|
android.util.Log.d("FeedAdapter", "显示纯文字动态: " + finalContent.substring(0, Math.min(30, finalContent.length())));
|
||||||
|
} else {
|
||||||
|
// 有封面的作品:显示封面区域,隐藏文字内容区域
|
||||||
|
binding.coverContainer.setVisibility(View.VISIBLE);
|
||||||
|
binding.textContentContainer.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
if (hasCover && finalCoverUrl != null) {
|
||||||
|
Glide.with(binding.worksCoverImage)
|
||||||
|
.load(finalCoverUrl)
|
||||||
|
.placeholder(R.drawable.bg_cover_placeholder)
|
||||||
|
.centerCrop()
|
||||||
|
.into(binding.worksCoverImage);
|
||||||
|
} else {
|
||||||
|
binding.worksCoverImage.setImageResource(R.drawable.bg_cover_placeholder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (coverUrl != null && !coverUrl.trim().isEmpty()) {
|
|
||||||
Glide.with(binding.worksCoverImage)
|
|
||||||
.load(coverUrl)
|
|
||||||
.placeholder(R.drawable.bg_cover_placeholder)
|
|
||||||
.centerCrop()
|
|
||||||
.into(binding.worksCoverImage);
|
|
||||||
} else {
|
|
||||||
binding.worksCoverImage.setImageResource(R.drawable.bg_cover_placeholder);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示作品类型图标(视频类型显示播放图标)
|
// 显示作品类型图标(视频类型显示播放图标)
|
||||||
if ("VIDEO".equals(work.getType())) {
|
if ("VIDEO".equals(work.getType()) || hasVideo) {
|
||||||
binding.worksTypeIcon.setVisibility(View.VISIBLE);
|
binding.worksTypeIcon.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
binding.worksTypeIcon.setVisibility(View.GONE);
|
binding.worksTypeIcon.setVisibility(View.GONE);
|
||||||
|
|
@ -430,4 +487,46 @@ public class FeedAdapter extends ListAdapter<FeedItem, RecyclerView.ViewHolder>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断URL是否为纯文字动态的占位图
|
||||||
|
* 后端为纯文字动态设置的特殊封面地址
|
||||||
|
*/
|
||||||
|
private static boolean isTextOnlyPlaceholder(String url) {
|
||||||
|
if (url == null || url.trim().isEmpty()) {
|
||||||
|
return true; // 空URL也视为占位图
|
||||||
|
}
|
||||||
|
|
||||||
|
// 特殊占位图地址列表
|
||||||
|
String[] placeholderPatterns = {
|
||||||
|
"TEXT_ONLY_DYNAMIC_PLACEHOLDER", // Android端发布纯文字动态时设置的标识
|
||||||
|
"text_only_placeholder", // 纯文字动态占位图标识
|
||||||
|
"placeholder/text", // 文字占位图路径
|
||||||
|
"default_text_cover", // 默认文字封面
|
||||||
|
"no_image_placeholder", // 无图片占位图
|
||||||
|
"/placeholder.", // 通用占位图
|
||||||
|
"crmebimage/public/content/", // 后端默认内容图片路径
|
||||||
|
"default_cover", // 默认封面
|
||||||
|
"empty_cover", // 空封面
|
||||||
|
};
|
||||||
|
|
||||||
|
String lowerUrl = url.toLowerCase();
|
||||||
|
for (String pattern : placeholderPatterns) {
|
||||||
|
if (lowerUrl.contains(pattern.toLowerCase())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否为空白图片或默认图片(根据文件名判断)
|
||||||
|
if (lowerUrl.endsWith("/default.png") ||
|
||||||
|
lowerUrl.endsWith("/default.jpg") ||
|
||||||
|
lowerUrl.endsWith("/placeholder.png") ||
|
||||||
|
lowerUrl.endsWith("/placeholder.jpg") ||
|
||||||
|
lowerUrl.endsWith("/empty.png") ||
|
||||||
|
lowerUrl.endsWith("/empty.jpg")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,12 +115,13 @@ public class LikesListActivity extends AppCompatActivity {
|
||||||
for (WorksResponse work : works) {
|
for (WorksResponse work : works) {
|
||||||
Integer likeCount = work.getLikeCount();
|
Integer likeCount = work.getLikeCount();
|
||||||
if (likeCount != null && likeCount > 0) {
|
if (likeCount != null && likeCount > 0) {
|
||||||
ConversationItem item = new ConversationItem();
|
String id = String.valueOf(work.getId());
|
||||||
item.setId(String.valueOf(work.getId()));
|
String title = work.getTitle() != null ? work.getTitle() : "作品";
|
||||||
item.setTitle(work.getTitle() != null ? work.getTitle() : "作品");
|
String lastMessage = likeCount + "人点赞了这个作品";
|
||||||
item.setLastMessage(likeCount + "人点赞了这个作品");
|
String timeText = "";
|
||||||
item.setAvatarUrl(work.getCoverImage());
|
|
||||||
item.setUnreadCount(likeCount);
|
ConversationItem item = new ConversationItem(id, title, lastMessage, timeText, likeCount, false);
|
||||||
|
item.setAvatarUrl(work.getCoverUrl());
|
||||||
items.add(item);
|
items.add(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -550,8 +550,13 @@ public class MainActivity extends AppCompatActivity {
|
||||||
// 加载热门作品
|
// 加载热门作品
|
||||||
refreshHotWorks();
|
refreshHotWorks();
|
||||||
} else {
|
} else {
|
||||||
// 应用其他分类筛选(带动画)
|
// 如果数据为空,先加载数据
|
||||||
applyCategoryFilterWithAnimation(currentCategory);
|
if (allFeedItems.isEmpty()) {
|
||||||
|
fetchDiscoverRooms();
|
||||||
|
} else {
|
||||||
|
// 应用其他分类筛选(带动画)
|
||||||
|
applyCategoryFilterWithAnimation(currentCategory);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -572,8 +577,13 @@ public class MainActivity extends AppCompatActivity {
|
||||||
// 刷新热门作品
|
// 刷新热门作品
|
||||||
refreshHotWorks();
|
refreshHotWorks();
|
||||||
} else {
|
} else {
|
||||||
// 应用其他分类筛选(带动画)
|
// 如果数据为空,先加载数据;否则刷新
|
||||||
applyCategoryFilterWithAnimation(currentCategory);
|
if (allFeedItems.isEmpty()) {
|
||||||
|
fetchDiscoverRooms();
|
||||||
|
} else {
|
||||||
|
// 重新加载数据
|
||||||
|
fetchDiscoverRooms();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -1883,62 +1893,95 @@ public class MainActivity extends AppCompatActivity {
|
||||||
// 增加请求ID,确保只有最新的筛选结果被应用
|
// 增加请求ID,确保只有最新的筛选结果被应用
|
||||||
final int requestId = ++filterRequestId;
|
final int requestId = ++filterRequestId;
|
||||||
|
|
||||||
|
// 如果是"推荐"或"全部",显示所有数据
|
||||||
|
if ("推荐".equals(c) || "全部".equals(c)) {
|
||||||
|
binding.roomsRecyclerView.animate()
|
||||||
|
.alpha(0.7f)
|
||||||
|
.setDuration(100)
|
||||||
|
.withEndAction(() -> {
|
||||||
|
if (requestId != filterRequestId) return;
|
||||||
|
|
||||||
|
adapter.submitList(new ArrayList<>(allFeedItems), () -> {
|
||||||
|
if (requestId != filterRequestId) return;
|
||||||
|
binding.roomsRecyclerView.animate()
|
||||||
|
.alpha(1.0f)
|
||||||
|
.setDuration(200)
|
||||||
|
.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (allFeedItems.isEmpty()) {
|
||||||
|
showEmptyState("暂无内容");
|
||||||
|
} else {
|
||||||
|
hideEmptyState();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.start();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 显示加载状态(如果数据量较大)
|
// 显示加载状态(如果数据量较大)
|
||||||
if (allRooms.size() > 50) {
|
if (allFeedItems.size() > 50) {
|
||||||
binding.loading.setVisibility(View.VISIBLE);
|
binding.loading.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用筛选管理器异步筛选
|
// 筛选FeedItem列表
|
||||||
if (filterManager != null) {
|
List<FeedItem> filtered = new ArrayList<>();
|
||||||
filterManager.filterRoomsAsync(allRooms, c, filteredRooms -> {
|
for (FeedItem item : allFeedItems) {
|
||||||
// 检查这个结果是否仍然是最新的请求
|
if (item == null) continue;
|
||||||
if (requestId != filterRequestId) {
|
|
||||||
// 这是一个旧的请求结果,忽略它
|
// 根据类型获取分类
|
||||||
return;
|
String itemCategory = null;
|
||||||
|
if (item.getType() == FeedItem.TYPE_ROOM && item.getRoom() != null) {
|
||||||
|
Room room = item.getRoom();
|
||||||
|
itemCategory = room.getCategoryName();
|
||||||
|
if (itemCategory == null || itemCategory.isEmpty()) {
|
||||||
|
itemCategory = room.getType();
|
||||||
}
|
}
|
||||||
|
} else if (item.getType() == FeedItem.TYPE_WORK && item.getWork() != null) {
|
||||||
// 隐藏加载状态
|
// 作品按分类筛选
|
||||||
binding.loading.setVisibility(View.GONE);
|
WorksResponse work = item.getWork();
|
||||||
|
itemCategory = work.getCategoryName();
|
||||||
// 添加淡入动画
|
if (itemCategory == null || itemCategory.isEmpty()) {
|
||||||
binding.roomsRecyclerView.animate()
|
itemCategory = work.getCategory();
|
||||||
.alpha(0.7f)
|
}
|
||||||
.setDuration(100)
|
} else if (item.getType() == FeedItem.TYPE_CHATROOM) {
|
||||||
.withEndAction(() -> {
|
// 聊天室暂时不按分类筛选,全部显示
|
||||||
// 再次检查请求ID(防止在动画期间又有新的筛选请求)
|
filtered.add(item);
|
||||||
if (requestId != filterRequestId) {
|
continue;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
// 如果分类匹配,添加到筛选结果
|
||||||
// 转换为FeedItem列表
|
if (c.equals(itemCategory)) {
|
||||||
List<FeedItem> feedItems = new ArrayList<>();
|
filtered.add(item);
|
||||||
for (Room room : filteredRooms) {
|
}
|
||||||
feedItems.add(FeedItem.fromRoom(room));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新列表数据(ListAdapter会自动处理DiffUtil动画)
|
|
||||||
adapter.submitList(feedItems, () -> {
|
|
||||||
// 最后一次检查请求ID
|
|
||||||
if (requestId != filterRequestId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 数据更新完成后,恢复透明度并添加淡入效果
|
|
||||||
binding.roomsRecyclerView.animate()
|
|
||||||
.alpha(1.0f)
|
|
||||||
.setDuration(200)
|
|
||||||
.start();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 更新空状态
|
|
||||||
updateEmptyStateForList(filteredRooms);
|
|
||||||
})
|
|
||||||
.start();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 降级到同步筛选(如果筛选管理器未初始化)
|
|
||||||
applyCategoryFilterSync(c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 隐藏加载状态
|
||||||
|
binding.loading.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
// 添加淡入动画
|
||||||
|
final List<FeedItem> finalFiltered = filtered;
|
||||||
|
binding.roomsRecyclerView.animate()
|
||||||
|
.alpha(0.7f)
|
||||||
|
.setDuration(100)
|
||||||
|
.withEndAction(() -> {
|
||||||
|
if (requestId != filterRequestId) return;
|
||||||
|
|
||||||
|
adapter.submitList(finalFiltered, () -> {
|
||||||
|
if (requestId != filterRequestId) return;
|
||||||
|
binding.roomsRecyclerView.animate()
|
||||||
|
.alpha(1.0f)
|
||||||
|
.setDuration(200)
|
||||||
|
.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (finalFiltered.isEmpty()) {
|
||||||
|
showEmptyState("该分类暂无内容");
|
||||||
|
} else {
|
||||||
|
hideEmptyState();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -2529,8 +2572,15 @@ public class MainActivity extends AppCompatActivity {
|
||||||
Log.d(TAG, "checkAndDisplayFeed() 提交 " + allFeedItems.size() + " 项到适配器");
|
Log.d(TAG, "checkAndDisplayFeed() 提交 " + allFeedItems.size() + " 项到适配器");
|
||||||
hideEmptyState();
|
hideEmptyState();
|
||||||
if (binding.loading != null) binding.loading.setVisibility(View.GONE);
|
if (binding.loading != null) binding.loading.setVisibility(View.GONE);
|
||||||
// 提交数据到适配器
|
|
||||||
adapter.submitList(new ArrayList<>(allFeedItems));
|
// 根据当前分类筛选数据
|
||||||
|
if ("热门".equals(currentCategory)) {
|
||||||
|
// 热门分类使用热门作品数据
|
||||||
|
// 不做任何操作,热门数据由 refreshHotWorks 加载
|
||||||
|
} else {
|
||||||
|
// 其他分类应用筛选
|
||||||
|
applyCategoryFilterWithAnimation(currentCategory);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新发现页面的空状态
|
// 更新发现页面的空状态
|
||||||
|
|
@ -2855,6 +2905,11 @@ public class MainActivity extends AppCompatActivity {
|
||||||
binding.roomsRecyclerView.setVisibility(View.VISIBLE);
|
binding.roomsRecyclerView.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 预加载所有作品和直播间数据(用于其他分类筛选)
|
||||||
|
if (allFeedItems.isEmpty()) {
|
||||||
|
fetchDiscoverRooms();
|
||||||
|
}
|
||||||
|
|
||||||
// 使用房间适配器显示推荐内容
|
// 使用房间适配器显示推荐内容
|
||||||
if (adapter != null) {
|
if (adapter != null) {
|
||||||
// 默认选中"热门"Tab并加载热门作品
|
// 默认选中"热门"Tab并加载热门作品
|
||||||
|
|
@ -4141,9 +4196,26 @@ public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从本地加载我的频道配置
|
* 从本地加载我的频道配置
|
||||||
|
* 注意:如果本地配置的分类名称与后端不匹配,需要重置为默认配置
|
||||||
*/
|
*/
|
||||||
private void loadMyChannelsFromPrefs() {
|
private void loadMyChannelsFromPrefs() {
|
||||||
android.content.SharedPreferences prefs = getSharedPreferences("channel_prefs", MODE_PRIVATE);
|
android.content.SharedPreferences prefs = getSharedPreferences("channel_prefs", MODE_PRIVATE);
|
||||||
|
|
||||||
|
// 检查配置版本,如果版本不匹配则重置配置
|
||||||
|
int configVersion = prefs.getInt("channel_config_version", 0);
|
||||||
|
int currentVersion = 2; // 版本2:分类名称与后端匹配(娱乐、游戏、音乐、户外)
|
||||||
|
|
||||||
|
if (configVersion < currentVersion) {
|
||||||
|
// 版本不匹配,清除旧配置,使用新的默认配置
|
||||||
|
Log.d(TAG, "频道配置版本过旧(" + configVersion + " < " + currentVersion + "),重置为默认配置");
|
||||||
|
prefs.edit()
|
||||||
|
.remove("my_channels")
|
||||||
|
.putInt("channel_config_version", currentVersion)
|
||||||
|
.apply();
|
||||||
|
initDefaultMyChannels();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
String channelsJson = prefs.getString("my_channels", "");
|
String channelsJson = prefs.getString("my_channels", "");
|
||||||
|
|
||||||
if (!channelsJson.isEmpty()) {
|
if (!channelsJson.isEmpty()) {
|
||||||
|
|
@ -4180,14 +4252,17 @@ public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化默认的我的频道配置
|
* 初始化默认的我的频道配置
|
||||||
|
* 注意:分类名称必须与后端数据库 eb_live_room_category 表中的 name 字段一致
|
||||||
|
* 后端分类:娱乐、游戏、音乐、户外、美食、体育、教育、科技
|
||||||
*/
|
*/
|
||||||
private void initDefaultMyChannels() {
|
private void initDefaultMyChannels() {
|
||||||
myChannels.clear();
|
myChannels.clear();
|
||||||
myChannels.add(new ChannelTagAdapter.ChannelTag(0, "推荐"));
|
myChannels.add(new ChannelTagAdapter.ChannelTag(0, "推荐"));
|
||||||
myChannels.add(new ChannelTagAdapter.ChannelTag(1, "直播"));
|
myChannels.add(new ChannelTagAdapter.ChannelTag(1, "娱乐"));
|
||||||
myChannels.add(new ChannelTagAdapter.ChannelTag(2, "视频"));
|
myChannels.add(new ChannelTagAdapter.ChannelTag(2, "游戏"));
|
||||||
myChannels.add(new ChannelTagAdapter.ChannelTag(3, "音乐"));
|
myChannels.add(new ChannelTagAdapter.ChannelTag(3, "音乐"));
|
||||||
Log.d(TAG, "使用默认我的频道配置");
|
myChannels.add(new ChannelTagAdapter.ChannelTag(4, "户外"));
|
||||||
|
Log.d(TAG, "使用默认我的频道配置(与后端分类匹配)");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -253,7 +253,12 @@ public class PublishCenterActivity extends AppCompatActivity {
|
||||||
request.setDescription(content);
|
request.setDescription(content);
|
||||||
request.setType("IMAGE");
|
request.setType("IMAGE");
|
||||||
request.setImageUrls(new ArrayList<>());
|
request.setImageUrls(new ArrayList<>());
|
||||||
request.setCoverUrl(coverUrl != null ? coverUrl : "");
|
// 如果没有封面,设置特殊标识URL,表示这是纯文字动态
|
||||||
|
if (coverUrl == null || coverUrl.isEmpty()) {
|
||||||
|
request.setCoverUrl("TEXT_ONLY_DYNAMIC_PLACEHOLDER");
|
||||||
|
} else {
|
||||||
|
request.setCoverUrl(coverUrl);
|
||||||
|
}
|
||||||
ApiClient.getService(this).publishWork(request).enqueue(new Callback<ApiResponse<Long>>() {
|
ApiClient.getService(this).publishWork(request).enqueue(new Callback<ApiResponse<Long>>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call<ApiResponse<Long>> call, Response<ApiResponse<Long>> response) {
|
public void onResponse(Call<ApiResponse<Long>> call, Response<ApiResponse<Long>> response) {
|
||||||
|
|
|
||||||
|
|
@ -166,4 +166,148 @@ public class WishTagAnimator {
|
||||||
|
|
||||||
animatorSet.start();
|
animatorSet.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渐隐旧心愿并渐显新心愿的替换动画
|
||||||
|
* @param wishTag 心愿牌View
|
||||||
|
* @param newText 新心愿文字
|
||||||
|
* @param newBackground 新心愿背景资源
|
||||||
|
* @param onComplete 动画完成回调
|
||||||
|
*/
|
||||||
|
public static void animateFadeOutAndReplace(android.widget.TextView wishTag, String newText,
|
||||||
|
int newBackground, Runnable onComplete) {
|
||||||
|
// 第一阶段:旧心愿渐隐
|
||||||
|
ObjectAnimator fadeOut = ObjectAnimator.ofFloat(wishTag, "alpha", 1f, 0f);
|
||||||
|
fadeOut.setDuration(600);
|
||||||
|
fadeOut.setInterpolator(new AccelerateDecelerateInterpolator());
|
||||||
|
|
||||||
|
fadeOut.addListener(new android.animation.AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(android.animation.Animator animation) {
|
||||||
|
// 更新内容
|
||||||
|
wishTag.setText(newText);
|
||||||
|
wishTag.setBackgroundResource(newBackground);
|
||||||
|
|
||||||
|
// 第二阶段:新心愿渐显
|
||||||
|
ObjectAnimator fadeIn = ObjectAnimator.ofFloat(wishTag, "alpha", 0f, 1f);
|
||||||
|
fadeIn.setDuration(600);
|
||||||
|
fadeIn.setInterpolator(new AccelerateDecelerateInterpolator());
|
||||||
|
|
||||||
|
// 添加轻微缩放效果
|
||||||
|
ObjectAnimator scaleX = ObjectAnimator.ofFloat(wishTag, "scaleX", 0.8f, 1.05f, 1f);
|
||||||
|
scaleX.setDuration(600);
|
||||||
|
|
||||||
|
ObjectAnimator scaleY = ObjectAnimator.ofFloat(wishTag, "scaleY", 0.8f, 1.05f, 1f);
|
||||||
|
scaleY.setDuration(600);
|
||||||
|
|
||||||
|
AnimatorSet fadeInSet = new AnimatorSet();
|
||||||
|
fadeInSet.playTogether(fadeIn, scaleX, scaleY);
|
||||||
|
|
||||||
|
fadeInSet.addListener(new android.animation.AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(android.animation.Animator animation) {
|
||||||
|
// 开始摇摆动画
|
||||||
|
startSwingAnimation(wishTag);
|
||||||
|
if (onComplete != null) {
|
||||||
|
onComplete.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fadeInSet.start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fadeOut.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 替换动画:旧心愿渐隐消失,新心愿从底部飘上来
|
||||||
|
* @param wishTag 心愿牌View
|
||||||
|
* @param newText 新心愿文字
|
||||||
|
* @param newBackground 新心愿背景资源
|
||||||
|
* @param startX 新心愿起始X坐标
|
||||||
|
* @param startY 新心愿起始Y坐标
|
||||||
|
* @param targetX 目标X坐标
|
||||||
|
* @param targetY 目标Y坐标
|
||||||
|
* @param onComplete 动画完成回调
|
||||||
|
*/
|
||||||
|
public static void animateReplaceWithFlyIn(android.widget.TextView wishTag, String newText,
|
||||||
|
int newBackground, float startX, float startY,
|
||||||
|
float targetX, float targetY, Runnable onComplete) {
|
||||||
|
// 保存原始位置
|
||||||
|
final float originalX = wishTag.getX();
|
||||||
|
final float originalY = wishTag.getY();
|
||||||
|
|
||||||
|
// 第一阶段:旧心愿渐隐消失(800ms)
|
||||||
|
ObjectAnimator fadeOut = ObjectAnimator.ofFloat(wishTag, "alpha", 1f, 0f);
|
||||||
|
fadeOut.setDuration(800);
|
||||||
|
fadeOut.setInterpolator(new AccelerateDecelerateInterpolator());
|
||||||
|
|
||||||
|
fadeOut.addListener(new android.animation.AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(android.animation.Animator animation) {
|
||||||
|
// 更新内容和背景
|
||||||
|
wishTag.setText(newText);
|
||||||
|
wishTag.setBackgroundResource(newBackground);
|
||||||
|
|
||||||
|
// 设置到起始位置(屏幕底部)
|
||||||
|
wishTag.setX(startX);
|
||||||
|
wishTag.setY(startY);
|
||||||
|
wishTag.setAlpha(0f);
|
||||||
|
wishTag.setScaleX(0.5f);
|
||||||
|
wishTag.setScaleY(0.5f);
|
||||||
|
wishTag.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
// 第二阶段:新心愿从底部飘上来(1200ms)
|
||||||
|
AnimatorSet flyInSet = new AnimatorSet();
|
||||||
|
|
||||||
|
// 移动到目标位置
|
||||||
|
ObjectAnimator moveX = ObjectAnimator.ofFloat(wishTag, "x", startX, originalX);
|
||||||
|
moveX.setDuration(1200);
|
||||||
|
moveX.setInterpolator(new DecelerateInterpolator(1.5f));
|
||||||
|
|
||||||
|
ObjectAnimator moveY = ObjectAnimator.ofFloat(wishTag, "y", startY, originalY);
|
||||||
|
moveY.setDuration(1200);
|
||||||
|
moveY.setInterpolator(new DecelerateInterpolator(2f));
|
||||||
|
|
||||||
|
// 渐显
|
||||||
|
ObjectAnimator fadeIn = ObjectAnimator.ofFloat(wishTag, "alpha", 0f, 1f);
|
||||||
|
fadeIn.setDuration(800);
|
||||||
|
|
||||||
|
// 缩放
|
||||||
|
ObjectAnimator scaleX = ObjectAnimator.ofFloat(wishTag, "scaleX", 0.5f, 1.1f, 1f);
|
||||||
|
scaleX.setDuration(1200);
|
||||||
|
scaleX.setInterpolator(new OvershootInterpolator(1.2f));
|
||||||
|
|
||||||
|
ObjectAnimator scaleY = ObjectAnimator.ofFloat(wishTag, "scaleY", 0.5f, 1.1f, 1f);
|
||||||
|
scaleY.setDuration(1200);
|
||||||
|
scaleY.setInterpolator(new OvershootInterpolator(1.2f));
|
||||||
|
|
||||||
|
// 轻微旋转
|
||||||
|
ObjectAnimator rotate = ObjectAnimator.ofFloat(wishTag, "rotation", 0f, -5f, 5f, -3f, 3f, 0f);
|
||||||
|
rotate.setDuration(1500);
|
||||||
|
|
||||||
|
flyInSet.playTogether(moveX, moveY, fadeIn, scaleX, scaleY, rotate);
|
||||||
|
|
||||||
|
flyInSet.addListener(new android.animation.AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(android.animation.Animator animation) {
|
||||||
|
// 确保位置正确
|
||||||
|
wishTag.setX(originalX);
|
||||||
|
wishTag.setY(originalY);
|
||||||
|
// 开始摇摆动画
|
||||||
|
startSwingAnimation(wishTag);
|
||||||
|
if (onComplete != null) {
|
||||||
|
onComplete.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
flyInSet.start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fadeOut.start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,10 @@ import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Queue;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
|
|
@ -55,6 +57,9 @@ public class WishTreeActivity extends AppCompatActivity {
|
||||||
// 心愿数据
|
// 心愿数据
|
||||||
private List<WishtreeResponse.Wish> myWishes = new ArrayList<>();
|
private List<WishtreeResponse.Wish> myWishes = new ArrayList<>();
|
||||||
private WishtreeResponse.Festival currentFestival;
|
private WishtreeResponse.Festival currentFestival;
|
||||||
|
|
||||||
|
// 心愿总数(从服务器获取)
|
||||||
|
private int totalWishCount = 0;
|
||||||
|
|
||||||
// 心愿牌View数组
|
// 心愿牌View数组
|
||||||
private TextView[] wishTags;
|
private TextView[] wishTags;
|
||||||
|
|
@ -72,6 +77,9 @@ public class WishTreeActivity extends AppCompatActivity {
|
||||||
R.drawable.bg_wish_tag_blue,
|
R.drawable.bg_wish_tag_blue,
|
||||||
R.drawable.bg_wish_tag_pink
|
R.drawable.bg_wish_tag_pink
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 心愿牌位置队列(先进先出,记录心愿牌索引的添加顺序)
|
||||||
|
private Queue<Integer> wishTagQueue = new LinkedList<>();
|
||||||
|
|
||||||
public static void start(Context context) {
|
public static void start(Context context) {
|
||||||
context.startActivity(new Intent(context, WishTreeActivity.class));
|
context.startActivity(new Intent(context, WishTreeActivity.class));
|
||||||
|
|
@ -182,17 +190,24 @@ public class WishTreeActivity extends AppCompatActivity {
|
||||||
* 从服务端加载我的心愿
|
* 从服务端加载我的心愿
|
||||||
*/
|
*/
|
||||||
private void loadMyWishes() {
|
private void loadMyWishes() {
|
||||||
|
// 重新加载时重置队列
|
||||||
|
resetWishTagQueue();
|
||||||
|
|
||||||
apiService.getMyWishes(1, 10).enqueue(new Callback<ApiResponse<WishtreeResponse.WishPage>>() {
|
apiService.getMyWishes(1, 10).enqueue(new Callback<ApiResponse<WishtreeResponse.WishPage>>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(@NonNull Call<ApiResponse<WishtreeResponse.WishPage>> call,
|
public void onResponse(@NonNull Call<ApiResponse<WishtreeResponse.WishPage>> call,
|
||||||
@NonNull Response<ApiResponse<WishtreeResponse.WishPage>> response) {
|
@NonNull Response<ApiResponse<WishtreeResponse.WishPage>> response) {
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().getData() != null) {
|
if (response.isSuccessful() && response.body() != null && response.body().getData() != null) {
|
||||||
myWishes = response.body().getData().list;
|
WishtreeResponse.WishPage page = response.body().getData();
|
||||||
|
myWishes = page.list;
|
||||||
if (myWishes == null) myWishes = new ArrayList<>();
|
if (myWishes == null) myWishes = new ArrayList<>();
|
||||||
|
// 使用服务器返回的总数
|
||||||
|
totalWishCount = page.total;
|
||||||
updateWishTags(false);
|
updateWishTags(false);
|
||||||
updateWishCount();
|
updateWishCount();
|
||||||
} else {
|
} else {
|
||||||
myWishes = new ArrayList<>();
|
myWishes = new ArrayList<>();
|
||||||
|
totalWishCount = 0;
|
||||||
updateWishTags(false);
|
updateWishTags(false);
|
||||||
updateWishCount();
|
updateWishCount();
|
||||||
}
|
}
|
||||||
|
|
@ -210,6 +225,9 @@ public class WishTreeActivity extends AppCompatActivity {
|
||||||
* @param animate 是否播放动画
|
* @param animate 是否播放动画
|
||||||
*/
|
*/
|
||||||
private void updateWishTags(boolean animate) {
|
private void updateWishTags(boolean animate) {
|
||||||
|
// 只在队列为空时初始化(首次加载或重新加载)
|
||||||
|
boolean needInitQueue = wishTagQueue.isEmpty();
|
||||||
|
|
||||||
for (int i = 0; i < wishTags.length; i++) {
|
for (int i = 0; i < wishTags.length; i++) {
|
||||||
if (wishTags[i] == null) continue;
|
if (wishTags[i] == null) continue;
|
||||||
|
|
||||||
|
|
@ -220,6 +238,11 @@ public class WishTreeActivity extends AppCompatActivity {
|
||||||
wishTags[i].setText(verticalText);
|
wishTags[i].setText(verticalText);
|
||||||
wishTags[i].setBackgroundResource(tagBackgrounds[i % tagBackgrounds.length]);
|
wishTags[i].setBackgroundResource(tagBackgrounds[i % tagBackgrounds.length]);
|
||||||
|
|
||||||
|
// 只在首次加载时按顺序加入队列
|
||||||
|
if (needInitQueue) {
|
||||||
|
wishTagQueue.offer(i);
|
||||||
|
}
|
||||||
|
|
||||||
if (animate) {
|
if (animate) {
|
||||||
WishTagAnimator.animateAppear(wishTags[i], i * 150L);
|
WishTagAnimator.animateAppear(wishTags[i], i * 150L);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -232,6 +255,13 @@ public class WishTreeActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新加载时重置队列
|
||||||
|
*/
|
||||||
|
private void resetWishTagQueue() {
|
||||||
|
wishTagQueue.clear();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 格式化文字为竖向显示
|
* 格式化文字为竖向显示
|
||||||
|
|
@ -252,9 +282,9 @@ public class WishTreeActivity extends AppCompatActivity {
|
||||||
* 更新祈愿值显示
|
* 更新祈愿值显示
|
||||||
*/
|
*/
|
||||||
private void updateWishCount() {
|
private void updateWishCount() {
|
||||||
int count = myWishes != null ? myWishes.size() : 0;
|
// 使用服务器返回的总数
|
||||||
binding.tvWishCount.setText("祈愿值:" + count + "/100");
|
binding.tvWishCount.setText("祈愿值:" + totalWishCount + "/100");
|
||||||
binding.progressWish.setProgress(count);
|
binding.progressWish.setProgress(Math.min(totalWishCount, 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -350,6 +380,9 @@ public class WishTreeActivity extends AppCompatActivity {
|
||||||
if (newWish != null) {
|
if (newWish != null) {
|
||||||
myWishes.add(0, newWish);
|
myWishes.add(0, newWish);
|
||||||
|
|
||||||
|
// 增加总数
|
||||||
|
totalWishCount++;
|
||||||
|
|
||||||
// 播放飘动动画
|
// 播放飘动动画
|
||||||
animateNewWishTag(newWish);
|
animateNewWishTag(newWish);
|
||||||
|
|
||||||
|
|
@ -371,8 +404,88 @@ public class WishTreeActivity extends AppCompatActivity {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 播放新心愿飘到树上的动画
|
* 播放新心愿飘到树上的动画
|
||||||
|
* 如果心愿数量超过10个,会替换最早的心愿(旧心愿渐隐,新心愿从底部飘上来)
|
||||||
*/
|
*/
|
||||||
private void animateNewWishTag(WishtreeResponse.Wish wish) {
|
private void animateNewWishTag(WishtreeResponse.Wish wish) {
|
||||||
|
FrameLayout container = findViewById(R.id.wishTagsContainer);
|
||||||
|
if (container == null) return;
|
||||||
|
|
||||||
|
int currentCount = myWishes.size();
|
||||||
|
|
||||||
|
if (currentCount <= wishTags.length) {
|
||||||
|
// 心愿数量不超过10个,找一个空位置添加
|
||||||
|
int targetIndex = findEmptyOrRandomSlot();
|
||||||
|
if (targetIndex < 0 || targetIndex >= wishTags.length) return;
|
||||||
|
|
||||||
|
TextView targetTag = wishTags[targetIndex];
|
||||||
|
if (targetTag == null) return;
|
||||||
|
|
||||||
|
// 设置心愿内容
|
||||||
|
String verticalText = formatVerticalText(wish.content, 5);
|
||||||
|
targetTag.setText(verticalText);
|
||||||
|
targetTag.setBackgroundResource(tagBackgrounds[targetIndex % tagBackgrounds.length]);
|
||||||
|
|
||||||
|
// 将新位置加入队列末尾
|
||||||
|
wishTagQueue.offer(targetIndex);
|
||||||
|
|
||||||
|
// 从屏幕底部中央飘到目标位置
|
||||||
|
float startX = container.getWidth() / 2f - targetTag.getWidth() / 2f;
|
||||||
|
float startY = container.getHeight();
|
||||||
|
float targetX = targetTag.getX();
|
||||||
|
float targetY = targetTag.getY();
|
||||||
|
|
||||||
|
WishTagAnimator.animateWishToTree(targetTag, startX, startY, targetX, targetY, 1500, () -> {
|
||||||
|
// 动画完成后启动摇摆动画
|
||||||
|
WishTagAnimator.startSwingAnimation(targetTag);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 心愿数量超过10个,从队列头部取出最早加入的心愿牌位置
|
||||||
|
Integer oldestIndex = wishTagQueue.poll();
|
||||||
|
if (oldestIndex == null) {
|
||||||
|
oldestIndex = 0; // 队列为空时默认第一个
|
||||||
|
}
|
||||||
|
|
||||||
|
TextView oldTag = wishTags[oldestIndex];
|
||||||
|
if (oldTag == null) return;
|
||||||
|
|
||||||
|
// 将这个位置重新加入队列末尾(因为新心愿会占用这个位置)
|
||||||
|
wishTagQueue.offer(oldestIndex);
|
||||||
|
|
||||||
|
// 设置新心愿内容
|
||||||
|
String verticalText = formatVerticalText(wish.content, 5);
|
||||||
|
int newBackground = tagBackgrounds[oldestIndex % tagBackgrounds.length];
|
||||||
|
|
||||||
|
// 获取目标位置
|
||||||
|
float targetX = oldTag.getX();
|
||||||
|
float targetY = oldTag.getY();
|
||||||
|
float startX = container.getWidth() / 2f - oldTag.getWidth() / 2f;
|
||||||
|
float startY = container.getHeight();
|
||||||
|
|
||||||
|
// 旧心愿渐隐消失,同时新心愿从底部飘上来
|
||||||
|
WishTagAnimator.animateReplaceWithFlyIn(oldTag, verticalText, newBackground,
|
||||||
|
startX, startY, targetX, targetY, () -> {
|
||||||
|
// 动画完成后启动摇摆动画
|
||||||
|
WishTagAnimator.startSwingAnimation(oldTag);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 找一个空位置或随机位置
|
||||||
|
*/
|
||||||
|
private int findEmptyOrRandomSlot() {
|
||||||
|
// 先找空位置
|
||||||
|
for (int i = 0; i < wishTags.length; i++) {
|
||||||
|
if (wishTags[i] != null && wishTags[i].getVisibility() == View.GONE) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 没有空位置,随机选一个
|
||||||
|
return (int) (Math.random() * wishTags.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保留原来的方法签名用于兼容
|
||||||
|
private void animateNewWishTagLegacy(WishtreeResponse.Wish wish) {
|
||||||
// 找到第一个可用的心愿牌位置
|
// 找到第一个可用的心愿牌位置
|
||||||
int targetIndex = Math.min(myWishes.size() - 1, wishTags.length - 1);
|
int targetIndex = Math.min(myWishes.size() - 1, wishTags.length - 1);
|
||||||
if (targetIndex < 0 || targetIndex >= wishTags.length) return;
|
if (targetIndex < 0 || targetIndex >= wishTags.length) return;
|
||||||
|
|
|
||||||
|
|
@ -112,11 +112,57 @@ public class WorkDetailActivity extends AppCompatActivity {
|
||||||
// 设置作品描述
|
// 设置作品描述
|
||||||
setupDescription();
|
setupDescription();
|
||||||
|
|
||||||
// 显示媒体内容
|
// 判断内容类型
|
||||||
if (workItem.getType() == WorkItem.WorkType.VIDEO) {
|
boolean hasVideo = workItem.getType() == WorkItem.WorkType.VIDEO ||
|
||||||
// 显示视频(暂时显示封面图,后续可以集成ExoPlayer)
|
!TextUtils.isEmpty(workItem.getVideoUrl());
|
||||||
|
|
||||||
|
// 检查是否有有效的图片列表(不包括封面,因为封面可能是无效的占位图)
|
||||||
|
boolean hasValidImageList = false;
|
||||||
|
List<String> imageUrls = workItem.getImageUrls();
|
||||||
|
if (imageUrls != null && !imageUrls.isEmpty()) {
|
||||||
|
for (String url : imageUrls) {
|
||||||
|
if (url != null && !url.isEmpty() && !isTextOnlyPlaceholder(url)) {
|
||||||
|
hasValidImageList = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查imageUris
|
||||||
|
List<Uri> imageUris = workItem.getImageUris();
|
||||||
|
if (!hasValidImageList && imageUris != null && !imageUris.isEmpty()) {
|
||||||
|
for (Uri uri : imageUris) {
|
||||||
|
if (uri != null && !isTextOnlyPlaceholder(uri.toString())) {
|
||||||
|
hasValidImageList = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String content = workItem.getDescription();
|
||||||
|
boolean hasContent = !TextUtils.isEmpty(content);
|
||||||
|
|
||||||
|
// 判断是否为纯文字动态:
|
||||||
|
// 核心逻辑:如果没有图片列表、没有视频,且有文字内容,就是纯文字动态
|
||||||
|
boolean isTextOnly = !hasValidImageList && !hasVideo && hasContent;
|
||||||
|
|
||||||
|
android.util.Log.d("WorkDetail", "内容类型判断: hasVideo=" + hasVideo +
|
||||||
|
", hasValidImageList=" + hasValidImageList + ", hasContent=" + hasContent +
|
||||||
|
", isTextOnly=" + isTextOnly);
|
||||||
|
|
||||||
|
if (isTextOnly) {
|
||||||
|
// 纯文字动态:显示文字内容区域
|
||||||
|
binding.imageViewPager.setVisibility(View.GONE);
|
||||||
|
binding.videoContainer.setVisibility(View.GONE);
|
||||||
|
binding.textContentContainer.setVisibility(View.VISIBLE);
|
||||||
|
binding.textContentText.setText(content);
|
||||||
|
|
||||||
|
android.util.Log.d("WorkDetail", "显示纯文字动态: " + content);
|
||||||
|
} else if (hasVideo) {
|
||||||
|
// 显示视频
|
||||||
binding.imageViewPager.setVisibility(View.GONE);
|
binding.imageViewPager.setVisibility(View.GONE);
|
||||||
binding.videoContainer.setVisibility(View.VISIBLE);
|
binding.videoContainer.setVisibility(View.VISIBLE);
|
||||||
|
binding.textContentContainer.setVisibility(View.GONE);
|
||||||
|
|
||||||
// 使用封面URI或视频URI显示预览图
|
// 使用封面URI或视频URI显示预览图
|
||||||
Uri videoUri = workItem.getVideoUri();
|
Uri videoUri = workItem.getVideoUri();
|
||||||
|
|
@ -162,16 +208,18 @@ public class WorkDetailActivity extends AppCompatActivity {
|
||||||
// 显示图片
|
// 显示图片
|
||||||
binding.videoContainer.setVisibility(View.GONE);
|
binding.videoContainer.setVisibility(View.GONE);
|
||||||
binding.imageViewPager.setVisibility(View.VISIBLE);
|
binding.imageViewPager.setVisibility(View.VISIBLE);
|
||||||
|
binding.textContentContainer.setVisibility(View.GONE);
|
||||||
|
|
||||||
List<Uri> imageUris = workItem.getImageUris();
|
// 重新获取imageUris(不重复定义变量)
|
||||||
|
List<Uri> displayImageUris = workItem.getImageUris();
|
||||||
// 如果 imageUris 为空,尝试从 imageUrls 恢复
|
// 如果 imageUris 为空,尝试从 imageUrls 恢复
|
||||||
if ((imageUris == null || imageUris.isEmpty()) && workItem.getImageUrls() != null && !workItem.getImageUrls().isEmpty()) {
|
if ((displayImageUris == null || displayImageUris.isEmpty()) && workItem.getImageUrls() != null && !workItem.getImageUrls().isEmpty()) {
|
||||||
imageUris = new ArrayList<>();
|
displayImageUris = new ArrayList<>();
|
||||||
for (String url : workItem.getImageUrls()) {
|
for (String url : workItem.getImageUrls()) {
|
||||||
if (url != null && !url.isEmpty()) {
|
if (url != null && !url.isEmpty()) {
|
||||||
try {
|
try {
|
||||||
Uri uri = Uri.parse(url);
|
Uri uri = Uri.parse(url);
|
||||||
imageUris.add(uri);
|
displayImageUris.add(uri);
|
||||||
android.util.Log.d("WorkDetail", "恢复图片URI: " + uri);
|
android.util.Log.d("WorkDetail", "恢复图片URI: " + uri);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
android.util.Log.e("WorkDetail", "解析图片URI失败: " + url, e);
|
android.util.Log.e("WorkDetail", "解析图片URI失败: " + url, e);
|
||||||
|
|
@ -181,28 +229,35 @@ public class WorkDetailActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果还是没有图片,尝试使用封面
|
// 如果还是没有图片,尝试使用封面
|
||||||
if ((imageUris == null || imageUris.isEmpty()) && workItem.getCoverUri() != null) {
|
if ((displayImageUris == null || displayImageUris.isEmpty()) && workItem.getCoverUri() != null) {
|
||||||
imageUris = new ArrayList<>();
|
displayImageUris = new ArrayList<>();
|
||||||
imageUris.add(workItem.getCoverUri());
|
displayImageUris.add(workItem.getCoverUri());
|
||||||
android.util.Log.d("WorkDetail", "使用封面作为图片: " + workItem.getCoverUri());
|
android.util.Log.d("WorkDetail", "使用封面作为图片: " + workItem.getCoverUri());
|
||||||
} else if ((imageUris == null || imageUris.isEmpty()) && !TextUtils.isEmpty(workItem.getCoverUrl())) {
|
} else if ((displayImageUris == null || displayImageUris.isEmpty()) && !TextUtils.isEmpty(workItem.getCoverUrl())) {
|
||||||
try {
|
try {
|
||||||
imageUris = new ArrayList<>();
|
displayImageUris = new ArrayList<>();
|
||||||
imageUris.add(Uri.parse(workItem.getCoverUrl()));
|
displayImageUris.add(Uri.parse(workItem.getCoverUrl()));
|
||||||
android.util.Log.d("WorkDetail", "使用封面URL作为图片: " + workItem.getCoverUrl());
|
android.util.Log.d("WorkDetail", "使用封面URL作为图片: " + workItem.getCoverUrl());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
android.util.Log.e("WorkDetail", "解析封面URL失败", e);
|
android.util.Log.e("WorkDetail", "解析封面URL失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageUris != null && !imageUris.isEmpty()) {
|
if (displayImageUris != null && !displayImageUris.isEmpty()) {
|
||||||
android.util.Log.d("WorkDetail", "显示图片数量: " + imageUris.size());
|
android.util.Log.d("WorkDetail", "显示图片数量: " + displayImageUris.size());
|
||||||
imageAdapter = new ImagePagerAdapter(imageUris);
|
imageAdapter = new ImagePagerAdapter(displayImageUris);
|
||||||
binding.imageViewPager.setAdapter(imageAdapter);
|
binding.imageViewPager.setAdapter(imageAdapter);
|
||||||
} else {
|
} else {
|
||||||
android.util.Log.w("WorkDetail", "没有可显示的图片");
|
android.util.Log.w("WorkDetail", "没有可显示的图片");
|
||||||
binding.imageViewPager.setVisibility(View.GONE);
|
// 如果没有图片但有内容,显示纯文字
|
||||||
Toast.makeText(this, "图片加载失败", Toast.LENGTH_SHORT).show();
|
if (hasContent) {
|
||||||
|
binding.imageViewPager.setVisibility(View.GONE);
|
||||||
|
binding.textContentContainer.setVisibility(View.VISIBLE);
|
||||||
|
binding.textContentText.setText(content);
|
||||||
|
} else {
|
||||||
|
binding.imageViewPager.setVisibility(View.GONE);
|
||||||
|
Toast.makeText(this, "暂无内容", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1322,5 +1377,47 @@ public class WorkDetailActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断URL是否为纯文字动态的占位图
|
||||||
|
* 后端为纯文字动态设置的特殊封面地址
|
||||||
|
*/
|
||||||
|
private boolean isTextOnlyPlaceholder(String url) {
|
||||||
|
if (url == null || url.trim().isEmpty()) {
|
||||||
|
return true; // 空URL也视为占位图
|
||||||
|
}
|
||||||
|
|
||||||
|
// 特殊占位图地址列表
|
||||||
|
String[] placeholderPatterns = {
|
||||||
|
"TEXT_ONLY_DYNAMIC_PLACEHOLDER", // Android端发布纯文字动态时设置的标识
|
||||||
|
"text_only_placeholder", // 纯文字动态占位图标识
|
||||||
|
"placeholder/text", // 文字占位图路径
|
||||||
|
"default_text_cover", // 默认文字封面
|
||||||
|
"no_image_placeholder", // 无图片占位图
|
||||||
|
"/placeholder.", // 通用占位图
|
||||||
|
"crmebimage/public/content/", // 后端默认内容图片路径
|
||||||
|
"default_cover", // 默认封面
|
||||||
|
"empty_cover", // 空封面
|
||||||
|
};
|
||||||
|
|
||||||
|
String lowerUrl = url.toLowerCase();
|
||||||
|
for (String pattern : placeholderPatterns) {
|
||||||
|
if (lowerUrl.contains(pattern.toLowerCase())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否为空白图片或默认图片(根据文件名判断)
|
||||||
|
if (lowerUrl.endsWith("/default.png") ||
|
||||||
|
lowerUrl.endsWith("/default.jpg") ||
|
||||||
|
lowerUrl.endsWith("/placeholder.png") ||
|
||||||
|
lowerUrl.endsWith("/placeholder.jpg") ||
|
||||||
|
lowerUrl.endsWith("/empty.png") ||
|
||||||
|
lowerUrl.endsWith("/empty.jpg")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ public class WorksResponse {
|
||||||
private Long id; // 作品ID
|
private Long id; // 作品ID
|
||||||
private String title; // 作品标题
|
private String title; // 作品标题
|
||||||
private String description; // 作品描述
|
private String description; // 作品描述
|
||||||
|
private String content; // 作品内容(纯文字动态)
|
||||||
private String type; // 作品类型:IMAGE 或 VIDEO
|
private String type; // 作品类型:IMAGE 或 VIDEO
|
||||||
private String coverUrl; // 封面图片URL
|
private String coverUrl; // 封面图片URL
|
||||||
private String videoUrl; // 视频URL(视频作品)
|
private String videoUrl; // 视频URL(视频作品)
|
||||||
|
|
@ -29,6 +30,8 @@ public class WorksResponse {
|
||||||
private Boolean isOwner; // 是否是当前用户的作品
|
private Boolean isOwner; // 是否是当前用户的作品
|
||||||
private Integer isHot; // 是否热门:1-是 0-否
|
private Integer isHot; // 是否热门:1-是 0-否
|
||||||
private String hotTime; // 设置热门的时间
|
private String hotTime; // 设置热门的时间
|
||||||
|
private String category; // 分类编码
|
||||||
|
private String categoryName; // 分类名称
|
||||||
|
|
||||||
public Long getId() {
|
public Long getId() {
|
||||||
return id;
|
return id;
|
||||||
|
|
@ -47,12 +50,28 @@ public class WorksResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
return description;
|
// 优先返回description,如果为空则返回content
|
||||||
|
if (description != null && !description.isEmpty()) {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDescription(String description) {
|
public void setDescription(String description) {
|
||||||
this.description = description;
|
this.description = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getContent() {
|
||||||
|
// 优先返回content,如果为空则返回description
|
||||||
|
if (content != null && !content.isEmpty()) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContent(String content) {
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
public String getType() {
|
public String getType() {
|
||||||
return type;
|
return type;
|
||||||
|
|
@ -223,4 +242,20 @@ public class WorksResponse {
|
||||||
public void setHotTime(String hotTime) {
|
public void setHotTime(String hotTime) {
|
||||||
this.hotTime = hotTime;
|
this.hotTime = hotTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCategory() {
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCategory(String category) {
|
||||||
|
this.category = category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCategoryName() {
|
||||||
|
return categoryName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCategoryName(String categoryName) {
|
||||||
|
this.categoryName = categoryName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- 底部导航图标颜色 - 选中时金色发光 -->
|
<!-- 底部导航图标颜色 - 选中时紫色 -->
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:color="#FFD700" android:state_checked="true" />
|
<item android:color="#A855F7" android:state_checked="true" />
|
||||||
<item android:color="#99FFFFFF" android:state_checked="false" />
|
<item android:color="#999999" android:state_checked="false" />
|
||||||
</selector>
|
</selector>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- 底部导航 - 深紫色半透明 + 顶部微光边 -->
|
<!-- 底部导航 - 白色背景 + 顶部灰色边线 -->
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<!-- 深紫色半透明背景 -->
|
<!-- 白色背景 -->
|
||||||
<item>
|
<item>
|
||||||
<shape android:shape="rectangle">
|
<shape android:shape="rectangle">
|
||||||
<solid android:color="#F0121225" />
|
<solid android:color="#FFFFFF" />
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
<!-- 顶部微光边线 -->
|
<!-- 顶部灰色边线 -->
|
||||||
<item android:bottom="-1dp" android:left="-1dp" android:right="-1dp">
|
<item android:bottom="-1dp" android:left="-1dp" android:right="-1dp">
|
||||||
<shape android:shape="rectangle">
|
<shape android:shape="rectangle">
|
||||||
<stroke
|
<stroke
|
||||||
android:width="1dp"
|
android:width="0.5dp"
|
||||||
android:color="#20A855F7" />
|
android:color="#E5E5E5" />
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
|
|
||||||
|
|
@ -30,15 +30,15 @@
|
||||||
<com.google.android.material.bottomnavigation.BottomNavigationView
|
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
android:id="@+id/bottomNavigation"
|
android:id="@+id/bottomNavigation"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="56dp"
|
||||||
android:minHeight="48dp"
|
android:minHeight="56dp"
|
||||||
android:background="@android:color/white"
|
android:background="@android:color/white"
|
||||||
android:paddingTop="2dp"
|
android:paddingTop="2dp"
|
||||||
android:paddingBottom="2dp"
|
android:paddingBottom="2dp"
|
||||||
android:fitsSystemWindows="false"
|
android:fitsSystemWindows="false"
|
||||||
android:elevation="8dp"
|
android:elevation="8dp"
|
||||||
app:itemIconTint="@color/bottom_nav_item_color"
|
app:itemIconTint="@color/bottom_nav_icon_glow_color"
|
||||||
app:itemTextColor="@color/bottom_nav_item_color"
|
app:itemTextColor="@color/bottom_nav_icon_glow_color"
|
||||||
app:itemIconSize="22dp"
|
app:itemIconSize="22dp"
|
||||||
app:itemTextAppearanceActive="@style/BottomNavigationView.TextAppearance"
|
app:itemTextAppearanceActive="@style/BottomNavigationView.TextAppearance"
|
||||||
app:itemTextAppearanceInactive="@style/BottomNavigationView.TextAppearance"
|
app:itemTextAppearanceInactive="@style/BottomNavigationView.TextAppearance"
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,10 @@
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
app:tabIndicatorColor="#FF4757"
|
app:tabIndicatorColor="#A855F7"
|
||||||
app:tabIndicatorFullWidth="false"
|
app:tabIndicatorFullWidth="false"
|
||||||
app:tabIndicatorHeight="3dp"
|
app:tabIndicatorHeight="3dp"
|
||||||
app:tabSelectedTextColor="#333333"
|
app:tabSelectedTextColor="#A855F7"
|
||||||
app:tabTextColor="#999999"
|
app:tabTextColor="#999999"
|
||||||
app:tabRippleColor="@android:color/transparent"
|
app:tabRippleColor="@android:color/transparent"
|
||||||
app:tabTextAppearance="@style/TabTextAppearance"
|
app:tabTextAppearance="@style/TabTextAppearance"
|
||||||
|
|
@ -167,11 +167,11 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:paddingStart="8dp"
|
android:paddingStart="8dp"
|
||||||
app:tabIndicatorColor="#FF4757"
|
app:tabIndicatorColor="#A855F7"
|
||||||
app:tabIndicatorFullWidth="false"
|
app:tabIndicatorFullWidth="false"
|
||||||
app:tabMode="scrollable"
|
app:tabMode="scrollable"
|
||||||
app:tabGravity="start"
|
app:tabGravity="start"
|
||||||
app:tabSelectedTextColor="#333333"
|
app:tabSelectedTextColor="#A855F7"
|
||||||
app:tabTextColor="#999999">
|
app:tabTextColor="#999999">
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabItem
|
<com.google.android.material.tabs.TabItem
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/deep_night_bg">
|
android:background="@color/white">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@drawable/bg_appbar_glass"
|
android:background="@color/white"
|
||||||
app:elevation="0dp">
|
app:elevation="0dp">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="💬 消息"
|
android:text="💬 消息"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/black"
|
||||||
android:textSize="@dimen/text_title"
|
android:textSize="@dimen/text_title"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
android:padding="6dp"
|
android:padding="6dp"
|
||||||
android:contentDescription="搜索"
|
android:contentDescription="搜索"
|
||||||
android:src="@drawable/ic_search_24"
|
android:src="@drawable/ic_search_24"
|
||||||
app:tint="@color/white"
|
app:tint="@color/black"
|
||||||
app:layout_constraintEnd_toStartOf="@id/addIcon"
|
app:layout_constraintEnd_toStartOf="@id/addIcon"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent" />
|
app:layout_constraintBottom_toBottomOf="parent" />
|
||||||
|
|
@ -55,7 +55,7 @@
|
||||||
android:padding="6dp"
|
android:padding="6dp"
|
||||||
android:contentDescription="添加"
|
android:contentDescription="添加"
|
||||||
android:src="@drawable/ic_add_24"
|
android:src="@drawable/ic_add_24"
|
||||||
app:tint="@color/white"
|
app:tint="@color/black"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent" />
|
app:layout_constraintBottom_toBottomOf="parent" />
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,12 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<!-- 心愿牌1 - 左边树枝 -->
|
<!-- 心愿牌1 - 左上树枝 -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/wishTag1"
|
android:id="@+id/wishTag1"
|
||||||
android:layout_width="26dp"
|
android:layout_width="26dp"
|
||||||
android:layout_height="68dp"
|
android:layout_height="68dp"
|
||||||
android:layout_marginStart="18dp"
|
android:layout_marginStart="55dp"
|
||||||
android:layout_marginTop="200dp"
|
android:layout_marginTop="200dp"
|
||||||
android:background="@drawable/bg_wish_tag_pink"
|
android:background="@drawable/bg_wish_tag_pink"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
|
|
@ -43,13 +43,13 @@
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:elevation="2dp" />
|
android:elevation="2dp" />
|
||||||
|
|
||||||
<!-- 心愿牌2 - 左上树枝 -->
|
<!-- 心愿牌2 - 左侧中树枝 -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/wishTag2"
|
android:id="@+id/wishTag2"
|
||||||
android:layout_width="26dp"
|
android:layout_width="26dp"
|
||||||
android:layout_height="68dp"
|
android:layout_height="68dp"
|
||||||
android:layout_marginStart="55dp"
|
android:layout_marginStart="95dp"
|
||||||
android:layout_marginTop="175dp"
|
android:layout_marginTop="230dp"
|
||||||
android:background="@drawable/bg_wish_tag_green"
|
android:background="@drawable/bg_wish_tag_green"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:paddingTop="14dp"
|
android:paddingTop="14dp"
|
||||||
|
|
@ -61,13 +61,13 @@
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:elevation="2dp" />
|
android:elevation="2dp" />
|
||||||
|
|
||||||
<!-- 心愿牌3 - 中左树枝 -->
|
<!-- 心愿牌3 - 中间偏左树枝 -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/wishTag3"
|
android:id="@+id/wishTag3"
|
||||||
android:layout_width="26dp"
|
android:layout_width="26dp"
|
||||||
android:layout_height="68dp"
|
android:layout_height="68dp"
|
||||||
android:layout_marginStart="95dp"
|
android:layout_marginStart="150dp"
|
||||||
android:layout_marginTop="160dp"
|
android:layout_marginTop="195dp"
|
||||||
android:background="@drawable/bg_wish_tag_gold"
|
android:background="@drawable/bg_wish_tag_gold"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:paddingTop="14dp"
|
android:paddingTop="14dp"
|
||||||
|
|
@ -79,13 +79,13 @@
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:elevation="2dp" />
|
android:elevation="2dp" />
|
||||||
|
|
||||||
<!-- 心愿牌4 - 中间树枝 -->
|
<!-- 心愿牌4 - 中间偏右树枝 -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/wishTag4"
|
android:id="@+id/wishTag4"
|
||||||
android:layout_width="26dp"
|
android:layout_width="26dp"
|
||||||
android:layout_height="68dp"
|
android:layout_height="68dp"
|
||||||
android:layout_marginStart="145dp"
|
android:layout_marginStart="210dp"
|
||||||
android:layout_marginTop="145dp"
|
android:layout_marginTop="210dp"
|
||||||
android:background="@drawable/bg_wish_tag_blue"
|
android:background="@drawable/bg_wish_tag_blue"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:paddingTop="14dp"
|
android:paddingTop="14dp"
|
||||||
|
|
@ -97,13 +97,13 @@
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:elevation="2dp" />
|
android:elevation="2dp" />
|
||||||
|
|
||||||
<!-- 心愿牌5 - 中右树枝 -->
|
<!-- 心愿牌5 - 右上树枝 -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/wishTag5"
|
android:id="@+id/wishTag5"
|
||||||
android:layout_width="26dp"
|
android:layout_width="26dp"
|
||||||
android:layout_height="68dp"
|
android:layout_height="68dp"
|
||||||
android:layout_marginStart="200dp"
|
android:layout_marginStart="280dp"
|
||||||
android:layout_marginTop="155dp"
|
android:layout_marginTop="195dp"
|
||||||
android:background="@drawable/bg_wish_tag_purple"
|
android:background="@drawable/bg_wish_tag_purple"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:paddingTop="14dp"
|
android:paddingTop="14dp"
|
||||||
|
|
@ -115,13 +115,13 @@
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:elevation="2dp" />
|
android:elevation="2dp" />
|
||||||
|
|
||||||
<!-- 心愿牌6 - 右上树枝 -->
|
<!-- 心愿牌6 - 右侧树枝末端 -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/wishTag6"
|
android:id="@+id/wishTag6"
|
||||||
android:layout_width="26dp"
|
android:layout_width="26dp"
|
||||||
android:layout_height="68dp"
|
android:layout_height="68dp"
|
||||||
android:layout_marginStart="255dp"
|
android:layout_marginStart="330dp"
|
||||||
android:layout_marginTop="165dp"
|
android:layout_marginTop="320dp"
|
||||||
android:background="@drawable/bg_wish_tag_yellow"
|
android:background="@drawable/bg_wish_tag_yellow"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:paddingTop="14dp"
|
android:paddingTop="14dp"
|
||||||
|
|
@ -133,13 +133,13 @@
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:elevation="2dp" />
|
android:elevation="2dp" />
|
||||||
|
|
||||||
<!-- 心愿牌7 - 右边树枝 -->
|
<!-- 心愿牌7 - 左下树枝 -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/wishTag7"
|
android:id="@+id/wishTag7"
|
||||||
android:layout_width="26dp"
|
android:layout_width="26dp"
|
||||||
android:layout_height="68dp"
|
android:layout_height="68dp"
|
||||||
android:layout_marginStart="310dp"
|
android:layout_marginStart="25dp"
|
||||||
android:layout_marginTop="185dp"
|
android:layout_marginTop="320dp"
|
||||||
android:background="@drawable/bg_wish_tag_orange"
|
android:background="@drawable/bg_wish_tag_orange"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:paddingTop="14dp"
|
android:paddingTop="14dp"
|
||||||
|
|
@ -151,13 +151,13 @@
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:elevation="2dp" />
|
android:elevation="2dp" />
|
||||||
|
|
||||||
<!-- 心愿牌8 - 左下树枝 -->
|
<!-- 心愿牌8 - 左下中树枝 -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/wishTag8"
|
android:id="@+id/wishTag8"
|
||||||
android:layout_width="26dp"
|
android:layout_width="26dp"
|
||||||
android:layout_height="68dp"
|
android:layout_height="68dp"
|
||||||
android:layout_marginStart="35dp"
|
android:layout_marginStart="75dp"
|
||||||
android:layout_marginTop="280dp"
|
android:layout_marginTop="290dp"
|
||||||
android:background="@drawable/bg_wish_tag_green"
|
android:background="@drawable/bg_wish_tag_green"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:paddingTop="14dp"
|
android:paddingTop="14dp"
|
||||||
|
|
@ -174,8 +174,8 @@
|
||||||
android:id="@+id/wishTag9"
|
android:id="@+id/wishTag9"
|
||||||
android:layout_width="26dp"
|
android:layout_width="26dp"
|
||||||
android:layout_height="68dp"
|
android:layout_height="68dp"
|
||||||
android:layout_marginStart="75dp"
|
android:layout_marginStart="130dp"
|
||||||
android:layout_marginTop="260dp"
|
android:layout_marginTop="310dp"
|
||||||
android:background="@drawable/bg_wish_tag_blue"
|
android:background="@drawable/bg_wish_tag_blue"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:paddingTop="14dp"
|
android:paddingTop="14dp"
|
||||||
|
|
@ -192,8 +192,8 @@
|
||||||
android:id="@+id/wishTag10"
|
android:id="@+id/wishTag10"
|
||||||
android:layout_width="26dp"
|
android:layout_width="26dp"
|
||||||
android:layout_height="68dp"
|
android:layout_height="68dp"
|
||||||
android:layout_marginStart="280dp"
|
android:layout_marginStart="240dp"
|
||||||
android:layout_marginTop="265dp"
|
android:layout_marginTop="300dp"
|
||||||
android:background="@drawable/bg_wish_tag_pink"
|
android:background="@drawable/bg_wish_tag_pink"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:paddingTop="14dp"
|
android:paddingTop="14dp"
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,36 @@
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<!-- 纯文字动态内容区域 -->
|
||||||
|
<ScrollView
|
||||||
|
android:id="@+id/textContentContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:fillViewport="true"
|
||||||
|
android:background="#1A1A1A">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="20dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="300dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textContentText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:lineSpacingExtra="8dp"
|
||||||
|
android:gravity="start" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
<!-- 右侧操作按钮(用户头像、关注、点赞、收藏、评论) -->
|
<!-- 右侧操作按钮(用户头像、关注、点赞、收藏、评论) -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- 底部导航 - 深紫色半透明 + 金色发光选中 -->
|
<!-- 底部导航 - 白色背景 + 紫色选中 -->
|
||||||
<com.google.android.material.bottomnavigation.BottomNavigationView xmlns:android="http://schemas.android.com/apk/res/android"
|
<com.google.android.material.bottomnavigation.BottomNavigationView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/bottomNavigation"
|
android:id="@+id/bottomNavigation"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="60dp"
|
android:layout_height="48dp"
|
||||||
android:minHeight="60dp"
|
android:minHeight="48dp"
|
||||||
android:background="@drawable/bg_bottom_nav_deep"
|
android:background="@android:color/white"
|
||||||
android:paddingTop="4dp"
|
android:paddingTop="2dp"
|
||||||
android:paddingBottom="4dp"
|
android:paddingBottom="2dp"
|
||||||
android:fitsSystemWindows="false"
|
android:fitsSystemWindows="false"
|
||||||
android:elevation="0dp"
|
android:elevation="8dp"
|
||||||
app:itemIconTint="@color/bottom_nav_icon_glow_color"
|
app:itemIconTint="@color/bottom_nav_icon_glow_color"
|
||||||
app:itemTextColor="@color/bottom_nav_icon_glow_color"
|
app:itemTextColor="@color/bottom_nav_icon_glow_color"
|
||||||
app:itemIconSize="24dp"
|
app:itemIconSize="22dp"
|
||||||
app:itemTextAppearanceActive="@style/BottomNavigationView.TextAppearance"
|
app:itemTextAppearanceActive="@style/BottomNavigationView.TextAppearance"
|
||||||
app:itemTextAppearanceInactive="@style/BottomNavigationView.TextAppearance"
|
app:itemTextAppearanceInactive="@style/BottomNavigationView.TextAppearance"
|
||||||
app:labelVisibilityMode="labeled"
|
app:labelVisibilityMode="labeled"
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<!-- 昵称 - 纯白色 -->
|
<!-- 昵称 - 黑色 -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/title"
|
android:id="@+id/title"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
|
@ -53,7 +53,7 @@
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:text="会话标题"
|
android:text="会话标题"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/black"
|
||||||
android:textSize="@dimen/text_lg"
|
android:textSize="@dimen/text_lg"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
app:layout_constraintEnd_toStartOf="@id/timeText"
|
app:layout_constraintEnd_toStartOf="@id/timeText"
|
||||||
|
|
@ -61,7 +61,7 @@
|
||||||
app:layout_constraintTop_toTopOf="@id/avatarContainer"
|
app:layout_constraintTop_toTopOf="@id/avatarContainer"
|
||||||
app:layout_constraintBottom_toTopOf="@id/lastMessage" />
|
app:layout_constraintBottom_toTopOf="@id/lastMessage" />
|
||||||
|
|
||||||
<!-- 最后一条消息 - 次要灰色 -->
|
<!-- 最后一条消息 - 灰色 -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/lastMessage"
|
android:id="@+id/lastMessage"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
|
@ -70,20 +70,20 @@
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:text="最后一条消息内容预览"
|
android:text="最后一条消息内容预览"
|
||||||
android:textColor="@color/glass_white_60"
|
android:textColor="#666666"
|
||||||
android:textSize="@dimen/text_sm"
|
android:textSize="@dimen/text_sm"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/avatarContainer"
|
app:layout_constraintBottom_toBottomOf="@id/avatarContainer"
|
||||||
app:layout_constraintEnd_toStartOf="@id/unreadBadge"
|
app:layout_constraintEnd_toStartOf="@id/unreadBadge"
|
||||||
app:layout_constraintStart_toStartOf="@id/title"
|
app:layout_constraintStart_toStartOf="@id/title"
|
||||||
app:layout_constraintTop_toBottomOf="@id/title" />
|
app:layout_constraintTop_toBottomOf="@id/title" />
|
||||||
|
|
||||||
<!-- 时间戳 - 更浅的灰色 -->
|
<!-- 时间戳 - 浅灰色 -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/timeText"
|
android:id="@+id/timeText"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="12:30"
|
android:text="12:30"
|
||||||
android:textColor="@color/glass_white_40"
|
android:textColor="#999999"
|
||||||
android:textSize="@dimen/text_xs"
|
android:textSize="@dimen/text_xs"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@id/title" />
|
app:layout_constraintTop_toTopOf="@id/title" />
|
||||||
|
|
@ -106,12 +106,12 @@
|
||||||
app:layout_constraintBottom_toBottomOf="@id/lastMessage"
|
app:layout_constraintBottom_toBottomOf="@id/lastMessage"
|
||||||
app:layout_constraintEnd_toEndOf="parent" />
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
<!-- 分割线 - 玻璃质感 -->
|
<!-- 分割线 -->
|
||||||
<View
|
<View
|
||||||
android:id="@+id/divider"
|
android:id="@+id/divider"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
android:background="@color/glass_white_10"
|
android:background="#EEEEEE"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="@id/title" />
|
app:layout_constraintStart_toStartOf="@id/title" />
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user