安卓界面的修改

This commit is contained in:
ShiQi 2025-12-18 14:20:41 +08:00
parent 65d92ce0ac
commit 07900a85ae
56 changed files with 3210 additions and 24 deletions

View File

@ -13,7 +13,8 @@ android {
versionCode = 1
versionName = "1.0"
buildConfigField("String", "API_BASE_URL", "\"http://10.0.2.2:3001/api/\"")
buildConfigField("String", "API_BASE_URL_EMULATOR", "\"http://10.0.2.2:3001/api/\"")
buildConfigField("String", "API_BASE_URL_DEVICE", "\"http://192.168.1.100:3001/api/\"")
}
buildTypes {

View File

@ -13,6 +13,44 @@
android:hardwareAccelerated="true"
android:largeHeap="true">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<activity
android:name="com.example.livestreaming.TabPlaceholderActivity"
android:exported="false" />
<activity
android:name="com.example.livestreaming.WatchHistoryActivity"
android:exported="false" />
<activity
android:name="com.example.livestreaming.MyFriendsActivity"
android:exported="false" />
<activity
android:name="com.example.livestreaming.FansListActivity"
android:exported="false" />
<activity
android:name="com.example.livestreaming.LikesListActivity"
android:exported="false" />
<activity
android:name="com.example.livestreaming.FollowingListActivity"
android:exported="false" />
<activity
android:name="com.example.livestreaming.EditProfileActivity"
android:exported="false" />
<activity
android:name="com.example.livestreaming.FishPondActivity"
android:exported="false" />

View File

@ -0,0 +1,76 @@
package com.example.livestreaming;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
public class ChatAdapter extends ListAdapter<ChatMessage, ChatAdapter.VH> {
public ChatAdapter() {
super(DIFF);
}
private static final DiffUtil.ItemCallback<ChatMessage> DIFF = new DiffUtil.ItemCallback<ChatMessage>() {
@Override
public boolean areItemsTheSame(@NonNull ChatMessage oldItem, @NonNull ChatMessage newItem) {
// Chat messages have no stable id; treat position as identity.
return oldItem == newItem;
}
@Override
public boolean areContentsTheSame(@NonNull ChatMessage oldItem, @NonNull ChatMessage newItem) {
String ou = oldItem.getUser();
String nu = newItem.getUser();
if (ou == null ? nu != null : !ou.equals(nu)) return false;
String om = oldItem.getMessage();
String nm = newItem.getMessage();
if (om == null ? nm != null : !om.equals(nm)) return false;
return oldItem.isSystem() == newItem.isSystem();
}
};
@NonNull
@Override
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_chat_message, parent, false);
return new VH(v);
}
@Override
public void onBindViewHolder(@NonNull VH holder, int position) {
ChatMessage m = getItem(position);
if (m == null) {
holder.messageText.setText("");
return;
}
if (m.isSystem()) {
holder.messageText.setText(m.getMessage() != null ? m.getMessage() : "");
holder.messageText.setAlpha(0.8f);
} else {
String user = m.getUser();
String msg = m.getMessage();
if (user == null || user.trim().isEmpty()) {
holder.messageText.setText(msg != null ? msg : "");
} else {
holder.messageText.setText(user + ": " + (msg != null ? msg : ""));
}
holder.messageText.setAlpha(1.0f);
}
}
static class VH extends RecyclerView.ViewHolder {
final TextView messageText;
VH(@NonNull View itemView) {
super(itemView);
messageText = itemView.findViewById(R.id.messageText);
}
}
}

View File

@ -0,0 +1,32 @@
package com.example.livestreaming;
public class ChatMessage {
private final String user;
private final String message;
private final boolean system;
public ChatMessage(String systemMessage, boolean system) {
this.user = null;
this.message = systemMessage;
this.system = system;
}
public ChatMessage(String user, String message) {
this.user = user;
this.message = message;
this.system = false;
}
public String getUser() {
return user;
}
public String getMessage() {
return message;
}
public boolean isSystem() {
return system;
}
}

View File

@ -0,0 +1,64 @@
package com.example.livestreaming;
import java.util.Objects;
public class ConversationItem {
private final String id;
private final String title;
private final String lastMessage;
private final String timeText;
private final int unreadCount;
private final boolean muted;
public ConversationItem(String id, String title, String lastMessage, String timeText, int unreadCount, boolean muted) {
this.id = id;
this.title = title;
this.lastMessage = lastMessage;
this.timeText = timeText;
this.unreadCount = unreadCount;
this.muted = muted;
}
public String getId() {
return id;
}
public String getTitle() {
return title;
}
public String getLastMessage() {
return lastMessage;
}
public String getTimeText() {
return timeText;
}
public int getUnreadCount() {
return unreadCount;
}
public boolean isMuted() {
return muted;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ConversationItem)) return false;
ConversationItem that = (ConversationItem) o;
return unreadCount == that.unreadCount
&& muted == that.muted
&& Objects.equals(id, that.id)
&& Objects.equals(title, that.title)
&& Objects.equals(lastMessage, that.lastMessage)
&& Objects.equals(timeText, that.timeText);
}
@Override
public int hashCode() {
return Objects.hash(id, title, lastMessage, timeText, unreadCount, muted);
}
}

View File

@ -0,0 +1,89 @@
package com.example.livestreaming;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import com.example.livestreaming.databinding.ItemConversationBinding;
public class ConversationsAdapter extends ListAdapter<ConversationItem, ConversationsAdapter.VH> {
public interface OnConversationClickListener {
void onConversationClick(ConversationItem item);
}
private final OnConversationClickListener onConversationClickListener;
public ConversationsAdapter(OnConversationClickListener onConversationClickListener) {
super(DIFF);
this.onConversationClickListener = onConversationClickListener;
}
@NonNull
@Override
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemConversationBinding binding = ItemConversationBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new VH(binding, onConversationClickListener);
}
@Override
public void onBindViewHolder(@NonNull VH holder, int position) {
holder.bind(getItem(position));
}
static class VH extends RecyclerView.ViewHolder {
private final ItemConversationBinding binding;
private final OnConversationClickListener onConversationClickListener;
VH(ItemConversationBinding binding, OnConversationClickListener onConversationClickListener) {
super(binding.getRoot());
this.binding = binding;
this.onConversationClickListener = onConversationClickListener;
}
void bind(ConversationItem item) {
binding.title.setText(item != null && item.getTitle() != null ? item.getTitle() : "");
binding.lastMessage.setText(item != null && item.getLastMessage() != null ? item.getLastMessage() : "");
binding.timeText.setText(item != null && item.getTimeText() != null ? item.getTimeText() : "");
int unread = item != null ? item.getUnreadCount() : 0;
if (unread > 0) {
binding.unreadBadge.setVisibility(View.VISIBLE);
binding.unreadBadge.setText(unread > 99 ? "99+" : String.valueOf(unread));
} else {
binding.unreadBadge.setVisibility(View.GONE);
}
if (item != null && item.isMuted()) {
binding.muteIcon.setVisibility(View.VISIBLE);
} else {
binding.muteIcon.setVisibility(View.GONE);
}
binding.getRoot().setOnClickListener(v -> {
if (item == null) return;
if (onConversationClickListener != null) onConversationClickListener.onConversationClick(item);
});
}
}
private static final DiffUtil.ItemCallback<ConversationItem> DIFF = new DiffUtil.ItemCallback<ConversationItem>() {
@Override
public boolean areItemsTheSame(@NonNull ConversationItem oldItem, @NonNull ConversationItem newItem) {
String o = oldItem.getId();
String n = newItem.getId();
return o != null && o.equals(n);
}
@Override
public boolean areContentsTheSame(@NonNull ConversationItem oldItem, @NonNull ConversationItem newItem) {
return oldItem.equals(newItem);
}
};
}

View File

@ -0,0 +1,177 @@
package com.example.livestreaming;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import com.bumptech.glide.Glide;
import com.example.livestreaming.databinding.ActivityEditProfileBinding;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import java.io.File;
public class EditProfileActivity extends AppCompatActivity {
private ActivityEditProfileBinding binding;
private static final String PREFS_NAME = "profile_prefs";
private static final String KEY_NAME = "profile_name";
private static final String KEY_BIO = "profile_bio";
private static final String KEY_AVATAR_URI = "profile_avatar_uri";
private static final String KEY_BIRTHDAY = "profile_birthday";
private static final String KEY_GENDER = "profile_gender";
private static final String KEY_LOCATION = "profile_location";
private Uri selectedAvatarUri = null;
private Uri pendingCameraUri = null;
private ActivityResultLauncher<String> pickImageLauncher;
private ActivityResultLauncher<Uri> takePictureLauncher;
public static void start(Context context) {
Intent intent = new Intent(context, EditProfileActivity.class);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityEditProfileBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
pickImageLauncher = registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
if (uri == null) return;
selectedAvatarUri = uri;
Glide.with(this).load(uri).circleCrop().into(binding.avatarPreview);
});
takePictureLauncher = registerForActivityResult(new ActivityResultContracts.TakePicture(), success -> {
if (!success) return;
if (pendingCameraUri == null) return;
selectedAvatarUri = pendingCameraUri;
Glide.with(this).load(pendingCameraUri).circleCrop().into(binding.avatarPreview);
});
binding.backButton.setOnClickListener(v -> finish());
binding.cancelButton.setOnClickListener(v -> finish());
loadFromPrefs();
binding.avatarRow.setOnClickListener(v -> showAvatarBottomSheet());
binding.saveButton.setOnClickListener(v -> {
String name = binding.inputName.getText() != null ? binding.inputName.getText().toString().trim() : "";
String bio = binding.inputBio.getText() != null ? binding.inputBio.getText().toString().trim() : "";
String birthday = binding.inputBirthday.getText() != null ? binding.inputBirthday.getText().toString().trim() : "";
String gender = binding.inputGender.getText() != null ? binding.inputGender.getText().toString().trim() : "";
String location = binding.inputLocation.getText() != null ? binding.inputLocation.getText().toString().trim() : "";
if (TextUtils.isEmpty(name)) {
Toast.makeText(this, "昵称不能为空", Toast.LENGTH_SHORT).show();
return;
}
getSharedPreferences(PREFS_NAME, MODE_PRIVATE)
.edit()
.putString(KEY_NAME, name)
.apply();
if (TextUtils.isEmpty(bio)) {
getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit().remove(KEY_BIO).apply();
} else {
getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit().putString(KEY_BIO, bio).apply();
}
if (selectedAvatarUri != null) {
getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit().putString(KEY_AVATAR_URI, selectedAvatarUri.toString()).apply();
}
if (TextUtils.isEmpty(birthday)) {
getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit().remove(KEY_BIRTHDAY).apply();
} else {
getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit().putString(KEY_BIRTHDAY, birthday).apply();
}
if (TextUtils.isEmpty(gender)) {
getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit().remove(KEY_GENDER).apply();
} else {
getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit().putString(KEY_GENDER, gender).apply();
}
if (TextUtils.isEmpty(location)) {
getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit().remove(KEY_LOCATION).apply();
} else {
getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit().putString(KEY_LOCATION, location).apply();
}
finish();
});
}
private void loadFromPrefs() {
String name = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_NAME, "");
String bio = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_BIO, "");
String birthday = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_BIRTHDAY, "");
String gender = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_GENDER, "");
String location = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_LOCATION, "");
String avatarUri = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_AVATAR_URI, null);
binding.inputName.setText(name);
binding.inputBio.setText(bio);
binding.inputBirthday.setText(birthday);
binding.inputGender.setText(gender);
binding.inputLocation.setText(location);
if (!TextUtils.isEmpty(avatarUri)) {
selectedAvatarUri = Uri.parse(avatarUri);
Glide.with(this).load(selectedAvatarUri).circleCrop().into(binding.avatarPreview);
}
}
private void showAvatarBottomSheet() {
BottomSheetDialog dialog = new BottomSheetDialog(this);
android.view.View view = getLayoutInflater().inflate(R.layout.bottom_sheet_avatar_picker, null);
dialog.setContentView(view);
android.view.View pick = view.findViewById(R.id.actionPickGallery);
android.view.View camera = view.findViewById(R.id.actionTakePhoto);
android.view.View cancel = view.findViewById(R.id.actionCancel);
pick.setOnClickListener(v -> {
dialog.dismiss();
pickImageLauncher.launch("image/*");
});
camera.setOnClickListener(v -> {
dialog.dismiss();
Uri uri = createTempCameraUri();
if (uri == null) return;
pendingCameraUri = uri;
takePictureLauncher.launch(uri);
});
cancel.setOnClickListener(v -> dialog.dismiss());
dialog.show();
}
private Uri createTempCameraUri() {
try {
File dir = new File(getCacheDir(), "images");
if (!dir.exists()) dir.mkdirs();
File file = new File(dir, "avatar_" + System.currentTimeMillis() + ".jpg");
return FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".fileprovider", file);
} catch (Exception e) {
Toast.makeText(this, "无法创建相机文件", Toast.LENGTH_SHORT).show();
return null;
}
}
}

View File

@ -0,0 +1,53 @@
package com.example.livestreaming;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.example.livestreaming.databinding.ActivityFansListBinding;
import java.util.ArrayList;
import java.util.List;
public class FansListActivity extends AppCompatActivity {
private ActivityFansListBinding binding;
public static void start(Context context) {
Intent intent = new Intent(context, FansListActivity.class);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityFansListBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.backButton.setOnClickListener(v -> finish());
FriendsAdapter adapter = new FriendsAdapter(item -> {
if (item == null) return;
Toast.makeText(this, "打开粉丝:" + item.getName(), Toast.LENGTH_SHORT).show();
});
binding.fansRecyclerView.setLayoutManager(new LinearLayoutManager(this));
binding.fansRecyclerView.setAdapter(adapter);
adapter.submitList(buildDemoFans());
}
private List<FriendItem> buildDemoFans() {
List<FriendItem> list = new ArrayList<>();
list.add(new FriendItem("f1", "小雨", "关注了你 · 2分钟前", true));
list.add(new FriendItem("f2", "阿宁", "关注了你 · 昨天", false));
list.add(new FriendItem("f3", "小星", "关注了你 · 周二", true));
list.add(new FriendItem("f4", "小林", "关注了你 · 上周", false));
list.add(new FriendItem("f5", "阿杰", "关注了你 · 上周", false));
list.add(new FriendItem("f6", "小七", "关注了你 · 上月", true));
return list;
}
}

View File

@ -0,0 +1,52 @@
package com.example.livestreaming;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.example.livestreaming.databinding.ActivityFollowingListBinding;
import java.util.ArrayList;
import java.util.List;
public class FollowingListActivity extends AppCompatActivity {
private ActivityFollowingListBinding binding;
public static void start(Context context) {
Intent intent = new Intent(context, FollowingListActivity.class);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityFollowingListBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.backButton.setOnClickListener(v -> finish());
FriendsAdapter adapter = new FriendsAdapter(item -> {
if (item == null) return;
Toast.makeText(this, "打开关注:" + item.getName(), Toast.LENGTH_SHORT).show();
});
binding.followingRecyclerView.setLayoutManager(new LinearLayoutManager(this));
binding.followingRecyclerView.setAdapter(adapter);
adapter.submitList(buildDemoFollowing());
}
private List<FriendItem> buildDemoFollowing() {
List<FriendItem> list = new ArrayList<>();
list.add(new FriendItem("fo1", "王者荣耀陪练", "主播 · 正在直播", true));
list.add(new FriendItem("fo2", "音乐电台", "主播 · 今日 20:00 开播", false));
list.add(new FriendItem("fo3", "户外阿杰", "主播 · 1小时前开播", true));
list.add(new FriendItem("fo4", "美食探店", "主播 · 昨天直播", false));
list.add(new FriendItem("fo5", "聊天小七", "主播 · 正在直播", true));
return list;
}
}

View File

@ -0,0 +1,50 @@
package com.example.livestreaming;
import java.util.Objects;
public class FriendItem {
private final String id;
private final String name;
private final String subtitle;
private final boolean online;
public FriendItem(String id, String name, String subtitle, boolean online) {
this.id = id;
this.name = name;
this.subtitle = subtitle;
this.online = online;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getSubtitle() {
return subtitle;
}
public boolean isOnline() {
return online;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FriendItem)) return false;
FriendItem that = (FriendItem) o;
return online == that.online
&& Objects.equals(id, that.id)
&& Objects.equals(name, that.name)
&& Objects.equals(subtitle, that.subtitle);
}
@Override
public int hashCode() {
return Objects.hash(id, name, subtitle, online);
}
}

View File

@ -0,0 +1,80 @@
package com.example.livestreaming;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import com.example.livestreaming.databinding.ItemFriendBinding;
public class FriendsAdapter extends ListAdapter<FriendItem, FriendsAdapter.VH> {
public interface OnFriendClickListener {
void onFriendClick(FriendItem item);
}
private final OnFriendClickListener onFriendClickListener;
public FriendsAdapter(OnFriendClickListener onFriendClickListener) {
super(DIFF);
this.onFriendClickListener = onFriendClickListener;
}
@NonNull
@Override
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemFriendBinding binding = ItemFriendBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new VH(binding, onFriendClickListener);
}
@Override
public void onBindViewHolder(@NonNull VH holder, int position) {
holder.bind(getItem(position));
}
static class VH extends RecyclerView.ViewHolder {
private final ItemFriendBinding binding;
private final OnFriendClickListener onFriendClickListener;
VH(ItemFriendBinding binding, OnFriendClickListener onFriendClickListener) {
super(binding.getRoot());
this.binding = binding;
this.onFriendClickListener = onFriendClickListener;
}
void bind(FriendItem item) {
binding.name.setText(item != null && item.getName() != null ? item.getName() : "");
binding.subtitle.setText(item != null && item.getSubtitle() != null ? item.getSubtitle() : "");
if (item != null && item.isOnline()) {
binding.onlineDot.setVisibility(View.VISIBLE);
} else {
binding.onlineDot.setVisibility(View.GONE);
}
binding.getRoot().setOnClickListener(v -> {
if (item == null) return;
if (onFriendClickListener != null) onFriendClickListener.onFriendClick(item);
});
}
}
private static final DiffUtil.ItemCallback<FriendItem> DIFF = new DiffUtil.ItemCallback<FriendItem>() {
@Override
public boolean areItemsTheSame(@NonNull FriendItem oldItem, @NonNull FriendItem newItem) {
String o = oldItem.getId();
String n = newItem.getId();
return o != null && o.equals(n);
}
@Override
public boolean areContentsTheSame(@NonNull FriendItem oldItem, @NonNull FriendItem newItem) {
return oldItem.equals(newItem);
}
};
}

View File

@ -0,0 +1,52 @@
package com.example.livestreaming;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.example.livestreaming.databinding.ActivityLikesListBinding;
import java.util.ArrayList;
import java.util.List;
public class LikesListActivity extends AppCompatActivity {
private ActivityLikesListBinding binding;
public static void start(Context context) {
Intent intent = new Intent(context, LikesListActivity.class);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityLikesListBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.backButton.setOnClickListener(v -> finish());
ConversationsAdapter adapter = new ConversationsAdapter(item -> {
if (item == null) return;
Toast.makeText(this, "查看获赞:" + item.getTitle(), Toast.LENGTH_SHORT).show();
});
binding.likesRecyclerView.setLayoutManager(new LinearLayoutManager(this));
binding.likesRecyclerView.setAdapter(adapter);
adapter.submitList(buildDemoLikes());
}
private List<ConversationItem> buildDemoLikes() {
List<ConversationItem> list = new ArrayList<>();
list.add(new ConversationItem("l1", "小雨", "赞了你的直播间", "09:12", 0, false));
list.add(new ConversationItem("l2", "阿宁", "赞了你的作品", "昨天", 0, false));
list.add(new ConversationItem("l3", "小星", "赞了你", "周二", 0, false));
list.add(new ConversationItem("l4", "小林", "赞了你的直播回放", "上周", 0, false));
list.add(new ConversationItem("l5", "阿杰", "赞了你的作品", "上周", 0, false));
return list;
}
}

View File

@ -23,6 +23,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.example.livestreaming.databinding.ActivityMainBinding;
import com.example.livestreaming.databinding.DialogCreateRoomBinding;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.tabs.TabLayout;
import com.example.livestreaming.net.ApiClient;
import com.example.livestreaming.net.ApiResponse;
import com.example.livestreaming.net.CreateRoomRequest;
@ -42,6 +43,9 @@ public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private RoomsAdapter adapter;
private final List<Room> allRooms = new ArrayList<>();
private String currentCategory = "推荐";
private final Handler handler = new Handler(Looper.getMainLooper());
private Runnable pollRunnable;
@ -76,12 +80,56 @@ public class MainActivity extends AppCompatActivity {
binding.roomsRecyclerView.setAdapter(adapter);
// 立即显示演示数据提升用户体验
adapter.submitList(buildDemoRooms(6));
allRooms.clear();
allRooms.addAll(buildDemoRooms(12));
applyCategoryFilter(currentCategory);
}
private void setupUI() {
binding.swipeRefresh.setOnRefreshListener(this::fetchRooms);
binding.topTabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
if (tab == null) return;
CharSequence title = tab.getText();
TabPlaceholderActivity.start(MainActivity.this, title != null ? title.toString() : "");
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
if (tab == null) return;
CharSequence title = tab.getText();
TabPlaceholderActivity.start(MainActivity.this, title != null ? title.toString() : "");
}
});
binding.categoryTabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
if (tab == null) return;
CharSequence title = tab.getText();
currentCategory = title != null ? title.toString() : "推荐";
applyCategoryFilter(currentCategory);
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
if (tab == null) return;
CharSequence title = tab.getText();
currentCategory = title != null ? title.toString() : "推荐";
applyCategoryFilter(currentCategory);
}
});
binding.roomsRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
@ -356,7 +404,9 @@ public class MainActivity extends AppCompatActivity {
if (rooms == null || rooms.isEmpty()) {
rooms = buildDemoRooms(12);
}
adapter.submitList(rooms);
allRooms.clear();
allRooms.addAll(rooms);
applyCategoryFilter(currentCategory);
adapter.bumpCoverOffset();
}
@ -365,12 +415,42 @@ public class MainActivity extends AppCompatActivity {
binding.loading.setVisibility(View.GONE);
binding.swipeRefresh.setRefreshing(false);
isFetching = false;
adapter.submitList(buildDemoRooms(12));
allRooms.clear();
allRooms.addAll(buildDemoRooms(12));
applyCategoryFilter(currentCategory);
adapter.bumpCoverOffset();
}
});
}
private void applyCategoryFilter(String category) {
String c = category != null ? category : "推荐";
if ("推荐".equals(c)) {
adapter.submitList(new ArrayList<>(allRooms));
return;
}
List<Room> filtered = new ArrayList<>();
for (Room r : allRooms) {
if (r == null) continue;
if (c.equals(getDemoCategoryForRoom(r))) {
filtered.add(r);
}
}
adapter.submitList(filtered);
}
private String getDemoCategoryForRoom(Room room) {
String[] categories = new String[]{"游戏", "才艺", "户外", "音乐", "美食", "聊天"};
try {
String seed = room != null && room.getId() != null ? room.getId() : room != null ? room.getTitle() : "";
int h = Math.abs(seed != null ? seed.hashCode() : 0);
return categories[h % categories.length];
} catch (Exception ignored) {
return "游戏";
}
}
private List<Room> buildDemoRooms(int count) {
List<Room> list = new ArrayList<>();
for (int i = 0; i < count; i++) {

View File

@ -3,12 +3,17 @@ package com.example.livestreaming;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.example.livestreaming.databinding.ActivityMessagesBinding;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import java.util.ArrayList;
import java.util.List;
public class MessagesActivity extends AppCompatActivity {
private ActivityMessagesBinding binding;
@ -24,6 +29,8 @@ public class MessagesActivity extends AppCompatActivity {
binding = ActivityMessagesBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setupConversationList();
BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation;
bottomNavigation.setSelectedItemId(R.id.nav_messages);
bottomNavigation.setOnItemSelectedListener(item -> {
@ -55,6 +62,28 @@ public class MessagesActivity extends AppCompatActivity {
});
}
private void setupConversationList() {
ConversationsAdapter adapter = new ConversationsAdapter(item -> {
if (item == null) return;
Toast.makeText(this, "打开会话:" + item.getTitle(), Toast.LENGTH_SHORT).show();
});
binding.conversationsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
binding.conversationsRecyclerView.setAdapter(adapter);
adapter.submitList(buildDemoConversations());
}
private List<ConversationItem> buildDemoConversations() {
List<ConversationItem> list = new ArrayList<>();
list.add(new ConversationItem("sys", "系统通知", "欢迎来到直播间~新手指南已送达", "09:12", 2, false));
list.add(new ConversationItem("a", "小王(主播)", "今晚8点开播记得来捧场", "昨天", 0, false));
list.add(new ConversationItem("b", "附近的人", "嗨~一起连麦吗?", "昨天", 5, false));
list.add(new ConversationItem("c", "运营小助手", "活动报名已通过,点击查看详情", "周二", 0, true));
list.add(new ConversationItem("d", "直播间群聊", "[图片]", "周一", 19, false));
list.add(new ConversationItem("e", "小李", "收到啦", "周一", 0, false));
list.add(new ConversationItem("f", "客服", "您好,请描述一下遇到的问题", "上周", 1, false));
return list;
}
@Override
protected void onResume() {
super.onResume();

View File

@ -0,0 +1,89 @@
package com.example.livestreaming;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.example.livestreaming.databinding.ActivityMyFriendsBinding;
import java.util.ArrayList;
import java.util.List;
public class MyFriendsActivity extends AppCompatActivity {
private ActivityMyFriendsBinding binding;
private FriendsAdapter adapter;
private final List<FriendItem> all = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMyFriendsBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.backButton.setOnClickListener(v -> finish());
adapter = new FriendsAdapter(item -> {
if (item == null) return;
Toast.makeText(this, "打开挚友:" + item.getName(), Toast.LENGTH_SHORT).show();
});
binding.friendsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
binding.friendsRecyclerView.setAdapter(adapter);
all.clear();
all.addAll(buildDemoFriends());
adapter.submitList(new ArrayList<>(all));
binding.searchEdit.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
String q = s != null ? s.toString().trim() : "";
applyFilter(q);
}
});
}
private void applyFilter(String query) {
if (query == null || query.trim().isEmpty()) {
adapter.submitList(new ArrayList<>(all));
return;
}
String q = query.toLowerCase();
List<FriendItem> filtered = new ArrayList<>();
for (FriendItem f : all) {
if (f == null) continue;
String name = f.getName() != null ? f.getName() : "";
String sub = f.getSubtitle() != null ? f.getSubtitle() : "";
if (name.toLowerCase().contains(q) || sub.toLowerCase().contains(q)) {
filtered.add(f);
}
}
adapter.submitList(filtered);
}
private List<FriendItem> buildDemoFriends() {
List<FriendItem> list = new ArrayList<>();
list.add(new FriendItem("1", "小王", "最近在线5分钟前", true));
list.add(new FriendItem("2", "小李", "共同关注:王者荣耀", false));
list.add(new FriendItem("3", "安安", "备注:一起连麦", true));
list.add(new FriendItem("4", "小陈", "最近在线:昨天", false));
list.add(new FriendItem("5", "小美", "共同关注:音乐", true));
list.add(new FriendItem("6", "老张", "最近在线3天前", false));
list.add(new FriendItem("7", "小七", "备注:挚友", true));
list.add(new FriendItem("8", "阿杰", "共同关注:美食", false));
return list;
}
}

View File

@ -0,0 +1,50 @@
package com.example.livestreaming;
import java.util.Objects;
public class NearbyUser {
private final String id;
private final String name;
private final String distanceText;
private final boolean isLive;
public NearbyUser(String id, String name, String distanceText, boolean isLive) {
this.id = id;
this.name = name;
this.distanceText = distanceText;
this.isLive = isLive;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getDistanceText() {
return distanceText;
}
public boolean isLive() {
return isLive;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof NearbyUser)) return false;
NearbyUser that = (NearbyUser) o;
return isLive == that.isLive
&& Objects.equals(id, that.id)
&& Objects.equals(name, that.name)
&& Objects.equals(distanceText, that.distanceText);
}
@Override
public int hashCode() {
return Objects.hash(id, name, distanceText, isLive);
}
}

View File

@ -0,0 +1,104 @@
package com.example.livestreaming;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import com.example.livestreaming.databinding.ItemNearbyUserBinding;
public class NearbyUsersAdapter extends ListAdapter<NearbyUser, NearbyUsersAdapter.VH> {
public interface OnUserClickListener {
void onUserClick(NearbyUser user);
}
private final OnUserClickListener onUserClickListener;
public NearbyUsersAdapter(OnUserClickListener onUserClickListener) {
super(DIFF);
this.onUserClickListener = onUserClickListener;
}
@NonNull
@Override
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemNearbyUserBinding binding = ItemNearbyUserBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new VH(binding, onUserClickListener);
}
@Override
public void onBindViewHolder(@NonNull VH holder, int position) {
holder.bind(getItem(position));
}
static class VH extends RecyclerView.ViewHolder {
private final ItemNearbyUserBinding binding;
private final OnUserClickListener onUserClickListener;
VH(ItemNearbyUserBinding binding, OnUserClickListener onUserClickListener) {
super(binding.getRoot());
this.binding = binding;
this.onUserClickListener = onUserClickListener;
}
void bind(NearbyUser user) {
binding.userName.setText(user != null && user.getName() != null ? user.getName() : "");
binding.distanceText.setText(user != null && user.getDistanceText() != null ? user.getDistanceText() : "");
try {
String seed = user != null && user.getId() != null ? user.getId() : String.valueOf(getBindingAdapterPosition());
int h = Math.abs(seed.hashCode());
int mode = h % 3;
String ratio;
if (mode == 0) {
ratio = "1:1";
} else if (mode == 1) {
ratio = "3:4";
} else {
ratio = "4:3";
}
ViewGroup.LayoutParams lp = binding.avatarImage.getLayoutParams();
if (lp instanceof ConstraintLayout.LayoutParams) {
ConstraintLayout.LayoutParams clp = (ConstraintLayout.LayoutParams) lp;
if (clp.dimensionRatio == null || !clp.dimensionRatio.equals(ratio)) {
clp.dimensionRatio = ratio;
binding.avatarImage.setLayoutParams(clp);
}
}
} catch (Exception ignored) {
}
if (user != null && user.isLive()) {
binding.liveBadge.setVisibility(View.VISIBLE);
} else {
binding.liveBadge.setVisibility(View.GONE);
}
binding.getRoot().setOnClickListener(v -> {
if (user == null) return;
if (onUserClickListener != null) onUserClickListener.onUserClick(user);
});
}
}
private static final DiffUtil.ItemCallback<NearbyUser> DIFF = new DiffUtil.ItemCallback<NearbyUser>() {
@Override
public boolean areItemsTheSame(@NonNull NearbyUser oldItem, @NonNull NearbyUser newItem) {
String o = oldItem.getId();
String n = newItem.getId();
return o != null && o.equals(n);
}
@Override
public boolean areContentsTheSame(@NonNull NearbyUser oldItem, @NonNull NearbyUser newItem) {
return oldItem.equals(newItem);
}
};
}

View File

@ -3,9 +3,14 @@ package com.example.livestreaming;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AlertDialog;
import com.bumptech.glide.Glide;
import com.example.livestreaming.databinding.ActivityProfileBinding;
import com.google.android.material.bottomnavigation.BottomNavigationView;
@ -13,6 +18,18 @@ public class ProfileActivity extends AppCompatActivity {
private ActivityProfileBinding binding;
private static final String PREFS_NAME = "profile_prefs";
private static final String KEY_NAME = "profile_name";
private static final String KEY_BIO = "profile_bio";
private static final String KEY_LEVEL = "profile_level";
private static final String KEY_FANS_BADGE = "profile_fans_badge";
private static final String KEY_BADGE = "profile_badge";
private static final String KEY_AVATAR_RES = "profile_avatar_res";
private static final String KEY_AVATAR_URI = "profile_avatar_uri";
private static final String KEY_BIRTHDAY = "profile_birthday";
private static final String KEY_GENDER = "profile_gender";
private static final String KEY_LOCATION = "profile_location";
public static void start(Context context) {
Intent intent = new Intent(context, ProfileActivity.class);
context.startActivity(intent);
@ -24,6 +41,10 @@ public class ProfileActivity extends AppCompatActivity {
binding = ActivityProfileBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
loadProfileFromPrefs();
setupEditableAreas();
setupNavigationClicks();
BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation;
bottomNavigation.setSelectedItemId(R.id.nav_profile);
bottomNavigation.setOnItemSelectedListener(item -> {
@ -52,10 +73,96 @@ public class ProfileActivity extends AppCompatActivity {
});
}
private void loadProfileFromPrefs() {
String n = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_NAME, null);
if (!TextUtils.isEmpty(n)) binding.name.setText(n);
String bio = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_BIO, null);
if (!TextUtils.isEmpty(bio)) {
binding.bioText.setText(bio);
binding.bioText.setTextColor(0xFF111111);
}
String avatarUri = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_AVATAR_URI, null);
if (!TextUtils.isEmpty(avatarUri)) {
Glide.with(this).load(avatarUri).into(binding.avatar);
} else {
int avatarRes = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getInt(KEY_AVATAR_RES, 0);
if (avatarRes != 0) {
binding.avatar.setImageResource(avatarRes);
}
}
// 等级/称号/徽章保持固定显示例如月亮/星耀/至尊不从本地缓存覆盖
}
private void setupEditableAreas() {
binding.name.setOnClickListener(v -> showEditDialog("编辑昵称", binding.name.getText() != null ? binding.name.getText().toString() : "", text -> {
binding.name.setText(text);
getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit().putString(KEY_NAME, text).apply();
}));
binding.bioText.setOnClickListener(v -> showEditDialog("编辑签名", binding.bioText.getText() != null ? binding.bioText.getText().toString() : "", text -> {
if (TextUtils.isEmpty(text)) {
binding.bioText.setText("填写个人签名更容易获得关注,点击此处添加");
binding.bioText.setTextColor(0xFF999999);
getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit().remove(KEY_BIO).apply();
} else {
binding.bioText.setText(text);
binding.bioText.setTextColor(0xFF111111);
getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit().putString(KEY_BIO, text).apply();
}
}));
}
private void setupNavigationClicks() {
binding.topActionSearch.setOnClickListener(v -> TabPlaceholderActivity.start(this, "搜索"));
binding.topActionClock.setOnClickListener(v -> TabPlaceholderActivity.start(this, "定位/发现"));
binding.topActionMore.setOnClickListener(v -> TabPlaceholderActivity.start(this, "更多"));
binding.following.setOnClickListener(v -> FollowingListActivity.start(this));
binding.followers.setOnClickListener(v -> FansListActivity.start(this));
binding.likes.setOnClickListener(v -> LikesListActivity.start(this));
binding.recordBtn.setOnClickListener(v -> Toast.makeText(this, "录制声音(待实现)", Toast.LENGTH_SHORT).show());
binding.action1.setOnClickListener(v -> TabPlaceholderActivity.start(this, "公园勋章"));
binding.action2.setOnClickListener(v -> WatchHistoryActivity.start(this));
binding.action3.setOnClickListener(v -> startActivity(new Intent(this, MyFriendsActivity.class)));
binding.editProfile.setOnClickListener(v -> EditProfileActivity.start(this));
binding.shareHome.setOnClickListener(v -> TabPlaceholderActivity.start(this, "分享主页"));
binding.addFriendBtn.setOnClickListener(v -> TabPlaceholderActivity.start(this, "加好友"));
}
private interface OnTextSaved {
void onSaved(String text);
}
private void showEditDialog(String title, String initialValue, OnTextSaved onSaved) {
EditText editText = new EditText(this);
editText.setText(initialValue != null ? initialValue : "");
editText.setSelection(editText.getText() != null ? editText.getText().length() : 0);
int pad = (int) (16 * getResources().getDisplayMetrics().density);
editText.setPadding(pad, pad, pad, pad);
new AlertDialog.Builder(this)
.setTitle(title)
.setView(editText)
.setNegativeButton("取消", null)
.setPositiveButton("保存", (d, w) -> {
String t = editText.getText() != null ? editText.getText().toString().trim() : "";
if (onSaved != null) onSaved.onSaved(t);
})
.show();
}
@Override
protected void onResume() {
super.onResume();
if (binding != null) {
loadProfileFromPrefs();
binding.bottomNavInclude.bottomNavigation.setSelectedItemId(R.id.nav_profile);
}
}

View File

@ -0,0 +1,127 @@
package com.example.livestreaming;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.example.livestreaming.databinding.ActivityTabPlaceholderBinding;
import com.example.livestreaming.net.Room;
import java.util.ArrayList;
import java.util.List;
public class TabPlaceholderActivity extends AppCompatActivity {
public static final String EXTRA_TITLE = "extra_title";
private ActivityTabPlaceholderBinding binding;
public static void start(Context context, String title) {
Intent intent = new Intent(context, TabPlaceholderActivity.class);
intent.putExtra(EXTRA_TITLE, title);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityTabPlaceholderBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
String title = getIntent() != null ? getIntent().getStringExtra(EXTRA_TITLE) : null;
if (title == null) title = "";
binding.titleText.setText(title);
binding.backButton.setOnClickListener(v -> finish());
renderByTitle(title);
}
private void renderByTitle(String title) {
if ("关注".equals(title)) {
showFollowRooms();
return;
}
if ("附近".equals(title)) {
showNearbyUsers();
return;
}
binding.genericScroll.setVisibility(View.VISIBLE);
binding.followRecyclerView.setVisibility(View.GONE);
binding.nearbyRecyclerView.setVisibility(View.GONE);
}
private void showFollowRooms() {
binding.genericScroll.setVisibility(View.GONE);
binding.followRecyclerView.setVisibility(View.VISIBLE);
binding.nearbyRecyclerView.setVisibility(View.GONE);
RoomsAdapter adapter = new RoomsAdapter(room -> {
if (room == null) return;
Intent intent = new Intent(TabPlaceholderActivity.this, RoomDetailActivity.class);
intent.putExtra(RoomDetailActivity.EXTRA_ROOM_ID, room.getId());
startActivity(intent);
});
StaggeredGridLayoutManager glm = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
glm.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
binding.followRecyclerView.setLayoutManager(glm);
binding.followRecyclerView.setAdapter(adapter);
adapter.submitList(buildFollowDemoRooms(16));
}
private void showNearbyUsers() {
binding.genericScroll.setVisibility(View.GONE);
binding.followRecyclerView.setVisibility(View.GONE);
binding.nearbyRecyclerView.setVisibility(View.VISIBLE);
NearbyUsersAdapter adapter = new NearbyUsersAdapter(user -> {
if (user == null) return;
Toast.makeText(this, "点击:" + user.getName(), Toast.LENGTH_SHORT).show();
});
StaggeredGridLayoutManager glm = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
glm.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
binding.nearbyRecyclerView.setLayoutManager(glm);
binding.nearbyRecyclerView.setAdapter(adapter);
adapter.submitList(buildNearbyDemoUsers(18));
}
private List<Room> buildFollowDemoRooms(int count) {
List<Room> list = new ArrayList<>();
for (int i = 0; i < count; i++) {
String id = "follow-" + i;
String title = "关注主播直播间 " + (i + 1);
String streamer = "已关注主播" + (i + 1);
boolean live = i % 4 != 0;
list.add(new Room(id, title, streamer, live));
}
return list;
}
private List<NearbyUser> buildNearbyDemoUsers(int count) {
List<NearbyUser> list = new ArrayList<>();
for (int i = 0; i < count; i++) {
String id = "nearby-" + i;
String name = "附近主播" + (i + 1);
boolean live = i % 3 == 0;
String distanceText;
if (i < 3) {
distanceText = (300 + i * 120) + "m";
} else {
float km = 0.8f + (i - 3) * 0.35f;
distanceText = String.format("%.1fkm", km);
}
list.add(new NearbyUser(id, name, distanceText, live));
}
return list;
}
}

View File

@ -0,0 +1,59 @@
package com.example.livestreaming;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.example.livestreaming.databinding.ActivityWatchHistoryBinding;
import com.example.livestreaming.net.Room;
import java.util.ArrayList;
import java.util.List;
public class WatchHistoryActivity extends AppCompatActivity {
private ActivityWatchHistoryBinding binding;
public static void start(Context context) {
Intent intent = new Intent(context, WatchHistoryActivity.class);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityWatchHistoryBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.backButton.setOnClickListener(v -> finish());
RoomsAdapter adapter = new RoomsAdapter(room -> {
if (room == null) return;
Intent intent = new Intent(WatchHistoryActivity.this, RoomDetailActivity.class);
intent.putExtra(RoomDetailActivity.EXTRA_ROOM_ID, room.getId());
startActivity(intent);
});
StaggeredGridLayoutManager glm = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
glm.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
binding.roomsRecyclerView.setLayoutManager(glm);
binding.roomsRecyclerView.setAdapter(adapter);
adapter.submitList(buildDemoHistory(18));
}
private List<Room> buildDemoHistory(int count) {
List<Room> list = new ArrayList<>();
for (int i = 0; i < count; i++) {
String id = "history-" + i;
String title = "看过的直播间 " + (i + 1);
String streamer = "主播" + (i + 1);
boolean live = i % 5 != 0;
list.add(new Room(id, title, streamer, live));
}
return list;
}
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/transparent" />
</shape>

View File

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

View File

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

View File

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

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#111111"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8l8,8l1.41,-1.41L7.83,13H20v-2z" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 KiB

View File

@ -0,0 +1,273 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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="match_parent"
android:background="#F6F7FB">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#F6F7FB">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="12dp"
android:paddingBottom="12dp">
<ImageView
android:id="@+id/backButton"
android:layout_width="24dp"
android:layout_height="24dp"
android:contentDescription="back"
android:src="@drawable/ic_arrow_back_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/titleText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:ellipsize="end"
android:maxLines="1"
android:text="编辑资料"
android:textColor="#111111"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@id/backButton"
app:layout_constraintEnd_toStartOf="@id/saveButton"
app:layout_constraintStart_toEndOf="@id/backButton"
app:layout_constraintTop_toTopOf="@id/backButton" />
<TextView
android:id="@+id/saveButton"
android:layout_width="wrap_content"
android:layout_height="32dp"
android:background="@drawable/bg_purple_999"
android:gravity="center"
android:paddingStart="14dp"
android:paddingEnd="14dp"
android:text="保存"
android:textColor="@android:color/white"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@id/backButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/backButton" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="14dp"
android:paddingBottom="24dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/avatarRow"
android:layout_width="0dp"
android:layout_height="72dp"
android:background="@drawable/bg_white_16"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/avatarLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="头像"
android:textColor="#111111"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="14dp" />
<TextView
android:id="@+id/avatarHint"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:ellipsize="end"
android:maxLines="1"
android:text="从相册选择或拍照"
android:textColor="#666666"
android:textSize="13sp"
app:layout_constraintEnd_toStartOf="@id/avatarPreview"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/avatarLabel" />
<ImageView
android:id="@+id/avatarPreview"
android:layout_width="64dp"
android:layout_height="64dp"
android:background="@drawable/bg_avatar_circle"
android:padding="10dp"
android:src="@drawable/ic_person_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/nameLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="昵称"
app:boxBackgroundMode="filled"
app:boxBackgroundColor="@android:color/white"
app:boxCornerRadiusTopStart="14dp"
app:boxCornerRadiusTopEnd="14dp"
app:boxCornerRadiusBottomStart="14dp"
app:boxCornerRadiusBottomEnd="14dp"
app:boxStrokeColor="#22000000"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/avatarRow"
android:layout_marginTop="12dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/inputName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/bioLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="个人签名"
app:boxBackgroundMode="filled"
app:boxBackgroundColor="@android:color/white"
app:boxCornerRadiusTopStart="14dp"
app:boxCornerRadiusTopEnd="14dp"
app:boxCornerRadiusBottomStart="14dp"
app:boxCornerRadiusBottomEnd="14dp"
app:boxStrokeColor="#22000000"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/nameLayout">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/inputBio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minLines="2"
android:maxLines="4" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/birthdayLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="生日(例如 2001-08-08"
app:boxBackgroundMode="filled"
app:boxBackgroundColor="@android:color/white"
app:boxCornerRadiusTopStart="14dp"
app:boxCornerRadiusTopEnd="14dp"
app:boxCornerRadiusBottomStart="14dp"
app:boxCornerRadiusBottomEnd="14dp"
app:boxStrokeColor="#22000000"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/bioLayout">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/inputBirthday"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/genderLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="性别(男/女/保密)"
app:boxBackgroundMode="filled"
app:boxBackgroundColor="@android:color/white"
app:boxCornerRadiusTopStart="14dp"
app:boxCornerRadiusTopEnd="14dp"
app:boxCornerRadiusBottomStart="14dp"
app:boxCornerRadiusBottomEnd="14dp"
app:boxStrokeColor="#22000000"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/birthdayLayout">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/inputGender"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/locationLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="所在地(例如 北京·朝阳)"
app:boxBackgroundMode="filled"
app:boxBackgroundColor="@android:color/white"
app:boxCornerRadiusTopStart="14dp"
app:boxCornerRadiusTopEnd="14dp"
app:boxCornerRadiusBottomStart="14dp"
app:boxCornerRadiusBottomEnd="14dp"
app:boxStrokeColor="#22000000"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/genderLayout">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/inputLocation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/cancelButton"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_marginTop="18dp"
android:background="@drawable/bg_gray_12"
android:gravity="center"
android:text="取消"
android:textColor="#111111"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/locationLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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="match_parent"
android:background="@android:color/white">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="14dp"
android:paddingBottom="14dp">
<ImageView
android:id="@+id/backButton"
android:layout_width="24dp"
android:layout_height="24dp"
android:contentDescription="back"
android:src="@drawable/ic_arrow_back_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/titleText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:ellipsize="end"
android:maxLines="1"
android:text="粉丝列表"
android:textColor="#111111"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@id/backButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/backButton"
app:layout_constraintTop_toTopOf="@id/backButton" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/fansRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingTop="6dp"
android:paddingBottom="16dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -97,13 +97,15 @@
<ImageView
android:id="@+id/centerButton"
android:layout_width="92dp"
android:layout_height="92dp"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_gravity="center"
android:background="@drawable/bg_fishpond_center_circle"
android:background="@android:color/transparent"
android:clipToOutline="true"
android:elevation="10dp"
android:padding="22dp"
android:src="@drawable/ic_globe_24" />
android:padding="0dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_globe_clean_filled" />
<ImageView
android:layout_width="72dp"

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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="match_parent"
android:background="@android:color/white">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="14dp"
android:paddingBottom="14dp">
<ImageView
android:id="@+id/backButton"
android:layout_width="24dp"
android:layout_height="24dp"
android:contentDescription="back"
android:src="@drawable/ic_arrow_back_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/titleText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:ellipsize="end"
android:maxLines="1"
android:text="关注列表"
android:textColor="#111111"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@id/backButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/backButton"
app:layout_constraintTop_toTopOf="@id/backButton" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/followingRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingTop="6dp"
android:paddingBottom="16dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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="match_parent"
android:background="@android:color/white">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="14dp"
android:paddingBottom="14dp">
<ImageView
android:id="@+id/backButton"
android:layout_width="24dp"
android:layout_height="24dp"
android:contentDescription="back"
android:src="@drawable/ic_arrow_back_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/titleText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:ellipsize="end"
android:maxLines="1"
android:text="获赞"
android:textColor="#111111"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@id/backButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/backButton"
app:layout_constraintTop_toTopOf="@id/backButton" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/likesRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingTop="6dp"
android:paddingBottom="16dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -5,16 +5,44 @@
android:layout_height="match_parent"
android:background="@android:color/white">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="14dp"
android:paddingBottom="14dp">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:paddingBottom="88dp"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:text="消息"
android:textColor="#111111"
android:textSize="18sp"
android:textStyle="bold" />
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/conversationsRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="88dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<include
android:id="@+id/bottomNavInclude"

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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="match_parent"
android:background="@android:color/white">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="14dp"
android:paddingBottom="12dp">
<ImageView
android:id="@+id/backButton"
android:layout_width="24dp"
android:layout_height="24dp"
android:contentDescription="back"
android:src="@drawable/ic_arrow_back_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/titleText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:ellipsize="end"
android:maxLines="1"
android:text="我的挚友"
android:textColor="#111111"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@id/backButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/backButton"
app:layout_constraintTop_toTopOf="@id/backButton" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/searchContainer"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_marginTop="12dp"
android:background="@drawable/bg_search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/backButton">
<ImageView
android:id="@+id/searchIcon"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginStart="12dp"
android:contentDescription="search"
android:src="@drawable/ic_search_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/searchEdit"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:background="@android:color/transparent"
android:hint="搜索挚友"
android:inputType="text"
android:singleLine="true"
android:textColor="#111111"
android:textColorHint="#999999"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/searchIcon"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/friendsRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingTop="6dp"
android:paddingBottom="16dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,242 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="match_parent">
<ProgressBar
android:id="@+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/topBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingHorizontal="12dp"
android:paddingVertical="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.button.MaterialButton
android:id="@+id/backButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:insetTop="0dp"
android:insetBottom="0dp"
android:minHeight="0dp"
android:minWidth="0dp"
android:text="返回"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/topTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:text="直播间"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/topRightGroup"
app:layout_constraintStart_toEndOf="@id/backButton"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/topRightGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/topViewerCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:text="0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/fullscreenButton"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/fullscreenButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:insetTop="0dp"
android:insetBottom="0dp"
android:minHeight="0dp"
android:minWidth="0dp"
android:text="全屏"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/playerArea"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/topBar">
<androidx.media3.ui.PlayerView
android:id="@+id/playerView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/offlineLayout"
android:layout_width="0dp"
android:layout_height="0dp"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="主播暂未开播"
android:textColor="#666666" />
</LinearLayout>
<TextView
android:id="@+id/liveTag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:paddingHorizontal="10dp"
android:paddingVertical="6dp"
android:text="LIVE"
android:textColor="@android:color/white"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/roomInfoLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingHorizontal="12dp"
android:paddingVertical="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/playerArea">
<TextView
android:id="@+id/roomTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:text="直播间"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/followButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/streamerName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="主播"
android:textColor="#666666"
app:layout_constraintEnd_toStartOf="@id/followButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomTitle" />
<com.google.android.material.button.MaterialButton
android:id="@+id/followButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="关注"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/chatLayout"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomInfoLayout">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/chatRecyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:overScrollMode="never"
android:paddingHorizontal="12dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
app:layout_constraintBottom_toTopOf="@id/chatInputBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/chatInputBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/chatInput"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="说点什么..."
android:imeOptions="actionSend"
android:inputType="text"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/sendButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/sendButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发送"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,246 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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="match_parent"
android:background="@android:color/white">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="14dp"
android:paddingBottom="14dp">
<ImageView
android:id="@+id/backButton"
android:layout_width="24dp"
android:layout_height="24dp"
android:contentDescription="back"
android:src="@drawable/ic_arrow_back_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/titleText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="#111111"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@id/backButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/backButton"
app:layout_constraintTop_toTopOf="@id/backButton" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:id="@+id/contentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.core.widget.NestedScrollView
android:id="@+id/genericScroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="12dp"
android:paddingBottom="24dp">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
app:strokeColor="#14000000"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="频道概览"
android:textColor="#111111"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:lineSpacingExtra="2dp"
android:text="这里先放一个通用样式骨架:你后续提出需求时,我会把对应模块替换成真实内容(列表/网格/筛选/推荐流等)。"
android:textColor="#666666"
android:textSize="13sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_weight="1"
android:text="筛选"
app:cornerRadius="12dp" />
<com.google.android.material.button.MaterialButton
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_marginStart="10dp"
android:layout_weight="1"
android:text="排序"
app:cornerRadius="12dp"
app:strokeWidth="1dp" />
<com.google.android.material.button.MaterialButton
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_marginStart="10dp"
android:layout_weight="1"
android:text="更多"
app:cornerRadius="12dp"
app:strokeWidth="1dp" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="18dp"
android:text="推荐内容"
android:textColor="#111111"
android:textSize="16sp"
android:textStyle="bold" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
app:strokeColor="#14000000"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="14dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="占位卡片 1"
android:textColor="#111111"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="后续可替换成直播间列表、短视频流、附近的人等。"
android:textColor="#666666"
android:textSize="13sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
app:strokeColor="#14000000"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="14dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="占位卡片 2"
android:textColor="#111111"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="这里可以放一个横滑专题、榜单、活动入口等。"
android:textColor="#666666"
android:textSize="13sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/followRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingTop="12dp"
android:paddingBottom="24dp"
android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/nearbyRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingTop="12dp"
android:paddingBottom="24dp"
android:visibility="gone" />
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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="match_parent"
android:background="@android:color/white">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="14dp"
android:paddingBottom="14dp">
<ImageView
android:id="@+id/backButton"
android:layout_width="24dp"
android:layout_height="24dp"
android:contentDescription="back"
android:src="@drawable/ic_arrow_back_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/titleText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:ellipsize="end"
android:maxLines="1"
android:text="观看历史"
android:textColor="#111111"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@id/backButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/backButton"
app:layout_constraintTop_toTopOf="@id/backButton" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/roomsRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingTop="8dp"
android:paddingBottom="16dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -5,16 +5,124 @@
android:layout_height="match_parent"
android:background="@android:color/white">
<TextView
android:id="@+id/title"
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:paddingBottom="88dp"
android:text="许愿树"
android:textColor="#111111"
android:textSize="18sp"
android:textStyle="bold" />
android:background="#FFF7F7"
android:paddingBottom="88dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/topBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingTop="14dp"
android:paddingBottom="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="WishTree"
android:textColor="#B28AE6"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/searchButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/searchButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@android:color/transparent"
android:contentDescription="Search"
android:padding="8dp"
android:src="@drawable/ic_search_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/notifyButton"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/notifyButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@android:color/transparent"
android:contentDescription="Notifications"
android:padding="8dp"
android:src="@drawable/ic_notifications_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/banner"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:background="@drawable/bg_wish_tree_banner"
android:paddingHorizontal="14dp"
android:paddingVertical="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/topBar">
<TextView
android:id="@+id/bannerText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:text="元旦许愿树 | 任意充值即翻倍"
android:textColor="#6E2DB7"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!--
<TextView
android:id="@+id/bannerTimer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_wish_tree_timer"
android:paddingHorizontal="10dp"
android:paddingVertical="6dp"
android:text="12:30 05:00"
android:textColor="@android:color/white"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
-->
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
android:id="@+id/wishTreeImage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:adjustViewBounds="true"
android:contentDescription="Wish Tree"
android:scaleType="fitCenter"
android:src="@drawable/wish_tree"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/banner" />
</androidx.constraintlayout.widget.ConstraintLayout>
<include
android:id="@+id/bottomNavInclude"

View File

@ -0,0 +1,46 @@
<?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:paddingTop="8dp"
android:paddingBottom="12dp">
<TextView
android:id="@+id/actionPickGallery"
android:layout_width="match_parent"
android:layout_height="52dp"
android:gravity="center"
android:text="从相册选择"
android:textColor="#111111"
android:textSize="16sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#11000000" />
<TextView
android:id="@+id/actionTakePhoto"
android:layout_width="match_parent"
android:layout_height="52dp"
android:gravity="center"
android:text="拍照"
android:textColor="#111111"
android:textSize="16sp" />
<View
android:layout_width="match_parent"
android:layout_height="8dp"
android:background="@android:color/transparent" />
<TextView
android:id="@+id/actionCancel"
android:layout_width="match_parent"
android:layout_height="52dp"
android:gravity="center"
android:text="取消"
android:textColor="#666666"
android:textSize="16sp" />
</LinearLayout>

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:padding="16dp">
<TextView
android:id="@+id/addressLabel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="推流地址"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/addressText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:textColor="#666666"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/addressLabel" />
<com.google.android.material.button.MaterialButton
android:id="@+id/copyAddressBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="复制地址"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/addressText" />
<TextView
android:id="@+id/keyLabel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="推流密钥"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/copyAddressBtn" />
<TextView
android:id="@+id/keyText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:textColor="#666666"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/keyLabel" />
<com.google.android.material.button.MaterialButton
android:id="@+id/copyKeyBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="复制密钥"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/keyText" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/messageText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="4dp"
android:textColor="@android:color/white"
android:textSize="14sp" />

View File

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="72dp"
android:background="@android:color/white"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<ImageView
android:id="@+id/avatar"
android:layout_width="44dp"
android:layout_height="44dp"
android:background="@drawable/bg_avatar_circle"
android:padding="6dp"
android:src="@drawable/ic_account_circle_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:text="会话标题"
android:textColor="#111111"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/timeText"
app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintTop_toTopOf="@id/avatar"
android:layout_marginStart="12dp"
android:layout_marginEnd="10dp" />
<TextView
android:id="@+id/lastMessage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:text="最后一条消息内容预览"
android:textColor="#666666"
android:textSize="13sp"
app:layout_constraintBottom_toBottomOf="@id/avatar"
app:layout_constraintEnd_toStartOf="@id/unreadBadge"
app:layout_constraintStart_toStartOf="@id/title"
android:layout_marginEnd="10dp" />
<TextView
android:id="@+id/timeText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="12:30"
android:textColor="#999999"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/title" />
<ImageView
android:id="@+id/muteIcon"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginEnd="8dp"
android:contentDescription="mute"
android:src="@android:drawable/ic_lock_silent_mode"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/lastMessage"
app:layout_constraintEnd_toStartOf="@id/unreadBadge" />
<TextView
android:id="@+id/unreadBadge"
android:layout_width="wrap_content"
android:layout_height="18dp"
android:background="@drawable/live_badge_background"
android:gravity="center"
android:minWidth="18dp"
android:paddingStart="6dp"
android:paddingEnd="6dp"
android:text="1"
android:textColor="@android:color/white"
android:textSize="11sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/lastMessage"
app:layout_constraintEnd_toEndOf="parent" />
<View
android:id="@+id/divider"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="#0F000000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/avatar" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="72dp"
android:background="@android:color/white"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<ImageView
android:id="@+id/avatar"
android:layout_width="44dp"
android:layout_height="44dp"
android:background="@drawable/bg_avatar_circle"
android:padding="6dp"
android:src="@drawable/ic_account_circle_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/onlineDot"
android:layout_width="10dp"
android:layout_height="10dp"
android:background="@drawable/live_badge_background"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/avatar"
app:layout_constraintEnd_toEndOf="@id/avatar" />
<TextView
android:id="@+id/name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:ellipsize="end"
android:maxLines="1"
android:text="挚友昵称"
android:textColor="#111111"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintTop_toTopOf="@id/avatar" />
<TextView
android:id="@+id/subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="12dp"
android:ellipsize="end"
android:maxLines="1"
android:text="最近在线 / 共同关注 / 备注"
android:textColor="#666666"
android:textSize="13sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintTop_toBottomOf="@id/name" />
<View
android:id="@+id/divider"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="#0F000000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/avatar" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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="6dp"
app:cardCornerRadius="14dp"
app:cardElevation="0dp"
app:strokeColor="#14000000"
app:strokeWidth="1dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp">
<ImageView
android:id="@+id/avatarImage"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#0A000000"
android:scaleType="centerCrop"
android:src="@drawable/ic_account_circle_24"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/liveBadge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:background="@drawable/live_badge_background"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:text="LIVE"
android:textColor="@android:color/white"
android:textSize="10sp"
android:visibility="gone"
app:layout_constraintStart_toStartOf="@id/avatarImage"
app:layout_constraintTop_toTopOf="@id/avatarImage" />
<TextView
android:id="@+id/userName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:ellipsize="end"
android:maxLines="1"
android:text="附近主播"
android:textColor="#111111"
android:textSize="13sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/avatarImage" />
<TextView
android:id="@+id/distanceText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:ellipsize="end"
android:maxLines="1"
android:text="1.2km"
android:textColor="#666666"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/userName" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="cache_images" path="images/" />
</paths>

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

View File

@ -0,0 +1,62 @@
param(
[Parameter(Mandatory=$true)][string]$InputPath,
[Parameter(Mandatory=$true)][string]$OutputPath,
[string]$BackupPath = ""
)
if (!(Test-Path -LiteralPath $InputPath)) {
throw "Input file not found: $InputPath"
}
if ($BackupPath -ne "") {
Copy-Item -LiteralPath $InputPath -Destination $BackupPath -Force
}
Add-Type -AssemblyName System.Drawing
# Load from a stream to avoid locking the input file (required if output overwrites input)
$fs = [System.IO.File]::Open($InputPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
try {
$bmp = New-Object System.Drawing.Bitmap($fs)
} finally {
$fs.Dispose()
}
$out = New-Object System.Drawing.Bitmap($bmp.Width, $bmp.Height, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
function IsBgPixel($c) {
$r = [int]$c.R
$g = [int]$c.G
$b = [int]$c.B
$nearGray = ([math]::Abs($r - $g) -le 10) -and ([math]::Abs($g - $b) -le 10)
if (-not $nearGray) { return $false }
# Typical checkerboard: very light gray and mid light gray
$isLight = ($r -ge 235 -and $g -ge 235 -and $b -ge 235)
$isMid = ($r -ge 205 -and $g -ge 205 -and $b -ge 205 -and $r -le 235)
return ($isLight -or $isMid)
}
for ($y = 0; $y -lt $bmp.Height; $y++) {
for ($x = 0; $x -lt $bmp.Width; $x++) {
$c = $bmp.GetPixel($x, $y)
if (IsBgPixel $c) {
$out.SetPixel($x, $y, [System.Drawing.Color]::FromArgb(0, $c.R, $c.G, $c.B))
} else {
$out.SetPixel($x, $y, [System.Drawing.Color]::FromArgb(255, $c.R, $c.G, $c.B))
}
}
}
$tmp = "$OutputPath.tmp"
try {
$out.Save($tmp, [System.Drawing.Imaging.ImageFormat]::Png)
} finally {
$bmp.Dispose()
$out.Dispose()
}
Move-Item -LiteralPath $tmp -Destination $OutputPath -Force

View File

@ -0,0 +1,70 @@
param(
[Parameter(Mandatory=$true)][string]$InputPath,
[Parameter(Mandatory=$true)][string]$OutputPath,
[string]$BackupPath = "",
[int]$AlphaThreshold = 1,
[int]$Padding = 0
)
if (!(Test-Path -LiteralPath $InputPath)) {
throw "Input file not found: $InputPath"
}
if ($BackupPath -ne "") {
Copy-Item -LiteralPath $InputPath -Destination $BackupPath -Force
}
Add-Type -AssemblyName System.Drawing
# Load from stream to avoid locking
$fs = [System.IO.File]::Open($InputPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
try {
$bmp = New-Object System.Drawing.Bitmap($fs)
} finally {
$fs.Dispose()
}
$minX = $bmp.Width
$minY = $bmp.Height
$maxX = -1
$maxY = -1
for ($y = 0; $y -lt $bmp.Height; $y++) {
for ($x = 0; $x -lt $bmp.Width; $x++) {
$c = $bmp.GetPixel($x, $y)
if ($c.A -ge $AlphaThreshold) {
if ($x -lt $minX) { $minX = $x }
if ($y -lt $minY) { $minY = $y }
if ($x -gt $maxX) { $maxX = $x }
if ($y -gt $maxY) { $maxY = $y }
}
}
}
if ($maxX -lt 0 -or $maxY -lt 0) {
# No non-transparent pixels, just copy input
$bmp.Dispose()
Copy-Item -LiteralPath $InputPath -Destination $OutputPath -Force
exit 0
}
$minX = [math]::Max(0, $minX - $Padding)
$minY = [math]::Max(0, $minY - $Padding)
$maxX = [math]::Min($bmp.Width - 1, $maxX + $Padding)
$maxY = [math]::Min($bmp.Height - 1, $maxY + $Padding)
$newW = $maxX - $minX + 1
$newH = $maxY - $minY + 1
$rect = New-Object System.Drawing.Rectangle($minX, $minY, $newW, $newH)
$cropped = $bmp.Clone($rect, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
$tmp = "$OutputPath.tmp"
try {
$cropped.Save($tmp, [System.Drawing.Imaging.ImageFormat]::Png)
} finally {
$cropped.Dispose()
$bmp.Dispose()
}
Move-Item -LiteralPath $tmp -Destination $OutputPath -Force