Making That Google Plus Profile Screen
We will see here a way to make the Google Plus profile screen. I find this screen pretty interesting (although it suffers from some minor bugs), and wanted to try to find a way to do that.
Note: I won’t talk about the weird bottom bar you have to use if you want to make a new post. I hate that ‘quick return’ bar
I’ll cut this post in three parts:
- The view hierarchy
- The parallax effect
- The sticky tabs (tabs about/posts/videos, …)
The View Hierarchy
The Container
Seems obvious we have a list at the bottom of the screen. The question is: what is this header?
I see 2 options here:
- The header is not a header of the list. That means there is a FrameLayout (or anything else) containing a header and a list (with a big padding and
clipToPadding
set to false). - The header is included in the gridview as a HeaderView.
Actually, there are some hints that indicate us that the second one is more plausible:
- We can scroll by touching anywhere in the header. Hmm ok but we also could with custom views. We would probably have to change the default behavior of the tabs by overriding the
onInterceptTouchEvent
andonTouchEvent
methods. - There is the edge effect at the top of the list. However, we could put the listView over the header in order to see the edge effect. The sticky tabs would also be visible because they are drawn after the listview’s
draw
. (See part 3). - I think this is easier to do this way (especially if you want to be compatible pre ICS).
The main xml should look like that:
1
|
|
Hmm ok it’s a
StaggeredGridView
, but it’s basically the same. Don’t blame me if I call that a ListView or a GridView thereafter.The header
Take a look at the overdraw. The header image is light red. That means pixels are drawn four times. ouch. I’m sorry but I guess it should be blue, right? I mean, there is the activity’s background, and… what else? There is obviously a perf issue here.
Note: If you want to know more about overdraws, read this post by Romain Guy.
Here is an example of hierarchy you could have:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
|
This is basically all concerning the view hierarchy.
Note: If you want to know how to make the rounded avatar, I strongly suggest you to read an excellent post by Evelio Tarazona Cáceres.
The Parallax Effect
There is a nice parallax effect on the header image. Basically, when you scroll up your list, the image has to scroll by
0.5 * translationY
instead of 1 * translationY
. You cannot cancel the translation on the parent, because it’s part of the GridView so you will have to translate the image by -0.5 * translationY
.
Just set a
OnScrollListener
like this:1 2 3 4 5 6 7 8 9 |
|
This would work. BUT:
- You don’t support pre API14 (we don’t care, but the G+ app does)
- If you use a TranslateAnimation for pre ICS devices, you will have an overdraw issue.
You just need to draw the visible part of the bitmap. Let’s create a custom ImageView, define a
setParallaxTranslation
method and override the draw
method:1 2 3 4 5 6 7 8 9 10 11 12 |
|
Then change the call from
mImageView.setTranslationY(-mListView.getChildAt(0).getTop() / 2);
tomImageView.setCurrentTranslation(mListView.getChildAt(0).getTop());
EXTRA 1:
You can add a
ColorFilter
to add a nice effect on the ImageView:1 2 3 4 5 6 |
|
With this, the ImageView will be darker when you scroll.
EXTRA 2: If you want to make more parallax stuff on your apps, take a look at theParalloid library by Christopher Jenkins.
The Sticky Header
Firstly, I would like to thank Flavien Laurent who helped me a lot for this part!
Note: The tabs are sticky and appear over the listView. That means this View is drawn over the GridView.
There are many ways to stick a header, and the simplest is to support only API14+ devices, which gives access to operations on Views like
View#setTranslationX/Y
.1st solution: The tabs are not part of the header
This seems to be the simplest way to do it, but this won’t work very well on preICS devices (who cares?).
You activity is composed of a FrameLayout containing a
ListView
and the stickyHorizontalScrollView
.1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
The
ListView
will contain the header, containing a dummy view. This view needs to has the same height as your tabs.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Now the only thing you need is to translate your sticky view:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
If you want to make it compatible preICS, do not use NineOldAndroids. NineOldAndroids is a wrapper and will use preICS animations. That means the draw of the view will be translated, but the touch events won’t be.
e.g. Imagine you have a button in 0,0 and you translate it in 150,150 with a
TranslateAnimation
. You will notice that if you click on 150,150 the button won’t be clicked but if you click on 0,0 the event will be called.
The only way to move your view preICS is to play with the margins. BUT you will have to call
requestLayout()
each time you set the new topMargin’s value. If you do that, you will notice lags. Follow this link for an example2nd solution: Dynamically change the parent of your stickyHeader view
In this solution, the tabs are part of the ListView’s header, until we need to stick them. This solution is better for the preICS devices, but requestLayout() will be called each time we change the parent (as we remove the view and then add it in the FrameLayout,
requestLayout()
will be called twice).
Here is the new Header View:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
Keep a boolean to know if you sticked your view (
mIsHeaderSticked
here).1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
|
3rd solution: Just draw the horizontalScrollView, and capture touch events
This solution is great if you only have to draw a stateless view, but can be pretty hard to implement if you have views you can interact with.
The idea here is to keep the tabs in the ListView’s header, but when we need to stick it, the
ListView
will draw the tabs over the list.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
In your custom ListView, provide a
setViewToDraw
method:1 2 3 4 5 6 7 8 |
|
and override the
dispatchDraw
:1 2 3 4 5 6 7 |
|
Your tabs should stick now but you cannot interact with them. Here is what I did, but unfortunately it doesn’t work very well, as I am unable to make the fling work… If you have any idea on how to improve it, please do not hesitate to contact me.
Firstly create an enum with the different states:
1 2 |
|
Then override the
dispatchTouchEvent
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
|
If you want to use this solution, you will clearly need to improve my implementation.
4th solution: Duplicate the Tabs view and just make it visible/invisible
This is not a really great way to implement the sticky tabs but this will work and you will avoid the calls to
requestLayout()
You will need to copy paste the tabs view in the header’s xml and in the main view’s xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
Please note this solution is not a great solution because you have duplicate views, and you will have to set twice your clickListeners.
Here is the overdraw of the sticky tabs:
We can notice the view is drawn twice, so I bet they use the solution 3 or 4.
Conclusion
I showed you 4 ways to make this screen:
- The tabs are in the main view’s xml, and we use the setTranslationY method to make it move (playing with margins on pre ICS devices)
- The tabs are in the listview’s header, and we change the tabs container’s parent when we need to stick it (but this calls requestLayout twice)
- The tabs are in the listview’s header, and we just redraw them over the list when we need to stick them (but the touch events are pretty hard to implement)
- The tabs are both in the main view and the listview’s header, and we play with setVisibility(INVISIBLE|VISIBLE), but we have a duplicated view, and we need to set clickListeners twice for each tab.
I was incapable to find a ‘good’ way to implement that sticky tabs but if you have any idea, please do not hesitate to comment or contact me! And if you don’t, then share it!
from :http://antoine-merle.com/
No comments: