[Android] Open Source China source code analysis II --- DrawerLayout, drawerlayout source code
From the startup interface to the main interface, the DrawerLayout under the v4 package is used. The activity_main.xml file is as follows:
<!-- A DrawerLayout is intended to be used as the top-level content view using match_parent for both width and height to consume the full space available. -->
<android.support.v4.widget.DrawerLayout
android:id="@+id/drawer_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="net.oschina.app.ui.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="@+id/realtabcontent"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/windows_bg">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dip">
<net.oschina.app.widget.MyFragmentTabHost
android:id="@android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dip"/>
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="?attr/lineColor"/>
</RelativeLayout>
<! -- Quick operation button -->
<ImageView
android:id="@+id/quick_option_iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:contentDescription="@null"
android:src="@drawable/btn_quickoption_selector"/>
</FrameLayout>
</LinearLayout>
<! -- Slide menu on the left -->
<fragment
android:id="@+id/navigation_drawer"
android:name="net.oschina.app.ui.NavigationDrawerFragment"
android:layout_width="@dimen/navigation_drawer_width"
android:layout_height="match_parent"
android:layout_gravity="start"
tools:layout="@layout/fragment_navigation_drawer"/>
</android.support.v4.widget.DrawerLayout>
Definition of the sidebar layout fragment_navigation_drawer.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="?attr/layout_bg_normal" >
<net.oschina.app.widget.CustomerScrollView
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1">
<include layout="@layout/fragment_navigation_drawer_items"/>
</net.oschina.app.widget.CustomerScrollView >
<include layout="@layout/fragment_navigation_drawer_foot"/>
</LinearLayout>
The layout of fragment_navigation_drawer_items.xml and fragment_navigation_drawer_foot.xml is relatively simple.
This section describes the custom control CustomerScollView, which inherits ScrollView:
package net.oschina.app.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.TranslateAnimation;
import android.widget.ScrollView;
/**
* ScrollView that can be dragged
*
*/
public class CustomerScrollView extends ScrollView {private static final int size = 4;
private View inner;
private float y;
private Rect normal = new Rect();
public CustomerScrollView(Context context) {super(context);
}
public CustomerScrollView(Context context, AttributeSet attrs) {super(context, attrs);
}
@Override
protected void onFinishInflate() {if (getChildCount() > 0) {inner = getChildAt(0);
}
}
@SuppressLint("ClickableViewAccessibility")@Override
public boolean onTouchEvent(MotionEvent ev) {if (inner == null) {return super.onTouchEvent(ev);
} else {commOnTouchEvent(ev);
}
return super.onTouchEvent(ev);
}
public void commOnTouchEvent(MotionEvent ev) {int action = ev.getAction();
switch (action) {case MotionEvent.ACTION_DOWN:
y = ev.getY();
break;
case MotionEvent.ACTION_UP:
if (isNeedAnimation()) {// Log.v("mlguitar", "will up and animation");animation();
}
break;
case MotionEvent.ACTION_MOVE:
final float preY = y;
float nowY = ev.getY();
/**
* Size = 4 indicates that the drag distance is 1/4 of the screen height
*/
int deltaY = (int) (preY - nowY) / size;
// Scroll
// scrollBy(0, deltaY);
y = nowY;
if (isNeedMove()) {if (normal.isEmpty()) {normal.set(inner.getLeft(), inner.getTop(),
inner.getRight(), inner.getBottom());
return;
}
int yy = inner.getTop() - deltaY;
// Move the Layout
inner.layout(inner.getLeft(), yy, inner.getRight(),
inner.getBottom() - deltaY);
}
break;
default:
break;
}
}
public void animation() {TranslateAnimation ta = new TranslateAnimation(0, 0, inner.getTop(),
normal.top);
ta.setDuration(200);
inner.startAnimation(ta);
inner.layout(normal.left, normal.top, normal.right, normal.bottom);
normal.setEmpty();
}
public boolean isNeedAnimation() {return !normal.isEmpty();
}
public boolean isNeedMove() {int offset = inner.getMeasuredHeight() - getHeight();
int scrollY = getScrollY();
if (scrollY == 0 || scrollY == offset) {return true;
}
return false;
}
}
Let's take a look at the effect:
You can see that when sliding up, there will be a significant rebound effect.
The View layout of the sliding menu of the oschina client uses the ScrollView that can be dragged. The class file is mermerscrollview.
- The target of the drag-and-drop operation is the Layout View of the menu in ScrollView. Therefore, in the onFinishInflate () function of CustomerScrollView, getChildAt (0) is used to obtain the View of the menu layout, this is the first step to get the object to be dragged. OnFinishInflate () indicates the layout file loaded by <include layout = "@ layout/fragment_navigation_drawer_items"/> in the source code.
- The drag-and-drop process is actually a process of "press-Move-lift". Therefore, you need to override onTouchEvent (MotionEvent ev). The move process actually moves the menu view according to the moving direction and distance, how can this function be implemented? The most important thing in the source code is the line of code inner. layout (inner. getLeft (), yy, inner. getRight (), inner. getBottom ()-deltaY); the four parameters of this method are relative to the coordinate origin of the inner control ScrollView. Not very familiar, you can look up the relevant knowledge of coordinates.
- When the finger is lifted, that is, MotionEvent. when the ACTION_UP event occurs, the view After dragging is restored to the original position, and an animation is attached to the movement process. Because the movement actually changes the position, the TranslateAnimation is used, because it is drag-and-drop, the start and end coordinates of X are both 0, and the start and end coordinates of Y are as follows: I believe that after reading the blog, I will understand why. Then the problem arises. If you want to automatically move it back, the trigger time will be in MotionEvent. in ACTION_UP, how to save the original position? Because four parameters in the upper left and lower right are required for moving, we can see this variable private Rect normal = new Rect () in CustomerScrollView. set (inner. getLeft (), inner. getTop (), inner. getRight (), inner. getBottom (); Method to record the initialization position of the menu view.
- After careful consideration, we found that the condition scrollY = 0 is actually scrolled to the top, while scrollY = offset is scrolled to the bottom, you can drag one of the two conditions to achieve the drag effect. Int offset = inner. getMeasuredHeight ()-getHeight (); equivalent to its own height minus the actual height that can be seen is equal to the height that is not seen.
// Whether to move
public boolean isNeedMove() { int offset = inner.getMeasuredHeight() - getHeight();
int scrollY = getScrollY();
if (scrollY == 0 || scrollY == offset) { return true;
}
return false;
}
- Why does the isNeedAnimation () function be required in the source code? Because inner. layout (normal. left, normal. top, normal. right, normal. bottom) is also used to restore to the original position. Therefore, normal must first have four parameter values. However, this normal value is valid only when the preceding conditions are met.
- Why do we need to leave this normal. setEmpty (); empty after the drag and drop occurs and the original position is restored? What is its intention? If you think about it carefully, you will find that the four values in the upper left and lower right of the normal set meet one of the 2.4 conditions and the specific value will exist. Therefore, this normal operation has two different Rect types. at the top, the four values in the upper left and lower right are (, the width of the actual menu is DP, and the actual measurement height of the menu), and the four values in the upper left and lower right are (0, negative [actual menu height minus screen height], actual menu width 240dp, screen height), so you need to clear.