I am working on a chat application but I am struggling to figure out how to display the calendar date on the top of chat messages; for example, something like this:
Vilen answer is right. I am just improving perfomance. Instead of doing view gone and visible for every view, we can add a text view programmatically when condition true.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:orientation="vertical"
android:padding="10dp">
<LinearLayout
android:id="@+id/timeTextLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_gray_rounded"
android:paddingBottom="@dimen/padding_extra_small"
android:paddingLeft="@dimen/padding_small"
android:paddingRight="@dimen/padding_small"
android:paddingTop="@dimen/padding_extra_small"
android:textColor="@color/gray"
android:textSize="@dimen/font_size_md" />
</LinearLayout>
ChatAdapter.java
public class ChatEGRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public static class TextViewHolder extends RecyclerView.ViewHolder {
public TextView textView;
public LinearLayout timeText;
public TextViewHolder(View v) {
super(v);
timeText = (LinearLayout) v.findViewById(R.id.timeText);
textView = (TextView) v.findViewById(R.id.textView);
}
}
private final List<Message> messages;
public ChatEGRecyclerAdapter(List<Message> messages) {
this.messages = messages;
}
// Create new views (invoked by the layout manager)
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item, parent, false);
return new TextViewHolder(v);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
final Message m = messages.get(position);
final Context context = viewHolder.itemView.getContext();
TextViewHolder holder = (TextViewHolder)viewHolder;
holder.textView.setVisibility(View.VISIBLE);
holder.textView.setText(m.getText());
//Group by Date
long previousTs = 0;
if(position >= 1){
Message previousMessage = messages.get(position-1);
previousTs = Long.parseLong(previousMessage.getSentTime());
}
Calendar cal1 = Calendar.getInstance();
Calendar cal2 = Calendar.getInstance();
cal1.setTimeInMillis(Long.parseLong(messages.get(position).getSentTime())*1000);
cal2.setTimeInMillis(previousTs*1000);
boolean sameDay = cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR);
if (!sameDay) {
TextView dateView = new TextView(context);
dateView.setText(messages.get(position).getSentTime());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER_HORIZONTAL;
dateView.setLayoutParams(params);
holder.timeText.addView(dateView);
}
}
@Override
public int getItemCount() {
return messages.size();
}
}
Also time formatting and checking for every message is expensive. To minimize this code execution actually we can compare the time stamp while sending the message. Get the time stamp of last message and compare with current message. If greater than 24 hrs then send to the server with isNewGroup:true. Then populating adapter can done without time comparison like below.
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
final Message m = messages.get(position);
final Context context = viewHolder.itemView.getContext();
TextViewHolder holder = (TextViewHolder)viewHolder;
holder.textView.setVisibility(View.VISIBLE);
holder.textView.setText(m.getText());
if (messages.get(position).getIsNewGroup()) {
TextView dateView = new TextView(context);
dateView.setText(messages.get(position).getSentTime());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER_HORIZONTAL;
dateView.setLayoutParams(params);
holder.timeText.addView(dateView);
}
}
Just create a viewgroup of messages within that date. Or I would recommend a structure like this:
ListView
- ListViewItem1
- (TextView with date)
- ListViewOfMsgsForThatDate
a) Message1ForThisDate
b) Message2ForThisDate
- ListViewItem2
- (TextView with date)
- ListViewOfMsgsForThatDate
a) Message1ForThisDate
b) Message2ForThisDate
Use this xml which helps to creating layout which you are looking for as per your link - Designing Android Chat Bubble (Chat UI) suggest.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:orientation="vertical">
<TextView
android:id="@+id/txtDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:layout_gravity="right"
android:textSize="12sp"
android:textColor="@android:color/darker_gray" />
<LinearLayout
android:id="@+id/contentWithBackground"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:background="@drawable/your_msg_bubble_bg"
android:paddingLeft="10dp"
android:paddingBottom="10dp"
android:orientation="vertical">
<TextView
android:id="@+id/txtMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:maxWidth="240dp" />
</LinearLayout>
</LinearLayout>
I update this code for date
texview:-
<TextView
android:id="@+id/txtDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:layout_gravity="right"
android:textSize="12sp"
android:textColor="@android:color/darker_gray" />
Ok; this is a solution that will give you an idea to build upon. It will solve your date issues and gives you a hint on how to solve how you display your times. This kind of program involves a lot of work to make it viable as an app. You need to take into account timezones, where you store the messages and so on.
Now stack overflow does not serve the purpose of designing programs for people, but to try and help steer people into the right direction.
For the purposes of answering your question; I am covering the basics, I'm not giving you the code ready to run, you will need to do some of the work :)
This could be solved in numerous ways I have chosen the following:
Using shared preferences to store the latest date time. I considered accessing the chat message arrays, but figured this may be simpler. I chose shared preferences so that the date of the last message would be retained when the app is closed and reopened. (Eventually the chat messages would need to be retained in a database [SQLite] so that the messages will still be there when the app is closed and opened). Please see my comment at the end.
public class ChatActivity extends ActionBarActivity {
public static final String MyPREF = "MyPrefs" ;
public static final String DATE_KEY = "DateKey" ;
SharedPreferences prefs;
SharedPreferences.Editor editor;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
initControls();
pref = getSharedPreferences(MyPREF, MODE_PRIVATE);
// Check that the shared preferences has a value
if(contains(DATE_KEY)){
editor = prefs.edit();
// get the current datetime.
editor.Long(DATE_KEY, date);
editor.commit();
// set the text of the textview android:id="@+id/date"
// with the current formatted date.
}
}
In your onclick listener, you test that the last stored date is todays date, if it's not, it is converted into a chatmessage, so it's value can be added to the array and displayed. You will have to tweak how you format these message types, and you can even add a unique id for these date messages and test for that, or test for no id.. there's many solutions.
sendBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/.../
}
ChatMessage chatMessage = new ChatMessage();
long LastDate = pref.getLong(DATE_KEY, date);
// create an empty chat message with only a date.
// check if the last used date is not today's date.
// if it's not today's date, save it and display it as
// an archived bubble.
if(!LastDate.isToday()){
ChatMessage dateholder = new ChatMessage();
// You'll need to format the date
dateholder.setDate(Formatted LastDate);
// set/change the text of the textview android:id="@+id/date"
// with the current formatted date.
}
// put the current date time into your preferences.
editor = prefs.edit();
editor.putString(DATE_KEY, date);
editor.commit();
/.../
// this is now setting up the new chat message.
chatMessage.setDate(DateFormat.getDateTimeInstance().format(new Date()));
/.../
displayMessage(chatMessage);
}
});
xml Ok, now you don't want the date floating at the top of the screen when there's only one message. So group it with the scroll list and then set this relative to the screen layout as the list alone was. A linear layout will do for this, just ensure it's orientation is set to vertical.
Pin these together.
<LinearLayout
android:layout_above="@+id/messageEdit"
android:layout_below="@+id/meLbl"
android:id="@+id/layout"
.../orentation:vertical ../
<TextView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
/..//>
<ListView
android:id="@+id/messagesContainer"
.../
...//>
</LinearLayout>
<TextView
android:id="@+id/meLbl"
/.../
/>
<TextView
android:id="@+id/friendLabel"
/.../
/>
Change display for each message You will need to stop displaying the date time for each message. If you wish to show the time for each message individually, and skip those within the same minute, as you mentioned in the comments, then I've given you the framework, to work out how to test for this and the program logic of whether or not it is displayed.
You need to change this in your:
public View getView(final int position, View convertView, ViewGroup parent) {
holder.txtMessage.setText(chatMessage.getMessage());
// TODO,
// do a check if(mins are the same don't post
// else post the format without the date)
// holder.txtInfo.setText(chatMessage.getDate());
Don't forget to apply all necessary imports.
I suggest if you start to use databases that you access the last date from there without shared preferences, or continue to use shared preferences. This becomes more complicated as there are multiple chats being saved, in which case using the database would be preferable.
As far as I understand you want to show time/date only for certain group of messages and not for each message. So here is how to do that. Precondition: I assume each message item has time stamp based on which we will do our grouping Idea: we will need each list item to have timeview:TextView element and we will show and hide that element based on it's position and TS (time stamp) Example:
item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical"
android:padding="@dimen/padding_small">
<TextView
android:id="@+id/timeText"
style="@style/DefaultText"
android:paddingBottom="@dimen/padding_small"
android:paddingRight="@dimen/padding_small"
android:paddingTop="@dimen/padding_small"
android:text="@string/hello_world"
android:textSize="@dimen/font_size_small_10"/>
<TextView
android:id="@+id/textView"
style="@style/DefaultText"
android:layout_width="wrap_content"
android:background="@drawable/bg_gray_rounded"
android:paddingBottom="@dimen/padding_extra_small"
android:paddingLeft="@dimen/padding_small"
android:paddingRight="@dimen/padding_small"
android:paddingTop="@dimen/padding_extra_small"
android:textColor="@color/gray"
android:textSize="@dimen/font_size_md"/>
</LinearLayout>
ChatRecyclerAdapter.java
public class ChatEGRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public static class TextViewHolder extends RecyclerView.ViewHolder {
public TextView textView;
public TextView timeText;
public TextViewHolder(View v) {
super(v);
timeText = (TextView) v.findViewById(R.id.timeText);
textView = (TextView) v.findViewById(R.id.textView);
}
}
private final List<Message> messages;
public ChatEGRecyclerAdapter(List<Message> messages) {
this.messages = messages;
}
// Create new views (invoked by the layout manager)
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item, parent, false);
return new TextViewHolder(v);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
final Message m = messages.get(position);
final Context context = viewHolder.itemView.getContext();
TextViewHolder holder = (TextViewHolder)viewHolder;
holder.textView.setVisibility(View.VISIBLE);
holder.textView.setText(m.getText());
long previousTs = 0;
if(position>1){
Message pm = messages.get(position-1);
previousTs = pm.getTimeStamp();
}
setTimeTextVisibility(m.getTimeStamp(), previousTs, holder.timeText);
}
private void setTimeTextVisibility(long ts1, long ts2, TextView timeText){
if(ts2==0){
timeText.setVisibility(View.VISIBLE);
timeText.setText(Utils.formatDayTimeHtml(ts1));
}else {
Calendar cal1 = Calendar.getInstance();
Calendar cal2 = Calendar.getInstance();
cal1.setTimeInMillis(ts1);
cal2.setTimeInMillis(ts2);
boolean sameMonth = cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH);
if(sameMonth){
timeText.setVisibility(View.GONE);
timeText.setText("");
}else {
timeText.setVisibility(View.VISIBLE);
timeText.setText(Utils.formatDayTimeHtml(ts2));
}
}
}
@Override
public int getItemCount() {
return messages.size();
}
}
what left is to create your RecylcerView and give it this adapter