FREELessons: 16Length: 2.7 hours

Next lesson playing in 5 seconds

Cancel
  • Overview
  • Transcript

4.2 Handling Watch Face States

In this lesson, I'll cover the different states that a watch face must account for and teach you how to efficiently navigate the lifecycle of an Android Wear watch face.

4.2 Handling Watch Face States

Hey everyone, this is Paul with Tuts+ and you're watching the Developing for Android Wear Course. In this lesson we will extend on our previously made watch face so that we can take into account changing time zones, entering ambient mode and changing the time every second. So before we start, let's go ahead and add some new values to the top of the engine that we will use throughout this lesson. So if we go down to the typeface, we'll go ahead and add a couple new lines and the first value will be private static final int MSG_UPDATE Time ID is equal to, and we'll just use the value of 1. And what this is for, is we will have a handler set up for keeping track of the time every second. So this is just the message ID that we will use to see what this handler should do. Below that, let's add a value for private, static, final long, DEFAULT_UPDATE_RATE_MS, and we'll make this equal to 1,000. And this value will be used as a default for updating our handler. Below that, we're gonna use another value, so private long mUpdateRateMs will also be equal to 1000. So this has the possibility of changing under certain conditions and then when we need to go back to the default, we can use the default UpdateRateMs value. Next, let's add a couple more lines and we will need to add a couple of booleans to act as flags so that we can keep track of the state of our watch. So the first one will be private boolean m has Timezone Receiver Been Registered and we'll default this to false. Next we'll use private boolean M is in mute mode, and this will be set to true when the user has set their watch to be muted so that they don't receive any notifications. The next one will be set for private boolean, M is low bit ambient, which is supported by some watches so that you have to be in a low bit mode when you're in ambient mode to protect against burn in. Next, we need to add a couple of flow values for off sets on the watch. For squared round devices because originally, we just forced some values in there. And now we're actually gonna change those values based off of the shape of the device. So, we'll use private flow mxoffset and a private flow myoffset. And then we can scroll down a little bit just below M time and we'll need to add in a broadcast receiver for when the time zone changes and a handler to keep track of time so that it changes every second. So first we'll start with final broadcast receiver, M time zone broadcast receiver is equal to new BroadcastReceiver. And then on receive, we'll just say mTime.clear. And we'll have the intent.getStringExtra. And this will have a value of time-zone. And then we will need to call mTime.setToNow. So that we can get the new time within the new time zone. Let's go ahead and throw on our semicolon at the end there. And then we need to do private final handler mTimeHandler. We'll import handler from androidosHandler is equal to new Handler. And we don't need to add anything to it so we'll use the no parameters version of it. So we'll add in our curly brackets. And then there's a method within here called handle message. And we can get rid of these super value and we'll put in a switch statement for message.what. And if the case is message update time ID which is the ID that we set at the top of this engine, then we know that we can go ahead and change the time. So we'll say invalidate. So everything gets redrawn, and then if is visible and it is not in ambient mode, then we can go ahead and grab the current time in milliseconds as long curTime is equal to system.currentTimeMillis. And then long delay is equal to mUpdateRateMs minus curTime Modulus m updateratems. And then we can call mtimehandler.sendemptymessagedelayed for our message update ID. And then the delay. So what this will do is when it started, it will invalidate the screen so that it redraws and then it'll wait a second and then start the message over again so that'll keep redrawing every second. But if the screen is not visible, or it's in ambient mode, then the system on time take will actually take precedence and this will never be called until we come back to either being visible or leaving ambient mode. Now that that's done we have a couple of different life cycle methods that are gonna be part of this engine. So let's go ahead and set those at the bottom and then we can start filling them out. So if we scroll down a little bit, let me make sure I throw on our semicolon at the end of our handler. And then we can go down to the very bottom, we can add a couple more lines and then we'll add the method onVisibilityChanged. We'll also need onPropertiesChanged. Next we'll need onAmbientModeChanged. onInterruptionFilterChanged. onApplyWindowInsets. And onDestroy. Outside of these life cycle methods, we're going to want to add a hover method called update timer that we can call from various places within this application so that we can set our handler to start updating the watch face every second. So let's go ahead and just create that now. So we'll use private, void, update timer. M time handler.removemessage for message update time ID. And then if is visible. And is not in ambient mode. Then we can go ahead and call m time handler.send empty message. And all we're gonna do is send in the message update ID without sending in a delay. Now, if we scroll back up to on visibility changed, you can start adding in methods for this. On Visibility Changed is called by the system when the user tilts their wrist so they can view their watch, even if it's in ambient mode or not. This is generally the case when the user see's their watch light up so that they can actually view the time. So in this situation we're going to call if Visible, if M has Time zone receiver been registered, and we're gonna add an exclamation point here so that it is not received. Then we'll go ahead and create an intent filter, called filter equal to new intent filter. And this will have an intent.action_timezone_changed. And let's go ahead and import IntentFilter. And then using the service, we'll say Digital Watch Face Service.this.register Receiver. We'll pass in our own m Time Zone Broadcast Receiver and the value of filter. And that will make it so that our service is now listening for any kind of change to the time zone. And then we can go ahead and say m has time zone receiver been registered and set that to true. Underneath this, as long as the screen is visible, we'll say mTime.clear. And we'll say TimeZone.getDefault.getID. And then mTime.setToNow. If the screen is not visible, then we will want to use else and if m has time zone receiver been registered, we'll call digital watch face service.this.unregistered receiver. I'll pass in our receiver. And then we'll set our boolean to false. So m Has Timezone Receiver Been Registered is equal to false. And then, no matter if this is visible or invisible, we need to go down to the bottom here and call updateTimer. Once that's done, we're all done with the onVisibilityChanged method, so we can scroll down and go to onPropertiesChanged. So what on properties changed does is it listens for changes in low bit ambient mode, or burn in protection, so this is where we will set that flag so that we know if we should be in low bit mode or not. So, this one will be pretty simple. We'll just say if properties.get boolean for the key property burn in protection, and if it's not there we'll just say false. M is low bit ambient is equal to properties.get Boolean, property low bit ambient or false, and once that's done we can scroll down a little bit more and we'll go to on ambient mode changed. So this is what's called by the system when we go in or out of ambient mode if the user clicks the button on their watch. Or the watch face just times out to go to ambient mode. So what we're gonna do here is change the color of our text and our background so it's either black and white or red and white because in ambient mode you actually want to have as little pixels set to white as you can, and keep most of it black, just to prevent burn in and overuse of the battery. So the first thing we're gonna do is check if in ambient mode, we'll set m text color paint.setColor to color.parsecolor and we'll pass in the color white so the text will now be white instead of red. And then we'll want to set the background color to black. So m background color paint.setColor. Color.parsecolor for black. And then else, if it's coming out of ambient mode, then we'll want to set our paints back to their original color. So m Text Color Paint.setColor, Color.parseColor for red, and then m Background Color Paint.setColor, Color.parseColor and we'll set this back to white. Next underneath the is ambient if statements, we'll say if M is low bit ambient, we'll want to turn off or on our anti-aliasing for efficiency on the watch as I need it. So m Text Color Paint.setAntiAlias to the opposite of in Ambient Mode. We'll also do the same thing for the background. So mBackgroundColorPaint.setAntiAlias(! inAmbientMode). Finally, we'll need to call invalidate and then updateTimer and this will redraw our watch face and get it set it either draw every minute or every second, depending on what the situation is with ambient mode. Next, we'll go down to on interruption filter change. And this is when your user either switches from receiving only high priority messages, all messages or no messages on their watch. And then if they're in muted mode, we'll also want to change the alpha on our watch face, so that it's a little bit more dim and harder to see, just because the user doesn't really wanna pay attention to their watch at that time. So let's add a new line, and we'll say boolean is Device Muted is equal to interruption filter. Is equal to android.support.wearable.watch Face.watch Face Service.interruption filter none. And then if is Device Muted we'll say m Update Rate Ms is equal to Time Unit.Minutes.toMillis and then we'll put in the value of one. So that our update time will only happen every minute. Else mUpdateRateMs is equal to the default value. Below that we'll need another if statement so if m is in mute mode is not equal to is device muted, we'll set m is in mute mode to be equal to is device muted. And then int alpha is equal to isDeviceMuted? 100 or 250. So if it is muted, we'll have it at 100 alpha, otherwise we'll have 255 so that it's fully opaque. And then we'll set m Text Color Paint.setAlpha to be our alpha value. And then we'll invalidate the screen. Finally we just need to call updateTimer. Once we're done with this method we can go down to on Apply Window Insets, and this is called when the watch face is first brought up so that you can initialize padding and keep track of what the shape of the device is. So if it's round or if it's squared. So what we're gonna do here is say myOffset is equal get Resources.get Dimension R.dimen.y_Offset. And this is a value that we'll create in a second here but before we do that, let's go ahead and check if Insets.isRound, so this is a round device. Then we're gonna set our mxOffset is equal to getResources.getDimension. R.dimen.x_offset_round. Else mxOffset is equal to getResources.getDimension. R.dimen.x_offset_square. And before we create this dimen values lets go ahead and go down to onDestory, and we're going to need to put in m Time Handler.remove Messages for our update message. So MSG_UPDATE_TIME_ID. That way when the user switches watch faces we won't have our timer continuously going, causing a memory leak in the system. So if we go back to wear > src > main > res > values, we can go back into our dimens file. And let's add values for those offsets. So the first one is dimen name is equal to y_offset, and this will be 90 DP. The next one is dimen name is equal to x_offset_square. We'll make this 15 DP. And the last one is dimen name is equal to x_offset_round and I'll make this equal to 25 DP. And over here let's go ahead and change the watch face text size to be 40 DP, just keep everything a little bit more easy to understand while we're on the canvas. So we'll save and close the dimens file. And let's go back up to on draw. So you'll notice that we always put down AM or PM depending on if we're in AM or PM, but this doesn't take into account updating every second. So what we're gonna do now, is add the seconds if we're not in ambient mode. And if we are in ambient mode, we'll just keep it set to AM and PM. So we'll leave our text time the same and then below that we'll say if is In Ambient Mode or m Is In Mute Mode, then we'll add in the am or the pm. Else timeText plus or equal to string.format: %02D, and then m time.second, and instead of these hard coded values that we have for draw text we're gonna use mxOffset and myOffset. Once we have that done we can go ahead and hit run. We'll hit OK and we'll go to our emulator. And we can go ahead and close the application and you can now see the watch face is counting down for every second. So if we hit the power button to put it into ambient mode It now switches it to black and white, and we now have a PM instead of it just counting the seconds. And, that's it for a basic watch face. Now, there are some things that you can do on top of this, that are fairly interesting, such as adding a settings screen, drawing out an analog watch face by using lines and drawing ticks along the outside of the screen. Or using bitmaps to actually place the images for your analog screen or any kind of other design you want to use. So since this is a canvas, you're pretty much free to do whatever you want as long as you can make it work. You just need to take into account your user's power on their watch and the amount of processing time it takes so that you're not skipping frames on their wearable device. But for getting started, you're pretty much all set. So I will see you in the next video.

Back to the top