Create custom layouts
More often than not you will find yourself in a position where you need some…custom behavior. Whether you want a view to keep a 16:9 aspect ratio, or want a view to scroll—and still fill the screen.
Don’t be afraid to create and use your own layouts. Not only will they have great reusability, they will save you time and ease your life. If you start repeating the same pattern in your layout files…maybe you should just create a custom View. Your layout will stay clean and simple while you have better maintainability when something changes.
Most important of all: If you like using
ViewTreeObserver to adjust your layouts drop everything right now. This post is for you.
What about ViewTreeObserver?
As the name suggests you will observe layout changes and react to those changes thus triggering yet another layout pass instead of just declaring how big you wanted your view to be in the first place. In my opinion there are little use cases where the usage of
ViewTreeObserver is acceptable (e.g. for animating some background image that depends on some heights) but I would like to claim this is not what it is used for most of the times. Avoid the unnecessary layout passes which could also impact your performance and do your layouting where it belongs.
If you have some dynamic sizes you should go and take part in the layout process yourself. Keep your code clean and maybe create something reusable in the process.
A square ImageView
I know I said “layouts”, but I want to start with an easy example. The goal is to have a square
height == width. The height can not just be determined before, since the drawable can scale, doesn’t have a 1:1 aspect ratio, or you use some custom drawable that takes the views dimensions.
While this requirement may seem abstract, I just recently had this exact use case, where I wanted to have draw a circle in the background with a scalable graphic above. This along with a more complex layout and keeping things squary or circly was not that simple any more.
I hope you did think along and had some thought about how you would take this on. Constraining a view to be square should be easy enough and as mentioned earlier you could use a
ViewTreeObserver. Please don’t.
Square the View
Since we simply extend
ImageView you should stick to using
ImageView in your code. Let the
ImageView do the heavy lifting and just add the contraint for the height. Omitting the constructors, the task is simple enough and this is literally all of the code you need.
If you don’t know about measuring, this is where a view’s size is determined. The
MeasureSpec contains the suggested or available height. By calling
setMeasuredDimension(with, height) the view defines its size, but in this case this is handled by the super.
The sample above could also have been solved using a custom layout which would only assign equal width and height values to the view in the first place, but this approach—while still valid—would not be very reusable by itself unless you have more specific constraints. And there are probably a bunch of other solutions to this problem, you should just try to get the right tool for the job.
Next I want to create a layout where there is a full screen View that has additional content below the fold. Just check the image for an idea of what I’m talking about.
The problem here is that the height of the top view will vary with every device. And maybe you have additional constraints. The basic approach would be done by putting a
LinearLayout inside a
ScrollView. The only further problem would be how to size the top view to be screen filling.
Again, please don’t use a
ViewTreeObserver. Yes, it will work, but it is not pretty. And it probably does not perform well either.
Creating the layout—A first approach
Since creating a complete custom layout is a little bit more complicated, I just want to extend
LinearLayout. I basically need its behavior, but I just want to add my constraints on the first view.
While admittedly this is a little bit dirty, go ahead and try it. It will work. We got the expected behavior and there is no
ViewTreeObserver involved. We apply the height during the layout process and are not wasting any resources. I don’t like this solution, because
getMeasuredHeight() does not seem too good to me. So let’s go one step further and create our own
Creating a custom layout
We have just been subclassing the
LinearLayout and were hoping for it to work. This might break or introduce bugs, since—let’s face it—the
LinearLayout has a couple of nice features with which we might conflict.
Measuring and layouting views is in fact a lot easier than it may sound, so I want to show one other solution to this same problem. For simplicity reasons I will only support 2 views. Adding loops to have an arbitrary amount of children in this case would just complicate things. The goal was to have one full screen view with another below the fold which is exactly what we will do now.
MeasureSpec to read and create the necessary layout constraints. While this might seem overwhelming I have to say, once you start creating custom layouts you will get used to this very quickly, just remember the 3 different kinds of layout sizes.
AT_MOSTnot bigger than
EXACTLYyou determine the size
UNSPECIFIED…do what you want
When layouting views you can assign them sizes or let them choose their own. When you feel curious, you can always look at the code from the Android framework and have a look at how they decided to handle things.
The code above deals with 2 views and they now have measured dimensions. All that remains to do is layouting them within out
ViewGroup. Since this is a simple example we just draw the first view to the top and the second one below.
That’s it! Of course there is still a lot of room for improvement, but we just created our own
ViewGroup that takes care of measuring and layouting its views. Once you get started creating your own views, you’ll quickly start reaping the benefits. You will be much quicker with some special use cases and you could even gain the little extra performance you might need.
Some things you should always keep in mind are the framework methods which can help you a lot. In the code above I made use of
getChildMeasureSpec() because I did not want to check for the different values myself. The child could have a fixed height, match the parent, or wrap its content, but this method will just take care of all those cases.
There are more, and if you are a little bit like me you will discover them bit by bit with some amazement and add them to your personal toolkit.