- Android styling: themes vs styles
- Theme != Style
- What’s in a style?
- Usage
- Scope
- What’s a theme?
- Usage
- Scope
- Separate Concerns
- Android: Changing app theme at runtime
- Changing the theme at runtime
- Possible issues
- Recursively change attributes on all your Views
- Possible issues
- There is a third option?
- What I recommend
- Do you know a better way of doing this?
- Create a Notification
- Add the support library
- Groovy
- Kotlin
- Create a basic notification
- Set the notification content
- Kotlin
- Kotlin
- Create a channel and set the importance
- Kotlin
- Set the notification’s tap action
- Kotlin
- Show the notification
- Kotlin
- Add action buttons
- Kotlin
- Add a direct reply action
- Add the reply button
- Kotlin
- Kotlin
- Kotlin
- Kotlin
- Retrieve user input from the reply
- Kotlin
- Kotlin
- Add a progress bar
- Kotlin
- Set a system-wide category
- Kotlin
- Show an urgent message
- Kotlin
- Set lock screen visibility
- Update a notification
- Remove a notification
- Best practices for messaging apps
- Use MessagingStyle
- Kotlin
- Use direct reply
- Enable smart reply
- Add notification metadata
Android styling: themes vs styles
The Android styling system offers a powerful way to specify your app’s visual design, but it can be easy to misuse. Proper use of it can make themes and styles easier to maintain, make branding updates less scary and make it straightforward to support dark modes. This is the first in a series of articles where Chris Banes and I will set out to demystify Android styling so that you can make stylish apps without pulling your hair out.
In this first article, I’ll take a look at the building blocks of the styling system: themes and styles.
Theme != Style
Both themes and styles use the same syntax but serve very different purposes. You can think of both as key-value stores where the keys are attributes and the values are resources. Let’s take a look at each.
What’s in a style?
A style is a collection of view attribute values. You can think of a style as a Map view attribute, resource> . That is the keys are all view attributes i.e. attributes that a widget declares and you might set in a layout file. Styles are specific to a single type of widget because different widgets support different sets of attributes:
Styles are a collection of view attributes; specific to a single type of widget
As you can see, each of the keys in the style are things you could set in a layout:
Extracting them to a style makes it easy to reuse across multiple views and maintain.
Usage
Styles are used by individual views from a layout:
Views can only apply a single style — contrast this to other styling systems such as css on the web where components can set multiple css classes.
Scope
A style applied to a view only applies to that view, not to any of its children. For example, if you have a ViewGroup with three buttons, setting the InlineAction style on the ViewGroup will not apply that style to the buttons. The values provided by the style are combined with those set directly in the layout (resolved using the styling precedence order).
What’s a theme?
A theme is a collection of named resources which can be referenced later by styles, layouts etc. They provide semantic names to Android resources so you can refer to them later e.g. colorPrimary is a semantic name for a given color:
These named resources are known as theme attributes, so a theme is Map theme attribute, resource> . Theme attributes are different from view attributes because they’re not properties specific to an individual view type but semantically named pointers to values which are applicable more broadly in an app. A theme provides concrete values for these named resources. In the example above the colorPrimary attribute specifies that the primary color for this theme is teal. By abstracting the resource with a theme, we can provide different concrete values (such as colorPrimary =orange) in different themes.
Themes are a collection of named resources, useful broadly across an app
A theme is similar to an interface. Programming to an interface allows you to decouple the public contract from the implementation allowing you to provide different implementations. Themes play a similar role; by writing our layouts and styles against theme attributes, we can use them under different themes, providing different concrete resources.
Roughly equivalent pseudo-code:
Which allows you to vary the way that MyView is rendered, without having to create variants of it:
Usage
You can specify a theme on components which have (or are) a Context e.g. Activity or Views / ViewGroup s:
You can also set a theme in code by wrapping an existing Context with a ContextThemeWrapper which you could then use to inflate a layout etc.
The power of themes really comes from how you use them; you can build more flexible widgets by referencing theme attributes. Different themes provide concrete values at a later time. For example, you might wish to set a background color on a section of your view hierarchy:
Rather than setting a static color ( #ffffff or a @color resource) we can delegate to the theme by using the ?attr/themeAttributeName syntax. This syntax means: query the theme for the value of this semantic attribute. This level of indirection allows us to provide different behavior (e.g. providing a different background color in light and dark themes) without having to create multiple layouts or styles which are mostly identical but for a few color variations. It isolates the elements that are changing within the theme.
Use the ?attr/themeAttributeName syntax to query the theme for the value of this semantic attribute
Scope
A Theme is accessed as a property of a Context and can be obtained from any object which is or has a Context e.g. Activity , View or ViewGroup . These objects exist in a tree, where an Activity contains ViewGroup s which contain View s etc. Specifying a theme at any level of this tree cascades to descendent nodes e.g. setting a theme on a ViewGroup applies to all the View s within it (in contrast to styles which only apply to a single view).
This can be extremely useful, say if you want a dark themed section of an otherwise light screen. Read more about this behavior here.
Note that this behavior only applies at layout inflation time. While Context offers a setTheme method, or Theme offers an applyStyle method, these need to be called before inflation. Setting a new theme or applying a style after inflation will not update existing views.
Separate Concerns
Understanding the different responsibilities and the interaction of styles and themes, helps to keep your styling resources more manageable.
For example, say you have a blue theme for your app, but some Pro screens get a fancy purple look and you want to provide dark themes with tweaked colors. If you tried to achieve this using only styles, you would have to create 4 styles for the permutations of Pro/non-Pro and light/dark. As styles are specific to a type of view ( Button , Switch etc) you’d need to create these permutations for each view type in your app.
If instead we use styles and themes we can isolate the parts which alter by theme as theme attributes so we only need to define a single style per view type. For the above example we might define 4 themes which each provide different values for the colorPrimary theme attribute, which these styles then refer to and automatically reflect the correct value from the theme.
This approach might seem more complicated as you need to consider the interaction of styles and themes, but it has the benefit of isolating the parts that change per theme. So if your app rebrands from blue to orange, you only need to change this in a single place, not scattered throughout your styling. It also helps fight a proliferation of styles. Ideally you only have a small number of styles per view type. If you don’t take advantage of theming, it’s easy for your styles.xml file to get out of hand and explode with different variations of similar styles, which becomes a maintenance headache.
Join us in the next article where we explore some common theme attributes and how to create your own:
Источник
Android: Changing app theme at runtime
Jun 28, 2015 · 5 min read
Every so often, I see a question posted on StackOverflow which is effectively asks how to change the themes of an app at runtime. The use case is often that there is a setting, button or check box which can switch between different colour themes or between something like day and night mode.
Every time such a requirement comes up, the fir s t thing a quick Google search shows is that it’s not possible to change themes at runtime. That being said, it is possible to change the theme of an Activity, however only in the`onCreate` method and only before `super` is called.
This is problematic because it’s hard to provide a seamless experience to the user if you have to restart the app or Activity in order to change the theme. So our second option is to recursively loop through all of our views and set their attributes each time an Activity or Fragment is created. This way, when the theme is changed, you can loop through all the Views again and change the attributes to reflect the new theme. Neither of these options is ideal, you may even want to consider a hybrid of these two approaches. I’ve provided an example implementation of each of these methods below.
Changing the theme at runtime
As mentioned before, it’s only possible to change the theme of an Activity in the `onCreate` method and that to only before `super` has been called. Changing the theme is fairly straight forward, something like below should do it:
This is pretty straight forward, however this works when an activity is first created and has no effect on the current open Activity or any backgrounded Activities. In order to affect change on the current Activity, we’ll have save the state of the current Activity and relaunch the activity, in order to make this experience seamless for the user, you have 2 options, either remove all transition animations for Activities or change them to provide a nice fade in effect. The result of this approach is shown in the video below.
As you can see, the approach produces a pretty nice result. If you don’t want a fade in effect, remove all animations for Activity transition and you should have a sudden change.
The code to achieve this is in my gist “Transition themes”.
Possible issues
- In order to achieve theme change in this manner, you have to make sure that all your View inherit attributes that matter from the theme and do not in-line any attributes that matter like background colour or text colour.
- Saving your Activity state and relaunching it may not be as smooth as in my example above. This depends a lot of how heavy your Activity and it’s layouts are. Some elements may need to be reloaded.
- Any Activities that are already open in the background will not have the theme change applied to it when you go back to them. The easiest solution to this is to close all the backgrounded Activities, or else, you’ll have to save their state, close them and relaunch them in `onStart` or `onResume`.
Recursively change attributes on all your Views
As much as we hope that the theme can contain all our formatting, we invariably need to override a text colour or background colour in-line in our layout or an in a style attribute and this needs to be changed programmatically. In this scenario, you would likely have to check the appropriate Views or all Views to see if they are consistent with your set theme. If you know which Views are likely to be affected and can check them directly, nothing could be better. If not, you will have to loop through all the View in your layout and check them. The code to do this depends heavily on your project and it’s requirements, however, the skeleton code for checking all your Views be something like this:
Possible issues
- Depending on how complex your screens are, your code for checking each View can become quite complex. An alternate solution can be to set the Views theme related attributes when we build our Activity, Fragment or Layout. This will still add to the complexity of your code.
- There is a time and performance cost to doing this for each layout.
There is a third option?
You could bundle duplicate layouts for each of your themes where the only difference between each layout is that the style or in-line style related attributes are different. Then in your code, depending on the selected theme you inflate or set the appropriate layout. This approach while very simple, however it has the same issues as the first option.
What I recommend
If this is a requirement for your app, I recommend you research what is possible before you try any of these approaches. If all you want to do is change some text colour and the colour of the Toolbar and tabs, this is possible without having to change the theme. I would take a look at the Design Support Library.
If you are going to do down one of the routes I have talked about above, I would recommend not getting too attached to any one approach and to combine all three approaches above. Find the best fit for your particular situation.
Also, if you’re going to need to change the colour of your drawable assets, my article on how to change the colour of drawable assets may help.
Do you know a better way of doing this?
I’m honestly asking the readers, if there are any out there, to chime in and tell me if there is a better way to handle runtime theme changes. It’s a topic I have researched and Google’d, however, I’m just not happy with what I’ve found so far. If you have a better approach or some advice on the matter, I’d love to hear it.
For more Android development article or follow me on LinkedIn, Twitter or Google+.
Источник
Create a Notification
Notifications provide short, timely information about events in your app while it’s not in use. This page teaches you how to create a notification with various features for Android 4.0 (API level 14) and higher. For an introduction to how notifications appear on Android, see the Notifications Overview. For sample code that uses notifications, see the Android Notifications Sample.
Notice that the code on this page uses the NotificationCompat APIs from the Android support library. These APIs allow you to add features available only on newer versions of Android while still providing compatibility back to Android 4.0 (API level 14). However, some new features such as the inline reply action result in a no-op on older versions.
Add the support library
Although most projects created with Android Studio include the necessary dependencies to use NotificationCompat , you should verify that your module-level build.gradle file includes the following dependency:
Groovy
Kotlin
Create a basic notification
A notification in its most basic and compact form (also known as collapsed form) displays an icon, a title, and a small amount of content text. In this section, you’ll learn how to create a notification that the user can click on to launch an activity in your app.
Figure 1. A notification with a title and text
For more details about each part of a notification, read about the notification anatomy.
Set the notification content
To get started, you need to set the notification’s content and channel using a NotificationCompat.Builder object. The following example shows how to create a notification with the following:
- A small icon, set by setSmallIcon() . This is the only user-visible content that’s required.
- A title, set by setContentTitle() .
- The body text, set by setContentText() .
- The notification priority, set by setPriority() . The priority determines how intrusive the notification should be on Android 7.1 and lower. (For Android 8.0 and higher, you must instead set the channel importance—shown in the next section.)
Kotlin
Notice that the NotificationCompat.Builder constructor requires that you provide a channel ID. This is required for compatibility with Android 8.0 (API level 26) and higher, but is ignored by older versions.
By default, the notification’s text content is truncated to fit one line. If you want your notification to be longer, you can enable an expandable notification by adding a style template with setStyle() . For example, the following code creates a larger text area:
Kotlin
For more information about other large notification styles, including how to add an image and media playback controls, see Create a Notification with Expandable Detail.
Create a channel and set the importance
Before you can deliver the notification on Android 8.0 and higher, you must register your app’s notification channel with the system by passing an instance of NotificationChannel to createNotificationChannel() . So the following code is blocked by a condition on the SDK_INT version:
Kotlin
Because you must create the notification channel before posting any notifications on Android 8.0 and higher, you should execute this code as soon as your app starts. It’s safe to call this repeatedly because creating an existing notification channel performs no operation.
Notice that the NotificationChannel constructor requires an importance , using one of the constants from the NotificationManager class. This parameter determines how to interrupt the user for any notification that belongs to this channel—though you must also set the priority with setPriority() to support Android 7.1 and lower (as shown above).
Although you must set the notification importance/priority as shown here, the system does not guarantee the alert behavior you’ll get. In some cases the system might change the importance level based other factors, and the user can always redefine what the importance level is for a given channel.
For more information about what the different levels mean, read about notification importance levels.
Set the notification’s tap action
Every notification should respond to a tap, usually to open an activity in your app that corresponds to the notification. To do so, you must specify a content intent defined with a PendingIntent object and pass it to setContentIntent() .
The following snippet shows how to create a basic intent to open an activity when the user taps the notification:
Kotlin
Notice this code calls setAutoCancel() , which automatically removes the notification when the user taps it.
The setFlags() method shown above helps preserve the user’s expected navigation experience after they open your app via the notification. But whether you want to use that depends on what type of activity you’re starting, which may be one of the following:
- An activity that exists exclusively for responses to the notification. There’s no reason the user would navigate to this activity during normal app use, so the activity starts a new task instead of being added to your app’s existing task and back stack. This is the type of intent created in the sample above.
- An activity that exists in your app’s regular app flow. In this case, starting the activity should create a back stack so that the user’s expectations for the Back and Up buttons is preserved.
For more about the different ways to configure your notification’s intent, read Start an Activity from a Notification.
Show the notification
To make the notification appear, call NotificationManagerCompat.notify() , passing it a unique ID for the notification and the result of NotificationCompat.Builder.build() . For example:
Kotlin
Remember to save the notification ID that you pass to NotificationManagerCompat.notify() because you’ll need it later if you want to update or remove the notification.
Add action buttons
A notification can offer up to three action buttons that allow the user to respond quickly, such as snooze a reminder or even reply to a text message. But these action buttons should not duplicate the action performed when the user taps the notification.
Figure 2. A notification with one action button
To add an action button, pass a PendingIntent to the addAction() method. This is just like setting up the notification’s default tap action, except instead of launching an activity, you can do a variety of other things such as start a BroadcastReceiver that performs a job in the background so the action does not interrupt the app that’s already open.
For example, the following code shows how to send a broadcast to a specific receiver:
Kotlin
For more information about building a BroadcastReceiver to run background work, see the Broadcasts guide.
If you’re instead trying to build a notification with media playback buttons (such as to pause and skip tracks), see how to create a notification with media controls.
Add a direct reply action
The direct reply action, introduced in Android 7.0 (API level 24), allows users to enter text directly into the notification, which is delivered to your app without opening an activity. For example, you can use a direct reply action to let users reply to text messages or update task lists from within the notification.
Figure 3. Tapping the «Reply» button opens the text input
The direct reply action appears as an additional button in the notification that opens a text input. When the user finishes typing, the system attaches the text response to the intent you had specified for the notification action and sends the intent to your app.
Add the reply button
To create a notification action that supports direct reply:
- Create an instance of RemoteInput.Builder that you can add to your notification action. This class’s constructor accepts a string that the system uses as the key for the text input. Later, your handheld app uses that key to retrieve the text of the input.
Kotlin
Kotlin
Caution: If you re-use a PendingIntent , a user may reply to a different conversation than the one they thought they did. You must either provide a request code that is different for each conversation or provide an intent that doesn’t return true when you call equals() on the reply intent of any other conversation. The conversation ID is frequently passed as part of the intent’s extras bundle, but is ignored when you call equals() .
Attach the RemoteInput object to an action using addRemoteInput() .
Kotlin
Kotlin
The system prompts the user to input a response when they trigger the notification action, as shown in figure 3.
Retrieve user input from the reply
To receive user input from the notification’s reply UI, call RemoteInput.getResultsFromIntent() , passing it the Intent received by your BroadcastReceiver :
Kotlin
After you’ve processed the text, you must update the notification by calling NotificationManagerCompat.notify() with the same ID and tag (if used). This is necessary to hide direct reply UI and confirm to the user that their reply was received and processed correctly.
Kotlin
When working with this new notification, use the context that’s passed to the receiver’s onReceive() method.
You should also append the reply to the bottom of the notification by calling setRemoteInputHistory() . However, if you’re building a messaging app, you should create a messaging-style notification and append the new message to the conversation.
For more advice for notifications from a messaging apps, see best practices for messaging apps.
Add a progress bar
Notifications can include an animated progress indicator that shows users the status of an ongoing operation.
Figure 4. The progress bar during and after the operation.
If you can estimate how much of the operation is complete at any time, use the «determinate» form of the indicator (as shown in figure 4) by calling setProgress(max, progress, false) . The first parameter is what the «complete» value is (such as 100); the second is how much is currently complete, and the last indicates this is a determinate progress bar.
As your operation proceeds, continuously call setProgress(max, progress, false) with an updated value for progress and re-issue the notification.
Kotlin
At the end of the operation, progress should equal max . You can either leave the progress bar showing when the operation is done, or remove it. In either case, remember to update the notification text to show that the operation is complete. To remove the progress bar, call setProgress(0, 0, false) .
To display an indeterminate progress bar (a bar that does not indicate percentage complete), call setProgress(0, 0, true) . The result is an indicator that has the same style as the progress bar above, except the progress bar is a continuous animation that does not indicate completion. The progress animation runs until you call setProgress(0, 0, false) and then update the notification to remove the activity indicator.
Remember to change the notification text to indicate that the operation is complete.
Set a system-wide category
Android uses some pre-defined system-wide categories to determine whether to disturb the user with a given notification when the user has enabled Do Not Disturb mode.
If your notification falls into one of the pre-defined notification categories defined in NotificationCompat —such as CATEGORY_ALARM , CATEGORY_REMINDER , CATEGORY_EVENT , or CATEGORY_CALL —you should declare it as such by passing the appropriate category to setCategory() .
Kotlin
This information about your notification category is used by the system to make decisions about displaying your notification when the device is in Do Not Disturb mode.
However, you are not required to set a system-wide category and should only do so if your notifications match one of the categories defined by in NotificationCompat .
Show an urgent message
Your app might need to display an urgent, time-sensitive message, such as an incoming phone call or a ringing alarm. In these situations, you can associate a full-screen intent with your notification. When the notification is invoked, users see one of the following, depending on the device’s lock status:
- If the user’s device is locked, a full-screen activity appears, covering the lockscreen.
- If the user’s device is unlocked, the notification appears in an expanded form that includes options for handling or dismissing the notification.
Caution: Notifications containing full-screen intents are substantially intrusive, so it’s important to use this type of notification only for the most urgent, time-sensitive messages. Note: If your app targets Android 10 (API level 29) or higher, you must request the USE_FULL_SCREEN_INTENT permission in your app’s manifest file in order for the system to launch the full-screen activity associated with the time-sensitive notification.
The following code snippet demonstrates how to associate your notification with a full-screen intent:
Kotlin
Set lock screen visibility
To control the level of detail visible in the notification from the lock screen, call setVisibility() and specify one of the following values:
- VISIBILITY_PUBLIC shows the notification’s full content.
- VISIBILITY_SECRET doesn’t show any part of this notification on the lock screen.
- VISIBILITY_PRIVATE shows basic information, such as the notification’s icon and the content title, but hides the notification’s full content.
When VISIBILITY_PRIVATE is set, you can also provide an alternate version of the notification content which hides certain details. For example, an SMS app might display a notification that shows You have 3 new text messages, but hides the message contents and senders. To provide this alternative notification, first create the alternative notification with NotificationCompat.Builder as usual. Then attach the alternative notification to the normal notification with setPublicVersion() .
However, the user always has final control over whether their notifications are visible on the lock screen and can even control that based on your app’s notification channels.
Update a notification
To update this notification after you’ve issued it, call NotificationManagerCompat.notify() again, passing it a notification with the same ID you used previously. If the previous notification has been dismissed, a new notification is created instead.
You can optionally call setOnlyAlertOnce() so your notification interupts the user (with sound, vibration, or visual clues) only the first time the notification appears and not for later updates.
Remove a notification
Notifications remain visible until one of the following happens:
- The user dismisses the notification.
- The user clicks the notification, and you called setAutoCancel() when you created the notification.
- You call cancel() for a specific notification ID. This method also deletes ongoing notifications.
- You call cancelAll() , which removes all of the notifications you previously issued.
- If you set a timeout when creating a notification using setTimeoutAfter() , the system cancels the notification after the specified duration elapses. If required, you can cancel a notification before the specified timeout duration elapses.
Best practices for messaging apps
Use the best practices listed here as a quick reference of what to keep in mind when creating notifications for your messaging and chat apps.
Use MessagingStyle
Starting in Android 7.0 (API level 24), Android provides a notification style template specifically for messaging content. Using the NotificationCompat.MessagingStyle class, you can change several of the labels displayed on the notification, including the conversation title, additional messages, and the content view for the notification.
The following code snippet demonstrates how to customize a notification’s style using the MessagingStyle class.
Kotlin
Starting in Android 8.0 (API level 26), notifications that use the NotificationCompat.MessagingStyle class display more content in their collapsed form. You can also use the addHistoricMessage() method to provide context to a conversation by adding historic messages to messaging-related notifications.
- Call MessagingStyle.setConversationTitle() to set a title for group chats with more than two people. A good conversation title might be the name of the group chat or, if it doesn’t have a specific name, a list of the participants in the conversation. Without this, the message may be mistaken as belonging to a one-to-one conversation with the sender of the most recent message in the conversation.
- Use the MessagingStyle.setData() method to include media messages such as images. MIME types, of the pattern image/* are currently supported.
Use direct reply
Direct Reply allows a user to reply inline to a message.
- After a user replies with the inline reply action, use MessagingStyle.addMessage() to update the MessagingStyle notification and do not retract or cancel the notification. Not cancelling the notification allows a user to send multiple replies from the notification.
- To make the inline reply action compatible with Wear OS, call Action.WearableExtender.setHintDisplayInlineAction(true) .
- Use the addHistoricMessage() method to provide context to a direct reply conversation by adding historic messages to the notification.
Enable smart reply
- To enable Smart Reply, call setAllowGeneratedResponses(true) on the reply action. This causes Smart Reply responses to be available to users when the notification is bridged to a Wear OS device. Smart Reply responses are generated by an entirely on-watch machine learning model using the context provided by the NotificationCompat.MessagingStyle notification, and no data is uploaded to the Internet to generate the responses.
Add notification metadata
- Assign notification metadata to tell the system how to handle your app notifications when the device is in Do Not Disturb mode. For example, use the addPerson() or setCategory(Notification.CATEGORY_MESSAGE) method to override the Do Not Disturb mode.
Content and code samples on this page are subject to the licenses described in the Content License. Java is a registered trademark of Oracle and/or its affiliates.
Источник