基础使用
这里要介绍的是google的DrawerLayout,行为可见google官方应用如gmail;而手Q的抽屉则是根据android-undergarment项目来定制的一个控件。
官方教程:Creating a Navigation Drawer
DrawerLayout添加在主内容区的上层,作为parent,下面的第一个child是主内容区域,第二个child则可以是其他任何东西,需要作为抽屉的view则需要声明android:layout_gravity。
DrawerLayout的setScrimColor可以设置抽屉拉出时右侧主内容剩余区域上面盖的颜色(默认0x99000000)。
高级应用
DrawerLayout默认只有在边缘的一个edge能够触发抽屉拉取的动作,而这个是通过ViewDragHelper这个类来实现的。
1 2 3 4 | private static final int EDGE_SIZE = 20; // dp private static final int BASE_SETTLE_DURATION = 256; // ms private static final int MAX_SETTLE_DURATION = 600; // ms |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public static void (DrawerLayout drawerLayout, float dp) {
if (drawerLayout == null) {
return;
}
try {
// find ViewDragHelper and set it accessible
Field leftDraggerField = drawerLayout.getClass().getDeclaredField("mLeftDragger");
leftDraggerField.setAccessible(true);
ViewDragHelper leftDragger = (ViewDragHelper) leftDraggerField.get(drawerLayout);
// find edgesize and set is accessible
Field edgeSizeField = leftDragger.getClass().getDeclaredField("mEdgeSize");
edgeSizeField.setAccessible(true);
int edgeSize = edgeSizeField.getInt(leftDragger);
edgeSizeField.setInt(leftDragger, Math.max(edgeSize, ViewUtils.dpToPx(dp)));
} catch (NoSuchFieldException e) {
// ignore
} catch (IllegalArgumentException e) {
// ignore
} catch (IllegalAccessException e) {
// ignore
}
} |
不建议自己处理onTouch,会导致抽屉不能平滑跟手,比如stackoverflow上有给出以下这种方案的,简直坑爹:
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 | // ======================== 触摸事件处理 ===================================
private float startX, startY;
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
startX = ev.getX();
startY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
float endX = ev.getX();
float endY = ev.getY();
if (startX > HOT_FIELD || Math.abs(endY - startY) > SENSIBILITY_Y) {
break;
}
// From left to right
if (endX - startX >= SENSIBILITY_X) {
handled = openDrawer();
}
// From right to left
if (startX - endX >= SENSIBILITY_X) {
handled = closeDrawer();
}
break;
}
if (handled) {
mDrawerLayout.cancelChildViewTouch();
}
return handled;
}
## 坑爹的bug们
### 初始化LayoutParam时可能出错
```java
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
LayoutParams layoutParams = null;
try {
// 出现异常时,用默认值
layoutParams = new LayoutParams(getContext(), attrs);
} catch (Throwable e) {
layoutParams = null;
}
if (layoutParams == null) {
layoutParams = new LayoutParams(-1, -1);
layoutParams.gravity = Gravity.NO_GRAVITY;
}
return layoutParams;
} |
这是由于多点触摸时候requestDisallowInterceptTouchEvent和DrawerLayout的innerViews问题。自己在外面继承DrawerLayout然后改一下行为。
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 | public class extends {
public (Context context) {
super(context);
}
public (Context context, AttributeSet attrs) {
super(context, attrs);
}
public (Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private boolean mIsDisallowIntercept = false;
@Override
public void (boolean disallowIntercept) {
// keep the info about if the innerViews do requestDisallowInterceptTouchEvent
mIsDisallowIntercept = disallowIntercept;
super.requestDisallowInterceptTouchEvent(disallowIntercept);
}
@Override
public boolean (MotionEvent ev) {
// the incorrect array size will only happen in the multi-touch scenario.
if (ev.getPointerCount() > 1 && mIsDisallowIntercept) {
requestDisallowInterceptTouchEvent(false);
boolean handled = super.dispatchTouchEvent(ev);
requestDisallowInterceptTouchEvent(true);
return handled;
} else {
return super.dispatchTouchEvent(ev);
}
}
} |
这也是极其坑爹的一个bug,原因是触摸EDGE的时候,事件触发到抽屉出现有一个延时:
1 | /**
* Length of time to delay before peeking the drawer.
*/
private static final int PEEK_DELAY = 160; // ms
@Override
public void (int edgeFlags, int pointerId) {
postDelayed(mPeekRunnable, PEEK_DELAY);
} |
抽屉有STATE_IDLE, STATE_DRAGGING和STATE_SETTLING三种状态,而这个偶然状况下,已经处于STATE_DRAGGING,而这个动作打开了抽屉20dp并试图再次置回STATE_DRAGGING,
1 | private boolean (float delta, float odelta, int pointerId, int edge) {
final float absDelta = Math.abs(delta);
final float absODelta = Math.abs(odelta);
if ((mInitialEdgesTouched[pointerId] & edge) != edge || (mTrackingEdges & edge) == 0 ||
(mEdgeDragsLocked[pointerId] & edge) == edge ||
(mEdgeDragsInProgress[pointerId] & edge) == edge ||
(absDelta <= mTouchSlop && absODelta <= mTouchSlop)) {
return false;
}
if (absDelta < absODelta * 0.5f && mCallback.onEdgeLock(edge)) {
mEdgeDragsLocked[pointerId] |= edge;
return false;
}
return (mEdgeDragsInProgress[pointerId] & edge) == 0 && absDelta > mTouchSlop;
} |
但这里由于mEdgeDragsInProgress[pointerId] & edge) == edge所以阻止了DrawerLayout回到STATE_DRAGGING。
解决方案是把DrawerLayout的ViewDragCallback中的mPeekRunnable进行修改,简单粗暴。
1 2 3 4 5 | private final Runnable mPeekRunnable = new Runnable() {
@Override public void () {
//peekDrawer();
}
}; |
共同學習,寫下你的評論
評論加載中...
作者其他優質文章