UI界面的编写
This commit is contained in:
parent
e91dbf43d3
commit
8802e066f1
|
|
@ -80,6 +80,10 @@
|
||||||
android:name="com.example.livestreaming.MessagesActivity"
|
android:name="com.example.livestreaming.MessagesActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.example.livestreaming.ConversationActivity"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.example.livestreaming.PlayerActivity"
|
android:name="com.example.livestreaming.PlayerActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
package com.example.livestreaming;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
|
import com.example.livestreaming.databinding.ActivityConversationBinding;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ConversationActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private static final String EXTRA_CONVERSATION_ID = "extra_conversation_id";
|
||||||
|
private static final String EXTRA_CONVERSATION_TITLE = "extra_conversation_title";
|
||||||
|
|
||||||
|
private ActivityConversationBinding binding;
|
||||||
|
|
||||||
|
private ConversationMessagesAdapter adapter;
|
||||||
|
private final List<ChatMessage> messages = new ArrayList<>();
|
||||||
|
|
||||||
|
public static void start(Context context, String conversationId, String title) {
|
||||||
|
Intent intent = new Intent(context, ConversationActivity.class);
|
||||||
|
intent.putExtra(EXTRA_CONVERSATION_ID, conversationId);
|
||||||
|
intent.putExtra(EXTRA_CONVERSATION_TITLE, title);
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
binding = ActivityConversationBinding.inflate(getLayoutInflater());
|
||||||
|
setContentView(binding.getRoot());
|
||||||
|
|
||||||
|
String title = getIntent() != null ? getIntent().getStringExtra(EXTRA_CONVERSATION_TITLE) : null;
|
||||||
|
binding.titleText.setText(title != null ? title : "会话");
|
||||||
|
|
||||||
|
binding.backButton.setOnClickListener(v -> finish());
|
||||||
|
|
||||||
|
setupMessages();
|
||||||
|
setupInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupMessages() {
|
||||||
|
adapter = new ConversationMessagesAdapter();
|
||||||
|
|
||||||
|
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
|
||||||
|
layoutManager.setStackFromEnd(false);
|
||||||
|
binding.messagesRecyclerView.setLayoutManager(layoutManager);
|
||||||
|
binding.messagesRecyclerView.setAdapter(adapter);
|
||||||
|
|
||||||
|
messages.clear();
|
||||||
|
String title = binding.titleText.getText() != null ? binding.titleText.getText().toString() : "";
|
||||||
|
messages.add(new ChatMessage(title, "你好~"));
|
||||||
|
messages.add(new ChatMessage("我", "在的,有什么需要帮忙?"));
|
||||||
|
adapter.submitList(new ArrayList<>(messages));
|
||||||
|
scrollToBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupInput() {
|
||||||
|
binding.sendButton.setOnClickListener(v -> sendMessage());
|
||||||
|
|
||||||
|
binding.messageInput.setOnEditorActionListener((v, actionId, event) -> {
|
||||||
|
if (event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
|
||||||
|
sendMessage();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
binding.messageInput.setOnFocusChangeListener((v, hasFocus) -> {
|
||||||
|
if (hasFocus) scrollToBottom();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessage() {
|
||||||
|
String text = binding.messageInput.getText() != null ? binding.messageInput.getText().toString().trim() : "";
|
||||||
|
if (TextUtils.isEmpty(text)) return;
|
||||||
|
|
||||||
|
messages.add(new ChatMessage("我", text));
|
||||||
|
adapter.submitList(new ArrayList<>(messages));
|
||||||
|
binding.messageInput.setText("");
|
||||||
|
scrollToBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scrollToBottom() {
|
||||||
|
if (messages.isEmpty()) return;
|
||||||
|
binding.messagesRecyclerView.post(() -> binding.messagesRecyclerView.scrollToPosition(messages.size() - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class ConversationMessagesAdapter extends ListAdapter<ChatMessage, RecyclerView.ViewHolder> {
|
||||||
|
|
||||||
|
private static final int TYPE_INCOMING = 1;
|
||||||
|
private static final int TYPE_OUTGOING = 2;
|
||||||
|
|
||||||
|
private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm", Locale.getDefault());
|
||||||
|
|
||||||
|
public ConversationMessagesAdapter() {
|
||||||
|
super(DIFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
ChatMessage msg = getItem(position);
|
||||||
|
if (msg == null) return TYPE_INCOMING;
|
||||||
|
String u = msg.getUsername();
|
||||||
|
return "我".equals(u) ? TYPE_OUTGOING : TYPE_INCOMING;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
||||||
|
if (viewType == TYPE_OUTGOING) {
|
||||||
|
View v = inflater.inflate(R.layout.item_conversation_message_outgoing, parent, false);
|
||||||
|
return new OutgoingVH(v);
|
||||||
|
}
|
||||||
|
View v = inflater.inflate(R.layout.item_conversation_message_incoming, parent, false);
|
||||||
|
return new IncomingVH(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||||
|
ChatMessage msg = getItem(position);
|
||||||
|
if (holder instanceof IncomingVH) {
|
||||||
|
((IncomingVH) holder).bind(msg);
|
||||||
|
} else if (holder instanceof OutgoingVH) {
|
||||||
|
((OutgoingVH) holder).bind(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class IncomingVH extends RecyclerView.ViewHolder {
|
||||||
|
private final TextView nameText;
|
||||||
|
private final TextView msgText;
|
||||||
|
private final TextView timeText;
|
||||||
|
|
||||||
|
IncomingVH(@NonNull View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
nameText = itemView.findViewById(R.id.nameText);
|
||||||
|
msgText = itemView.findViewById(R.id.messageText);
|
||||||
|
timeText = itemView.findViewById(R.id.timeText);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bind(ChatMessage message) {
|
||||||
|
if (message == null) return;
|
||||||
|
nameText.setText(message.getUsername() != null ? message.getUsername() : "");
|
||||||
|
msgText.setText(message.getMessage() != null ? message.getMessage() : "");
|
||||||
|
timeText.setText(TIME_FORMAT.format(new Date(message.getTimestamp())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class OutgoingVH extends RecyclerView.ViewHolder {
|
||||||
|
private final TextView msgText;
|
||||||
|
private final TextView timeText;
|
||||||
|
|
||||||
|
OutgoingVH(@NonNull View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
msgText = itemView.findViewById(R.id.messageText);
|
||||||
|
timeText = itemView.findViewById(R.id.timeText);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bind(ChatMessage message) {
|
||||||
|
if (message == null) return;
|
||||||
|
msgText.setText(message.getMessage() != null ? message.getMessage() : "");
|
||||||
|
timeText.setText(TIME_FORMAT.format(new Date(message.getTimestamp())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final DiffUtil.ItemCallback<ChatMessage> DIFF = new DiffUtil.ItemCallback<ChatMessage>() {
|
||||||
|
@Override
|
||||||
|
public boolean areItemsTheSame(@NonNull ChatMessage oldItem, @NonNull ChatMessage newItem) {
|
||||||
|
return oldItem.getTimestamp() == newItem.getTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean areContentsTheSame(@NonNull ChatMessage oldItem, @NonNull ChatMessage newItem) {
|
||||||
|
return oldItem.getTimestamp() == newItem.getTimestamp();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,9 @@ import com.example.livestreaming.databinding.ActivityEditProfileBinding;
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
public class EditProfileActivity extends AppCompatActivity {
|
public class EditProfileActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
|
@ -29,6 +32,7 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||||
private static final String KEY_NAME = "profile_name";
|
private static final String KEY_NAME = "profile_name";
|
||||||
private static final String KEY_BIO = "profile_bio";
|
private static final String KEY_BIO = "profile_bio";
|
||||||
private static final String KEY_AVATAR_URI = "profile_avatar_uri";
|
private static final String KEY_AVATAR_URI = "profile_avatar_uri";
|
||||||
|
private static final String KEY_AVATAR_RES = "profile_avatar_res";
|
||||||
private static final String KEY_BIRTHDAY = "profile_birthday";
|
private static final String KEY_BIRTHDAY = "profile_birthday";
|
||||||
private static final String KEY_GENDER = "profile_gender";
|
private static final String KEY_GENDER = "profile_gender";
|
||||||
private static final String KEY_LOCATION = "profile_location";
|
private static final String KEY_LOCATION = "profile_location";
|
||||||
|
|
@ -107,7 +111,14 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedAvatarUri != null) {
|
if (selectedAvatarUri != null) {
|
||||||
getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit().putString(KEY_AVATAR_URI, selectedAvatarUri.toString()).apply();
|
Uri persisted = persistAvatarToInternalStorage(selectedAvatarUri);
|
||||||
|
if (persisted != null) {
|
||||||
|
getSharedPreferences(PREFS_NAME, MODE_PRIVATE)
|
||||||
|
.edit()
|
||||||
|
.putString(KEY_AVATAR_URI, persisted.toString())
|
||||||
|
.remove(KEY_AVATAR_RES)
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TextUtils.isEmpty(birthday)) {
|
if (TextUtils.isEmpty(birthday)) {
|
||||||
|
|
@ -132,6 +143,42 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Uri persistAvatarToInternalStorage(Uri sourceUri) {
|
||||||
|
if (sourceUri == null) return null;
|
||||||
|
InputStream in = null;
|
||||||
|
OutputStream out = null;
|
||||||
|
try {
|
||||||
|
File dir = new File(getFilesDir(), "avatars");
|
||||||
|
if (!dir.exists()) dir.mkdirs();
|
||||||
|
File file = new File(dir, "avatar.jpg");
|
||||||
|
|
||||||
|
in = getContentResolver().openInputStream(sourceUri);
|
||||||
|
if (in == null) return null;
|
||||||
|
out = new FileOutputStream(file, false);
|
||||||
|
|
||||||
|
byte[] buf = new byte[8 * 1024];
|
||||||
|
int n;
|
||||||
|
while ((n = in.read(buf)) > 0) {
|
||||||
|
out.write(buf, 0, n);
|
||||||
|
}
|
||||||
|
out.flush();
|
||||||
|
|
||||||
|
return Uri.fromFile(file);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Toast.makeText(this, "头像保存失败", Toast.LENGTH_SHORT).show();
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (in != null) in.close();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (out != null) out.close();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void loadFromPrefs() {
|
private void loadFromPrefs() {
|
||||||
String name = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_NAME, "");
|
String name = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_NAME, "");
|
||||||
String bio = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_BIO, "");
|
String bio = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_BIO, "");
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.constraintlayout.widget.ConstraintSet;
|
import androidx.constraintlayout.widget.ConstraintSet;
|
||||||
|
|
||||||
import com.example.livestreaming.databinding.ActivityFishPondBinding;
|
import com.example.livestreaming.databinding.ActivityFishPondBinding;
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||||
|
|
||||||
public class FishPondActivity extends AppCompatActivity {
|
public class FishPondActivity extends AppCompatActivity {
|
||||||
|
|
@ -265,7 +266,10 @@ public class FishPondActivity extends AppCompatActivity {
|
||||||
|
|
||||||
String bio = "真诚交友 · " + c + " · 在线";
|
String bio = "真诚交友 · " + c + " · 在线";
|
||||||
|
|
||||||
avatarView.setImageResource(a);
|
Glide.with(avatarView)
|
||||||
|
.load(a)
|
||||||
|
.circleCrop()
|
||||||
|
.into(avatarView);
|
||||||
nameView.setText(n);
|
nameView.setText(n);
|
||||||
locationView.setText(c);
|
locationView.setText(c);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -202,14 +202,22 @@ public class MainActivity extends AppCompatActivity {
|
||||||
String avatarUri = getSharedPreferences("profile_prefs", MODE_PRIVATE)
|
String avatarUri = getSharedPreferences("profile_prefs", MODE_PRIVATE)
|
||||||
.getString("profile_avatar_uri", null);
|
.getString("profile_avatar_uri", null);
|
||||||
if (!TextUtils.isEmpty(avatarUri)) {
|
if (!TextUtils.isEmpty(avatarUri)) {
|
||||||
Glide.with(this).load(avatarUri).circleCrop().into(binding.avatarButton);
|
Glide.with(this)
|
||||||
|
.load(Uri.parse(avatarUri))
|
||||||
|
.circleCrop()
|
||||||
|
.error(R.drawable.ic_account_circle_24)
|
||||||
|
.into(binding.avatarButton);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int avatarRes = getSharedPreferences("profile_prefs", MODE_PRIVATE)
|
int avatarRes = getSharedPreferences("profile_prefs", MODE_PRIVATE)
|
||||||
.getInt("profile_avatar_res", 0);
|
.getInt("profile_avatar_res", 0);
|
||||||
if (avatarRes != 0) {
|
if (avatarRes != 0) {
|
||||||
binding.avatarButton.setImageResource(avatarRes);
|
Glide.with(this)
|
||||||
|
.load(avatarRes)
|
||||||
|
.circleCrop()
|
||||||
|
.error(R.drawable.ic_account_circle_24)
|
||||||
|
.into(binding.avatarButton);
|
||||||
} else {
|
} else {
|
||||||
binding.avatarButton.setImageResource(R.drawable.ic_account_circle_24);
|
binding.avatarButton.setImageResource(R.drawable.ic_account_circle_24);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,21 @@ package com.example.livestreaming;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.RectF;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.example.livestreaming.databinding.ActivityMessagesBinding;
|
import com.example.livestreaming.databinding.ActivityMessagesBinding;
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||||
|
|
@ -18,6 +28,22 @@ public class MessagesActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private ActivityMessagesBinding binding;
|
private ActivityMessagesBinding binding;
|
||||||
|
|
||||||
|
private final List<ConversationItem> conversations = new ArrayList<>();
|
||||||
|
private ConversationsAdapter conversationsAdapter;
|
||||||
|
|
||||||
|
private int swipedPosition = RecyclerView.NO_POSITION;
|
||||||
|
private RectF deleteButtonRect;
|
||||||
|
private final Paint deleteBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
private final Paint deleteTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
|
||||||
|
private int lastSwipePosition = RecyclerView.NO_POSITION;
|
||||||
|
private float lastSwipeDx;
|
||||||
|
|
||||||
|
private float touchDownX;
|
||||||
|
private float touchDownY;
|
||||||
|
private boolean touchIsClick;
|
||||||
|
private int touchSlop;
|
||||||
|
|
||||||
public static void start(Context context) {
|
public static void start(Context context) {
|
||||||
Intent intent = new Intent(context, MessagesActivity.class);
|
Intent intent = new Intent(context, MessagesActivity.class);
|
||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
|
|
@ -63,13 +89,203 @@ public class MessagesActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupConversationList() {
|
private void setupConversationList() {
|
||||||
ConversationsAdapter adapter = new ConversationsAdapter(item -> {
|
deleteBackgroundPaint.setColor(0xFFE53935);
|
||||||
|
deleteTextPaint.setColor(Color.WHITE);
|
||||||
|
deleteTextPaint.setTextAlign(Paint.Align.CENTER);
|
||||||
|
deleteTextPaint.setTextSize(sp(14));
|
||||||
|
|
||||||
|
conversationsAdapter = new ConversationsAdapter(item -> {
|
||||||
if (item == null) return;
|
if (item == null) return;
|
||||||
Toast.makeText(this, "打开会话:" + item.getTitle(), Toast.LENGTH_SHORT).show();
|
ConversationActivity.start(this, item.getId(), item.getTitle());
|
||||||
});
|
});
|
||||||
binding.conversationsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
|
binding.conversationsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||||
binding.conversationsRecyclerView.setAdapter(adapter);
|
binding.conversationsRecyclerView.setAdapter(conversationsAdapter);
|
||||||
adapter.submitList(buildDemoConversations());
|
|
||||||
|
conversations.clear();
|
||||||
|
conversations.addAll(buildDemoConversations());
|
||||||
|
conversationsAdapter.submitList(new ArrayList<>(conversations));
|
||||||
|
|
||||||
|
attachSwipeToDelete(binding.conversationsRecyclerView);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attachSwipeToDelete(RecyclerView recyclerView) {
|
||||||
|
final float actionWidth = dp(96);
|
||||||
|
touchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
|
||||||
|
ItemTouchHelper.SimpleCallback callback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
|
||||||
|
@Override
|
||||||
|
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
|
||||||
|
// 不允许滑动直接删除(保持为“露出删除按钮”的交互)
|
||||||
|
if (conversationsAdapter != null) {
|
||||||
|
conversationsAdapter.notifyItemChanged(viewHolder.getBindingAdapterPosition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
|
||||||
|
super.clearView(recyclerView, viewHolder);
|
||||||
|
|
||||||
|
int pos = viewHolder.getBindingAdapterPosition();
|
||||||
|
if (pos == RecyclerView.NO_POSITION) return;
|
||||||
|
|
||||||
|
float dx = (pos == lastSwipePosition) ? lastSwipeDx : viewHolder.itemView.getTranslationX();
|
||||||
|
boolean shouldOpen = dx <= -actionWidth * 0.2f;
|
||||||
|
|
||||||
|
if (shouldOpen) {
|
||||||
|
if (swipedPosition != RecyclerView.NO_POSITION && swipedPosition != pos && conversationsAdapter != null) {
|
||||||
|
int old = swipedPosition;
|
||||||
|
swipedPosition = RecyclerView.NO_POSITION;
|
||||||
|
deleteButtonRect = null;
|
||||||
|
conversationsAdapter.notifyItemChanged(old);
|
||||||
|
}
|
||||||
|
swipedPosition = pos;
|
||||||
|
viewHolder.itemView.setTranslationX(-actionWidth);
|
||||||
|
} else {
|
||||||
|
if (swipedPosition == pos) {
|
||||||
|
swipedPosition = RecyclerView.NO_POSITION;
|
||||||
|
deleteButtonRect = null;
|
||||||
|
}
|
||||||
|
viewHolder.itemView.setTranslationX(0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos == lastSwipePosition) {
|
||||||
|
lastSwipePosition = RecyclerView.NO_POSITION;
|
||||||
|
lastSwipeDx = 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) {
|
||||||
|
// 返回 1f 防止触发“滑动就删除”的默认行为
|
||||||
|
return 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getSwipeEscapeVelocity(float defaultValue) {
|
||||||
|
return defaultValue * 3f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
|
||||||
|
float dX, float dY, int actionState, boolean isCurrentlyActive) {
|
||||||
|
if (actionState != ItemTouchHelper.ACTION_STATE_SWIPE) {
|
||||||
|
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int position = viewHolder.getBindingAdapterPosition();
|
||||||
|
if (position == RecyclerView.NO_POSITION) {
|
||||||
|
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float clampedDx;
|
||||||
|
if (position == swipedPosition) {
|
||||||
|
if (!isCurrentlyActive) {
|
||||||
|
clampedDx = -actionWidth;
|
||||||
|
} else {
|
||||||
|
if (dX > 0) {
|
||||||
|
// 已展开时右滑:从 -actionWidth 平滑回到 0
|
||||||
|
clampedDx = Math.min(0f, -actionWidth + dX);
|
||||||
|
} else {
|
||||||
|
// 正常左滑打开
|
||||||
|
clampedDx = Math.min(0, Math.max(dX, -actionWidth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 未展开的 item:只允许左滑露出
|
||||||
|
clampedDx = Math.min(0, Math.max(dX, -actionWidth));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCurrentlyActive) {
|
||||||
|
lastSwipePosition = position;
|
||||||
|
lastSwipeDx = clampedDx;
|
||||||
|
}
|
||||||
|
|
||||||
|
View itemView = viewHolder.itemView;
|
||||||
|
float left;
|
||||||
|
float top = itemView.getTop();
|
||||||
|
float bottom = itemView.getBottom();
|
||||||
|
float right = itemView.getRight();
|
||||||
|
left = right + clampedDx;
|
||||||
|
|
||||||
|
deleteButtonRect = new RectF(left, top, right, bottom);
|
||||||
|
c.drawRect(deleteButtonRect, deleteBackgroundPaint);
|
||||||
|
|
||||||
|
float centerX = deleteButtonRect.centerX();
|
||||||
|
float centerY = deleteButtonRect.centerY();
|
||||||
|
Paint.FontMetrics fm = deleteTextPaint.getFontMetrics();
|
||||||
|
float textY = centerY - (fm.ascent + fm.descent) / 2f;
|
||||||
|
c.drawText("删除", centerX, textY, deleteTextPaint);
|
||||||
|
|
||||||
|
super.onChildDraw(c, recyclerView, viewHolder, clampedDx, dY, actionState, isCurrentlyActive);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
new ItemTouchHelper(callback).attachToRecyclerView(recyclerView);
|
||||||
|
|
||||||
|
recyclerView.setOnTouchListener((v, event) -> {
|
||||||
|
if (swipedPosition == RecyclerView.NO_POSITION || deleteButtonRect == null) return false;
|
||||||
|
|
||||||
|
int action = event.getActionMasked();
|
||||||
|
if (action == MotionEvent.ACTION_DOWN) {
|
||||||
|
touchDownX = event.getX();
|
||||||
|
touchDownY = event.getY();
|
||||||
|
touchIsClick = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action == MotionEvent.ACTION_MOVE) {
|
||||||
|
float dx = Math.abs(event.getX() - touchDownX);
|
||||||
|
float dy = Math.abs(event.getY() - touchDownY);
|
||||||
|
if (dx > touchSlop || dy > touchSlop) {
|
||||||
|
touchIsClick = false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action == MotionEvent.ACTION_UP) {
|
||||||
|
if (!touchIsClick) return false;
|
||||||
|
|
||||||
|
boolean hit = deleteButtonRect.contains(event.getX(), event.getY());
|
||||||
|
int pos = swipedPosition;
|
||||||
|
recoverSwipedItem();
|
||||||
|
if (hit) {
|
||||||
|
deleteConversationAt(pos);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recoverSwipedItem() {
|
||||||
|
int pos = swipedPosition;
|
||||||
|
swipedPosition = RecyclerView.NO_POSITION;
|
||||||
|
deleteButtonRect = null;
|
||||||
|
if (pos != RecyclerView.NO_POSITION && conversationsAdapter != null) {
|
||||||
|
conversationsAdapter.notifyItemChanged(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteConversationAt(int position) {
|
||||||
|
if (position < 0 || position >= conversations.size()) return;
|
||||||
|
conversations.remove(position);
|
||||||
|
if (conversationsAdapter != null) {
|
||||||
|
conversationsAdapter.submitList(new ArrayList<>(conversations));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float dp(float value) {
|
||||||
|
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, getResources().getDisplayMetrics());
|
||||||
|
}
|
||||||
|
|
||||||
|
private float sp(float value) {
|
||||||
|
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, value, getResources().getDisplayMetrics());
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ConversationItem> buildDemoConversations() {
|
private List<ConversationItem> buildDemoConversations() {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import android.content.Intent;
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.ClipboardManager;
|
import android.content.ClipboardManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.net.Uri;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
|
@ -94,11 +95,21 @@ public class ProfileActivity extends AppCompatActivity {
|
||||||
|
|
||||||
String avatarUri = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_AVATAR_URI, null);
|
String avatarUri = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_AVATAR_URI, null);
|
||||||
if (!TextUtils.isEmpty(avatarUri)) {
|
if (!TextUtils.isEmpty(avatarUri)) {
|
||||||
Glide.with(this).load(avatarUri).circleCrop().into(binding.avatar);
|
Glide.with(this)
|
||||||
|
.load(Uri.parse(avatarUri))
|
||||||
|
.circleCrop()
|
||||||
|
.error(R.drawable.ic_account_circle_24)
|
||||||
|
.into(binding.avatar);
|
||||||
} else {
|
} else {
|
||||||
int avatarRes = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getInt(KEY_AVATAR_RES, 0);
|
int avatarRes = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getInt(KEY_AVATAR_RES, 0);
|
||||||
if (avatarRes != 0) {
|
if (avatarRes != 0) {
|
||||||
binding.avatar.setImageResource(avatarRes);
|
Glide.with(this)
|
||||||
|
.load(avatarRes)
|
||||||
|
.circleCrop()
|
||||||
|
.error(R.drawable.ic_account_circle_24)
|
||||||
|
.into(binding.avatar);
|
||||||
|
} else {
|
||||||
|
binding.avatar.setImageResource(R.drawable.ic_account_circle_24);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,7 +168,20 @@ public class ProfileActivity extends AppCompatActivity {
|
||||||
binding.action3.setOnClickListener(v -> startActivity(new Intent(this, MyFriendsActivity.class)));
|
binding.action3.setOnClickListener(v -> startActivity(new Intent(this, MyFriendsActivity.class)));
|
||||||
|
|
||||||
binding.editProfile.setOnClickListener(v -> EditProfileActivity.start(this));
|
binding.editProfile.setOnClickListener(v -> EditProfileActivity.start(this));
|
||||||
binding.shareHome.setOnClickListener(v -> TabPlaceholderActivity.start(this, "分享主页"));
|
binding.shareHome.setOnClickListener(v -> {
|
||||||
|
// TabPlaceholderActivity.start(this, "分享主页");
|
||||||
|
String idText = binding.idLine.getText() != null ? binding.idLine.getText().toString() : "";
|
||||||
|
String digits = !TextUtils.isEmpty(idText) ? idText.replaceAll("\\D+", "") : "";
|
||||||
|
if (TextUtils.isEmpty(digits)) digits = "24187196";
|
||||||
|
|
||||||
|
String url = "https://live.example.com/u/" + digits;
|
||||||
|
|
||||||
|
ClipboardManager cm = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
|
||||||
|
if (cm != null) {
|
||||||
|
cm.setPrimaryClip(ClipData.newPlainText("profile_url", url));
|
||||||
|
Toast.makeText(this, "主页链接已复制", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
binding.addFriendBtn.setOnClickListener(v -> TabPlaceholderActivity.start(this, "加好友"));
|
binding.addFriendBtn.setOnClickListener(v -> TabPlaceholderActivity.start(this, "加好友"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.example.livestreaming;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
@ -361,7 +362,11 @@ public class TabPlaceholderActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(avatarUri)) {
|
if (!TextUtils.isEmpty(avatarUri)) {
|
||||||
Glide.with(this).load(avatarUri).circleCrop().into(binding.moreAvatar);
|
Glide.with(this)
|
||||||
|
.load(Uri.parse(avatarUri))
|
||||||
|
.circleCrop()
|
||||||
|
.error(R.drawable.ic_account_circle_24)
|
||||||
|
.into(binding.moreAvatar);
|
||||||
} else if (avatarRes != 0) {
|
} else if (avatarRes != 0) {
|
||||||
binding.moreAvatar.setImageResource(avatarRes);
|
binding.moreAvatar.setImageResource(avatarRes);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,138 +1 @@
|
||||||
package com.example.livestreaming;
|
package com.example.livestreaming;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import androidx.appcompat.app.AppCompatActivity;
import com.example.livestreaming.databinding.ActivityWishTreeBinding;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
public class WishTreeActivity extends AppCompatActivity {
private ActivityWishTreeBinding binding;
private final Handler handler = new Handler(Looper.getMainLooper());
private Runnable timerRunnable;
public static void start(Context context) {
Intent intent = new Intent(context, WishTreeActivity.class);
context.startActivity(intent);
}
@Override
protected void onStart() {
super.onStart();
startBannerCountdown();
}
@Override
protected void onStop() {
super.onStop();
stopBannerCountdown();
}
private void startBannerCountdown() {
stopBannerCountdown();
timerRunnable = new Runnable() {
@Override
public void run() {
updateBannerTimer();
handler.postDelayed(this, 1000);
}
};
handler.post(timerRunnable);
}
private void stopBannerCountdown() {
if (timerRunnable != null) {
handler.removeCallbacks(timerRunnable);
timerRunnable = null;
}
}
private void updateBannerTimer() {
if (binding == null) return;
try {
long now = System.currentTimeMillis();
TimeZone tz = TimeZone.getDefault();
Calendar c = Calendar.getInstance(tz);
c.setTimeInMillis(now);
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
c.add(Calendar.DAY_OF_MONTH, 1);
long nextMidnight = c.getTimeInMillis();
long diff = Math.max(0, nextMidnight - now);
long totalSeconds = diff / 1000;
long hours = totalSeconds / 3600;
long minutes = (totalSeconds % 3600) / 60;
long seconds = totalSeconds % 60;
SimpleDateFormat fmt = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
fmt.setTimeZone(tz);
String current = fmt.format(new Date(now));
String remain = String.format(Locale.getDefault(), "%02d:%02d:%02d", hours, minutes, seconds);
binding.bannerTimer.setText(current + " " + remain);
} catch (Exception ignored) {
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityWishTreeBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
startBannerCountdown();
BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation;
bottomNavigation.setSelectedItemId(R.id.nav_wish_tree);
bottomNavigation.setOnItemSelectedListener(item -> {
int id = item.getItemId();
if (id == R.id.nav_wish_tree) {
return true;
}
if (id == R.id.nav_home) {
startActivity(new Intent(this, MainActivity.class));
finish();
return true;
}
if (id == R.id.nav_friends) {
startActivity(new Intent(this, FishPondActivity.class));
finish();
return true;
}
if (id == R.id.nav_messages) {
MessagesActivity.start(this);
finish();
return true;
}
if (id == R.id.nav_profile) {
ProfileActivity.start(this);
finish();
return true;
}
return true;
});
}
@Override
protected void onResume() {
super.onResume();
if (binding != null) {
binding.bottomNavInclude.bottomNavigation.setSelectedItemId(R.id.nav_wish_tree);
}
}
}
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
|
|
||||||
import com.example.livestreaming.databinding.ActivityWishTreeBinding;
|
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.TimeZone;
|
|
||||||
|
|
||||||
public class WishTreeActivity extends AppCompatActivity {
|
|
||||||
|
|
||||||
private ActivityWishTreeBinding binding;
|
|
||||||
|
|
||||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
|
||||||
private Runnable timerRunnable;
|
|
||||||
|
|
||||||
public static void start(Context context) {
|
|
||||||
Intent intent = new Intent(context, WishTreeActivity.class);
|
|
||||||
context.startActivity(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
startBannerCountdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
stopBannerCountdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startBannerCountdown() {
|
|
||||||
stopBannerCountdown();
|
|
||||||
timerRunnable = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
updateBannerTimer();
|
|
||||||
handler.postDelayed(this, 1000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
handler.post(timerRunnable);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stopBannerCountdown() {
|
|
||||||
if (timerRunnable != null) {
|
|
||||||
handler.removeCallbacks(timerRunnable);
|
|
||||||
timerRunnable = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateBannerTimer() {
|
|
||||||
if (binding == null) return;
|
|
||||||
try {
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
TimeZone tz = TimeZone.getDefault();
|
|
||||||
Calendar c = Calendar.getInstance(tz);
|
|
||||||
c.setTimeInMillis(now);
|
|
||||||
c.set(Calendar.HOUR_OF_DAY, 0);
|
|
||||||
c.set(Calendar.MINUTE, 0);
|
|
||||||
c.set(Calendar.SECOND, 0);
|
|
||||||
c.set(Calendar.MILLISECOND, 0);
|
|
||||||
c.add(Calendar.DAY_OF_MONTH, 1);
|
|
||||||
long nextMidnight = c.getTimeInMillis();
|
|
||||||
long diff = Math.max(0, nextMidnight - now);
|
|
||||||
|
|
||||||
long totalSeconds = diff / 1000;
|
|
||||||
long hours = totalSeconds / 3600;
|
|
||||||
long minutes = (totalSeconds % 3600) / 60;
|
|
||||||
long seconds = totalSeconds % 60;
|
|
||||||
|
|
||||||
SimpleDateFormat fmt = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
|
|
||||||
fmt.setTimeZone(tz);
|
|
||||||
String current = fmt.format(new Date(now));
|
|
||||||
String remain = String.format(Locale.getDefault(), "%02d:%02d:%02d", hours, minutes, seconds);
|
|
||||||
binding.bannerTimer.setText(current + " " + remain);
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
binding = ActivityWishTreeBinding.inflate(getLayoutInflater());
|
|
||||||
setContentView(binding.getRoot());
|
|
||||||
|
|
||||||
startBannerCountdown();
|
|
||||||
|
|
||||||
BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation;
|
|
||||||
bottomNavigation.setSelectedItemId(R.id.nav_wish_tree);
|
|
||||||
bottomNavigation.setOnItemSelectedListener(item -> {
|
|
||||||
int id = item.getItemId();
|
|
||||||
if (id == R.id.nav_wish_tree) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (id == R.id.nav_home) {
|
|
||||||
startActivity(new Intent(this, MainActivity.class));
|
|
||||||
finish();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (id == R.id.nav_friends) {
|
|
||||||
startActivity(new Intent(this, FishPondActivity.class));
|
|
||||||
finish();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (id == R.id.nav_messages) {
|
|
||||||
MessagesActivity.start(this);
|
|
||||||
finish();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (id == R.id.nav_profile) {
|
|
||||||
ProfileActivity.start(this);
|
|
||||||
finish();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
if (binding != null) {
|
|
||||||
binding.bottomNavInclude.bottomNavigation.setSelectedItemId(R.id.nav_wish_tree);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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="#F1F3F5" />
|
||||||
|
<corners android:radius="16dp" />
|
||||||
|
</shape>
|
||||||
|
|
@ -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="#4F7BFF" />
|
||||||
|
<corners android:radius="16dp" />
|
||||||
|
</shape>
|
||||||
125
android-app/app/src/main/res/layout/activity_conversation.xml
Normal file
125
android-app/app/src/main/res/layout/activity_conversation.xml
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
<?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"
|
||||||
|
android:background="@android:color/white">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/topBar"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:background="@android:color/white"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/backButton"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:contentDescription="back"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:src="@drawable/ic_arrow_back_24"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/avatarView"
|
||||||
|
android:layout_width="36dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:background="@drawable/bg_avatar_circle"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:src="@drawable/ic_account_circle_24"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/backButton"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/backButton"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/backButton" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/titleText"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text="会话"
|
||||||
|
android:textColor="#111111"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/avatarView"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/avatarView"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/avatarView" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/topDivider"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="#EEEEEE"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/topBar" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/messagesRecyclerView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:overScrollMode="never"
|
||||||
|
android:paddingTop="6dp"
|
||||||
|
android:paddingBottom="6dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/inputBar"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/topDivider" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/inputBar"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="#FAFAFA"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingBottom="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/messageInput"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_white_16"
|
||||||
|
android:hint="输入消息..."
|
||||||
|
android:imeOptions="actionSend"
|
||||||
|
android:inputType="text"
|
||||||
|
android:maxLines="4"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:textColor="#111111"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/sendButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:minWidth="64dp"
|
||||||
|
android:text="发送"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textSize="13sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -101,6 +101,18 @@
|
||||||
app:use_controller="true"
|
app:use_controller="true"
|
||||||
app:show_buffering="when_playing" />
|
app:show_buffering="when_playing" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/exitFullscreenButton"
|
||||||
|
android:layout_width="44dp"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:layout_gravity="top|start"
|
||||||
|
android:layout_margin="12dp"
|
||||||
|
android:background="@drawable/bg_circle_white_60"
|
||||||
|
android:contentDescription="退出全屏"
|
||||||
|
android:src="@drawable/ic_arrow_back_24"
|
||||||
|
android:tint="@android:color/white"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
<!-- 横屏按钮 -->
|
<!-- 横屏按钮 -->
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/fullscreenButton"
|
android:id="@+id/fullscreenButton"
|
||||||
|
|
@ -202,12 +214,19 @@
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/followButton"
|
android:id="@+id/followButton"
|
||||||
style="@style/Widget.Material3.Button.TonalButton"
|
style="@style/Widget.Material3.Button.TonalButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="92dp"
|
||||||
android:layout_height="32dp"
|
android:layout_height="40dp"
|
||||||
|
android:paddingHorizontal="14dp"
|
||||||
android:text="关注"
|
android:text="关注"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
app:backgroundTint="#FF2FD3C7"
|
||||||
app:icon="@drawable/ic_heart_24"
|
app:icon="@drawable/ic_heart_24"
|
||||||
app:iconSize="14dp" />
|
app:iconSize="14dp"
|
||||||
|
app:iconTint="@android:color/white" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,19 +52,18 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/backButton" />
|
app:layout_constraintTop_toBottomOf="@id/backButton" />
|
||||||
|
|
||||||
<ImageView
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
android:id="@+id/avatar"
|
android:id="@+id/avatar"
|
||||||
android:layout_width="90dp"
|
android:layout_width="90dp"
|
||||||
android:layout_height="90dp"
|
android:layout_height="90dp"
|
||||||
android:layout_margin="3dp"
|
android:layout_margin="3dp"
|
||||||
android:background="@drawable/bg_avatar_circle_transparent"
|
|
||||||
android:clipToOutline="true"
|
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
android:src="@drawable/wish_tree_checker_backup"
|
android:src="@drawable/wish_tree_checker_backup"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/avatarRing"
|
app:layout_constraintBottom_toBottomOf="@id/avatarRing"
|
||||||
app:layout_constraintEnd_toEndOf="@id/avatarRing"
|
app:layout_constraintEnd_toEndOf="@id/avatarRing"
|
||||||
app:layout_constraintStart_toStartOf="@id/avatarRing"
|
app:layout_constraintStart_toStartOf="@id/avatarRing"
|
||||||
app:layout_constraintTop_toTopOf="@id/avatarRing" />
|
app:layout_constraintTop_toTopOf="@id/avatarRing"
|
||||||
|
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.MaterialComponents.Circular" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/name"
|
android:id="@+id/name"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
<?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:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:paddingTop="1dp"
|
||||||
|
android:paddingBottom="1dp">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Guideline
|
||||||
|
android:id="@+id/maxBubbleWidthGuideline"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintGuide_percent="0.5" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/avatarView"
|
||||||
|
android:layout_width="34dp"
|
||||||
|
android:layout_height="34dp"
|
||||||
|
android:background="@drawable/bg_avatar_circle"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:src="@drawable/ic_account_circle_24"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/nameText"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text="对方"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/avatarView"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/avatarView" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/messageText"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:layout_marginTop="0dp"
|
||||||
|
android:background="@drawable/bg_bubble_incoming"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:paddingTop="6dp"
|
||||||
|
android:paddingBottom="6dp"
|
||||||
|
android:text="消息内容"
|
||||||
|
android:textColor="#111111"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintWidth_default="wrap"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/maxBubbleWidthGuideline"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/avatarView"
|
||||||
|
app:layout_constraintHorizontal_bias="0"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/avatarView" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/timeText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="1dp"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="12:30"
|
||||||
|
android:textColor="#AAAAAA"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/messageText"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/messageText" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
<?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:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:paddingTop="1dp"
|
||||||
|
android:paddingBottom="1dp">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Guideline
|
||||||
|
android:id="@+id/maxBubbleWidthGuideline"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintGuide_percent="0.5" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/avatarView"
|
||||||
|
android:layout_width="34dp"
|
||||||
|
android:layout_height="34dp"
|
||||||
|
android:background="@drawable/bg_avatar_circle"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:src="@drawable/ic_account_circle_24"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/messageText"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="10dp"
|
||||||
|
android:background="@drawable/bg_bubble_outgoing"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:paddingTop="6dp"
|
||||||
|
android:paddingBottom="6dp"
|
||||||
|
android:text="消息内容"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintWidth_default="wrap"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/maxBubbleWidthGuideline"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/avatarView"
|
||||||
|
app:layout_constraintHorizontal_bias="1"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/timeText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="1dp"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="12:31"
|
||||||
|
android:textColor="#AAAAAA"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/messageText"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/messageText" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -26,4 +26,12 @@
|
||||||
<item name="android:textSize">14sp</item>
|
<item name="android:textSize">14sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="ShapeAppearanceOverlay" parent="ShapeAppearanceOverlay.MaterialComponents" />
|
||||||
|
|
||||||
|
<style name="ShapeAppearanceOverlay.MaterialComponents" />
|
||||||
|
|
||||||
|
<style name="ShapeAppearanceOverlay.MaterialComponents.Circular" parent="ShapeAppearanceOverlay.MaterialComponents">
|
||||||
|
<item name="cornerSize">50%</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user