question:未修复好直播功能+未部署

This commit is contained in:
xiao12feng8 2025-12-30 19:20:52 +08:00
parent 37ee807c8a
commit b3726557e5
16 changed files with 638 additions and 147 deletions

View File

@ -44,7 +44,9 @@ import com.google.android.material.textfield.MaterialAutoCompleteTextView;
import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textfield.TextInputLayout;
import com.example.livestreaming.net.ApiClient; import com.example.livestreaming.net.ApiClient;
import com.example.livestreaming.net.ApiResponse; import com.example.livestreaming.net.ApiResponse;
import com.example.livestreaming.net.ConversationResponse;
import com.example.livestreaming.net.CreateRoomRequest; import com.example.livestreaming.net.CreateRoomRequest;
import com.example.livestreaming.net.PageResponse;
import com.example.livestreaming.net.Room; import com.example.livestreaming.net.Room;
import com.example.livestreaming.net.StreamConfig; import com.example.livestreaming.net.StreamConfig;
@ -52,6 +54,7 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Callback; import retrofit2.Callback;
@ -1826,7 +1829,19 @@ public class MainActivity extends AppCompatActivity {
/** /**
* 获取所有直播间并筛选出关注用户的直播间 * 获取所有直播间并筛选出关注用户的直播间
*/ */
private void fetchAndFilterFollowRooms(List<Map<String, Object>> private void fetchAndFilterFollowRooms(List<Map<String, Object>> followList) {
// 从关注列表中提取用户ID
if (followList == null || followList.isEmpty()) {
if (adapter != null) {
adapter.submitList(new ArrayList<>());
}
return;
}
// TODO: 实现根据关注列表筛选直播间
if (adapter != null) {
adapter.submitList(new ArrayList<>());
}
}
/** /**
* 构建发现页面的房间列表推荐算法前端实现 * 构建发现页面的房间列表推荐算法前端实现

View File

@ -0,0 +1,61 @@
package com.example.livestreaming;
import java.io.Serializable;
public class Post implements Serializable {
private String id;
private String userId;
private String userName;
private String userAvatar;
private String content;
private String imageUrl;
private String category;
private long timestamp;
private int likeCount;
private int commentCount;
private boolean isLiked;
public Post() {}
public Post(String id, String userId, String userName, String content, String category) {
this.id = id;
this.userId = userId;
this.userName = userName;
this.content = content;
this.category = category;
this.timestamp = System.currentTimeMillis();
}
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; }
public String getUserName() { return userName; }
public void setUserName(String userName) { this.userName = userName; }
public String getUserAvatar() { return userAvatar; }
public void setUserAvatar(String userAvatar) { this.userAvatar = userAvatar; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public String getImageUrl() { return imageUrl; }
public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
public String getCategory() { return category; }
public void setCategory(String category) { this.category = category; }
public long getTimestamp() { return timestamp; }
public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
public int getLikeCount() { return likeCount; }
public void setLikeCount(int likeCount) { this.likeCount = likeCount; }
public int getCommentCount() { return commentCount; }
public void setCommentCount(int commentCount) { this.commentCount = commentCount; }
public boolean isLiked() { return isLiked; }
public void setLiked(boolean liked) { isLiked = liked; }
}

View File

@ -0,0 +1,80 @@
package com.example.livestreaming;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class PostAdapter extends RecyclerView.Adapter<PostAdapter.PostViewHolder> {
private List<Post> posts = new ArrayList<>();
public void setPosts(List<Post> posts) {
this.posts = posts != null ? posts : new ArrayList<>();
notifyDataSetChanged();
}
public void addPost(Post post) {
if (post != null) {
posts.add(0, post);
notifyItemInserted(0);
}
}
@NonNull
@Override
public PostViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_post, parent, false);
return new PostViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull PostViewHolder holder, int position) {
Post post = posts.get(position);
holder.bind(post);
}
@Override
public int getItemCount() {
return posts.size();
}
static class PostViewHolder extends RecyclerView.ViewHolder {
private final TextView tvUserName;
private final TextView tvContent;
private final TextView tvTime;
private final TextView tvLikeCount;
private final TextView tvCommentCount;
public PostViewHolder(@NonNull View itemView) {
super(itemView);
tvUserName = itemView.findViewById(R.id.tv_user_name);
tvContent = itemView.findViewById(R.id.tv_content);
tvTime = itemView.findViewById(R.id.tv_time);
tvLikeCount = itemView.findViewById(R.id.tv_like_count);
tvCommentCount = itemView.findViewById(R.id.tv_comment_count);
}
public void bind(Post post) {
if (tvUserName != null) tvUserName.setText(post.getUserName());
if (tvContent != null) tvContent.setText(post.getContent());
if (tvTime != null) {
SimpleDateFormat sdf = new SimpleDateFormat("MM-dd HH:mm", Locale.getDefault());
tvTime.setText(sdf.format(new Date(post.getTimestamp())));
}
if (tvLikeCount != null) tvLikeCount.setText(String.valueOf(post.getLikeCount()));
if (tvCommentCount != null) tvCommentCount.setText(String.valueOf(post.getCommentCount()));
}
}
}

View File

@ -0,0 +1,43 @@
package com.example.livestreaming;
import android.content.Context;
import android.content.SharedPreferences;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class PostManager {
private static final String PREFS_NAME = "posts_prefs";
private static final String KEY_POSTS = "posts";
private static final Gson gson = new Gson();
public static void savePost(Context context, Post post) {
List<Post> posts = getAllPosts(context);
posts.add(0, post);
savePosts(context, posts);
}
public static List<Post> getAllPosts(Context context) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
String json = prefs.getString(KEY_POSTS, "[]");
Type type = new TypeToken<List<Post>>(){}.getType();
List<Post> posts = gson.fromJson(json, type);
return posts != null ? posts : new ArrayList<>();
}
public static List<Post> getPostsByCategory(Context context, String category) {
return getAllPosts(context).stream()
.filter(p -> category.equals(p.getCategory()))
.collect(Collectors.toList());
}
private static void savePosts(Context context, List<Post> posts) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit().putString(KEY_POSTS, gson.toJson(posts)).apply();
}
}

View File

@ -0,0 +1,43 @@
package com.example.livestreaming;
import android.app.Activity;
import android.app.AlertDialog;
import android.widget.EditText;
import android.widget.Toast;
public class PublishPostHelper {
public interface PublishCallback {
void onSuccess(Post post);
void onError(String error);
}
public static void showPublishDialog(Activity activity, String category, PublishCallback callback) {
EditText input = new EditText(activity);
input.setHint("输入内容...");
new AlertDialog.Builder(activity)
.setTitle("发布动态")
.setView(input)
.setPositiveButton("发布", (dialog, which) -> {
String content = input.getText().toString().trim();
if (content.isEmpty()) {
Toast.makeText(activity, "内容不能为空", Toast.LENGTH_SHORT).show();
return;
}
Post post = new Post();
post.setId(String.valueOf(System.currentTimeMillis()));
post.setContent(content);
post.setCategory(category);
post.setUserName("用户");
post.setTimestamp(System.currentTimeMillis());
PostManager.savePost(activity, post);
if (callback != null) {
callback.onSuccess(post);
}
})
.setNegativeButton("取消", null)
.show();
}
}

View File

@ -0,0 +1,96 @@
package com.example.livestreaming;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.example.livestreaming.net.Room;
public class RoomAdapter extends ListAdapter<Room, RoomAdapter.RoomViewHolder> {
private OnRoomClickListener listener;
public interface OnRoomClickListener {
void onRoomClick(Room room);
}
public RoomAdapter() {
super(new DiffUtil.ItemCallback<Room>() {
@Override
public boolean areItemsTheSame(@NonNull Room oldItem, @NonNull Room newItem) {
return oldItem.getId() != null && oldItem.getId().equals(newItem.getId());
}
@Override
public boolean areContentsTheSame(@NonNull Room oldItem, @NonNull Room newItem) {
return oldItem.equals(newItem);
}
});
}
public void setOnRoomClickListener(OnRoomClickListener listener) {
this.listener = listener;
}
@NonNull
@Override
public RoomViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_room, parent, false);
return new RoomViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RoomViewHolder holder, int position) {
Room room = getItem(position);
holder.bind(room, listener);
}
static class RoomViewHolder extends RecyclerView.ViewHolder {
private final TextView tvTitle;
private final TextView tvStreamer;
private final TextView tvLikeCount;
private final ImageView ivCover;
private final View liveIndicator;
public RoomViewHolder(@NonNull View itemView) {
super(itemView);
tvTitle = itemView.findViewById(R.id.roomTitle);
tvStreamer = itemView.findViewById(R.id.streamerName);
tvLikeCount = itemView.findViewById(R.id.likeCount);
ivCover = itemView.findViewById(R.id.coverImage);
liveIndicator = itemView.findViewById(R.id.liveBadge);
}
public void bind(Room room, OnRoomClickListener listener) {
if (tvTitle != null) tvTitle.setText(room.getTitle());
if (tvStreamer != null) tvStreamer.setText(room.getStreamerName());
if (tvLikeCount != null) tvLikeCount.setText(String.valueOf(room.getViewerCount()));
if (liveIndicator != null) {
liveIndicator.setVisibility(room.isLive() ? View.VISIBLE : View.GONE);
}
if (ivCover != null && room.getCoverImage() != null) {
Glide.with(itemView.getContext())
.load(room.getCoverImage())
.placeholder(android.R.drawable.ic_menu_gallery)
.into(ivCover);
}
itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onRoomClick(room);
}
});
}
}
}

View File

@ -100,12 +100,28 @@ public class RoomDetailActivity extends AppCompatActivity {
// WebSocket - 弹幕 // WebSocket - 弹幕
private WebSocket chatWebSocket; private WebSocket chatWebSocket;
private OkHttpClient chatWsClient; private OkHttpClient chatWsClient;
private static final String WS_CHAT_BASE_URL = "ws://192.168.1.164:8081/ws/live/chat/";
// WebSocket - 在线人数 // WebSocket - 在线人数
private WebSocket onlineCountWebSocket; private WebSocket onlineCountWebSocket;
private OkHttpClient onlineCountWsClient; private OkHttpClient onlineCountWsClient;
private static final String WS_ONLINE_BASE_URL = "ws://192.168.1.164:8081/ws/live/";
// 动态获取WebSocket URL
private String getWsChatBaseUrl() {
String baseUrl = ApiClient.getCurrentBaseUrl(this);
if (baseUrl == null || baseUrl.isEmpty()) {
baseUrl = "http://192.168.1.164:8081/";
}
// http:// 转换为 ws://
return baseUrl.replace("http://", "ws://").replace("https://", "wss://") + "ws/live/chat/";
}
private String getWsOnlineBaseUrl() {
String baseUrl = ApiClient.getCurrentBaseUrl(this);
if (baseUrl == null || baseUrl.isEmpty()) {
baseUrl = "http://192.168.1.164:8081/";
}
return baseUrl.replace("http://", "ws://").replace("https://", "wss://") + "ws/live/";
}
// WebSocket 心跳检测 - 弹幕 // WebSocket 心跳检测 - 弹幕
private Runnable chatHeartbeatRunnable; private Runnable chatHeartbeatRunnable;
@ -265,7 +281,7 @@ public class RoomDetailActivity extends AppCompatActivity {
.pingInterval(30, java.util.concurrent.TimeUnit.SECONDS) // OkHttp 内置 ping .pingInterval(30, java.util.concurrent.TimeUnit.SECONDS) // OkHttp 内置 ping
.build(); .build();
Request request = new Request.Builder() Request request = new Request.Builder()
.url(WS_CHAT_BASE_URL + roomId) .url(getWsChatBaseUrl() + roomId)
.build(); .build();
chatWebSocket = chatWsClient.newWebSocket(request, new WebSocketListener() { chatWebSocket = chatWsClient.newWebSocket(request, new WebSocketListener() {
@ -372,7 +388,7 @@ public class RoomDetailActivity extends AppCompatActivity {
String clientId = (userIdStr != null && !userIdStr.isEmpty()) ? String clientId = (userIdStr != null && !userIdStr.isEmpty()) ?
userIdStr : userIdStr :
"guest_" + System.currentTimeMillis(); "guest_" + System.currentTimeMillis();
String wsUrl = WS_ONLINE_BASE_URL + roomId + "?clientId=" + clientId; String wsUrl = getWsOnlineBaseUrl() + roomId + "?clientId=" + clientId;
onlineCountWsClient = new OkHttpClient.Builder() onlineCountWsClient = new OkHttpClient.Builder()
.pingInterval(30, java.util.concurrent.TimeUnit.SECONDS) .pingInterval(30, java.util.concurrent.TimeUnit.SECONDS)
@ -1050,14 +1066,19 @@ public class RoomDetailActivity extends AppCompatActivity {
if (ijkSurface == null) return; if (ijkSurface == null) return;
IjkMediaPlayer p = new IjkMediaPlayer(); IjkMediaPlayer p = new IjkMediaPlayer();
p.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0); // 优化缓冲设置减少卡顿
p.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 1); // 开启缓冲
p.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 1); p.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 1);
p.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "nobuffer"); p.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "nobuffer");
p.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", 1); p.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", 100000); // 100ms分析时长
p.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", 1024); p.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", 10240); // 10KB探测大小
p.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 1); p.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 5); // 允许丢帧
p.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", 300); p.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", 3000); // 3秒缓存
p.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", 1); p.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min_frames", 50); // 最小缓冲帧数
p.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", 0); // 关闭无限缓冲
p.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "reconnect", 1); // 断线重连
p.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "reconnect_streamed", 1);
p.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "reconnect_delay_max", 5); // 最大重连延迟5秒
p.setOnPreparedListener(mp -> { p.setOnPreparedListener(mp -> {
binding.offlineLayout.setVisibility(View.GONE); binding.offlineLayout.setVisibility(View.GONE);
@ -1066,8 +1087,15 @@ public class RoomDetailActivity extends AppCompatActivity {
}); });
p.setOnErrorListener((IMediaPlayer mp, int what, int extra) -> { p.setOnErrorListener((IMediaPlayer mp, int what, int extra) -> {
android.util.Log.e("IjkPlayer", "播放错误: what=" + what + ", extra=" + extra);
if (ijkFallbackTried || TextUtils.isEmpty(ijkFallbackHlsUrl)) { if (ijkFallbackTried || TextUtils.isEmpty(ijkFallbackHlsUrl)) {
binding.offlineLayout.setVisibility(View.VISIBLE); binding.offlineLayout.setVisibility(View.VISIBLE);
// 5秒后尝试重新连接
handler.postDelayed(() -> {
if (!isFinishing() && !isDestroyed() && room != null && room.isLive()) {
fetchRoom(); // 重新获取房间信息并播放
}
}, 5000);
return true; return true;
} }
ijkFallbackTried = true; ijkFallbackTried = true;
@ -1075,12 +1103,28 @@ public class RoomDetailActivity extends AppCompatActivity {
return true; return true;
}); });
// 添加缓冲监听
p.setOnInfoListener((mp, what, extra) -> {
if (what == IMediaPlayer.MEDIA_INFO_BUFFERING_START) {
android.util.Log.d("IjkPlayer", "开始缓冲...");
// 可以显示加载指示器
} else if (what == IMediaPlayer.MEDIA_INFO_BUFFERING_END) {
android.util.Log.d("IjkPlayer", "缓冲结束");
// 隐藏加载指示器
} else if (what == IMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
android.util.Log.d("IjkPlayer", "视频开始渲染");
binding.offlineLayout.setVisibility(View.GONE);
}
return false;
});
ijkPlayer = p; ijkPlayer = p;
try { try {
p.setSurface(ijkSurface); p.setSurface(ijkSurface);
p.setDataSource(url); p.setDataSource(url);
p.prepareAsync(); p.prepareAsync();
} catch (Exception e) { } catch (Exception e) {
android.util.Log.e("IjkPlayer", "播放器初始化失败: " + e.getMessage());
if (!TextUtils.isEmpty(ijkFallbackHlsUrl)) { if (!TextUtils.isEmpty(ijkFallbackHlsUrl)) {
startHls(ijkFallbackHlsUrl, null); startHls(ijkFallbackHlsUrl, null);
} else { } else {

View File

@ -1,39 +1,32 @@
package com.example.livestreaming.net; package com.example.livestreaming.net;
import com.google.gson.annotations.SerializedName;
public class ConversationResponse { public class ConversationResponse {
private Integer id;
@SerializedName("id") private Integer targetUserId;
private String id; private String targetUserName;
private String targetUserAvatar;
@SerializedName("title")
private String title;
@SerializedName("lastMessage")
private String lastMessage; private String lastMessage;
private String lastMessageTime;
@SerializedName("timeText")
private String timeText;
@SerializedName("unreadCount")
private Integer unreadCount; private Integer unreadCount;
@SerializedName("muted") public Integer getId() { return id; }
private Boolean muted; public void setId(Integer id) { this.id = id; }
@SerializedName("avatarUrl") public Integer getTargetUserId() { return targetUserId; }
private String avatarUrl; public void setTargetUserId(Integer targetUserId) { this.targetUserId = targetUserId; }
@SerializedName("otherUserId") public String getTargetUserName() { return targetUserName; }
private Integer otherUserId; public void setTargetUserName(String targetUserName) { this.targetUserName = targetUserName; }
public String getTargetUserAvatar() { return targetUserAvatar; }
public void setTargetUserAvatar(String targetUserAvatar) { this.targetUserAvatar = targetUserAvatar; }
public String getId() { return id; }
public String getTitle() { return title; }
public String getLastMessage() { return lastMessage; } public String getLastMessage() { return lastMessage; }
public String getTimeText() { return timeText; } public void setLastMessage(String lastMessage) { this.lastMessage = lastMessage; }
public String getLastMessageTime() { return lastMessageTime; }
public void setLastMessageTime(String lastMessageTime) { this.lastMessageTime = lastMessageTime; }
public Integer getUnreadCount() { return unreadCount; } public Integer getUnreadCount() { return unreadCount; }
public Boolean getMuted() { return muted; } public void setUnreadCount(Integer unreadCount) { this.unreadCount = unreadCount; }
public String getAvatarUrl() { return avatarUrl; }
public Integer getOtherUserId() { return otherUserId; }
} }

View File

@ -1,32 +1,26 @@
package com.example.livestreaming.net; package com.example.livestreaming.net;
import com.google.gson.annotations.SerializedName;
import java.util.List; import java.util.List;
public class PageResponse<T> { public class PageResponse<T> {
@SerializedName("list")
private List<T> list; private List<T> list;
@SerializedName("total")
private Long total;
@SerializedName("page")
private Integer page; private Integer page;
@SerializedName("limit")
private Integer limit; private Integer limit;
private Integer total;
@SerializedName("totalPage")
private Integer totalPage; private Integer totalPage;
public List<T> getList() { return list; } public List<T> getList() { return list; }
public Long getTotal() { return total; } public void setList(List<T> list) { this.list = list; }
public Integer getPage() { return page; }
public Integer getLimit() { return limit; }
public Integer getTotalPage() { return totalPage; }
public boolean hasMore() { public Integer getPage() { return page; }
return page != null && totalPage != null && page < totalPage; public void setPage(Integer page) { this.page = page; }
}
public Integer getLimit() { return limit; }
public void setLimit(Integer limit) { this.limit = limit; }
public Integer getTotal() { return total; }
public void setTotal(Integer total) { this.total = total; }
public Integer getTotalPage() { return totalPage; }
public void setTotalPage(Integer totalPage) { this.totalPage = totalPage; }
} }

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#8B4513" />
<corners android:radius="4dp" />
</shape>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFEF3" />
<corners android:radius="8dp" />
<stroke
android:width="1dp"
android:color="#FFD700" />
</shape>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFF8E1" />
<corners android:radius="8dp" />
<stroke
android:width="1dp"
android:color="#FFCC00"
android:dashWidth="4dp"
android:dashGap="2dp" />
</shape>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="许下你的愿望"
android:textSize="18sp"
android:textStyle="bold"
android:gravity="center"
android:layout_marginBottom="16dp" />
<EditText
android:id="@+id/editWish"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="写下你的愿望..."
android:minLines="3"
android:gravity="top" />
</LinearLayout>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp"
android:gravity="center">
<ImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:src="@android:drawable/btn_star_big_on" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="愿望已挂上许愿树"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginTop="16dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="愿你心想事成"
android:textColor="#666"
android:layout_marginTop="8dp" />
</LinearLayout>

View File

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@mipmap/ic_launcher" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv_user_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#999"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="14sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_like_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableStart="@android:drawable/btn_star"
android:drawablePadding="4dp"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_comment_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:drawableStart="@android:drawable/ic_menu_edit"
android:drawablePadding="4dp"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@ -1,119 +1,82 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" <androidx.cardview.widget.CardView 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:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="6dp" android:layout_margin="4dp"
app:cardCornerRadius="12dp" app:cardCornerRadius="8dp"
app:cardElevation="0dp"> app:cardElevation="2dp">
<androidx.constraintlayout.widget.ConstraintLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<ImageView <ImageView
android:id="@+id/coverImage" android:id="@+id/coverImage"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="120dp"
android:background="@drawable/bg_cover_placeholder" android:scaleType="centerCrop" />
android:scaleType="centerCrop"
app:layout_constraintDimensionRatio="H,4:3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView <TextView
android:id="@+id/liveBadge" android:id="@+id/liveBadge"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_gravity="top|end"
android:layout_marginTop="8dp" android:layout_margin="8dp"
android:paddingStart="10dp" android:background="@android:color/holo_red_light"
android:paddingEnd="10dp" android:paddingHorizontal="6dp"
android:paddingTop="4dp" android:paddingVertical="2dp"
android:paddingBottom="4dp" android:text="直播中"
android:text="LIVE"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="11sp" android:textSize="10sp"
android:visibility="gone" android:visibility="gone" />
app:layout_constraintStart_toStartOf="@id/coverImage"
app:layout_constraintTop_toTopOf="@id/coverImage" />
<androidx.constraintlayout.widget.ConstraintLayout <LinearLayout
android:id="@+id/infoContainer" android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="10dp" android:layout_gravity="bottom"
android:paddingEnd="10dp" android:background="#80000000"
android:paddingTop="8dp" android:orientation="vertical"
android:paddingBottom="10dp" android:padding="8dp">
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/coverImage">
<TextView <TextView
android:id="@+id/roomTitle" android:id="@+id/roomTitle"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="2" android:maxLines="1"
android:text="王者荣耀陪练" android:textColor="@android:color/white"
android:textColor="#111111" android:textSize="14sp" />
android:textSize="13sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView <LinearLayout
android:id="@+id/streamerAvatar" android:layout_width="match_parent"
android:layout_width="18dp" android:layout_height="wrap_content"
android:layout_height="18dp" android:orientation="horizontal">
android:layout_marginTop="8dp"
android:background="@drawable/bg_avatar_circle"
android:padding="3dp"
android:src="@drawable/ic_account_circle_24"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomTitle" />
<TextView <TextView
android:id="@+id/streamerName" android:id="@+id/streamerName"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="6dp" android:layout_weight="1"
android:layout_marginTop="8dp" android:textColor="#CCC"
android:ellipsize="end" android:textSize="12sp" />
android:maxLines="1"
android:text="虚拟主播"
android:textColor="#666666"
android:textSize="12sp"
app:layout_constraintEnd_toStartOf="@id/likeIcon"
app:layout_constraintStart_toEndOf="@id/streamerAvatar"
app:layout_constraintTop_toBottomOf="@id/roomTitle" />
<ImageView <ImageView
android:id="@+id/likeIcon" android:id="@+id/likeIcon"
android:layout_width="16dp" android:layout_width="14dp"
android:layout_height="16dp" android:layout_height="14dp"
android:layout_marginTop="8dp" android:src="@android:drawable/btn_star_big_on"
android:src="@drawable/ic_heart_24" android:visibility="gone" />
app:layout_constraintEnd_toStartOf="@id/likeCount"
app:layout_constraintTop_toBottomOf="@id/roomTitle" />
<TextView <TextView
android:id="@+id/likeCount" android:id="@+id/likeCount"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="4dp" android:layout_marginStart="4dp"
android:layout_marginTop="8dp" android:textColor="#CCC"
android:text="184"
android:textColor="#888888"
android:textSize="12sp" android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent" android:visibility="gone" />
app:layout_constraintTop_toBottomOf="@id/roomTitle" /> </LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </FrameLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>