【问题标题】:How do I display the calendar date on the top of chat messages?如何在聊天消息顶部显示日历日期?
【发布时间】:2015-10-20 01:16:51
【问题描述】:

我正在开发一个聊天应用程序,但我正在努力弄清楚如何在聊天消息顶部显示日历日期;例如,像这样:

另一个带有时间戳的图像:

正如您在示例中看到的,日期显示在新一批文本消息的顶部。我也想做同样的事情,只是针对与特定日期相关的一批消息。假设我有 10 月 19 日的消息,它会在顶部显示 10 月 19 日,然后是 10 月 20 日的消息,依此类推...这是类似于我的工作示例代码:

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

代码的构造与我的相同,只是日期显示在顶部,这是我坚持的。我为每条消息显示了时间戳,我只想以“2015 年 10 月 19 日星期一”的格式显示这批消息的日期;从 10 月 19 日开始,仅在顶部一次,以及过去和未来消息的日历日期,如图所示。有什么线索吗? 谢谢!

【问题讨论】:

  • 我现在有时间显示每条消息。我想做两件事,1)将同一时间的消息组合在一起,比如如果有 3-4 条消息都在 7:54 发送,那么第一条消息的顶部只会显示 7:54,其余的没有将显示时间,直到分钟更改为 7:55,第二件事是我想在顶部显示一次对话的日期,例如如果消息都是在 10 月 10 日发送的,那么它会在顶部显示 10 月 10 日通过 10 月 11 日的消息等(如果有),例如按天分组,类似于标准 android 原生短信的行为
  • 能否请您发布您用于消息的布局?
  • 在示例代码中,它叫 list_tem_chat_message.xml ,这是一个非常小的项目,只有 3 个 java 文件和 2 个布局文件

标签: android android-layout android-fragments android-activity android-date


【解决方案1】:

据我了解,您只想为某些消息组而不是每条消息显示时间/日期。所以这里是如何做到这一点。 前提条件:我假设每个消息项都有时间戳,我们将根据它进行分组 想法:我们需要每个列表项都有 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();
    }

}

剩下的就是创建你的 RecyclerView 并给它这个适配器

【讨论】:

  • 我真的需要使用recyclerview吗?我附上了代码示例,基于此,当您集成它时,您的代码示例是否适用于普通列表视图?用于日期的参数是 txtInfo,用于消息的参数是 ChatAdapter.java 中的 txtMessage
  • 这种方法也适用于列表视图,但为什么需要它? ListView 即将被弃用,因为 RecyclerView 是一个更简单灵活的组件,它自动实现 ViewHolder 模式以摆脱 android 垃圾。无论如何,无论您使用什么视图,这里的技巧是比较消息的时间戳并相应地显示和隐藏日期文本视图。
  • 对于属于同一天的消息,它是否可以在顶部显示日历日,仅一次?比如说,如果我在 10 月 8 日与某人进行了一次对话,那么对于当天发送的整个消息列表,它应该只在顶部显示 2015 年 10 月 8 日。下一次我看到顶部的日历日是我进行对话的第二天,无论是第二天还是 10 天后。
  • 我不太清楚你所说的“JUST ONCE”是什么意思,每天一次,或者不管有多少天的对话只显示最后一天?
  • 每天一次,在顶部
【解决方案2】:

Vilen 的回答是对的。我只是在提高性能。我们可以在条件为真时以编程方式添加一个文本视图,而不是为每个视图执行视图消失和可见。

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

【讨论】:

  • 不要忘记通过删除非新组项目的视图来支持回收。而且也不加两次..
【解决方案3】:

好的;这是一个解决方案,可以为您提供构建的想法。它将解决您的日期问题,并提示您如何解决如何显示您的时间。这种程序需要大量工作才能使其作为应用程序可行。您需要考虑时区、存储消息的位置等。

现在堆栈溢出的目的不是为人们设计程序,而是试图帮助人们朝着正确的方向前进。

为了回答您的问题;我介绍的是基础知识,我不会给你准备好运行的代码,你需要做一些工作:)

这可以通过多种方式解决,我选择了以下方法:

使用共享偏好来存储最新的日期时间。我考虑访问聊天消息数组,但认为这可能更简单。我选择了共享首选项,以便在关闭和重新打开应用程序时保留最后一条消息的日期。 (最终聊天消息需要保留在数据库 [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 监听器中,您测试最后存储的日期是今天的日期,如果不是,则将其转换为聊天消息,因此可以将其值添加到数组中并显示。您将不得不调整这些消息类型的格式,您甚至可以为这些日期消息添加一个唯一的 id 并对其进行测试,或者测试是否没有 id.. 有很多解决方案。

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

更改每条消息的显示您需要停止显示每条消息的日期时间。如果您希望单独显示每条消息的时间,并在同一分钟内跳过这些消息,正如您在 cmets 中提到的,那么我已经为您提供了框架,以制定如何测试这一点以及程序逻辑是否或不显示。

你需要在你的:

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

不要忘记应用所有必要的导入。

我建议您是否开始使用您从那里访问最后日期的数据库而没有共享首选项,或者继续使用共享首选项。这变得更加复杂,因为要保存多个聊天,在这种情况下使用数据库会更好。

【讨论】:

    【解决方案4】:

    只需在该日期内创建一个消息视图组。或者我会推荐这样的结构:

    ListView
     - ListViewItem1
        - (TextView with date)
        - ListViewOfMsgsForThatDate
          a) Message1ForThisDate
          b) Message2ForThisDate
    - ListViewItem2
       - (TextView with date)
       - ListViewOfMsgsForThatDate
          a) Message1ForThisDate
          b) Message2ForThisDate
    

    【讨论】:

    • 你有相同结构的示例代码吗?会有用的
    【解决方案5】:

    使用此 xml,它有助于根据您的 link - Designing Android Chat Bubble (Chat UI) 建议创建您正在寻找的布局。

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

    我为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" />
    

    【讨论】:

      【解决方案6】:

      @Vilen Answer 帮我解决这个问题。

      通过 Api 响应以不同的键提供根据日期和时间的数据,以便在适配器中轻松管理我

      API 响应之类的......

          {
        "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)
              }
      

      In Both Viewholder 绑定功能

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

      Bindig 是简单的 xml。我是通过Bindig完成的。

      【讨论】:

        【解决方案7】:

        我想这个问题不需要在已经接受的解决方案上叠加另一个答案,但这可能会有所帮助。牢记@Vilen 的建议我做了:

        -> 将日期字符串值推送到 API 中的每个子节点

        -> 比较适配器onBindViewHolder()中每个人的日期

        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, model: Message) {
            val isSameUser: Boolean =
                if (position > 0) model.userId == getItem(position - 1).userId else false
            val isSameDate: Boolean =
                if (position > 0) model.date == getItem(position - 1).date else false
        
            when (viewHolder) {
                // Based on data I initiated val [viewHolder] with appropriate 
                // enum class member in [getItemViewType()]
                EnumViewHolder.SenderViewHolder ->
                    (holder as SenderMessageViewHolder).bindData(model, isSameDate, isSameUser)
                EnumViewHolder.MessageViewHolder ->
                    (holder as MessageViewHolder).bindData(model, isSameDate, isSameUser)
                else -> false
            }
        }
        ...
        ....
        inner class MessageViewHolder(private val binding: MessageBinding) : RecyclerView.ViewHolder(binding.root) {
            fun bindData(item: Message,
                         isSameDate: Boolean,
                         isSameUser: Boolean
            ) {
                binding.messageTextTextView.text = item.text
                binding.messageTimeTextView.text = item.time
        
                if (isSameDate) {
                    binding.messageDateTextView.visibility = View.GONE
                } else binding.messageDateTextView.text = item.date
                
                // Omit user profile picture in case of repeated message
                if (isSameUser && isSameDate) {
                    binding.messageLinearLayout.setBackgroundResource(R.drawable.message_background)
                    binding.userImageImageView.visibility = View.INVISIBLE
                } else {
                    // loading image URL to imageView
                    ...
                }
            }
        }
        // I did the same for SenderMessageViewHolder()
        

        这是我得到的结果: Screenshot

        【讨论】:

          猜你喜欢
          • 2022-06-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-03-29
          • 2020-07-28
          • 2017-06-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多