You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
@OverrideprotectedvoidonAttachedToWindow() {
super.onAttachedToWindow();
// Get our 2 child Viewmenu = this.getChildAt(0);
content = this.getChildAt(1);
// Initially hide the menumenu.setVisibility(View.GONE);
}
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
menu = this.getChildAt(0);
content = this.getChildAt(1);
and initially hide the menu. Note that View.GONE tells the view to not take up space in the layout
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
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.
@OverrideprotectedvoidonLayout(booleanchanged, intleft, inttop, intright, intbottom) {
// True if MainLayout 's size and position has changed// If true, calculate child views sizeif(changed) {
// Note: LayoutParams are used by views to tell their parents how they want to be laid out// content View occupies the full height and widthLayoutParamscontentLayoutParams = (LayoutParams)content.getLayoutParams();
contentLayoutParams.height = this.getHeight();
contentLayoutParams.width = this.getWidth();
// menu View occupies the full height, but certain widthLayoutParamsmenuLayoutParams = (LayoutParams)menu.getLayoutParams();
menuLayoutParams.height = this.getHeight();
menuLayoutParams.width = this.getWidth() - menuRightMargin;
}
// Layout the child viewsmenu.layout(left, top, right - menuRightMargin, bottom);
content.layout(left + contentXOffset, top, right + contentXOffset, bottom);
}
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
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
publicvoidtoggleMenu() {
// Do nothing if sliding is in progressif(currentMenuState == MenuState.HIDING || currentMenuState == MenuState.SHOWING)
return;
switch(currentMenuState) {
caseHIDDEN:
currentMenuState = MenuState.SHOWING;
menu.setVisibility(View.VISIBLE);
menuScroller.startScroll(0, 0, menu.getLayoutParams().width,
0, SLIDING_DURATION);
break;
caseSHOWN:
currentMenuState = MenuState.HIDING;
menuScroller.startScroll(contentXOffset, 0, -contentXOffset,
0, SLIDING_DURATION);
break;
default:
break;
}
// Begin queryingmenuHandler.postDelayed(menuRunnable, QUERY_INTERVAL);
// Invalite this whole MainLayout, causing onLayout() to be calledthis.invalidate();
}
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
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
// Begin queryingmenuHandler.postDelayed(menuRunnable, QUERY_INTERVAL);
Here we call computeScrollOffset to check if the scrolling is finished or not
privatevoidadjustContentPosition(booleanisScrolling) {
intscrollerXOffset = menuScroller.getCurrX();
//Log.d("MainLayout.java adjustContentPosition()", "scrollerOffset " + scrollerOffset);// Translate content View accordinglycontent.offsetLeftAndRight(scrollerXOffset - contentXOffset);
contentXOffset = scrollerXOffset;
// Invalite this whole MainLayout, causing onLayout() to be calledthis.invalidate();
// Check if animation is in progressif (isScrolling)
menuHandler.postDelayed(menuRunnable, QUERY_INTERVAL);
elsethis.onMenuSlidingComplete();
}
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
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.
publicbooleanonContentTouch(Viewv, MotionEventevent) {
// Do nothing if sliding is in progressif(currentMenuState == MenuState.HIDING || currentMenuState == MenuState.SHOWING)
returnfalse;
// getRawX returns X touch point corresponding to screen// getX sometimes returns screen X, sometimes returns content View XintcurX = (int)event.getRawX();
intdiffX = 0;
switch(event.getAction()) {
caseMotionEvent.ACTION_DOWN:
//Log.d("MainLayout.java onContentTouch()", "Down x " + curX);prevX = curX;
returntrue;
caseMotionEvent.ACTION_MOVE:
//Log.d("MainLayout.java onContentTouch()", "Move x " + curX);// Set menu to Visible when user start dragging the content Viewif(!isDragging) {
isDragging = true;
menu.setVisibility(View.VISIBLE);
}
// How far we have moved since the last positiondiffX = curX - prevX;
// Prevent user from dragging beyond borderif(contentXOffset + diffX <= 0) { // Don't allow dragging beyond left border // Use diffX will make content cross the border, so only translate by -contentXOffset diffX = -contentXOffset; } else if(contentXOffset + diffX > mainLayoutWidth - menuRightMargin) {// Don't allow dragging beyond menu widthdiffX = mainLayoutWidth - menuRightMargin - contentXOffset;
}
// Translate content View accordinglycontent.offsetLeftAndRight(diffX);
contentXOffset += diffX;
// Invalite this whole MainLayout, causing onLayout() to be calledthis.invalidate();
prevX = curX;
lastDiffX = diffX;
returntrue;
caseMotionEvent.ACTION_UP:
//Log.d("MainLayout.java onContentTouch()", "Up x " + curX);Log.d("MainLayout.java onContentTouch()", "Up lastDiffX " + lastDiffX);
// Start scrolling// Remember that when content has a chance to cross left border, lastDiffX is set to 0if(lastDiffX > 0) {
// User wants to show menucurrentMenuState = MenuState.SHOWING;
// No need to set to Visible, because we have set to Visible in ACTION_MOVE//menu.setVisibility(View.VISIBLE);//Log.d("MainLayout.java onContentTouch()", "Up contentXOffset " + contentXOffset);// Start scrolling from contentXOffsetmenuScroller.startScroll(contentXOffset, 0, menu.getLayoutParams().width - contentXOffset,
0, SLIDING_DURATION);
} elseif(lastDiffX < 0) {
// User wants to hide menucurrentMenuState = MenuState.HIDING;
menuScroller.startScroll(contentXOffset, 0, -contentXOffset,
0, SLIDING_DURATION);
}
// Begin queryingmenuHandler.postDelayed(menuRunnable, QUERY_INTERVAL);
// Invalite this whole MainLayout, causing onLayout() to be calledthis.invalidate();
// Done draggingisDragging = false;
prevX = 0;
lastDiffX = 0;
returntrue;
default:
break;
}
returnfalse;
}
The text was updated successfully, but these errors were encountered:
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
We then need declare the constructors
and override some useful methods
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
and initially hide the menu. Note that View.GONE tells the view to not take up space in the layout
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
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.
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
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
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
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
Here we call computeScrollOffset to check if the scrolling is finished or not
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
HANDLING GESTURE
To support gesture, we first attach OnTouchListener to the content view. We do this in onMeasure()
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.
The text was updated successfully, but these errors were encountered: