为了账号安全,请及时绑定邮箱和手机立即绑定

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

标签:
Android

基础使用

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

Google Desgin GuildLines内的介绍

官方教程: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


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
   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  
       }  
   }


来反射设置左侧的触发区域,类似地可以修改右侧触发区域以及打开动画的间隔(当然你也可以直接去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
   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
  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
  16
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();  
       }  
   };

原文链接:http://www.apkbus.com/blog-705730-61033.html

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消