字幕列表 影片播放 列印英文字幕 (inspiring jingle music) (scattered applause) - Hello, everyone. Okay, cool, the mic's working. So, yes, welcome to this talk about efficient Android layouts. If you've never met me before, I've been doing Android drawup for about seven years, first the Travel App company, the Expedia, then currently at Trello. As per usual, my thing doesn't work. All right, I'm gonna give my presentation over here. You know, you can click to engage, to rate sessions, and ask questions, of which I will probably then answer the questions at the end if you ask them. Anyways, so this talks about efficient Android layouts, and when I was writing it, what I found that I was really interested in wasn't so much so the efficiency in terms of performance, but the efficiency in terms of leverage that you have as a developer, and so I started thinking about it in the way that Archimedes was referring to, like, how fulcrums work, where, like, if you just give him a proper place to stand, he can move the Earth, and so that's sort of the focus of this talk, is like, how to get the most leverage as a developer, because a lot of Android teams are fairly small, and you're asked to do a lot of things. And so in my case, it would be if you give me a a standing desk, I will write you an Android app, and I hope that you're not expecting me to make any more terrible jokes this entire time, 'cause it won't happen. So, this is really just kind of a mish-mash of things, there's no narrative, so I'm just trying to cover up the fact that I have no transition here. Let's talk about ViewGroups. In particular, picking ViewGroups, which ViewGroup are you gonna use for any particular layout, and I think the main thing is the simpler the ViewGroup you can get away with, generally, the better, because the more complex ones require a lot more maintenance, and you can run into a lot more bugs, and so on the higher end of things, I'd say probably RelativeLayout's one of the most complex. ConstraintLayout is yet to be seen, but it looks like it's probably going to be more complex than RelativeLayout when it's finally done. Somewhere in the middle of there is LinearLayout, and then down there at the bottom is FrameLayout, which is one my favorites, because it's so simple. And there's a lot of other views in between, but these are the main building blocks for most applications. So RelativeLayout and ConstraintLayout, they sort of occupy the same space in Android right now, which is that they position views relative to each other, and RelativeLayout's sort of limited in this regard, but it's what we've had since the beginning, whereas ConstraintLayout is new and can do, like, all these amazing things, but there's some key problems with both of them besides the fact that they're fairly complex. One of them is that RelativeLayout is fairly slow, and then the issue with ConstraintLayout is that it's fairly alpha-ish at the moment. They haven't officially released it yet, So there's alpha out on Maven central, but a few times they've completely changed the API around, so it's not necessarily production ready. I mean, you could play around with it now, and you could use it if you want, but you're gonna end up with some of that cutting-edge problems that you end up when you're trying to experimental technology. LinearLayout is great, stacking views vertically and horizontally, you can distribute the weight, so this is a simple view, so rows are stacked vertically, and then also I distributed the weight between those two spinners equally. I'm actually fairly okay with nested linear layouts as an opposition to RelativeLayout, and I actually, last time I told someone this, this was like by far the biggest question that I got, is everyone coming to me later saying, "But I love my RelativeLayouts, that's all I use, I was then that nested LinearLayouts are the worst things in existence and so I can't believe that you just said that." SO to head off that question that I was going to be getting on the sessions thing, is that LinearLayouts are sometimes slow. So if you use layout wait, and you nest them pretty deep, then they an get pretty slow, but that's only a sometimes thing, whereas by opposition, RelativeLayout always has to do two passes, it's always slow. So the hope is that eventually ConstraintLayout will be our savior and save us from the situation of having to decide between the two of them. But in the meantime, I think really what's most important is just to focus on profiling. So whatever layout that you do end up with just turn on profile GPU rendering, and see if things are running fast enough on whatever test device that you're using, hopefully like a really slow one. And if you've never used profile GPU rendering, I highly recommend Googling that and looking into that, 'cause then you get these nice bars that show you whether or not you're hitting 60 frames a second, and what sort of things you're spending too much time on if you don't. But really I talked about all that, so I can talk about FrameLayout, which is my favorite layout in the world, because it's so incredibly simple. All it can do is position things based on the parent bounds. So that is, you can position things inside the center of the FrameLayout, or you can position things on one of the eight cardinal directions of the FrameLayout. Wait, am I getting that right? Yeah, eight. So, but there's a lot you can do with this. It turns out that a lot of layouts, if you just wanna have like say, a simple progress bar in the center of some large screen, like that's a FrameLayout, you don't have to do anything complicated with RelativeLayout or what have you. It's also really great as a simple layout for overlapping views, so if you need two views to be on top of each other, FrameLayout is a great container for that. It's also good for things like clickable item backgrounds, so if you have some image that takes up a very small amount of space, but if you wanna have multiple views that compose a single thing that you click, it's good to have like a FrameLayout as the parent of that, that can actually have the click detection so when you click on it, it actually looks like something is happening. So a good example of this, like in the Trello application, is the notification bar in the upper right corner. So this always present on the screen, it's a single FrameLayout, and there's a icon inside of it that, that white icon is always present, and then if you have unread messages, it'll put that little red thing on top of it. And so, the white icon centered and the red icon is actually pegged to the upper right corner, but then you can use the margin or to push it in so it doesn't just ram up against the sides. And on top of all of that, I can just have these views be very simply positioned, and then pair with clickable item background behind that, so when you actually click on it something happens. Another thing I really like using FrameLayouts for is what I'm calling "toggle containers", so if you have two different states that you toggle between, sometimes you just have a single view that you actually change, sometimes I've found it more handy to have multiple views that you can switch between. And so a FrameLayout's a good way to contain two things in exactly the same spot, and then toggle between them. So a good example of that in the Trello app is the avatar view. So this is whenever you represent a member of a card or something like that, if the user has their avatar Seth and we wanna show that, if they've never taken a picture, then we wanna show their initials. And so it's essentially choosing between an image view or a text view. Littler fancier versions of which that allow you to render a circle, but basically lets you toggle between these two. So the avatar view brings up the next thing I wanna talk about, which is view reuse. We use this avatar view all over the application, so these are just three screens, like the Trello board, a open Trello card, some activity on the side, and there's actually I think, three or four other locations we use an avatar view within the application. And so the question becomes, how do I reuse this in multiple places without having to rewrite the code everywhere, 'cause that would be kind of dumb. So the most obvious way is to use something called an include. So if you've never seen it before, the include tag allows you to point to a direct layout, and then it's as if that layout was just copy and pasted into the code right there. And you can't modify much of what you're including, but you can modify any of the layout params, that's any of the things that starts with layout_, so that's a nice way to be able to include something that may have been may have been match-paired, but you don't quite want it to be in the end. But the problem here is that okay, you get the XML in every single location, but you don't get any of the logic. So now I have to come up with some way to then apply like, find these particular views that were in the include, and then add the logic for actually binding that to the view. So what I actually prefer these days is using custom view. So with a custom view, I call, instead of include, I actually just have the view in reference directly, and then you need to write the actual custom view itself, but it's not very hard, because this isn't a custom view that's doing custom drawing or anything like that, it's just taking the place of what would have been in that include. And so with this custom avatar view, I'm extending Framelayout, so I'm saying the topmost is gonna be a Framelayout, remember I'm toggling between the two states. I've got an image view and a text view, and then inside of a constructor itself, it actually inflates all the views that are underneath it. So I don't need to, as a parent using avatar view, I don't have to worry about what's inside of it. It's handling all of that for me. And then I can have this one nice bind method where I take my member object and figure out whether I should be loading an icon or loading the text. So this makes my life a lot easier. One thing worth noting though, if you're using this sort of custom view setup, this is like a very hand-wavy version of what would be the included XML. But if you include the XML like this, you end up with a view hierarchy that looks like this. You wend up with an avatarview on the top, it's a FrameLayout, and then it inflates another FrameLayout, which then has the text view and image view. So obviously, this middle FrameLayout is pointless, we don't really need it, the lint check in Android is particularly harsh when you do this, something like "has no reason to live" or something like that, "has no reason to exist". So we wanna get rid of that, and the way that we do that is through a layout inflator trick. Which is normally when you're using layout inflator, everywhere you'll see it, there'll be a third parameter there, and it'll be false. And that's because most of the time that's what you want. But in this one particular case, you want it to be true, which happens to be the default. And when it's true, what happens is that the XML that's inflated tries to attach itself to the view group that you passed in as the second parameter. In this case, it's this. And then in the XML, if you use something called the merge tag instead of a FrameLayout, what happens is, it tries to then merge these views into the parent view group without any interstitial frame layout. And then so you end up with the hierarchy that you actually want no unnecessary FrameLayouts involved. A third view-specific piece of advice I had to do is with custom drawing. So this is useful in cases of, particularly complex views, you can can save a lot of time by just drawing yourself instead of trying to figure out how to wedge these views into what you want it to look like into normal views. So a good example of this in the Trello app is the labels, so there's these green and blue and red and yellow and purple labels that are on these cards. So when we first had the Trello app out, there was like six colors, and that was it, that was the most you could apply to any card. And whoever was working on it back in the day did not know about custom drawing, and decided that those would just be six views. So that meant that every single card potentially have six views inflated. But then later on, Trello changed this, that it allowed any number of labels to be drawn, so then you could end up with this nightmare scenario, where every single card could have dozens of labels on them if someone's going really crazy. And then we were talking about recycling those views, it just gets really slow, and if you talk about putting something like this on a tablet, it gets really, really slow because you can see even more cards, and it's rendering even more views. So it was much simpler, then to just take all of those views that were being rendered, and instead, have one custom view that draws really simple shapes. So there's sort of two steps to it, and custom views used to be very intimidating to me, I used to be very scared of custom views, 'cause I thought they looked really hard, but they really are not. And the first step is just telling the custom view how big it should be. That is, how much space does it need to take up? 'Cause I have my label view, which is really nice, but no one knows exactly how much space it's going to take up. So on measure is what you use to tell any parent view group how much space you need. And it turns out, a lot of the times, you can actually skip this whole step entirely. And the reason I say that is because in any view, you can specify, "I want this view to be 48 dp by 48 dp." If it turns out that your custom view is just always going to be the same size, like skip this entirely, just define it in your XML, and you don't have to worry about that. In this particular case because the size varies based on the number of labels, I had to write my own measure. And so a quick way of going going through onMeasure, the message signature that gets called, you have these width measureSpec, and height measureSpec, which was sort of confusing to me at first, but it turns out that these are just packed integers. So it's a single integer that, these two parameters basically take the place of four parameters, which is that a width mode in size and a height mode in size. So the size is just a dimension value, the mode though is telling you how it wants you to handle that particular size that it passed. And there's three different MeasureSpecs for the mode. One is exactly, which means the parent view group wants you to be this exact size. The other is at_most, so it take up as much space as possible, and undefined means you get to define whatever ideal width you would like in the situation. And so your typical onMeasure looks something like this, where you grab, and this is just for the width, and then you would copy the same code for the height. You'd grab the mode and the size, if the measure spec is exactly, you probably just wanna pass back the size that it gave you. You don't wanna screw up the parent view group too much, or else it might get confused. Otherwise, calculate what your desired width is, and if the width spec is at most, then make sure that whatever your desire width is not larger than that size. Otherwise, if it's undefined, you just get to pick whatever desired width you want. Then, once you've done this for both the width and the height, you are by contract required to call set measure set dimension, in order to tell the view what you decided for the width and height. 'Cause there's no return value for onMeasure, you just actually have to call this measure at the end. So that's measuring how big the view is, and the second is onDraw, and this one is pretty simple, it just gives you a canvas, and you draw. And so I'm gonna leave this up to you, because what am I gonna say about canvas? This is not a talk about how to use canvas. Another thing worth considering, is maybe in some cases, you don't actually need a custom view, you could just write your own custom drawable. And the advantage here is there you could take this custom-written code and apply it to any different view. So that's good if you want so me special custom background of sorts. In that case, onmeasure just becomes something like get intrinsic height and get intrinsic width on a drawable, and then ondraw becomes draw. But again, I don't wanna spent do much time on this, you can research more about it later, I highly recommend Cyril Motier gave a talk about Android drawables a few years ago, and I highly recommend that talk if you wanna learn more about that later. And I'll be posting these slides too, if you wanna get the links. Alright, styles, let's move away from views, well, not that far from views. But talk about kind of another layer above views, which is styles. So if you are applying XML to a view, this view has no style. Not because it's uncool, but because there is no style tag on it. And then, if you have a style, all it does is I'm creating some style resources, which has the same attribute inside of it, and then the view itself then applies that style on top of whatever, actually, the style's applied first and then whatever attributes are applied on top of it. But essentially then, in the same way that includes or taking layout XML and just stuffing it into a view group, styles basically take a bunch of attributes and stuff it into a view. And so where is this useful? It's very efficient when you need to style a bunch of what I call, semantically identical views the same way. And so what I mean by semantically-identically is that each view does exactly the same thing in your hierarchy. So a good example of this is a calculator, because in a calculator you want all these buttons, or at least the main number ones to look the same. Another way to put it is that all the style views should change at once. So whenever you wanna change something. So if I wanna change the text size of one of those buttons, my expectation is that all of them change at once. So that saves me a whole bundle of time. I see a lot of people though, myself in the past, especially, misusing styles in very inefficient ways, ways that end up biting you in the long-run. And one way is single-use styles, so that is you have a view that's representing a style, and that style's only used once. I feel like that's just extra work that didn't need to be there. Some people really like separating all this code out, but it's so easy to refactor later and create a style. There's even a refactoring option in Android Studio that lets you do this. So, not really necessary. But more importantly, is where you have two views that are coincidentally using the same attributes. So I've got these two text views, and I say, "Oh look, they're using the same text color and text color hint, great, I'll use a style here." But if you look at the ids, you can tell that these two mean something very different from each other, one's supposed to be title, and one's the body. And so what happens is, suppose later on I decide, "Oh I want the title to be like a different color." Well, if I change the color of the title now, that also incidentally changes the body. And so this style which was supposed to be handy is now just a hindrance, because it's very hard to modify that style without having some unintended consequences later. I liken this to in Java, imagine I have two constants, one is like the number of columns I'm gonna show in some grid. And the other is the number of retries I'll do in some http request if it fails. And so I think, "Aw, these are the same value, I'm gonna optimize this and have a single constant." And problematic for two reasons, one is that three is already a constant, but the other is I've lost all semantic meaning. These meant something very different; if I wanna increase the number of retries for http, suddenly now I've changed how my UI looks as well, incidentally. So that's mistakes people can make with styles. So themes are sort of like styles on steroids. Styles you can apply to individual views. Themes are essentially things that you can apply to multiple views at once. And so that can be a view group, it can be an activity, or it can be the entire application. It allows you to apply default styles as well, so if I come up with, I want all of my buttons to look slightly different across the app, without themes I would have to go take that style and actually add it to all of my XML. Whereas with themes I can say, I would just like to have a default style for all buttons, and it automatically gets inflated for everything. And then the last lazy thing that it helps you with, at least in the context of my talk, it allows you to configure your system-created views. So if you've got popup windows or toolbars or something that the system creates, and that's one fewer thing you have to create. But before you could theme on a view level, there was a lot of problems with oh, I have to create some attributes that affect just this one weird popup, but then it screws up another part of my app. But it's very useful for configuring just things that the system will create. So there's three ways to apply it, you can apply it like I said to the entire application, you can apply it to individual activity, if you do that, it ends up overriding whatever's in the application. And on top of that, you can apply to an individual view. And in the view case, it ends up actually overlaying, so you can just overlay like a few changes to it, an individual views theme. And the view themeing is very, very handy. I don't know if anyone here's worked in the days of Holo probably remembers that there was a holo.light.width action bar, and that was because there was no way to theme just the action bar part of the screen differently. So you have to say in the theme, I want to define most of the screen to be light, but I want this one part of it to be dark. Whereas nowadays, you can say, "I would like just a light theme," and then manually apply a dark theme to the tool bar itself, so it makes things so much easier. So in terms of theming, I highly-recommend people look into AppCompat, which is one of the support libraries that Google puts out. If you're not already using it, amongst other things it makes theming a lot easier. For one thing, it gives you material on all devices, like that's the latest design language from Google, and without this, there's a lot of subtle differences between Holo and Material, in terms of spacing, and also in terms of just the visual metaphors that they're using. And so it's so much easier to start from one single baseline, and then theme from there. Another thing is, it gives you all these baseline themes and styles. So you might wanna change the default look of all your buttons, but you don't wanna have to actually go and define a style, which defines every single attribute that a button has to have. You just wanna take the main one and tweak it a little bit, like add a little padding to all of your buttons. And so AppCompat makes it easy then, to take the AppCompat button style and extend from that, and then modify it. Without that, it becomes sort of a nightmare, especially between Holo and Material. And the third really important thing it enables is that it allows you to do view theming pre-Lollipop, in XML, and that was one of my favorite things, because Lollipop had this view theming which seemed really cool, but I was like, "Oh, but you can't get it backported." They actually did manage to backport that all the way back to I think, some API that you shouldn't even be using anymore. I think 11. Sorry people who are still having to support apps on 11. So a few examples of things you can do with themes, the one that gets touted everywhere is the color theming, so in this case, instead of having to use individual drawables for everything, I can just set up colors, and most of the things in Android will just get colored automatically. So bam, it's like a broad brushstroke you can make. These are some examples of applying default styles. So, just in case you've never seen this before, so for example the top line, defines the button style for the entire application, so that gets applied to every button. The spinner item style is handy because what if I just wanna use the built-in spinner item layout row that Android provides, but I still wanna style it a little bit, I can use that here. Text appearance is nice because then the text appearance can apply to text views, and then you can still apply another style on top of that. Another useful thing you can do with themes is that you can set up attributes which are then referenced in your XML. So in these case selectable item background, which is like one of my favorite attributes, if you refer to it with that ?attribute/ instead of the @drawable that you normally use, then it derives that value from the theme instead of going to it directly. So why is this useful, if you happen to have an app that supports multiple themes, it makes it very easy then to swap between those values, but more importantly, your system might have multiple ideas of what a selectable item backgrounds it's, because pre-Lollipop, here wasn't any Ripple drawables. It was just usually just a flat color that you changed to whenever you click on something, whereas post-Lollipop, you wanna have these ripples because it looks really cool. And so if you use a selectable item background, then the theme can automatically figure out which one it wants to take. Alright, I'm gonna do a quick water break before I move on to the next section. Sorry. Alright, resources, so, resources are you know, all the things that go into your app that aren't just pure Java code. And before I can talk about resources, I wanna talk about device configurations. So if we look at this screenshot there's like a whole bunch of things that one can derive about it in terms of its configuration. So for example, I can say it's in portrait orientation, it's got a height of 731 density in a pint of pixels, it's got a width of 411 of them, it's a Nexus 6p, so it's got a density of xxxhdpi, it happens to be in English right now, the English locale, so it's showing English U.S. and it's version 24 'cause it's running the latest n builds. So these are all things that the Android system knows about the device, and you can query this all manually on your own if you want, but actually with resources you can just have it select things automatically. And some of these device things will change throughout execution, some of them won't. So portrait vs. landscape, unless you're locking your orientation, that can change very rapidly. You change the locale, people probably won't change that often, but they can change it while your app's running. And then some things like the density and what operating system version probably aren't going to be changing while you're running your app. So what sort of things do you wanna vary on this? Well landscape versus portrait, I think is a classic example, because it usually presents a different mode of operation. The built-in calculator app, when it's in portrait, only shows four rows, but when it's got more space to stretch out, it can possibly show some of the more cool functions, just by default. Locale is a very easy one, you wanna have your app translated in a different languages, you just have have it select different text strings based on the locale, so on the left it's in English, on the right it's in Japanese. You can have things break on the width of the screen, so on the phone the card when it's opened is small enough that it just decides to take up the full width, whereas at some point, if the device gets large enough, it just looks kind of ridiculous having it be full width, and so we start having a break point at some moment with width. And another example of that would be like our search results, we have the staggered grid view, and again, on the wide tablet it wouldn't make sense to have a single column, it makes sense to fill it up as much as possible, and so we can vary the number of columns based on that. And then also on the mobile phone you can see the top result is some small board display, because it's a small device, whereas on the larger tablet, we can show the nice big rectangle, which would look nicer if that actually had a background, but whatever. So you could do this all in Java code like I said earlier, but it's a lot easier if you just leverage the resource qualifier system. And what this system does is you define alternative resources for different device configuration and then at run time Android will automatically pick the correct resource based on the device configuration. So it's go through an query everything and figure out which of the resources you defined makes most sense in this situation. And so you define this by the names of the folders, so in your resources directory if you have something that's just the default values, that means it has no resource qualifiers attached to it, it's the default fallback in all cases. Whereas if you do a single dash and then a resource qualifier, so this one has one resource qualifier, it's xxxhdpi, and you can have multiple qualifiers if you want. You can actually apply as many qualifiers as you want to a single value, although usually it isn't handy if you do it to many different values. And one other thing worth noting is that if you do have multiple qualifiers, they have to go in a particular order, so look up the documentation, the documentation has this huge table of all the different qualifiers that you can use, and you have to put them in the order of that table for Android to parse it correctly. So that same documentation page also lists out the algorithm, but pretty much it's just like a process of elimination. It tries to find the most specific resource, given the current configuration. So imagine I start with some value, and I've got something in values with smallest width 600dp, smallest width means that regardless of orientation, what is the smallest width that you can possibly have for the device, which is useful for figuring out kind of the device class, like tablet versus phone. And it also has to be in portrait. So then it would select from this if those are true, but if it turns out one or the other isn't true, then it'll start looking to see other things it can eliminate. So then it'll look maybe for just the single sw 600 dp, oh, it turns out the phone doesn't qualify for that, so then it'll see is the phone in portrait, and if it doesn't qualify for that, then it'll fall back to the base values here. So that's why it's hand to have a default value for everything, the only thing you don't really need a default value for is drawables because the way that Android works it'll automatically scale if you don't have something in the right directory. So if you only have xxxhdpi assets and your device happens to be mpi, it'll just scale everything down, which isn't great performance-wise, 'cause having to do all that extra work, but at least you don't have to worry about that when you're developing quickly. So in terms of using resource qualifiers in the correct way, what I think is important is to think of these resources as code. In particular, to think of each resource that you're inserting somewhere as a parameter to some method function, a method or function, and that the parameter's determined based on the device configuration. So for example, if you're thinking about this in terms of code, the code on the left is insane and dumb because I'd have to write a new square function per number that I want to square, whereas the one on the right has this parameter. So you wanna think of it more in terms of the things on the right. So one simple example, on that I like to use a lot, is actually letting the resource qualifier system determine some Boolean logic for me. So this is a simple one where I basically just wanna know whether it's in portrait or not. Yes, you could query this from resources fairly easily, but this is just an example. So I could say, "By default, is portrait is false, and then in particular, when it is in portrait, then it's true." And then I can get this Boolean value out. But this is really handy if you have multiple different configurations and multiple ways that that Boolean could run, it could do all that calculation for you, you don't have to think about it. A more classic example is using it for different layouts. So I could say I'm gonna call set content view, and I have these three different versions of layout, one that's the default, one that shows up in landscape orientation, and one that shows up in portrait orientation. And I made this slide far before I realized it's very improbably to actually end up ever without it being in landscape or portrait, you'd have to have a square screen for that. But basically, it's select the right one of these, but would be more clever, 'cause this you'll probably end up with some duplicated code. 'Cause chances are, there's not that much that changes between portrait and landscape, so it's then if you can use that code reuse, the include, and then it can switch on just that part of the code that changes. So I've got my LinearLayout, and inside of it somewhere there's an include, and that's the only part that changes based on orientation. So now I can have a single activity main, and I can have a layout that's the default one, and then the layout that just modifies in the portrait. Along the same lines, let's look inside that include, suppose both those includes have text view which are supposed to be pretty much the same thing, but all that they really modify on is what the text size is. But this again, this seems kind of like a waste to have two different layouts here, if all I really wanna modify is the text size. So here what I can do is I can then reference a dimension, and then that dimension can be then determined based on the qualifiers as well. And then to take this even a step further, let's suppose we have style somewhere in the application, that's the same sort of thing, all that modified is the text size, so I can again, have this be a dimension, but now this style can be applied all over the the application instead of applying that dimension trick to just one particular view. So in this example, I have an activity main on top, and then by default to go to to one include, but if it happens to be in portrait, it'll go to a different include. Both of those include a text view, which is supposed to be ostensibly the same between both, so they use the same style. And then that style, based on the current configuration determines what the text size is. So you can really go pretty deep with this and write very little duplicated code between all of your layouts, if all that you're doing is changing things based on device configuration. And as an aside, this is why generally speaking, you shouldn't override config changes on Android. That's a pretty common beginner way to get around the problem of, oh, I rotate my phone and then my activity got destroyed and I didn't want that to happen, 'cause where did my all my data go? And then someone says, "Hey, if you just override config changes, everything works out, and all of your data stays around." there's two problems with that, one is that it doesn't necessarily help you because you probably only override config changes for orientation changes, but there's a lot of other ways that the configuration could change on the fly. But for two, it means that you bypass this system entirely, cause you're basically telling the Android system, "I've got this, don't worry about it." This whole resource qualifier system is a major part of the reason why when you rotate your phone, the activity gets destroyed and recreated again, because it wants you to re-inflate everything, because when you re inflate everything something might have changed based on selecting a different layout. Alright, drawables, so this is, let's see how much time I have, good. Alright, so drawables is the last section I wanna talk about, and I wanna outline sort of a nightmare scenario that you may or may not have gone through. I certainly gone through this, many times. So imagine I'm interacting with design, and they send me a mock-up of a new screen. And in particular this is the login screen, and they wanted to add this login with SSL thing at the bottom. So then I look at this, and I start working on it, but then I tell design, "I need this asset, 'cause I'm not good at design or anything, I need you to give this to me." So design says, "Okay, sure, no problem." So they send you over a zip file, you unzip it, and you get this one file that is who knows how big this is supposed to be. So you tell design. "Okay, this isn't enough, I need more than this, I need one in all the different densities." And design says, "Oh sure," they go do some research on how that works, and then they send you back a file like this, and a zip that contains this. So now you've got all the assets that you need, but then you have to go through an rename everything and put it in the right folders, and then import into your project, it's kind of just a pain. And that's a real pain to do with every asset, and then the kicker on top of all of this is that at the very end, design says, "Actually, I wanna tweak the color, and here's a new set of assets." And so now you have to go through this whole process again, and it's a gigantic pain in the ass. So my recommendation here, I've been working with the design team at Trello, and we figured out a whole bunch of ways to reduce all of this pain and all this friction. And it's just made things so much simpler, and it's basically to think of assets as code, as much as possible. Don't think of them as bitmaps that you get from design as much as you can, think of it as things that you can execute in your application. Because then it's so much faster to tweak and change things on the fly. So the first example of this is drawable XML, which has been in Android since the very beginning. And drawable XML are resources that you can define, you can do things like draw simple shapes, you can do things like set up state selectors, so that's where if you press a button and it looks slightly different, that's a state selector. You can use it as a layer list, and this is really handy because if you have two drawables that you actually wanna layer on top of each other with multiple states in between both of them, you might have thought, "Okay, now I need to design to composite all of these images for me." Actually, you can just set up a layer list, and then change the two layers independently, you get that nice composition going on. So a detailed example for this are the login buttons that I worked on once. And so these login buttons are entirely done through drawable XML. So the first step to making these login buttons work is that I need to create that button outline. And the button outline itself is just its own file. So we're not worrying about the click state right now. And it uses a shape drawable, the first important part is you can tell it what type of shape you want, you can also make ovals and stuff like that. But you're limited to very simple shapes with drawable XML. I wanna say that it's mostly filled with transparent space, in fact, that should be the default, but on some older versions of Android it was not defaulting to transparent for solid, I don't remember exactly why. Then I do want that white outline though, so I give it a stroke, which then determines that outline, and I would like to have a small radius on it so it gets that nice little pretty button look. So that's just creating the outline, the blue actually comes from the background of the entire screen, I just put it there 'cause otherwise it would be really hard to see with my white background on the slides. So then we need to add some behavior to it. When I click it, I wanna actually be able to tell that I clicked it, and so we need to add a selector to it, which you can see in this beautiful little two frame GIF. And so the way that I'm doing this is actually layering a selector on top of that outline I just talked about. So I'm using a layer list, and that layer list allows me to take two drawables and put one on top of the other. And I'm saying the top layer is that outline that I just showed you, that's the previous code that I showed you, is what's going on top., So that's always going to be drawn. And then the other layer is a selector, and the selector just has two states in it, and one of those states is when it's pressed, then I want you to draw this other shape. So again, I'm using another shape drawable in order to determine what should be drawn inside of it, and in this case it's a little simpler, 'cause I can just say, "I want you to have a solid color, but then again, also have the corner so it doesn't end up bleeding out of the corners." And then when it's not pressed and in the default state, then it'll just be transparent. So that's great an all, but then in version 21 of Android they added these nice ripple drawables which look really pretty and doing that requires a while different set of code. And so for that, you end up using something that's a ripple XML, something we added in version 21. And that inside there is where you say what the color of the ripple should be. And then after that, I add, okay, I still have the same button outline as what's actually being drawn in that ripple, but then the last part of this is that I define a mask. And that mask basically says, "This is the outline of where the ripple should appear." And so then the actual solid color inside of it doesn't really matter, it's just the fact that this drawable will draw to this particular area that matters. And so I was able to get away with all of this and have a different version for different versions of Android, by using the resource qualifier system, so at the bottom there's this outline that I'm always going to be using, but then the default button uses the state selector, which will always work all the way back to version one of Android, I think. And then drawable v.21 for that ripple drawable, I can use there. So drawable XML, a fairly good way to skip a lot of work. And then I just had to ask design for the colors, I didn't have to ask them for anything else in particular. Vector drawables, so like I said, the shape drawables lets you draw very simple shapes, nothing complicated. Vector drawables lets you do any sort of vector drawing you want, or most vector drawing that you want. And so it allows you to do very complex shapes, and the advantage here to using a vector is that you don't have to then worry about density of the screen all that much. 'Cause before I was having to get these PNGs from design that was in all the different densities, here they can just give you a single vector, and then it's automatically drawn at whatever's the best resolution for that screen. So that's a huge time-saver, but there is one problem with the way that Android implemented vector drawable. Oh, so another point is that vector drawables were added recently in Android, but there was a back compatible library in the support libraries for using vectors all the way back to I think, 14 or something like that. But there is a big problem with the way that Android did it, which is that they came up with their own vector drawable format that is not actually SVG. And if your designers are anything like my designers, they know how to speak SVG really well, all of their tool know how to output in SVG, and none of them know how to output as vector drawables. So you need some way to convert these SVGs that your designers are giving you into vector drawables in the app. So there's sort of two ways of doing that, yeah, sad design, there's two ways of doing this. And one is in Android Studio you can say, "I want a new vector asset," and that'll bring up this nice little wizard, and then you can pass in the SVG and it'll convert that into a vector drawable as best it can. There are some SVGs that it doesn't work very well with and won't convert. So that's good, but I still am even lazier than this, because I don't wanna have to go through a wizard every time I import a new asset. So instead, we wrote his before all the vector drawable back compat stuff happened, but we're still using it, which is this Android plugin that we wrote called Victor. And what Victor does, is you define any number of source sets, anywhere that you have your SVGs, and it'll just slurp all those up, and then output something that the system can render. And for awhile it just output PNGs but then eventually we were able to get to actually grab the code that is in this new vector asset stuff and then use that to convert it straight into vector drawables. So that's great because actually with Trello, our designers have their own Git repository, which is where they put all of their compiled SVGs, and then we can just have that as a Git submodule, and import it, and then we just have to update a commit pointer to get new assets from design, that's great. And then so the last thing I'd like to say about drawables that has really saved a lot of time recently, is that difference between skeuomorphic and flat design. So skeumorphic design is where you have things that look exactly like what they're supposed to be, and so on the left is Andie Graph, which is an app that my friend wrote, which makes your phone act exactly like any TI-83, Ti-84, whatever, and look exactly like it. So it looks very realistic, that's very skeumorphic, whereas on the right you have the normal calculator, which is flat, and every button is just this flat color. And what's really nice here is that as nice as the thing on the left looks, on the right all the icons and all the text is just a flat color. And what that means is that it's very easy to tint those colors and change them on the fly. With the buttons on the left, it would be very hard to tint them in any way that would be reasonable. So actually, in the Trello app, all of our assets are flat black colors. So they're black on alpha, and then in code, we take any of those, and tint it whatever color we want. And so that's super-handy from the perspective of design, because they don't have to create multiple assets for whatever color they want. Every time they wanna change the colors, we can just say, "Oh, that's easy, let's go change it in code," and then we're done. So in terms of tinting images, there's sort of a few ways to do it, one is to do it via XML, but besides the fact that image view has had this tint attribute forever, which doesn't quite work all that well, it's not backwards compatible. It got added into recent versions of Android to be able to tint drawables in XML, but they haven't figured out any way to actually backport that functionality. So I ended up doing actually most of the tinting in code, it's very simple with image view and drawables, you can call set colorfilter, and then you just pass in the color. And then it turns out that for a black icon on alpha, you wanna use the Porter/Duff mode of source in. That's actually why the XML imageview of the tint attribute there, I don't like it very much, 'cause it uses a Porter/Duff mode that isn't compatible. I think you have to create white icons on alpha, which we're not doing at the moment. Now if you want a really comprehensive solution, this exists in the support libraries is drawable compat, and with that you actually wrap the drawable, and then you can call set tint, or set tint list on that wrapped drawable. And main advantage that has over just calling color filter directly is that it can handle tint lists, so you can have multiple different selected states for that wrapped drawable in color, and you can tint all of it equally. But since we're not actually using that in the app, we don't end up doing that very often, and so set color filter's just a faster, easier way of doing it. Anyways, so that was all that I've got. This was a slide I was told to put up as well, to let us know what you think, click the happy face if you liked it or the sad face if didn't like it. And thanks very much for coming to listen to the talk, the middle link is to my blog, where I've written more about some of these things and then some parts of this talk were taken from other talks that were more detailed, in particularly the styles and themes, I went into much more detail on that. So you can look at my old speaker deck to find those talks if you're more interested in learning some of the more nitty-gritty details there. And thank you very much. (audience applause) Were there any questions? - Yes, actually there was one of them, and the question was, whether performance was lost in virtual machines and the. My take on it is that, well, the question might mean that if you use like an emulator, and you're doing this, you'll have a slide with the graphics performance in different layouts, will it affect emulators, do you know that, if? - So yeah, so let's see here. - I guess that's what-- - I think you're talking about this one-- - [Interviewer] (Mumbles) Yeah. Could you actually find out that your layout are kind of-- - Through an emulator? No, I'd use a real device for this. - [Interviewer] Yeah, you'd use a real device. - Yeah so I wouldn't, for things like profiling, the emulator can really make things awkward because it could be the case that you have this GPU, if you're working on a Windows desktop or something, you're gonna have a GPU that's bigger than your phone, running the rendering. And that's gonna throw things off a little bit, obviously. Or it could be the case that whatever, I think it used to be that it was translating from ARM into x86, and then that would be really slow, and so all the CPU operations would be really slow. Basically yeah, I would use an actual device for all sort of profiling purposes. - Okay, and do you, you touched upon this new ConstraintLayout, is that something you played with or? - Yeah, I played with ConstraintLayout. - You believe it's the savior, or? - I hope so, 'cause it does a lot of things that, it's sort of like this composite of RelativeLayout and LinearLayout right now, that allows you to create very complex layouts without having too much nesting. And so the hope is that you'll be able to create these complex layouts without much nesting, and that through the constraint system, it can better figure out what are the more performing way to lay things out quickly. So I'm hoping that it saves us from having to use RelativeLayout more often. (laughs) - [Interviewer] Yeah, I think you have more questions. Please come down and talk with them afterwards. - Yeah. - [Interviewer] But thank you very much, Dan, and remember to rate the session and I can see some of the speakers here from the Android track, so thank you very much, it has been a pleasure. And yeah, please give him a hand. (audience applause) - Thank you.
B1 中級 GOTO 2016--高效的Android佈局--丹尼爾-盧。 (GOTO 2016 • Efficient Android Layouts • Daniel Lew) 57 5 colin 發佈於 2021 年 01 月 14 日 更多分享 分享 收藏 回報 影片單字