UI界面的编写
This commit is contained in:
parent
e91dbf43d3
commit
8802e066f1
|
|
@ -80,6 +80,10 @@
|
|||
android:name="com.example.livestreaming.MessagesActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name="com.example.livestreaming.ConversationActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name="com.example.livestreaming.PlayerActivity"
|
||||
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 java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
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_BIO = "profile_bio";
|
||||
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_GENDER = "profile_gender";
|
||||
private static final String KEY_LOCATION = "profile_location";
|
||||
|
|
@ -107,7 +111,14 @@ public class EditProfileActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
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)) {
|
||||
|
|
@ -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() {
|
||||
String name = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_NAME, "");
|
||||
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 com.example.livestreaming.databinding.ActivityFishPondBinding;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
|
||||
public class FishPondActivity extends AppCompatActivity {
|
||||
|
|
@ -265,7 +266,10 @@ public class FishPondActivity extends AppCompatActivity {
|
|||
|
||||
String bio = "真诚交友 · " + c + " · 在线";
|
||||
|
||||
avatarView.setImageResource(a);
|
||||
Glide.with(avatarView)
|
||||
.load(a)
|
||||
.circleCrop()
|
||||
.into(avatarView);
|
||||
nameView.setText(n);
|
||||
locationView.setText(c);
|
||||
|
||||
|
|
|
|||
|
|
@ -202,14 +202,22 @@ public class MainActivity extends AppCompatActivity {
|
|||
String avatarUri = getSharedPreferences("profile_prefs", MODE_PRIVATE)
|
||||
.getString("profile_avatar_uri", null);
|
||||
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;
|
||||
}
|
||||
|
||||
int avatarRes = getSharedPreferences("profile_prefs", MODE_PRIVATE)
|
||||
.getInt("profile_avatar_res", 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 {
|
||||
binding.avatarButton.setImageResource(R.drawable.ic_account_circle_24);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,21 @@ package com.example.livestreaming;
|
|||
|
||||
import android.content.Context;
|
||||
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.util.TypedValue;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.example.livestreaming.databinding.ActivityMessagesBinding;
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
|
|
@ -18,6 +28,22 @@ public class MessagesActivity extends AppCompatActivity {
|
|||
|
||||
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) {
|
||||
Intent intent = new Intent(context, MessagesActivity.class);
|
||||
context.startActivity(intent);
|
||||
|
|
@ -63,13 +89,203 @@ public class MessagesActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
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;
|
||||
Toast.makeText(this, "打开会话:" + item.getTitle(), Toast.LENGTH_SHORT).show();
|
||||
ConversationActivity.start(this, item.getId(), item.getTitle());
|
||||
});
|
||||
binding.conversationsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
binding.conversationsRecyclerView.setAdapter(adapter);
|
||||
adapter.submitList(buildDemoConversations());
|
||||
binding.conversationsRecyclerView.setAdapter(conversationsAdapter);
|
||||
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.content.Intent;
|
|||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.os.Bundle;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
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);
|
||||
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 {
|
||||
int avatarRes = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getInt(KEY_AVATAR_RES, 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.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, "加好友"));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.example.livestreaming;
|
|||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
|
|
@ -361,7 +362,11 @@ public class TabPlaceholderActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
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) {
|
||||
binding.moreAvatar.setImageResource(avatarRes);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,138 +1 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
}
}
}
|
||||
|
|
@ -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: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
|
||||
android:id="@+id/fullscreenButton"
|
||||
|
|
@ -202,12 +214,19 @@
|
|||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/followButton"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="32dp"
|
||||
android:layout_width="92dp"
|
||||
android:layout_height="40dp"
|
||||
android:paddingHorizontal="14dp"
|
||||
android:text="关注"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="12sp"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:textAllCaps="false"
|
||||
app:backgroundTint="#FF2FD3C7"
|
||||
app:icon="@drawable/ic_heart_24"
|
||||
app:iconSize="14dp" />
|
||||
app:iconSize="14dp"
|
||||
app:iconTint="@android:color/white" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
|
|
|||
|
|
@ -52,19 +52,18 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/backButton" />
|
||||
|
||||
<ImageView
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="90dp"
|
||||
android:layout_height="90dp"
|
||||
android:layout_margin="3dp"
|
||||
android:background="@drawable/bg_avatar_circle_transparent"
|
||||
android:clipToOutline="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/wish_tree_checker_backup"
|
||||
app:layout_constraintBottom_toBottomOf="@id/avatarRing"
|
||||
app:layout_constraintEnd_toEndOf="@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
|
||||
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>
|
||||
</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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user