【发布时间】:2021-05-21 16:37:42
【问题描述】:
我正在开发一个聊天应用程序,我正在处理 SearchableActivity,用户在其中输入他想要搜索的文本。输出为this。如您所见,RecyclerView 显示用户和/或消息。我有一个包含消息的列表,一个包含上一个列表的消息中的用户数据的列表和一个用于存储用户部分的用户的地图。
问题是这种结构在代码上很复杂并且不是很有效。如果你注意到了,我打开了软键盘,因为当用户 Map 和消息列表都不为空时,关闭软键盘会引发 this (我不知道是什么造成了这种行为)以及其他一些不一致的地方甚至导致应用崩溃。
鉴于上述细节,我希望能够完美地展示这两个项目。这是代码: SearchableActivity.java
public class SearchableActivity extends AppCompatActivity {
private Map<String, User> searchedUsers;
private List<Message> searchedMessages;
private List<User> searchedMessagesUser;
private String searchUserID;
private TextView notFoundTV;
private RecyclerView searchRV;
private SearchableAdapter searchableAdapter;
private MaterialCardView searchTipCV;
private boolean isUserNameValid = true;
private boolean doesMessageFitQuery = false;
private final DatabaseReference messagesReference = FirebaseDatabase.getInstance().getReference()
.child(KEY_COLLECTION_MESSAGE);
private final DatabaseReference usersReference = FirebaseDatabase.getInstance().getReference()
.child(KEY_COLLECTION_USER);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_searchable);
ActionBar actionBar = this.getSupportActionBar();
actionBar.setTitle("");
actionBar.setDisplayHomeAsUpEnabled(true);
setUpRV();
notFoundTV = findViewById(R.id.notFoundTV);
searchTipCV = findViewById(R.id.searchTipCV);
}
private void setUpRV() {
searchedUsers = new HashMap<>();
searchedMessages = new ArrayList<>();
searchedMessagesUser = new ArrayList<>();
searchableAdapter = new SearchableAdapter(SearchableActivity.this,
searchedUsers,
searchedMessages,
searchedMessagesUser
);
searchRV = findViewById(R.id.searchRV);
searchRV.setHasFixedSize(true);
searchRV.setLayoutManager(new LinearLayoutManager(SearchableActivity.this));
searchRV.setAdapter(searchableAdapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_searchable, menu);
SearchView searchView = (SearchView) menu.findItem(R.id.searchIT).getActionView();
searchView.setMaxWidth(Integer.MAX_VALUE);
searchView.setIconified(false);
searchView.setFocusable(true);
searchView.requestFocusFromTouch();
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String query) {
if (!TextUtils.isEmpty(query)) {
search(query);
}
return true;
}
});
return true;
}
@Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
private void search(String query) {
searchedUsers.clear();
searchedMessages.clear();
searchedMessagesUser.clear();
isUserNameValid = true;
String finalQuery = query.trim();
if (finalQuery.isEmpty()) {
setViewsVisibility(View.GONE, View.VISIBLE, View.GONE);
isUserNameValid = false;
} else if (finalQuery.length() < 4 || !finalQuery.matches("^[a-zA-Z0-9]+$")) {
isUserNameValid = false;
}
messagesReference.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NotNull DataSnapshot dataSnapshot) {
if (dataSnapshot.getValue() == null && isUserNameValid) {
searchUser(finalQuery);
} else {
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
doesMessageFitQuery = false;
Message message = snapshot.getValue(Message.class);
String[] wordsFromMessage = message.getMessage().split(" ");
int i;
for (i = 0; i < wordsFromMessage.length; i++) {
if (wordsFromMessage[i].toLowerCase().startsWith(finalQuery.toLowerCase())) {
doesMessageFitQuery = true;
break;
}
}
if (doesMessageFitQuery
&& (message.getSenderID().equals(CURRENT_USER.getId())
|| message.getReaderID().equals(CURRENT_USER.getId()))) {
if (message.getReaderID().equals(CURRENT_USER.getId())) {
searchUserID = message.getSenderID();
} else {
searchUserID = message.getReaderID();
}
searchedMessages.add(message);
usersReference.child(searchUserID).addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
User user = snapshot.getValue(User.class);
searchedMessagesUser.add(user);
if (isUserNameValid) {
searchUser(finalQuery);
} else {
refreshRV();
}
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
}
});
} else if (isUserNameValid) {
searchUser(finalQuery);
} else {
setViewsVisibility(View.GONE, View.GONE, View.VISIBLE);
}
}
}
}
@Override
public void onCancelled(@NotNull DatabaseError databaseError) { }
});
}
private void searchUser(String finalQuery) {
usersReference.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NotNull DataSnapshot dataSnapshot) {
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
User user = snapshot.getValue(User.class);
if (user.getUserName().equals(CURRENT_USER.getUserName())) continue;
if (user.getUserName().toLowerCase().startsWith(finalQuery.toLowerCase())) {
searchedUsers.put(user.getId(), user);
}
refreshRV();
}
}
@Override
public void onCancelled(@NotNull DatabaseError databaseError) { }
});
}
private void setViewsVisibility(int searchVis, int searchTipVis, int notFoundVis) {
searchRV.setVisibility(searchVis);
searchTipCV.setVisibility(searchTipVis);
notFoundTV.setVisibility(notFoundVis);
}
private void refreshRV() {
setViewsVisibility(View.VISIBLE, View.GONE, View.GONE);
if (searchedUsers.size() > 0 && searchedMessages.size() > 0) {
setupRV(searchedUsers, searchedMessages, searchedMessagesUser);
} else if (searchedUsers.size() > 0) {
setupRV(searchedUsers, new ArrayList<>(), new ArrayList<>());
} else if (searchedMessages.size() > 0) {
setupRV(new HashMap<>(), searchedMessages, searchedMessagesUser);
} else {
setViewsVisibility(View.GONE, View.GONE, View.VISIBLE);
}
}
private void setupRV(@NotNull Map<String, User> searchedUsers,
@NotNull List<Message> searchedMessages,
@NotNull List<User> searchedMessagesUsers) {
searchableAdapter = new SearchableAdapter(
SearchableActivity.this,
searchedUsers,
searchedMessages,
searchedMessagesUsers
);
searchRV.setAdapter(searchableAdapter);
}
}
SearchableAdapter.java
public class SearchableAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final Context context;
private final Map<String, User> searchedUsers;
private final List<User> searchedMessagesUsers;
private final List<Message> searchedMessages;
private int messageListPosition;
private boolean areUsersLeft = true;
public static final int NO_ITEM = -1;
public static final int ITEM_USER = 0;
public static final int ITEM_MSG_SENT = 1;
public static final int ITEM_MSG_RECEIVED = 2;
public SearchableAdapter(Context context, Map<String, User> searchedUsers, List<Message> searchedMessages, List<User> searchedMessagesUsers) {
this.context = context;
this.searchedUsers = searchedUsers;
this.searchedMessages = searchedMessages;
this.searchedMessagesUsers = searchedMessagesUsers;
messageListPosition = 0;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view;
switch (viewType) {
case ITEM_USER:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_user_searched, parent, false);
return new SearchableAdapter.UserHolder(view);
case ITEM_MSG_SENT:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_recent_chat_sent, parent, false);
return new SearchableAdapter.MessageSentHolder(view);
case ITEM_MSG_RECEIVED:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_recent_chat_received, parent, false);
return new SearchableAdapter.MessageReceivedHolder(view);
}
return null;
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
switch (holder.getItemViewType()) {
case ITEM_USER:
String key = (String) searchedUsers.keySet().toArray()[position];
((SearchableAdapter.UserHolder) holder).bind(searchedUsers.get(key), position);
break;
case ITEM_MSG_SENT:
((SearchableAdapter.MessageSentHolder) holder).bind(
searchedMessages.get(messageListPosition),
searchedMessagesUsers.get(messageListPosition),
messageListPosition
);
if (messageListPosition + 1 != searchedMessages.size()) {
messageListPosition++;
}
break;
case ITEM_MSG_RECEIVED:
((SearchableAdapter.MessageReceivedHolder) holder).bind(
searchedMessages.get(messageListPosition),
searchedMessagesUsers.get(messageListPosition),
messageListPosition
);
if (messageListPosition + 1 != searchedMessages.size()) {
messageListPosition++;
}
break;
}
}
@Override
public int getItemCount() {
if (!searchedMessages.isEmpty() && !searchedUsers.isEmpty()) {
return searchedUsers.size() + searchedMessages.size();
} else if (!searchedMessages.isEmpty()) {
return searchedMessages.size();
} else if (!searchedUsers.isEmpty()) {
return searchedUsers.size();
}
return 0;
}
@Override
public int getItemViewType(int position) {
if (areUsersLeft && !searchedUsers.isEmpty() && position < searchedUsers.size()) {
if (searchedUsers.size() - 1 == position) {
areUsersLeft = false;
}
return ITEM_USER;
} else if (!searchedMessages.isEmpty()) {
if (!searchedUsers.isEmpty() && messageListPosition < searchedMessages.size()) {
if (searchedMessages.get(messageListPosition).getSenderID().equals(Constants.CURRENT_USER.getId())) {
return ITEM_MSG_SENT;
} else {
return ITEM_MSG_RECEIVED;
}
} else if (searchedUsers.isEmpty() && position < searchedMessages.size()) {
if (searchedMessages.get(position).getSenderID().equals(Constants.CURRENT_USER.getId())) {
return ITEM_MSG_SENT;
} else {
return ITEM_MSG_RECEIVED;
}
}
}
return NO_ITEM;
}
class UserHolder extends RecyclerView.ViewHolder {
public ImageView profileIV;
public TextView defaultProfileTV, usernameTV, aboutTV, usersSectionTV;
public UserHolder(@NonNull View itemView) {
super(itemView);
defaultProfileTV = itemView.findViewById(R.id.defaultProfileTV);
usernameTV = itemView.findViewById(R.id.usernameTV);
aboutTV = itemView.findViewById(R.id.aboutTV);
profileIV = itemView.findViewById(R.id.profileIV);
usersSectionTV = itemView.findViewById(R.id.usersSectionTV);
}
void bind(User user, int position) {
if (position == 0) {
usersSectionTV.setVisibility(View.VISIBLE);
}
usernameTV.setText(user.getUserName());
aboutTV.setText(user.getAbout());
showProfilePic(defaultProfileTV, profileIV, user);
itemView.setOnClickListener(v -> openChat(user));
}
}
class MessageReceivedHolder extends RecyclerView.ViewHolder {
public TextView defaultProfileTV, usernameTV, dateLastMesTV, previewMessageTV, messagesSectionTV;
public ImageView profileIV;
public CardView statusCV;
public MessageReceivedHolder(@NonNull View itemView) {
super(itemView);
defaultProfileTV = itemView.findViewById(R.id.defaultProfileTV);
usernameTV = itemView.findViewById(R.id.usernameTV);
dateLastMesTV = itemView.findViewById(R.id.dateLastMesTV);
previewMessageTV = itemView.findViewById(R.id.previewMessageTV);
profileIV = itemView.findViewById(R.id.profileIV);
statusCV = itemView.findViewById(R.id.statusCV);
messagesSectionTV = itemView.findViewById(R.id.messagesSectionTV);
}
void bind(Message message, User user, int position) {
statusCV.setVisibility(View.GONE);
if (position == 0) {
messagesSectionTV.setVisibility(View.VISIBLE);
}
usernameTV.setText(user.getUserName());
previewMessageTV.setText(message.getMessage());
formatDate(dateLastMesTV, message.getDate());
showProfilePic(defaultProfileTV, profileIV, user);
itemView.setOnClickListener(v -> openChat(user));
}
}
class MessageSentHolder extends RecyclerView.ViewHolder {
public TextView defaultProfileTV, usernameTV, dateLastMesTV, previewMessageTV, messagesSectionTV;
public ImageView profileIV, seenIV, notSeenIV;
public CardView statusCV;
MessageSentHolder(View itemView) {
super(itemView);
defaultProfileTV = itemView.findViewById(R.id.defaultProfileTV);
usernameTV = itemView.findViewById(R.id.usernameTV);
dateLastMesTV = itemView.findViewById(R.id.dateLastMesTV);
previewMessageTV = itemView.findViewById(R.id.previewMessageTV);
profileIV = itemView.findViewById(R.id.profileIV);
seenIV = itemView.findViewById(R.id.seenIV);
notSeenIV = itemView.findViewById(R.id.notSeenIV);
statusCV = itemView.findViewById(R.id.statusCV);
messagesSectionTV = itemView.findViewById(R.id.messagesSectionTV);
}
void bind(Message message, User user, int position) {
statusCV.setVisibility(View.GONE);
if (position == 0) {
messagesSectionTV.setVisibility(View.VISIBLE);
}
if (message.isSeen()) {
seenIV.setVisibility(View.VISIBLE);
notSeenIV.setVisibility(View.GONE);
} else {
seenIV.setVisibility(View.GONE);
notSeenIV.setVisibility(View.VISIBLE);
}
usernameTV.setText(user.getUserName());
previewMessageTV.setText(message.getMessage());
showProfilePic(defaultProfileTV, profileIV, user);
formatDate(dateLastMesTV, message.getDate());
itemView.setOnClickListener(v -> openChat(user));
}
}
private void formatDate(TextView dateLastMesTV, String date) {
SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy", Locale.getDefault());
Date currentDay = new Date(System.currentTimeMillis());
Date chatDay = new Date(Long.parseLong(date));
if (formatter.format(currentDay).equals(formatter.format(chatDay))) {
dateLastMesTV.setText(
DateFormat.getTimeInstance(
DateFormat.SHORT,
Locale.getDefault()
).format(chatDay)
);
} else {
SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yy", Locale.getDefault());
dateLastMesTV.setText(dateFormat.format(Long.valueOf(date)));
}
}
private void showProfilePic(TextView defaultProfileTV, ImageView profileIV, User user) {
if (user.getImageURL().equals(Constants.KEY_IMAGE_URL_DEFAULT)) {
defaultProfileTV.setVisibility(View.VISIBLE);
defaultProfileTV.setText(user.getUserName().substring(0, 1));
} else {
profileIV.setVisibility(View.VISIBLE);
Glide.with(context)
.load(user.getImageURL())
.circleCrop()
.into(profileIV);
}
}
private void openChat(User user) {
Intent i = new Intent(context, ChatActivity.class);
i.putExtra(Constants.INTENT_USER, user);
context.startActivity(i);
}
}
欢迎提出任何建议。也许 Lists/Map 背后的逻辑可以简化,我不知道。我已经想的不够清楚了。以下是相关的数据库结构以防万一:
> Message
> > (message ID)
> > > date
> > > message
> > > readerID
> > > seen
> > > senderID
> User
> > (user ID)
> > > about
> > > id
> > > imageURL
> > > lastSeen
> > > userName
【问题讨论】:
标签: android android-recyclerview android-adapter