android drawer,Android DrawerLayout使用心得、踩坑经验以及定制化

严峰
2023-12-01

基础使用

这里要介绍的是google的DrawerLayout,行为可见google官方应用如gmail;而手Q的抽屉则是根据android-undergarment项目来定制的一个控件。

DrawerLayout添加在主内容区的上层,作为parent,下面的第一个child是主内容区域,第二个child则可以是其他任何东西,需要作为抽屉的view则需要声明android:layout_gravity。

DrawerLayout的setScrimColor可以设置抽屉拉出时右侧主内容剩余区域上面盖的颜色(默认0x99000000)。

高级应用

DrawerLayout默认只有在边缘的一个edge能够触发抽屉拉取的动作,而这个是通过ViewDragHelper这个类来实现的。

1

2

3

4private 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

EDGE_SIZE是触发区域,默认20dp,而BASE_SETTLE_DURATION和MAX_SETTLE_DURATION则是控制抽屉从打开到关闭之间的这个间隔。由于是私有静态常量,可以通过

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23public 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

}

}

来反射设置左侧的触发区域,类似地可以修改右侧触发区域以及打开动画的间隔(当然你也可以直接去ViewDragHelper里面修改)。

不建议自己处理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;

}

多点触摸的时候DrawerLayout抛出ArrayIndexOutOfBoundsException

这是由于多点触摸时候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

36public 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

2

3

4

5

6

7

8/**

* 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

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16private 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 

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

5private final Runnable mPeekRunnable = new Runnable() {

@Override public void () {

//peekDrawer();

}

};

 类似资料: