Как отображать календарную дату в верхней части сообщений чата?

Я работаю над приложением для чата, но я изо всех сил пытаюсь понять, как отображать дату календаря в верхней части сообщений чата; например, что-то вроде этого:

Сообщения чата

Еще одно изображение с отметками времени:

«Сообщения

Как вы видите в примере, дата отображается поверх нового пакета текстовых сообщений. Я хотел бы сделать то же самое, только для пакета сообщений, связанных с определенной датой. Скажем, если у меня есть сообщения за 19 октября, сверху отображается 19 октября, а затем сообщения за 20 октября и так далее... Вот рабочий пример кода, похожий на мой:

http://www.codeproject.com/Tips/897826/Designing-Android-Chat-Bubble-Chat-UI

Конструкция кода такая же, как у меня, за исключением даты, отображаемой сверху, на которой я застрял. У меня есть временные метки, отображаемые для каждого сообщения, я просто хочу отображать дату в формате «понедельник, 19 октября 2015 г.» для пакета сообщений; только один раз сверху, с 19 октября, а также даты календаря для прошлых и будущих сообщений, как показано на изображении. Любые подсказки? Спасибо!


person Community    schedule 20.10.2015    source источник
comment
У меня есть время показа для каждого сообщения на данный момент. Я хочу сделать 2 вещи: 1) объединить сообщения с одинаковым временем вместе, например, если есть 3-4 сообщения, все отправленные в 7:54, оно будет показывать только 7:54 вверху для первого сообщения, для остальных нет время будет отображаться до тех пор, пока минута не изменится на 7:55, а во-вторых, я хотел бы отобразить день разговора только один раз сверху, например, если все сообщения были отправлены 10 октября, тогда сверху отображается 10 октября. по сообщениям от 11 октября и т. д., если они есть, например, группировать их по дням, поведение аналогично стандартному собственному текстовому сообщению Android   -  person    schedule 22.10.2015
comment
не могли бы вы опубликовать макет, который вы используете для сообщения?   -  person Blackbelt    schedule 23.10.2015
comment
это в примере кода, он называется list_tem_chat_message.xml, это очень маленький проект, всего 3 java-файла и 2 файла макета   -  person    schedule 23.10.2015


Ответы (6)


Насколько я понимаю, вы хотите показывать время/дату только для определенной группы сообщений, а не для каждого сообщения. Итак, вот как это сделать. Предварительное условие: я предполагаю, что каждый элемент сообщения имеет отметку времени, на основе которой мы будем группировать. Идея: нам потребуется, чтобы каждый элемент списка имел элемент timeview:TextView, и мы будем показывать и скрывать этот элемент на основе его позиции и TS (отметка времени) Пример:

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();
    }

}

осталось создать свой RecylcerView и дать ему этот адаптер

person Vilen    schedule 22.10.2015
comment
мне действительно нужно использовать recyclerview? У меня есть пример кода, основанный на этом, работает ли ваш пример кода для обычного списка, когда вы его интегрируете? параметры, используемые для даты, — это txtInfo, а для сообщений — txtMessage в ChatAdapter.java. - person ; 22.10.2015
comment
этот подход будет работать и для просмотра списка, но зачем он вам? ListView собирается устареть из-за RecyclerView, который является более простым и гибким компонентом, и он автоматически реализует шаблон ViewHolder, чтобы избавиться от мусора Android. в любом случае, независимо от того, какое представление вы используете, хитрость здесь заключается в том, чтобы сравнить метку времени сообщения и, соответственно, показать и скрыть текстовое представление даты. - person Vilen; 22.10.2015
comment
будет ли он работать для отображения календарного дня сверху, ТОЛЬКО ОДИН РАЗ, для сообщений, принадлежащих к одному и тому же дню? Скажем, если я разговариваю с кем-то 8 октября, он должен показать 8 октября 2015 года вверху только один раз для всего списка сообщений, отправленных в этот день. В следующий раз, когда я вижу календарный день сверху, это следующий день, когда у меня был разговор, независимо от того, будет ли это на следующий день или через 10 дней. - person ; 22.10.2015
comment
Я не совсем уверен, что вы имеете в виду под ТОЛЬКО ОДИН РАЗ, один раз в день или независимо от того, сколько дней разговора там показывают только последний день? - person Vilen; 22.10.2015
comment
один раз в день вверху - person ; 22.10.2015
comment
да, вам нужно только изменить эту строку cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH); в cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR); поэтому он будет показывать дату и время для каждого дня, то есть, если у вас есть 4 дня разговора, у вас будет 4 текста даты - person Vilen; 22.10.2015
comment
мне нравится эта логика, она умная, позвольте мне внести некоторые коррективы и вернуться к вам, я планирую другой подход, но с похожей логикой, если оба эти примера работают, вы победитель! если нет, я дам вам знать, с какими проблемами я сталкиваюсь, должен быть более простой подход, я надеюсь - person ; 22.10.2015
comment
что полезного в setTimeTextVisibility? как я могу сослаться на это - person ; 22.10.2015
comment
пожалуйста, посмотрите на код, это метод в классе ChatEGRecyclerAdapter, который решает, показывать ли текст даты или нет - person Vilen; 22.10.2015
comment
Я получаю ошибки на Utils. как на это ссылаются? что я должен импортировать для этого? - person ; 22.10.2015
comment
Давайте продолжим обсуждение в чате. - person Vilen; 22.10.2015

Вилен ответ правильный. Я просто улучшаю производительность. Вместо того, чтобы делать вид видимым для каждого вида, мы можем программно добавить текстовый вид, когда условие истинно.

<?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();
    }

}

Кроме того, форматирование времени и проверка каждого сообщения обходятся дорого. Чтобы свести к минимуму выполнение этого кода, мы можем сравнить отметку времени при отправке сообщения. Получите отметку времени последнего сообщения и сравните с текущим сообщением. Если больше 24 часов, отправьте на сервер с isNewGroup:true. Затем заполнение адаптера можно выполнить без сравнения времени, как показано ниже.

@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);
        }
    }  
person Community    schedule 30.10.2017
comment
Не забудьте поддержать переработку, удалив представление для не новых групповых элементов. Да еще и не добавлять дважды.. - person Shirane85; 01.11.2018

Ok; это решение, которое даст вам идею для дальнейшего развития. Это решит ваши проблемы с датой и даст вам подсказку о том, как решить, как вы отображаете свое время. Этот тип программы требует большой работы, чтобы сделать ее жизнеспособной в качестве приложения. Вам нужно учитывать часовые пояса, где вы храните сообщения и так далее.

Теперь переполнение стека служит не для разработки программ для людей, а для того, чтобы попытаться направить людей в правильном направлении.

Для ответа на ваш вопрос; Я рассказываю об основах, я не даю вам код, готовый к запуску, вам нужно будет выполнить часть работы :)

Это можно решить разными способами, я выбрал следующие:

Использование общих настроек для хранения последней даты и времени. Я подумал о доступе к массивам сообщений чата, но решил, что это может быть проще. Я выбрал общие настройки, чтобы дата последнего сообщения сохранялась при закрытии и повторном открытии приложения. (В конечном итоге сообщения чата необходимо будет сохранить в базе данных [SQLite], чтобы сообщения оставались там, когда приложение закрывается и открывается). Пожалуйста, смотрите мой комментарий в конце.

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.
        }

    }

В вашем прослушивателе onclick вы проверяете, что последняя сохраненная дата является сегодняшней датой, если это не так, она преобразуется в сообщение чата, поэтому ее значение можно добавить в массив и отобразить. Вам нужно будет настроить форматирование этих типов сообщений, и вы даже можете добавить уникальный идентификатор для этих сообщений о дате и проверить это или проверить отсутствие идентификатора.. есть много решений.

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 Хорошо, теперь вы не хотите, чтобы дата плавала в верхней части экрана, когда есть только одно сообщение. Поэтому сгруппируйте его со списком прокрутки, а затем установите его относительно макета экрана, как это было со списком. Для этого подойдет линейный макет, просто убедитесь, что его ориентация установлена ​​​​на вертикаль.

Скрепите их вместе.

<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"
        /.../
         />

Изменить отображение для каждого сообщения. Вам нужно перестать отображать дату и время для каждого сообщения. Если вы хотите показать время для каждого сообщения отдельно и пропустить их в течение той же минуты, как вы упомянули в комментариях, то я дал вам структуру, чтобы разработать, как проверить это и логику программы того, является ли или не отображается.

Вам нужно изменить это в вашем:

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());

Не забудьте применить все необходимые импорты.

Я предлагаю, если вы начнете использовать базы данных, вы получите доступ к последней дате оттуда без общих настроек или продолжите использовать общие настройки. Это усложняется, поскольку сохраняется несколько чатов, и в этом случае предпочтительнее использовать базу данных.

person Community    schedule 22.10.2015

Просто создайте группу просмотра сообщений в пределах этой даты. Или я бы рекомендовал такую ​​структуру:

ListView
 - ListViewItem1
    - (TextView with date)
    - ListViewOfMsgsForThatDate
      a) Message1ForThisDate
      b) Message2ForThisDate
- ListViewItem2
   - (TextView with date)
   - ListViewOfMsgsForThatDate
      a) Message1ForThisDate
      b) Message2ForThisDate
person MetaSnarf    schedule 20.10.2015
comment
у вас есть пример кода для той же структуры? было бы полезно - person ; 20.10.2015

Используйте этот xml, который помогает создать макет, который вы ищете, в соответствии с вашим ссылка — Проектирование Android Chat Bubble (интерфейс чата).

<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>

enter image description here

Я обновляю этот код для 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" />
person pRaNaY    schedule 22.10.2015

@Vilen Ответ помогите мне с этой проблемой.

По Api respone предоставляет данные, соответствующие дате и времени в другом ключе, чтобы мне было легко управлять мной в адаптере

Апи ответ вроде......

    {
  "status": "success",
  "unread": "0",
  "data": [
    {
      "from_id": "152000000",
      "to_id": "15500545452",
      "message": "do their Jami hit",
      "profile_image": "1667822677.jpg",
      "addedon": "8:34 am",
      "groupby": "April 2, 2021"
    }
  ],
  
  "message": "Chat History fetch successfully."
}

В привязке ViewHolder

  val dateGroupBy=""
        if (position > 0) {
                    dateGroupBy = list.get(position - 1).groupby
        }
 if (holder is MassageReceiverViewHolder) {
            holder.bind(data, dateGroupBy)
        } else if (holder is MassageSenderViewHolder) {
            holder.bind(data, dateGroupBy)
        }

В функции привязки обоих просмотрщиков

if (dateGroupBy != data.groupby) {
            binding.tvDateGroup.visibility = View.VISIBLE
            binding.tvDateGroup.text = data.groupby
        }

Bindig — это просто ваш XML. Я сделал это через Bindig.

person Umesh Yadav    schedule 06.04.2021