Make your own sliding menu on Android tutorial – Part 2
Issue #152
This is the part 2 of the tutorial. If you forget, here is the link to part 1.
Link to Github
In the first part, we learn about the idea, the structure of the project and how MainActivity uses the MainLayout. Now we learn how to actually implement the MainLayout
DISPLAY MENU AND CONTENT VIEW
First we have MainLayout as a subclass of LinearLayout
1 | public class MainLayout extends LinearLayout |
We then need declare the constructors
1 | public MainLayout(Context context, AttributeSet attrs) { |
and override some useful methods
1 |
|
onAttachedToWindow() is called when MainLayout is attached to window. At this point it has a Surface and will start drawing. Note that this function is guaranteed to be called before onDraw. Here we set child views to our view and content variable
1 | menu = this.getChildAt(0); |
and initially hide the menu. Note that View.GONE tells the view to not take up space in the layout
1 | menu.setVisibility(View.GONE); |
In onMeasure(), we compute menuRightMargin, this variable is the amount of right space the menu should not occupy. In this case, we want the menu to take up 90% amount of the screen width. onMeasure() is called to ask all children to measure themselves and compute the measurement of this layout based on the children
1 |
|
Finally, we need to override onLayout(), this is called from layout when this view should assign a size and position to each of its children. This is where we position the menu and content view.
1 |
|
Note for the use of the contentXOffset variable. It is the content that is moving, not the menu. So contentXOffset is used to translate the content horizontally when it is moving
ADDING ANIMATION
So the main idea of sliding menu is to change contentXOffset and call offsetLeftAndRight for the content to move the content. But for the content ‘s new position to survive, we need to actually layout it on onLayout(), as shown in previous code snippet For more information, see Flyin menu using offsetLeftAndRight not preserving after a layout
To better control sliding state, we declare MenuState enumeration
1 | private enum MenuState { |
HIDDEN state is when menu is fully hidden, and SHOWN state is when menu is fully shown. HIDDING state is when menu is about to hide, and SHOWING state is when menu is about to show. Initially currentMenuState is set to HIDDEN so that the menu won’t show up on first launch.
The main method of our MainLayout is toggleMenu, which, as it name implied, allow us to toggle menu
1 | public void toggleMenu() { |
Here we use a Scroller to faciliate sliding animation. Note that Scroller does not perform any visual effect, it is just a base for us to track animation by querying the Scroller’s methods. Bills has a good answer on SO Android: Scroller Animation?
The Scroller uses a custom Interpolator to make the sliding more natural. It moves faster in the end. The formula is here
1 | interpolator(t) = (t-1)5 + 1 |
If the menu is in HIDDEN state, we set its visibility to VISIBLE and start scrolling. Here the content is moving horizontally, so we scroll from left edge to menu width. Note that menu takes up only 90% of the screen width.
If the menu is in SHOWN state, we start scrolling from the content ‘s current x position to the left edge.
The 3rd parameter to the startScroll() method is the distance we want to scroll, a negative sign indicates that we want to scroll from right to left.
You can tweak SLIDING_DURATION and QUERY_INTERVAL to your desire. SLIDING_DURATION is the duration of the scrolling. QUERY_INTERVAL is how often we perform querying the Scroller for information. I set it to 16ms so that we have an fps of about 60, which is too high :D
Here the querying is achieved via calling adjustContentPosition() in MenuRunnable
1 | // Begin querying |
1 | // Query Scroller |
Here we call computeScrollOffset to check if the scrolling is finished or not
1 | private void adjustContentPosition(boolean isScrolling) { |
We base on getCurrX to update out contentXOffset and translate content view. Remember to call invalidate() everytime the content position is changed. We continue moving the content view until scrolling is finished
Finally, in onMenuSlidingComplete(), we set the currentMenuState accordingly
1 | private void onMenuSlidingComplete() { |
HANDLING GESTURE
To support gesture, we first attach OnTouchListener to the content view. We do this in onMeasure()
1 | content.setOnTouchListener(new OnTouchListener() { |
And in onContentTouch() we handle the ACTION_DOWN, ACTION_MOVE and ACTION_UP to allow dragging the content view
Please note that we use getRawX() instead of getX() for consistent behavior. More information see PeyloW ‘s answer here How do I know if a MotionEvent is relative or absolute?
Here I use curX and diffX to track previous position and how the difference in distance. When user is dragging, we continuously update the content view ‘s position. Please also prevent user from dragging beyond the left edge and right margin border
When the user release his/her finger, we base on lastDiffX to decide if the menu should show or hide.
1 | public boolean onContentTouch(View v, MotionEvent event) { |