Lesson 190. Notifications. channels

Lesson 190. Notifications. channels


Android Oreo (API 26) has the ability to create message channels. In this lesson we will understand how to do it and why it is necessary.

For each application, the user can customize the message. To do this, go to the system settings, select Apps there, find in the list and open the desired program and select the Notifications section.

By default they look like this:

The settings are a little bit, and they will touch all the messages from this app.

Channels allow you to expand these settings and apply them selectively. The application developer creates a channel and specifies its channel ID when creating messages. The user in the system settings of the program sees this channel and can configure it: importance, sound, vibration and more. As a result, all messages belonging to this channel will be displayed with these settings.

That is, by creating a channel, the developer allows the user to customize the behavior of a particular group of messages.

Let’s create a channel:

NotificationManager notificationManager =
       (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
   NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "My channel",
           NotificationManager.IMPORTANCE_HIGH);
   channel.setDescription("My channel description");
   channel.enableLights(true);
   channel.setLightColor(Color.RED);
   channel.enableVibration(false);
   notificationManager.createNotificationChannel(channel);
}

Channels are only valid for Android Oreo and above, so we use Android version verification. Next, I will not include this check in the examples so as not to clutter the code.

In the NotificationChannel constructor we specify the ID, name and importance. Here are some other details and settings. By the name of the methods, everything is clear.

We create a channel using the createNotificationChannel method.

Now, the Notifications settings for your application look like this:

Two channels have appeared: default and our own My channel. Default settings will be used for messages for which no channel was specified.

Open My channel settings:

Note that Vibrate is off. We explicitly stated this when creating a feed using the enableVibration (false) method.

You can now specify a channel ID when creating messages, and messages will be displayed according to the settings of that channel.

NotificationCompat.Builder builder =
       new NotificationCompat.Builder(this, CHANNEL_ID)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("Title")
               .setContentText("Notification text");

The channel ID is specified in the message builder constructor. And now this constructor will NOT be marked as Deprecated if you are using the appCompat library version 26 and above.

At what point to create a channel? You can start the program. Even if the channel has already been created, nothing will happen. But judging by the fact that the user cannot delete the channels, I think we can use any flag thread that we set to true after the first creation of the channels, and in the future it will tell us that the channels have already been created.

Group

Consider the example of a mail client. Suppose that it works not only with mail but also with a calendar. That is, it can send us messages of two types: letters and events.

Accordingly, we can create two channels, one for emails and the other for events. As a result, the user will be able to set up a separate message about the emails and separately about the events. It is convenient.

But our app does support multiple accounts. And for each account, we need to create two message channels.

When creating 4 channels, the settings will look like this:

You can improve this by using groups. Grouping is simply a way to visually split channels in settings.

Create a group like this:

NotificationManager notificationManager =
       (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.createNotificationChannelGroup(
       new NotificationChannelGroup(GROUP_ID, "Group 1"));

In the constructor we specify an ID and a name.

Next, when creating a channel, we use the setGroup method to specify which group the channel will belong to.

NotificationChannel channel = new NotificationChannel(...);
// ...
channel.setGroup(GROUP_ID);
notificationManager.createNotificationChannel(channel);

So for each account of our application, we can create a group and specify it when creating message channels for that account:

User A (group)
Mail (channel)
Events (channel)
User B (group)
Mail (channel)
Events (channel)

The settings now look better:

Channels are grouped by account.

Getting channel information

At any time after you create a channel, you can get information about it.

NotificationChannel channel = notificationManager.getNotificationChannel(CHANNEL_ID);

The getNotificationChannel method will return a NotificationChannel or null object if a channel with the specified ID was not found. Using different channel get-methods, you will be able to find out how the user has set up your channel. But you won’t be able to reconfigure it, set methods just won’t work.

The only thing you can change is the name of the channel and its description. To do this, simply rebuild the channel with the new parameters and the same ID.

If you considered the channel settings and for some reason decided that the user was wrong, then you can ask him or her to change the settings.

For example, if a user has turned off message display for a channel, we open the settings for that channel.

NotificationChannel channel = notificationManager.getNotificationChannel(CHANNEL_ID);

if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
   Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
   intent.putExtra(Settings.EXTRA_CHANNEL_ID, channel.getId());
   intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
   startActivity(intent);
}

If getImportance is IMPORTANCE_NONE, it means that the channel has been disabled by the user. We create an Intent with the channel ID and package application and run Activity.

Of course, in a real application, you need to act not so clumsily, but first ask the user’s opinion and explain why you want him to change the channel settings.

deleting a channel

To delete a channel, use the deleteNotificationChannel method

notificationManager.deleteNotificationChannel(CHANNEL_ID);

Technically, of course, you can use delete and then create a feed to restore your settings. But it is not recommended to do so. In addition, in the settings at the bottom, the user will see how many channels have been deleted.

And he will understand that you just rebuild the channel and reset its settings.

Importance vs Priority

If you remember, when creating a message, we can give priority to the builder. Starting with Android Oreo, message priorities have been declared outdated and overridden by channel parameter – importance.




Discuss in the forum [1 reply]

Lesson 189. Notifications. message grouping

Lesson 189. Notifications. message grouping


Android 7 (API 24) has the ability to group messages. Even if you don’t explicitly implement it, the system will create the group itself if you have 4 or more messages.

It looks like this:

You can expand / collapse / delete a group. You can also tap or delete a separate message. If the number of messages in a group is less than 4, then the messages become separate from the group.

All your messages will fall into one unnamed group. By default, clicking on this group will open the application.

Let’s look at what group management options we have been given.

We can:

1) create multiple groups and decide for yourself which one the new message will go to. A group will be displayed when there are at least two messages in it.

2) add text description to the group

3) Add to the group PendingIntent, which will work after clicking on the group

An example is the mail application. The user has several accounts and new emails are coming in. It would be wise to create a separate group for each account. Each such group will only display emails that are in a specific account.

Let’s consider in practice. Create a message:

NotificationCompat.Builder mBuilder =
       new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("Sender " + 1)
               .setContentText("Subject text " + 1)
               .setGroup(GROUP_KEY);

Notification notification = mBuilder.build();

NotificationManager notificationManager =
       (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(1, notification);

The message is created as usual. Only setGroup method added. In it, we need to specify a String group key to let the system know which group to post validity messages to.

Create a group:

NotificationCompat.Builder mBuilder =
       new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentInfo("user_mail.com")
               .setGroup(GROUP_KEY)
               .setGroupSummary(true);

Notification notification = mBuilder.build();

NotificationManager notificationManager =
       (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(-100, notification);

The group is created just like the message. But the texts we usually pass to setContentTitle and setContentTitle are not displayed in the group, so I don’t use them in the example. However, in later versions of Android, the display of groups may change, and they will suddenly start to display titile and text. It can and does make sense to transfer anything there.

But the text from the setContentInfo method will be displayed and this allows us to provide some text description for the group so that the user understands what it is about. Since we are considering an example mail program, you can use the username of the user account. The user will see in which group the content of which account is displayed.

In setGroup we pass the String group key. We use it to create messages that should fall into this group.

In setGroupSummary we specify true. This means that the message is a group.

In the notify method, just like with a regular message, you must specify an id. Be careful here to make sure that the messages and groups do not match the id. The band is still a regular message. And if the id matches, one message will replace the other.

Alternatively, emails for new emails can be retrieved and group ids for account id. Then they don’t exactly intersect.

As a result, you get the following picture

Three new letters came together in one group. And the description of the group shows that it reflects the letters of the account This e-mail address is being protected from spambots. You need JavaScript enabled to view it..

When to create a group

GroupsSense to display every time you create a message that belongs to this group. If a group already exists and is displayed, then nothing will happen (unless you have changed the group settings).

And if we only display the group once and the user at some point removes it from the messages, then all our new messages will be left out of the group.

Sort in group

You can sort messages in a group using the setSortKey method in the builder of each message. Pass a string value to it, and sort it.




Discuss in the forum [1 reply]

Lesson 188. Notifications. custom messages

Lesson 188. Notifications. custom messages


Android enables us to create a layout for messages ourselves.

Consider a simple example:

layout / notification.xml




   


Height 64dp – standard message height.

We will only show TextView. It is recommended to use @ style / TextAppearance.Compat.Notification. * Styles for your text to be displayed correctly on any version of Android.

The message binder code looks like this:

RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.notification);
remoteViews.setTextViewText(R.id.textView, "Custom notification text");
remoteViews.setOnClickPendingIntent(R.id.root, rootPendingIntent);

NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
       .setSmallIcon(R.mipmap.ic_launcher)
       .setContent(remoteViews);

We create RemoteViews from the layout file.

With the setTextViewText method, we place the text in View c id = R.id.textView.

And with the setOnClickPendingIntent method, we specify PendingIntent, which will be called when you click on View with id = R.id.root. In our example, root is the LinearLayout root. Accordingly, when you click on the message, this PendingIntent will be used to start Activity / Service / BroadcastReceiver.

The builder still needs to specify an icon that will be visible in the notification area. But the setContentTitle and setContentText methods are not required. Instead, we use setContent and pass RemoteViews created there.

As a result, we will see your message

For comparison purposes, a standard message is displayed.

There is another, newer, way to create custom messages – using the DecoratedCustomViewStyle style.

RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.notification);
remoteViews.setTextViewText(R.id.textView, "Custom notification text");
remoteViews.setOnClickPendingIntent(R.id.root, rootPendingIntent);

NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
       .setSmallIcon(R.mipmap.ic_launcher)
       .setCustomContentView(remoteViews)
       .setStyle(new NotificationCompat.DecoratedCustomViewStyle());

The difference with the old method is that we call the setCustomContentView method, not setContent, and use the DecoratedCustomViewStyle style.

result:

Note that in this case, customize not all the message, but its contents. And the rest of the message, such as the icon, time or action buttons, will remain in place.

Using DecoratedCustomViewStyle enables us to customize and extend the message.

example

RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.notification);
remoteViews.setTextViewText(R.id.textView, "Custom notification text");
remoteViews.setOnClickPendingIntent(R.id.root, rootPendingIntent);

RemoteViews remoteViewsExtended = new RemoteViews(getPackageName(), R.layout.extended_notification);
remoteViewsExtended.setTextViewText(R.id.textView, "Extended custom notification text");
remoteViewsExtended.setOnClickPendingIntent(R.id.root, rootPendingIntent);

NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
       .setSmallIcon(R.mipmap.ic_launcher)
       .setCustomContentView(remoteViews)
       .setCustomBigContentView(remoteViewsExtended)
       .setStyle(new NotificationCompat.DecoratedCustomViewStyle());

Here, we customize both the normal message type (setCustomContentView) and the extended message (setCustomBigContentView).

result:

The height of the extended message layout should be no more than 256dp.




Discuss in the forum [0 replies]

Lesson 187. Notifications. Action buttons. Reply.

Lesson 187. Notifications. Action buttons. Reply.


Android 4.1 has the ability to add buttons to messages.

This is done using the addAction method.

Intent deleteIntent = new Intent(this, MyService.class);
deleteIntent.setAction("ru.startandroid.notifications.action_delete");
PendingIntent deletePendingIntent = PendingIntent.getService(this, 0, deleteIntent, 0);

NotificationCompat.Builder builder =
       new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("Title")
               .setContentText("Notification text")
               .addAction(android.R.drawable.ic_delete, "Delete", deletePendingIntent);

We first create a PendingIntent, which will be called after a button is clicked. Then we pass it to the addAction method, and with it the icon and text for the button.

A button will be displayed when the message is opened.

Pressing the button will not close the message itself. If you need to close it, use cancel in the click handler.

You can add up to three Action buttons. Buttons should not duplicate the action that occurs after clicking on a message.

For the latest versions of Android, for some reason the button icon, just the text, is not displayed.

Reply

Starting with API 24, it was possible to add an input line to the message. This can be handy, for example, in chat applications. The user will be able to reply directly to the message.

Consider an example implementation:

// id
int itemId = ...;

// Intent
Intent intent = new Intent(this, MyService.class);
intent.setAction(ACTION_REPLY);
intent.putExtra(EXTRA_ITEM_ID, itemId);

// PendingIntent
PendingIntent replyPendingIntent =
       PendingIntent.getService(getApplicationContext(),
               itemId, intent, PendingIntent.FLAG_UPDATE_CURRENT);

// RemoteInput
RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_TEXT_REPLY)
       .setLabel("Type message")
       .build();

// Action
NotificationCompat.Action action =
       new NotificationCompat.Action.Builder(android.R.drawable.ic_menu_send,
               "Reply", replyPendingIntent)
               .addRemoteInput(remoteInput)
               .build();

// Notification builder
NotificationCompat.Builder builder =
       new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("Title")
               .setContentText("Notification text")
               .addAction(action);

// Notification
Notification notification = builder.build();

// Show notification
NotificationManager notificationManager =
       (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(itemId, notification);

Let’s parse the code in order.

We have some itemId. This may be, for example, the chat id that received the new message.

We create Intent and PendingIntent. Nothing new here. We will call MyService and pass it an itemId. In PendingIntent we use itemId as requestCode.

Next we create RemoteInput. Here we configure everything regarding the input field that will be displayed in the message. In the Bilder constructor it is necessary to specify the key, which we will use in the future, in order to get the text entered by the user from Bundle. You can pass text to the setLabel method, which will be used as a hint in the input field.

Create an Action button using the Bilder. We pass the standard set there: icon, text and PendingIntent. And in the addRemoteInput method we pass the previously created RemoteInput. This will be the Action button of the Reply button, after which a line of input will appear.

We then use the Action created in the message builder, create the message and display it.

We use itemId in the notify method. Accordingly, knowing the chat id, we can always update or delete the message.

Note that the PendingIntent that we create and use in the Reply button will not be used after clicking on the message, or even after clicking on Reply. It will be used when the user clicks on the submit text button.

In this example, by the way, after clicking on the message, nothing will happen, because in the message builder I did not use the setContentIntent method, not to complicate the example.

run

The message creates an Action button Reply. It opens the input bar.

When you click on the submit button, the system launches MyService, which we specified in PedningIntent, and displays a progress bar. But it will rotate indefinitely until the software is updated or the message is deleted.

Let’s see how in MyService we can get user input and remove progress bar from a message:

if (ACTION_REPLY.equals(intent.getAction())) {

   // Get reply text
   CharSequence replyText = null;
   Bundle results = RemoteInput.getResultsFromIntent(intent);
   if (results != null) {
       replyText = results.getCharSequence(EXTRA_TEXT_REPLY);
   }

   // Get itemId
   int itemId = intent.getIntExtra(EXTRA_ITEM_ID, 0);

   // Perform operations with replyText and itemId
   ...

   // Create new notification
   Notification repliedNotification =
           new NotificationCompat.Builder(getBaseContext())
                   .setSmallIcon(R.mipmap.ic_launcher)
                   .setContentText("Replied")
                   .build();

   // Update notification
   NotificationManager mNotificationManager =
           (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
   mNotificationManager.notify(itemId, repliedNotification);

}

With RemoteInput.getResultsFromIntent, we get Bundle from Intent. From this Bundle we can retrieve the text entered by the user in the message. To do this, we use the EXTRA_TEXT_REPLY key (previously used in the RemoteInput builder).

Then we get itemId from Intent.

We now have a chat id and text entered by the user. Can we save it to the database, send it to the server or do something else. It depends on the logic of the application.

Next, we need to understand the message. I will remind that after sending the text, it reflects the progress bar. In this example, we create a simple message with the text Replied and replace it (using the same itemId in the notify method) with the message from which the text was sent.

We try to send the text from the message again

This time we have updated the message in the handler and the progress bar is gone.

What you will do with the message after sending the text is your decision. For example, you can just delete it. Or, if you are displaying the latest chat messages in a message, you can update the expiry message to reflect the new message and re-create the Reply button there.




Discuss in the forum [0 replies]

Lesson 186. Notifications. advanced messages

Lesson 186. Notifications. advanced messages


Android 4.1 (API 16) has advanced messages. Pulling down the message will display additional information.

To create an advanced message, you must add a style to the builder. There are several styles. They are all heirs to the abstract class NotificationCompat.Style. It is usually clear from the style name what it can be used for.

Consider, for example, the BigTextStyle style to reflect long text.

Next I will give only the code of the builder. And how to get and display messages from the builder, you can see in the previous lessons.

code:

String longText = "To have a notification appear in an expanded view, " +
       "first create a NotificationCompat.Builder object " +
       "with the normal view options you want. " +
       "Next, call Builder.setStyle() with an " +
       "expanded layout object as its argument.";

NotificationCompat.Builder builder =
       new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("Title")
               .setContentText("Notification text")
               .setStyle(new NotificationCompat.BigTextStyle().bigText(longText));

We call the setStyle method in the message builder, in which we need to pass the style. We create a BigTextStyle style and pass it a long text to the bigText method.

The message will now display a long text message.

The BigPictureStyle style will help to show the big picture:

BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.startandroid, options);

NotificationCompat.Builder builder =
       new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("Title")
               .setContentText("Notification text")
               .setStyle(new NotificationCompat.BigPictureStyle().bigPicture(bitmap));

We create a bitmap and pass it to BigPictureStyle.

When opened, the message will display a picture

InboxStyle Style – Lists up to 5 of your rows

NotificationCompat.Builder builder =
       new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("Title")
               .setContentText("Notification text")
               .setStyle(new NotificationCompat.InboxStyle()
                       .addLine("Line 1")
                       .addLine("Line 2")
                       .addLine("Line 3"));

The addLine method adds rows

result:

MessagingStyle style is convenient for displaying recent chat messages:

NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle("You");
messagingStyle.setConversationTitle("Android chat")
       .addMessage("Всем привет!", System.currentTimeMillis(), "Ivan")
       .addMessage("Кто перешел на новую студию, как оно?", System.currentTimeMillis(), "Ivan")
       .addMessage("Я пока не переходил, жду отзывов", System.currentTimeMillis(), "Andrey")
       .addMessage("Я перешел", System.currentTimeMillis(), null)
       .addMessage("Было несколько проблем, но все решаемо", System.currentTimeMillis(), null)
       .addMessage("Ок, спасибо!", System.currentTimeMillis(), "Ivan");

NotificationCompat.Builder builder =
       new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("Title")
               .setContentText("Notification text")
               .setStyle(messagingStyle);

In the MessagingStyle constructor, you need to specify under which name the user will see your messages in this. Usually, in chats, you use the word You (or Me).

IN setConversationTitle the name of the chat is specified. This is usually used when chatting with more than two people.

Next, the method addMessage messages are added. The message consists of three fields: text, time, sender. If the sender is null, then this is considered a user message, and the name we specified in the MessagingStyle constructor, ie You, will be used.

You can add as many messages as you want, but once the number of messages is greater than MessagingStyle.MAXIMUM_RETAINED_MESSAGES, old messages will start to be deleted. It is convenient and allows not to bother checking for quantity.

Messages will be displayed in the same order that you added them.

It will look like this:

Message time is not displayed. As far as I understand, superficially looking the weekend, it is not used anywhere at all. Maybe in future releases this will change.

The addMessage method also works with the Message object. This object contains fields: text, time and author. But there is a setData method for specifying MIME data, such as pictures.

Some styles have a couple of common methods that may be useful: setBigContentTitle and setSummaryText.

Consider them in the InboxStyle example:

NotificationCompat.Builder builder =
       new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("Title")
               .setContentText("Notification text")
               .setStyle(new NotificationCompat.InboxStyle()
                       .addLine("Line 1")
                       .addLine("Line 2")
                       .addLine("Line 3")
                       .setBigContentTitle("Extended title")
                       .setSummaryText("+5 more"));

The code is almost the same as what we looked at earlier. Two methods are added:

setBigContentTitle – allows you to specify the message header that will be displayed when the message is opened

setSummaryText – The text will be displayed at the bottom of the extended message

result:

When the message is opened, the title changes and the summary text is displayed at the bottom.




Discuss in the forum [1 reply]

Lesson 185. Notifications. Activity Opening Modes

Lesson 185. Notifications. Activity Opening Modes


In the last lesson, we already looked at how to display Activity after clicking on a message. But there was a simple case. In this lesson, let’s look at more interesting cases.

To understand everything that goes on, you will need knowledge of Activity Tasks Stack. You can read about this in the documentation or in Lesson 116.

For example, let’s take a mail application consisting of three screens: a list of letters (MainActivity), the contents of a letter (DetailsActivity) and information about the new version (WhatsNewActivity). When you start MainActivity, it displays the letters, and when you click on the letter, DetailsActivity opens with the contents of the letter.

The application has some service that is connected to the server. When a new letter appears on the server, the service downloads it and shows the message to the user. The user clicks on it and opens DetailsActivity to view the email.

Similarly, the service receives information about a new version of the application and creates messages for the event. Clicking on the message will open WhatsNewActivity, which will detail the new features of the application.

But there are different ways to open two of these Activities.

We will open DetailsActivity in the same way as if it had been opened from MainActivity. That is, we will open two Activities (one after the other): MainActivity and DetailsActivity. By clicking Back in the started DetailsActivity, the user will be taken to MainActivity.

The WhatsNewActivity screen is different. It is only intended to be opened from the message. You can’t open it from the app. That is, there is simply no such list or button in the app that would open WhatsNewActivity. Because it is not particularly important information, and if you suddenly wanted to see it, you can go to the site of the program.

Let’s look at how these two options are implemented

DetailsActivity as part of the program

Let’s first configure DetailsActivity in the manifest:


   

The parentActivityName attribute and the meta-data tag do exactly the same thing here – they report that DetailsActivity opens with MainActivity. That is, MainActivity is the parent of DetailsActivity. The only difference is that meta-data works for Android 4.0.3 and below and parentActivityName for Android 4.1 and above. That is, we specify the parentActivity parameter in two different ways to ensure compatibility. If your application no longer supports Android below 4.1, you may not specify meta-data.

Next we create a message. All the same as we covered in the previous lesson. Only the PendingIntent creation will be different.

// Create PendingIntent
Intent resultIntent = new Intent(this, DetailsActivity.class);
resultIntent.putExtra(EXTRA_ITEM_ID, itemId);

TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(DetailsActivity.class);
stackBuilder.addNextIntent(resultIntent);

PendingIntent resultPendingIntent =
       stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);

EXTRA_ITEM_ID is your string constant

Consider the steps that are happening here.

Let’s create a regular Intent to open DetailsActivity. We pass the id there so Activity knows what information it needs to display. Everything is clear and usual here.

We then create a TaskStackBuilder, a tool that will help us generate a sequence of Activity calls. We need to first start the Parent Activity for DetailsActivity (ie MainActivity) and then DetailsActivity.

We call the addParentStack method and specify DetailsActivity in it, that is, we ask in the call stack to add Activity, which is the parent for DetailsActivity. The TaskStackBuilder goes to the manifest and sees that MainActivity is spelled as DetailsActivity as the parent (parentActivityName). TaskStackBuilder adds MainActivity to the call stack.

At addNextIntent, we just pass Intent to run DetailsActivity. TaskStackBuilder will add it to your call stack.

As a result, TaskStackBuilder contains two Activities in the call stack: first MainActivity and then DetailsActivity.

With the getPendingIntent method, it forms a PendingIntent that we can pass to the message builder. And after clicking on the message, the Activities that were in the call stack generated in TaskStackBuilder will be opened.

Run, click on the message

And two Activity opens. Clicking Back in DetailsActivity we get to MainActivity.

Everything works, but one little thing remains. The message does not close after clicking on it. You can use setAutoCancel, as we did in the last lesson, but this is not quite right in this case.

Let’s say you get a message about a new email. But you did not open the expiry message, but decided to go to the application immediately, they already saw a new letter, opened it, read it and closed the application. The letter is now read by you and the message is still hanging, although it is no longer relevant.

It will be more correct to delete the message when opening the corresponding message. Consider how this can be implemented.

Message creation code:

long itemId = 12345678910L;
int notificationId = ((Long)itemId).intValue();

// Create PendingIntent
Intent resultIntent = new Intent(this, DetailsActivity.class);
resultIntent.putExtra(EXTRA_ITEM_ID, itemId);

TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(DetailsActivity.class);
stackBuilder.addNextIntent(resultIntent);

PendingIntent resultPendingIntent =
       stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);

// Create Notification
NotificationCompat.Builder builder =
       new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("Title")
               .setContentText("Notification text")
               .setContentIntent(resultPendingIntent);

Notification notification = builder.build();

// Show Notification
NotificationManager notificationManager =
       (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(notificationId, notification);

The whole code is already familiar to us. I will go through only part id.

You have id letters (itemId), let it be long. Using itemId, you can create an id for the notification (notificationId) and then specify it in the notify method.

Now, to delete a message, you need to use the same notificationId in the cancel method.

We implement this in DetailsActivity:

long itemId = getIntent().getLongExtra(EXTRA_ITEM_ID, 0);

int notificationId = ((Long)itemId).intValue();

NotificationManager notificationManager =
       (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.cancel(notificationId);

We get itemId from Intent. With itemId we again form the notificationId and use it in the cancel method.

As a result, when you open a letter in DetailsActivity, the message corresponding to that letter will be deleted. And no matter, you came to this screen by yourself or by tapping on a message.

It is important that the notificationId used in and deleting the message coincide. In our example, we compute it with itemId simply by taking int part from long. You may have another way of obtaining or generating this id.

WhatsNewActivity, separate from the application

Consider another option. I would remind that it involves opening the Activity separately from the program.

To do this, Activity must be set up as follows in the manifest:


launchMode is set to singleTask for Activity to search or create its own task. we make taskAffinity empty so that Activity does not disappear into the main application tray. excludeFromRecents is required so that the activity’s activity does not appear in the recent list of applications.

Message creation is done according to the usual scheme, only Intent needs to add a couple of flags for Activity to start in a new empty TASK

Intent resultIntent = new Intent(this, WhatsNewActivity.class);
resultIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
       Intent.FLAG_ACTIVITY_CLEAR_TASK);

As a result, clicking on the message will open Activity in a separate from your application TASK.

And it will not be visible in the list of recently launched applications




Discuss in the forum [3 replies]

Lesson 184. Android Notifications. Message. foundations

Lesson 184. Android Notifications. Message. foundations


Notifications are messages that the user sees at the top of the screen when he or she receives a new email, message, update, etc. In the next few lessons, we will take a closer look at what opportunities developers have for displaying messages.

In this tutorial, we will cover the basics of displaying / updating / deleting a message and handling it.

display

The simple message creation code looks like this:

NotificationCompat.Builder builder =
       new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("Title")
               .setContentText("Notification text");

Notification notification = builder.build();

NotificationManager notificationManager =
       (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(1, notification);

We use a builder that specifies an icon, title and text for the message. With the build method, we get a ready message.

Next, we use NotificationManager and its notify method to show the generated message. In addition to notification, you must pass an id. This is so that we can later use this id to update or delete the message.

The constructor of new NotificationCompat.Builder (Context) will be marked as Deprecated if you use the appCompat library of version 26 and above. It happened because a new constructor appeared in Android API 26 and it is recommended to use it. Don’t pay attention to it yet. In one of these lessons, we will look at using the right constructor.

When we run this code, we will see a message

It displays the icon and the two texts that we specified in the builder. Clicking on it will not lead to anything because we did not implement the click handler. We’ll do it later.

renewal

We have displayed the message and now want to recover it. To do this, simply show the message again using the notify method and use the same id.

It will look like this:

NotificationCompat.Builder builder =
       new NotificationCompat.Builder(this)
               .setSmallIcon(android.R.drawable.ic_dialog_email)
               .setContentTitle("Title change")
               .setContentText("Notification text change");

Notification notification = builder.build();

NotificationManager notificationManager =
       (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(1, notification);

The code is exactly the same as the code we used to display the message. Only in the builder we use other texts and an icon. The most important thing is that in the notify method we again use id = 1. NotificationManager from this id will find the message we displayed a little earlier and replace it with a new one.

several messages

To display a new message rather than update an existing one, you must use a different id in the notify method.

the first message

NotificationCompat.Builder builder =
       new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("Title")
               .setContentText("Notification text");

Notification notification = builder.build();

NotificationManager notificationManager =
       (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(1, notification);

the second message

NotificationCompat.Builder builder =
       new NotificationCompat.Builder(this)
               .setSmallIcon(android.R.drawable.ic_dialog_email)
               .setContentTitle("Title 2")
               .setContentText("Notification text 2");

Notification notification = builder.build();

NotificationManager notificationManager =
       (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(2, notification);

We used different ids in the notify method and received two different messages

removal

To delete a message, we use NotificationManager and its cancel method, specifying the message id.

NotificationManager notificationManager =
       (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.cancel(1);

Or cancelAll can delete all messages at once

NotificationManager notificationManager =
       (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.cancelAll();

When you delete a message, you do not need to check whether it is displayed or not. If the message is gone for some reason, then nothing will happen.

Click processing

You must use PendingIntent to perform any action after clicking on the message. PendingIntent is an Intent container. This container can be used to further run the Intent attached to it.

We will create an Intent to run, for example, Activity, pack that Intent into PendingIntent, and pass PeningIntent to a message. When you click on a message, the system will retrieve PedningIntent from it and use its nested Intent to launch the Activity.

Let’s see how this looks in practice:

Intent resultIntent = new Intent(this, MainActivity.class);
PendingIntent resultPendingIntent = PendingIntent.getActivity(this, 0, resultIntent,
               PendingIntent.FLAG_UPDATE_CURRENT);

We create Intent to run Activity and pack it into PedningIntent.

You can read more about PedningIntent and its parameters in Lesson 119. There, I examined in detail the different cases on the BroadcastReceiver message and call examples.

The created PendingIntent we will need to send to the builder of the message. The full message creation code will look like this:

// Create PendingIntent
Intent resultIntent = new Intent(this, MainActivity.class);
PendingIntent resultPendingIntent = PendingIntent.getActivity(this, 0, resultIntent,
               PendingIntent.FLAG_UPDATE_CURRENT);

// Create Notification
NotificationCompat.Builder builder =
       new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("Title")
               .setContentText("Notification text")
               .setContentIntent(resultPendingIntent);

Notification notification = builder.build();

// Show Notification
NotificationManager notificationManager =
       (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(1, notification);

We pass PendingIntent to the message builder setContentIntent method.

Clicking on the message will open MainActivity

Note that the message is not automatically deleted after clicking on it. To fix this, you can use the autoCancel parameter in the message builder

setAutoCancel(true)

The message created with this flag will be closed after clicking on it.

Message Builder has a few more methods that can be helpful.

setNumber – Adds a number to a message

setContentInfo – Adds text to the right

In the old versions it looks like this

In recent versions, it has moved to the top of the message

setColor – will add a background color to the icon

setWhen – you can specify your own time for the message (when). By default when = message creation time

setShowWhen – Show whether time is in the message

setUsesChronometer – Instead of a static time, a counter (00:00) will be displayed in the message, indicating how much time has elapsed since. It may be useful for a stopwatch or a call.

setOngoing – such message cannot be closed or removed by the user. It will be displayed on top of regular messages.

setVibrate, setSound, setLights – device vibration, sound and LED settings

setPriority – Ability to set priority. Available values ​​are -2 (NotificationCompat.PRIORITY_MIN) to 2 (NotificationCompat.PRIORITY_MAX). The behavior may differ across Android versions, but the overall meaning is the same – the higher the priority, the higher the likelihood that the user will see your message.

setTimeoutAfter – the ability to set a timeout (in ms) after which the message itself will be deleted. Added to API 26.

setLargeIcon – the ability to set your picture as a message icon.

BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.startandroid, options);

NotificationCompat.Builder builder =
        new NotificationCompat.Builder(this)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle("Title")
                .setContentText("Notification text")
                .setLargeIcon(bitmap);

The icon from setSmallIcon will be visible in the status bar when the message bar is not opened.

The message itself will look like this:

setProgress – the ability to display the progress bar in the message

The method has three parameters:

max – the maximum value of the progress bar. Enter 0 if you want to hide the progress bar.

progress – the current value of progressbar. May be from 0 to max.

indeterminate – if true, an “infinite” progress bar will be displayed

Consider an example:

int max = 100;

// show notification with indeterminate progressbar
builder = new NotificationCompat.Builder(this)
        .setSmallIcon(R.mipmap.ic_launcher)
        .setContentTitle("Some operation")
        .setContentText("Preparing")
        .setProgress(max,0, true);

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


new Thread(new Runnable() {
    @Override
    public void run() {

        try {TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        int progress = 0;

        while (progress < max) {

            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            progress += 10;

            // show notification with current progress
            builder.setProgress(max, progress, false)
                    .setContentText(progress + " of " + max);
            notificationManager.notify(1, builder.build());

        }

        // show notification without progressbar
        builder.setProgress(0, 10, false)
                .setContentText("Completed");
        notificationManager.notify(1, builder.build());
    }
}).start();

First, we display the infinite progress bar and the Preparing text. That is, we pretend to be preparing for the operation.

Then, in a separate thread, we simulate the operation. Every 300 ms we increase the value of progress and update the message so that the progress bar shows the current progress. And also in the text we show the values ​​of progress and maximum.

After performing the operation, we hide the progress bar and display Completed text.

Again, I highly recommend reading and understanding Lesson 119. In it, I explore in detail why the PendingIntent of the last message replaces the PendingIntent of the previous messages, and how this can be avoided using, for example, requestCode.




Discuss in the forum [3 replies]

Lesson 183. ConstraintSet. ConstraintLayout software setup

Lesson 183. ConstraintSet. ConstraintLayout software setup


With ConstraintSet you can programmatically create bindings, chains, barriers and customize them.

For a complete list of operations, see the official documentation. I’ll look at some of them.

For examples I will use the following code:

import android.os.Bundle;
import android.support.constraint.ConstraintLayout;
import android.support.constraint.ConstraintSet;
import android.support.transition.TransitionManager;
import android.support.v7.app.AppCompatActivity;

import butterknife.BindDimen;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class MainActivity extends AppCompatActivity {

   @BindView(R.id.container)
   ConstraintLayout constraintLayout;

   @BindDimen(R.dimen.some_margin)
   int someMargin;

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

   @OnClick(R.id.button)
   void onButtonClick() {
       ConstraintSet set = new ConstraintSet();

       // copy constraints settings from current ConstraintLayout to set
       set.clone(constraintLayout);

       // change constraints settings
       changeConstraints(set);

       // enable animation
       TransitionManager.beginDelayedTransition(constraintLayout);

       // apply constraints settings from set to current ConstraintLayout
       set.applyTo(constraintLayout);
   }

   private void changeConstraints(ConstraintSet set) {

   }

}

The code uses a little bit of the ButterKnife library. If you haven’t heard of it, I recommend reading the review. But overall, it’s pretty clear what she’s doing.

The onButtonClick method will be called when the button is clicked. We create ConstraintSet in it and use the clone method to read the settings of the current ConstraintLayout, which is now displayed on the screen. In the changeConstraints method, we will change the settings, and then use the applyTo method to apply these settings to the current ConstraintLayout.

That is, they considered the settings, changed something, and recorded it back. The changes we make will be immediately displayed on the screen. And TransitionManager will add animation to these changes.

Some_margin variable was created in values.xml


   32dp

ButterKnife puts its value in someMargin variable. Sometimes I will use this value for indentation.

Creating a bind (connect)

Consider the following screen

TextView3 is tied to TextView1.

Let’s programmatically detach TextView3 from TextView1 and bind to TextView2.

private void changeConstraints(ConstraintSet set) {
   set.clear(R.id.textView3, ConstraintSet.LEFT);
   set.clear(R.id.textView3, ConstraintSet.TOP);

   set.connect(R.id.textView3, ConstraintSet.LEFT, R.id.textView2, ConstraintSet.LEFT);
   set.connect(R.id.textView3, ConstraintSet.BOTTOM, R.id.textView2, ConstraintSet.TOP, someMargin);
}

The clear method removes the left and top bindings for TextView3. The connect method first binds the left boundary of TextView3 to the left boundary of TextView2 and then the lower boundary of TextView3 to the upper boundary of TextView2. For vertical binding, we specify the indentation immediately.

run

TextView3 got rid of TextView1 and tied to TextView2.

In principle, the clear method for the left TextView3 bind could be called because we would then create a new left bind and the old bind (to TextView1) would go it alone.

In the connect method, we specified the id View they worked with. You can use the ConstraintSet constant instead of id to create a parent or mother attachment.PARENT_ID.

set.connect(R.id.textView3, ConstraintSet.LEFT, ConstraintSet.PARENT_ID, ConstraintSet.LEFT, 0);
set.connect(R.id.textView3, ConstraintSet.RIGHT, ConstraintSet.PARENT_ID, ConstraintSet.RIGHT, 0);

Width adjustment (constrainWidth)

Consider the following screen

Set the Button2 button to full screen width. To do this, you must create a two-sided horizontal binding to the edges of the parent and set match_constraint width.

The Button2 button on the left is already tied to the parent. Therefore, only the right binding will need to be created.

set.setMargin(R.id.button2, ConstraintSet.START, 0);
set.connect(R.id.button2, ConstraintSet.RIGHT, ConstraintSet.PARENT_ID, ConstraintSet.RIGHT, 0);
set.constrainWidth(R.id.button2, ConstraintSet.MATCH_CONSTRAINT);

The setMargin method Zeroes the indentation on the left, the connect method creates the right binding to the father or mother, and the constrainWidth method sets the width of match_constraint.

run

Similarly, there is a method for height adjustment – constrainHeight.

bias

bias is the same scroll at the top of Properties that allows you to specify which side will be closer to View when double-sided.

We use the same example from Button2. Let’s create the right binding to the father or mother to get a two-sided horizontal binding and set the value bias = 75%.

set.setMargin(R.id.button2, ConstraintSet.START, 0);
set.connect(R.id.button2, ConstraintSet.RIGHT, ConstraintSet.PARENT_ID, ConstraintSet.RIGHT, 0);
set.setHorizontalBias(R.id.button2, 0.7f);

run

The changes on the screen appear small, but previously the button had a fixed indentation from the left edge, and now it is located three-quarters of the width of the screen from the left edge. We changed the absolute indentation to relative.

ConstraintSet contains centerHorizontally and centerVertically methods. They allow you to immediately create two-sided bindings and specify bias values. They are designed to make you write less code. Under the hood, there will be calls to the connect and setHorizontalBias methods.

Chains

Consider the following screen

Programmatically connect Button2 and Button3 in a horizontal chain, stretch them to the full screen, and show them weight 3 and 2, respectively.

   set.setMargin(R.id.button2, ConstraintSet.START, 0);
   set.setMargin(R.id.button3, ConstraintSet.START, 0);
   set.constrainWidth(R.id.button2, ConstraintSet.MATCH_CONSTRAINT);
   set.constrainWidth(R.id.button3, ConstraintSet.MATCH_CONSTRAINT);
   int[] chainViews = {R.id.button2, R.id.button3};
   float[] chainWeights = {3, 2};
   set.createHorizontalChain(ConstraintSet.PARENT_ID, ConstraintSet.LEFT,
           ConstraintSet.PARENT_ID, ConstraintSet.RIGHT,
           chainViews, chainWeights,
           ConstraintSet.CHAIN_SPREAD);

First, we remove the left indents by the setMargin method. Then we adjust the width of match_constraint so that the weight can be applied.

In the chainViews array we specify View, which must be merged into a chain. In the chainWeights array, we specify scales for View from chainViews.

In the createHorizontalChain method, we specify that the leash must be tied to the parent left and right, and the leash type should be spread.

If you do not need to specify the weight, then pass null instead of chainWeights. Well, in this case it is not necessary to put width in match_constraint

run

A little clarification. There is an error in the createHorizontalChain method that causes the chainviews to not be weighted for the first View from the chainViews array. It may be corrected in future versions. In the meantime, you can simply set the weight manually with the setHorizontalWeight method

set.setHorizontalWeight(R.id.button2, 3);

To remove or add View to existing threads, use the removeFromHorizontalChain and addToHorizontalChain methods.

Guides (guideline)

Consider the following screen

Let’s create a vertical guide and tie all three TextViews to it

set.create(R.id.guideline, ConstraintSet.VERTICAL_GUIDELINE);
set.setGuidelinePercent(R.id.guideline, 0.2f);

set.connect(R.id.textView, ConstraintSet.LEFT, R.id.guideline, ConstraintSet.RIGHT, 0);
set.connect(R.id.textView2, ConstraintSet.LEFT, R.id.guideline, ConstraintSet.RIGHT, 0);
set.connect(R.id.textView3, ConstraintSet.LEFT, R.id.guideline, ConstraintSet.RIGHT, 0);

set.setMargin(R.id.textView, ConstraintSet.START, 0);
set.setMargin(R.id.textView2, ConstraintSet.START, 0);
set.setMargin(R.id.textView3, ConstraintSet.START, 0);

With the create method, we create a vertical guide and setGuidelinePercent specifies that it will be indented at 20% of the screen width.

Next, the connect method binds all TextView and the setMargin method.

Guideline variable created in values.xml


   1

run

clone

With the clone method, we can read the settings not only from the current ConstrainLayout but also from the layout file.

Suppose we have two layout files

1) activity_main


This is the basic layout that will be used at startup.

2) activity_main2

We will read this screen from the layout file and apply it programmatically to the current screen.

Activity code:

public class MainActivity extends AppCompatActivity {

   private final ConstraintSet set = new ConstraintSet();
   private final ConstraintSet set2 = new ConstraintSet();
   private ConstraintLayout constraintLayout;
   private ConstraintSet currentSet;

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

       constraintLayout = (ConstraintLayout) findViewById(R.id.container);

       // copy constraints settings from current ConstraintLayout to set
       set.clone(constraintLayout);

       // copy constraints settings from activity_main2 to set2
       set2.clone(MainActivity.this, R.layout.activity_main2);

       currentSet = set;

       findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {

               // change current set
               currentSet = (currentSet == set ? set2 : set);

               // enable animation
               TransitionManager.beginDelayedTransition(constraintLayout);

               // apply settings to current ConstraintLayout
               currentSet.applyTo(constraintLayout);
           }
       });

   }
}

In the set variable, we read the settings of the current ConstraintLayout that was created with activity_main. In set2, we read the ConstraintLayout settings from the activity_main2 layout file.

At the push of a button we place current or set2 in the currentSet, and apply these settings to the current ConstraintLayout. That is, the screen will switch between the initial settings and the settings with activity_main2.

run

To use TransitionManager for API <19, use the support version

compile 'com.android.support:transition:25.3.1'




Discuss in the forum [1 reply]

Lesson 182. ConstraintLayout: chain, weight, barrier, group, circular

Lesson 182. ConstraintLayout: chain, weight, barrier, group, circular


We continue to consider ConstraintLayout.


Chain

Chain can be translated as a chain. A chain will allow you to distribute multiple View evenly in the available free space.

To create a chain, you must select View and center them horizontally or vertically.

The chain can be in one of three modes.

spread

Free space is evenly distributed between the View and the boundaries of the parent.

spread_inside

Free space is evenly distributed between View only. Extreme View pressed against the boundaries of the father.

packed

Free space is evenly distributed between the Extreme View and the borders of the parent. You can use margin to indent between View.

Chain modes are switched by clicking on the chain icon.

In packed mode, you can use the scroll to adjust the position of all the View relative to the borders of the parent.

Not only the boundaries of the parent but also other objects can be used as binding objects.

weight

The chain allows us to specify the value of weight for View. This is the same weight we normally use in LinearLayout.

We need weight attributes to work with weights. By default, they are not in the main Properties list.

To see all the attributes, you need to click on the two-arrow icon. But every time it is inconvenient to run there. There is another solution. Note, at the bottom of the Properties is the Favorite Attributes section. We will add the attributes we need there.

To do this, use the search for the word weight and add the attributes found in Favorite by clicking on the star.

The weight attributes now appear in the main Properties list.

As with LinearLayout, you need to set the View size to 0dp to use the weight. Let’s put the left TextView weight = 2 and the right = 1. And leave the central TextView wrap_content.

The first and third TextViews now share 2: 1 free space.

Barriers

Barriers were presented at Google IO 2017. To date (12/07/17) they are not yet available in the ConstraintLayout release, but are in beta.

To use the beta, you must add the google maven repository and specify the constraint beta for constraint-layout in dependencies

repositories {
    maven {
        url 'https://maven.google.com'
    }
}
dependencies {
    ...
    compile 'com.android.support.constraint:constraint-layout:1.1.0-beta3'
}

Suppose we have a screen like this.

Two text and picture.

The picture should be to the right of both texts, regardless of their size. That is, if the first text takes up more space in width, then the picture should be more right than the first text. And if the second text is wider, then the second text is more correct.

This is a fairly common case. Usually, this is because we add both texts to LinearLayout (or RelativeLayout), set the width to wrap_content, and have the image to the right of that LinearLayout. In this case, LinearLayout will be the width of the widest text and the image will be located to the right of both texts. But UI optimization tutorials tell us to use as few different ViewGroup’s on the screen as possible because it doesn’t affect performance the best. Ok, let’s listen to them.

In order not to add extra LinearLayout, we can use a barrier. The barrier can be configured to be to the right of both TextViews, no matter which one is wider. After that, all you have to do is tie the picture to this barrier.

Add and configure the barrier

We do 4 steps:
1) Add a vertical barrier
2) In Component Tree, drag both TextView into the barrier. Thus, we inform the barriers that View is guided by.
3) In Properties we set barrierDirection to end (or right) mode. This means that the barrier will be located to the right of all the View that was added in paragraph 2.
4) Attach the picture to the barrier.

Now, as you change the width of the TextView, the barrier will shift, followed by the picture.

Group

The groups were unveiled at Google IO 2017 and are not yet officially available. Working with them requires the same steps as barriers.

The group allows you to create a set of View that can be used for group operations.

let’s create a group

We add a group to the screen, and then drag and drop all the TextView into it. It doesn’t matter where the group is on the screen. The main thing – which View was added to it.

In the layout file, the group will look like this

    

All added View is contained in the constraint_referenced_ids attribute.

We can now work with this group in code, just like normal View.

Group group = (Group) findViewById(R.id.group);
group.setVisibility(View.GONE);

All members of this View group will become invisible.

There is not enough documentation on this topic yet, and the possibilities are not clear. That is, it is not clear that the group delegates its View and what does not. For example, visibility works, but the click handler is gone. We will wait for the official release.

Circular

Using circular position, we can adjust two View so that one is at a certain distance and at a certain angle from the other

The View B image is at an angle and some distance from View A

To get this result, we need to configure three attributes for View B

– layout_constraintCircle – Specify the id view that will be the center of the circle, ie View A

– layout_constraintCircleRadius – Distance from center of circle to View B

– layout_constraintCircleAngle – angle (degrees, 0 to 360)

In the next lesson, we’ll look at ConstraintSet




Discuss in the forum [1 reply]

Lesson 181. ConstraintLayout: match_constraints, toolbar tools, guidelines, aspect ratio

Lesson 181. ConstraintLayout: match_constraints, toolbar tools, guidelines, aspect ratio


We continue to consider ConstraintLayout.

match_constraints

At ConstraintLayout, we can still use wrap_content as a width / height View or specify a fixed size in dp. But match_parent is not recommended here. The help is written as follows:
You should not use match_parent for any view in a ConstraintLayout. Instead of using “match constraints” (0dp)

Instead of match_parent, we are offered to use match_constraints, aka 0dp. If match_parent extends View to its parent size, match_constraints View will take up space available between the objects to which it is attached.

Let’s look at all three modes in the TextView example, which is tied to the left border on the left and the button to the right.

wrap content

Width in content

fixed size

We explicitly specify a width of 150dp

match constraints (0dp)

TetxView is stretched between binding objects.

These modes can be switched to Properties not only by layout_width property, but also by clicking on a special icon

Note that this icon varies depending on the mode. With wrap_content it looks like a Christmas tree, with a fixed size – like a segment, and with match_constraints – like a spring.

Because match_constraints stretches your View between binding objects, it needs a two-sided binding. If your View is anchored, for example, only to the left, and no right anchors, match_constraints will work as wrap_content.

Center alignment

You can use two-sided binding to align the two View centered.

And you can use the scroll to change the center position.

Text alignment

Let’s say we have two TextViews with different font sizes. And we need to put them next so that the texts are on the same line.

View can be aligned on the bottom border by binding between the bottom borders

But in this case the texts will not be on one line.

You can use text alignment to correct this

In this case, the texts are on the same line and the lower boundaries are not.

Tools in the toolbar

There are several tools in the toolbar that can help.

I numbered them in the screenshot. Let’s go in order.

1) Show / hide bindings

If enabled, you will see all your bindings on the screen. If disabled, only the bindings of the selected View will be visible.

2) auto-bindings

If enabled, you can create parental bindings if you bring View to the parental boundary.

Or you can create a double-sided bindings by bringing View to the parent center

3) Remove all bindings

Clicking this button will remove all your snaps on the screen. If you accidentally pressed, then press CTRL + Z and it will return.

4) Create bindings

Creates snaps for all View on the screen. But it does not do very well. It may be corrected in the future, but for now I do not recommend using it.

5) Indentation

Here you can automatically specify which indent will be used by default when creating the binding.

The first bind was created with an indent of 8. Then I changed the default value to 24. And the second bind was created with an indent of 24.

6) Miscellaneous

At this point there are three types of operations on the View

to collect

Put together a few selected View, first horizontally, then vertically.

This operation does not create any bindings.

stretch

Stretches the View between nearby objects.

You can stretch several objects evenly

This operation does not create any bindings.

Place evenly

Helps to position View evenly

7) Alignment

Horizontal: Left, Center, Right

Vertical: text, bottom edge, center, top edge

The bottom row of buttons is centering. It creates a two-sided binding. There are two modes.

Normal when an anchor is created to nearby objects

And parental, when an attachment is created to the boundaries of the father.

8) Guides

These are the lines you can use to create bindings.

Consider an example with vertical guides. Add it to the screen and adjust the position

You can position it either by indenting to the left, or indenting to the right, or by the percentage of parent width.

Let’s look at some use cases

simple

Use the guide to split the screen width into two TextViews

the example is more complicated

Here are some guides that we used to build the TextView grid

Aspect ratio

Consider the example of View height. If View has two-sided vertical bindings and height is set to match_constraints (0dp), View will be stretched in height between the bind objects. We can adjust it so that the height in this case does not stretch but depends on the width of the View.

This is done so

For the Two Sided Vertical View, we set the height to 0dp. View stretches in height. Then, turn on the aspect ratio by clicking on the triangle. Set the ratio of width to height – 3 to 1. That is, the height will now be three times less than the width.

We change the width by adding text to the TextView

As the width changes, so does the height to maintain a set aspect ratio of 3: 1.

If you have horizontal and vertical two-sided bindings for View, and match_constraints is set to height and width, then you can toggle aspect ratio: height depends on width or width on height. To switch, click on the triangle

Initially, the aspect ratio is off

The height and width of the View are independent of each other.

Then we turn on the mode when the height depends on the width. A bold vertical line appears in the square to the top right.

As the width changes, the height changes. And manually change the height does not work.

Then the width depends on the height. A bold horizontal line appears in the square to the top right.

As the height changes, the width changes. And manually changing the width does not work.

There are a couple more cool ConstraintLayout features: chains and barriers. The next lesson will be about them.




Discuss in the forum [0 replies]