Android通知直接回复

时间:2020-02-23 14:29:06  来源:igfitidea点击:

Android Notification Direct Reply操作可让我们回复通知消息,它在Whatsapp和Facebook Messenger通知消息等聊天通知中非常流行。

Android Nougat引入了一些新功能。
它提供了一些很棒的功能,例如内联回复操作和捆绑通知。
在本教程中,我们将在应用程序中实现内联回复

Android通知直接回复

内联回复操作(也称为直接回复)使我们能够从通知本身中回复消息。
通过消除打开用于提供输入的应用程序的需求,它使生活变得更轻松。
这些功能通常在消息传递应用程序中看到。
直接答复结合使用了通知操作和远程输入。
远程输入API提供了一种从我们的应用程序中的通知中访问输入文本的机制。

RemoteInput需要以下字符串作为输入。

  • 唯一键:此键用于正确识别以后从内联通知中输入的文本。

  • 标签:这作为提示文本显示给用户。

让我们实现一个基本应用程序,该应用程序会触发一个通知,并将"内联回复"设置为"操作"。

Android通知直接回复代码

下面给出了" activity_main.xml"布局的代码。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
  xmlns:app="https://schemas.android.com/apk/res-auto"
  xmlns:tools="https://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context="com.theitroad.directreplynotification.MainActivity">

  <Button
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="BASIC INLINE REPLY NOTIFICATION"
      android:id="@+id/btn_basic_inline_reply"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintRight_toRightOf="parent"
      app:layout_constraintTop_toTopOf="parent" 

  <TextView
      android:id="@+id/txt_inline_reply"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="Replied text will be displayed here"
      android:layout_marginTop="8dp"
      app:layout_constraintTop_toBottomOf="@+id/btn_basic_inline_reply"
      android:layout_marginLeft="8dp"
      app:layout_constraintLeft_toLeftOf="parent"
      android:layout_marginRight="8dp"
      app:layout_constraintRight_toRightOf="parent" 

  <Button
      android:id="@+id/btn_inline_replies_with_history"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="INLINE REPLIES WITH HISTORY"
      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintRight_toRightOf="parent"
      android:layout_marginTop="8dp"
      app:layout_constraintTop_toBottomOf="@+id/txt_inline_reply" 

</android.support.constraint.ConstraintLayout>

注意:我们将在本教程的后面部分介绍第二个按钮。

下面给出了MainActivity.java类的代码。

package com.theitroad.directreplynotification;

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.RemoteInput;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

  String KEY_REPLY = "key_reply";
  public static final int NOTIFICATION_ID = 1;

  Button btnBasicInlineReply;
  TextView txtReplied;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);

      clearExistingNotifications();

      btnBasicInlineReply = (Button) findViewById(R.id.btn_basic_inline_reply);
      txtReplied = (TextView) findViewById(R.id.txt_inline_reply);
      btnBasicInlineReply.setOnClickListener(this);

  }

  @Override
  public void onClick(View v) {
      switch (v.getId()) {
          case R.id.btn_basic_inline_reply:

              //Create notification builder
              NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                      .setSmallIcon(android.R.drawable.stat_notify_chat)
                      .setContentTitle("Inline Reply Notification");

              String replyLabel = "Enter your reply here";

              //Initialise RemoteInput
              RemoteInput remoteInput = new RemoteInput.Builder(KEY_REPLY)
                      .setLabel(replyLabel)
                      .build();

              //PendingIntent that restarts the current activity instance.
              Intent resultIntent = new Intent(this, MainActivity.class);
              resultIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
              PendingIntent resultPendingIntent = PendingIntent.getActivity(this, 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT);

              //Notification Action with RemoteInput instance added.
              NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(
                      android.R.drawable.sym_action_chat, "REPLY", resultPendingIntent)
                      .addRemoteInput(remoteInput)
                      .setAllowGeneratedReplies(true)
                      .build();

              //Notification.Action instance added to Notification Builder.
              builder.addAction(replyAction);

              Intent intent = new Intent(this, MainActivity.class);
              intent.putExtra("notificationId", NOTIFICATION_ID);
              intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
              PendingIntent dismissIntent = PendingIntent.getActivity(getBaseContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);

              builder.addAction(android.R.drawable.ic_menu_close_clear_cancel, "DISMISS", dismissIntent);

              //Create Notification.
              NotificationManager notificationManager =
                      (NotificationManager)
                              getSystemService(Context.NOTIFICATION_SERVICE);

              notificationManager.notify(NOTIFICATION_ID,
                      builder.build());
              break;

      }
  }

  private void clearExistingNotifications()
  {
      int notificationId = getIntent().getIntExtra("notificationId", 0);
      NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
      manager.cancel(notificationId);
  }
}

在通知上设置了两个通知动作,即" REPLY"和" DISMISS"。
单击REPLY将触发直接答复功能。
在模拟器上运行以上代码将得到以下输出。

在上面的gif中,单击通知中的发送图标会显示一个指示器。
这意味着通知正在等待活动的确认。
我们尚未在活动中处理回复的文本。
内联回复文本是使用RemoteInput实例中的键集检索的,如下面的MainActivity.java的更新代码所示。

package com.theitroad.directreplynotification;

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.RemoteInput;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.util.Random;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

  String KEY_REPLY = "key_reply";
  public static final int NOTIFICATION_ID = 1;

  Button btnBasicInlineReply;
  TextView txtReplied;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);

      btnBasicInlineReply = (Button) findViewById(R.id.btn_basic_inline_reply);
      txtReplied = (TextView) findViewById(R.id.txt_inline_reply);
      btnBasicInlineReply.setOnClickListener(this);

      clearExistingNotifications()
  }

  @Override
  protected void onNewIntent(Intent intent) {
      super.onNewIntent(intent);
      processInlineReply(intent);
      
  }

  @Override
  public void onClick(View v) {
      switch (v.getId()) {
          case R.id.btn_basic_inline_reply:

              //Create notification builder
              NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                      .setSmallIcon(android.R.drawable.stat_notify_chat)
                      .setContentTitle("Inline Reply Notification");

              String replyLabel = "Enter your reply here";

              //Initialise RemoteInput
              RemoteInput remoteInput = new RemoteInput.Builder(KEY_REPLY)
                      .setLabel(replyLabel)
                      .build();

              int randomRequestCode = new Random().nextInt(54325);

              //PendingIntent that restarts the current activity instance.
              Intent resultIntent = new Intent(this, MainActivity.class);
              //Set a unique request code for this pending intent
              PendingIntent resultPendingIntent = PendingIntent.getActivity(this, randomRequestCode, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT);

              //Notification Action with RemoteInput instance added.
              NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(
                      android.R.drawable.sym_action_chat, "REPLY", resultPendingIntent)
                      .addRemoteInput(remoteInput)
                      .setAllowGeneratedReplies(true)
                      .build();

              //Notification.Action instance added to Notification Builder.
              builder.addAction(replyAction);

              Intent intent = new Intent(this, MainActivity.class);
              intent.putExtra("notificationId", NOTIFICATION_ID);
              intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
              PendingIntent dismissIntent = PendingIntent.getActivity(getBaseContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);

              builder.addAction(android.R.drawable.ic_menu_close_clear_cancel, "DISMISS", dismissIntent);

              //Create Notification.
              NotificationManager notificationManager =
                      (NotificationManager)
                              getSystemService(Context.NOTIFICATION_SERVICE);

              notificationManager.notify(NOTIFICATION_ID,
                      builder.build());
              break;

      }
  }

  private void clearExistingNotifications() {
      int notificationId = getIntent().getIntExtra("notificationId", 0);
      NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
      manager.cancel(notificationId);
  }

  private void processInlineReply(Intent intent) {
      Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);

      if (remoteInput != null) {
          String reply = remoteInput.getCharSequence(
                  KEY_REPLY).toString();

          //Set the inline reply text in the TextView
          txtReplied.setText("Reply is "+reply);

          //Update the notification to show that the reply was received.
          NotificationCompat.Builder repliedNotification =
                  new NotificationCompat.Builder(this)
                          .setSmallIcon(
                                  android.R.drawable.stat_notify_chat)
                          .setContentText("Inline Reply received");

          NotificationManager notificationManager =
                  (NotificationManager)
                          getSystemService(Context.NOTIFICATION_SERVICE);
          notificationManager.notify(NOTIFICATION_ID,
                  repliedNotification.build());

      }
  }
}

在上面的代码中,按下回复键时会调用onNewIntent(这要归功于Intent标志SINGLE_TOP和CLEAR_TOP)。

方法processInlineReply()是我们获取通知中输入的文本并在TextView中进行设置的地方。
然后更新通知(使用与创建时相同的NOTIFICATION_ID)以摆脱进度指示器并显示已收到回复。

在另一个世界中,我们可以通过使用以下代码片段取消通知,而不是更新通知。

//Cancel notification
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
          manager.cancel(NOTIFICATION_ID);

//Remove this from the MainActivity.java
NotificationManager notificationManager =
                  (NotificationManager)
                          getSystemService(Context.NOTIFICATION_SERVICE);
          notificationManager.notify(NOTIFICATION_ID,
                  repliedNotification.build());

上面更新的代码将在模拟器中提供以下输出。

注意:可以通过在构建器实例上添加多个addAction(remoteInput)实例来在通知中添加多个内联回复动作。
尽管建议每个通知仅使用一个内联回复操作按钮。

带有回复历史记录的通知

我们也可以借助setRemoteInputHistory方法在Notification中显示以前的内联响应。

在此应用程序中,我们将以LinkedList的形式存储以内联回复形式返回的字符序列,如下所示。

package com.theitroad.directreplynotification;

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.RemoteInput;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

  String KEY_REPLY = "key_reply";
  String KEY_REPLY_HISTORY = "key_reply_history";
  public static final int NOTIFICATION_ID = 1;

  Button btnBasicInlineReply, btnInlineReplyHistory;
  TextView txtReplied;

  private static List<CharSequence> responseHistory = new LinkedList<>();

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);

      btnBasicInlineReply = (Button) findViewById(R.id.btn_basic_inline_reply);
      btnInlineReplyHistory = (Button) findViewById(R.id.btn_inline_replies_with_history);
      txtReplied = (TextView) findViewById(R.id.txt_inline_reply);
      btnBasicInlineReply.setOnClickListener(this);
      btnInlineReplyHistory.setOnClickListener(this);

      clearExistingNotifications();

  }

  @Override
  protected void onNewIntent(Intent intent) {
      super.onNewIntent(intent);
      processInlineReply(intent);

  }

  @Override
  public void onClick(View v) {
      switch (v.getId()) {
          case R.id.btn_basic_inline_reply:
              createInlineNotification();
              break;

          case R.id.btn_inline_replies_with_history:

              if (!responseHistory.isEmpty()) {
                  CharSequence[] history = new CharSequence[responseHistory.size()];
                  createInlineNotificationWithHistory(responseHistory.toArray(history));
              } else {
                  createInlineNotificationWithHistory(null);
              }
              break;

      }
  }

  private void clearExistingNotifications() {
      int notificationId = getIntent().getIntExtra("notificationId", 0);
      NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
      manager.cancel(notificationId);
  }

  private void processInlineReply(Intent intent) {

      Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);

      if (remoteInput != null) {
          CharSequence charSequence = remoteInput.getCharSequence(
                  KEY_REPLY);

          if (charSequence != null) {
              //Set the inline reply text in the TextView

              String reply = charSequence.toString();

              txtReplied.setText("Reply is " + reply);

              //Update the notification to show that the reply was received.
              NotificationCompat.Builder repliedNotification =
                      new NotificationCompat.Builder(this)
                              .setSmallIcon(
                                      android.R.drawable.stat_notify_chat)
                              .setContentText("Inline Reply received");

              NotificationManager notificationManager =
                      (NotificationManager)
                              getSystemService(Context.NOTIFICATION_SERVICE);
              notificationManager.notify(NOTIFICATION_ID,
                      repliedNotification.build());

              /**Uncomment the below code to cancel the notification.
               * Comment the above code too.
               * **/
          /*NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
          manager.cancel(NOTIFICATION_ID);*/

          } else {

              String reply = remoteInput.getCharSequence(KEY_REPLY_HISTORY).toString();
              responseHistory.add(0, reply);
              if (!responseHistory.isEmpty()) {
                  CharSequence[] history = new CharSequence[responseHistory.size()];
                  createInlineNotificationWithHistory(responseHistory.toArray(history));
              } else {
                  createInlineNotificationWithHistory(null);
              }

          }

      }
  }

  private void createInlineNotification() {

      //Create notification builder
      NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
              .setSmallIcon(android.R.drawable.stat_notify_chat)
              .setContentTitle("Inline Reply Notification");

      String replyLabel = "Enter your reply here";

      //Initialise RemoteInput
      RemoteInput remoteInput = new RemoteInput.Builder(KEY_REPLY)
              .setLabel(replyLabel)
              .build();

      int randomRequestCode = new Random().nextInt(54325);

      //PendingIntent that restarts the current activity instance.
      Intent resultIntent = new Intent(this, MainActivity.class);
      resultIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
      //Set a unique request code for this pending intent
      PendingIntent resultPendingIntent = PendingIntent.getActivity(this, randomRequestCode, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT);

      //Notification Action with RemoteInput instance added.
      NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(
              android.R.drawable.sym_action_chat, "REPLY", resultPendingIntent)
              .addRemoteInput(remoteInput)
              .setAllowGeneratedReplies(true)
              .build();

      //Notification.Action instance added to Notification Builder.
      builder.addAction(replyAction);

      Intent intent = new Intent(this, MainActivity.class);
      intent.putExtra("notificationId", NOTIFICATION_ID);
      intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
      PendingIntent dismissIntent = PendingIntent.getActivity(getBaseContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);

      builder.addAction(android.R.drawable.ic_menu_close_clear_cancel, "DISMISS", dismissIntent);

      //Create Notification.
      NotificationManager notificationManager =
              (NotificationManager)
                      getSystemService(Context.NOTIFICATION_SERVICE);

      notificationManager.notify(NOTIFICATION_ID,
              builder.build());

  }

  private void createInlineNotificationWithHistory(CharSequence[] history) {

      //Create notification builder
      NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
              .setSmallIcon(android.R.drawable.stat_notify_chat)
              .setContentTitle("Inline Reply Notification With History");

      String replyLabel = "Enter your reply here";

      //Initialise RemoteInput
      RemoteInput remoteInput = new RemoteInput.Builder(KEY_REPLY_HISTORY)
              .setLabel(replyLabel)
              .build();

      int randomRequestCode = new Random().nextInt(54325);

      //PendingIntent that restarts the current activity instance.
      Intent resultIntent = new Intent(this, MainActivity.class);
      resultIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
      //Set a unique request code for this pending intent
      PendingIntent resultPendingIntent = PendingIntent.getActivity(this, randomRequestCode, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT);

      //Notification Action with RemoteInput instance added.
      NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(
              android.R.drawable.sym_action_chat, "REPLY", resultPendingIntent)
              .addRemoteInput(remoteInput)
              .setAllowGeneratedReplies(true)
              .build();

      //Notification.Action instance added to Notification Builder.
      builder.addAction(replyAction);

      Intent intent = new Intent(this, MainActivity.class);
      intent.putExtra("notificationId", NOTIFICATION_ID);
      intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
      PendingIntent dismissIntent = PendingIntent.getActivity(getBaseContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);

      builder.addAction(android.R.drawable.ic_menu_close_clear_cancel, "DISMISS", dismissIntent);

      if (history != null) {
          builder.setRemoteInputHistory(history);
      }

      //Create Notification.
      NotificationManager notificationManager =
              (NotificationManager)
                      getSystemService(Context.NOTIFICATION_SERVICE);

      notificationManager.notify(NOTIFICATION_ID,
              builder.build());

  }

}

我们添加了一个新按钮来处理带历史记录的内联回复。
在上面的代码中," responseHistory"保存了回复的历史记录。
以下代码段用于设置通知中的先前内联回复。

if (history != null) {
          builder.setRemoteInputHistory(history);
      }

方法processInlineReply()的ʻelse`部分使用新的内联回复历史记录更新当前通知,如下所示:

else {

              String reply = remoteInput.getCharSequence(KEY_REPLY_HISTORY).toString();
              responseHistory.add(0, reply);
              if (!responseHistory.isEmpty()) {
                  CharSequence[] history = new CharSequence[responseHistory.size()];
                  createInlineNotificationWithHistory(responseHistory.toArray(history));
              } else {
                  createInlineNotificationWithHistory(null);
              }

          }

responseHistory.add(0,reply);在回复历史记录中添加最新的内联回复。

注意:以上概念仅用于演示目的。
您可以根据需要进行调整。

内联回复不适用于Android N之前的版本。
因此,要使应用程序在其上起作用,可以在活动中添加基本EditText。