ViewDragHelper实战 自己打造Drawerlayout

jopen 4年前

转载请标明出处:
http://blog.csdn.net/lmj623565791/article/details/47396187
本文出自:【张鸿洋的博客】

一、概述

中间拖了蛮长时间了,在上一篇我们介绍了ViewDragHelper,详情:ViewDragHelper完全解析,当然了,上一篇都是小示例的形式去演示代码功能,并不能给人一种实用的感觉。那么,本篇博客就准备实用ViewDragHelper来实现一个DrawerLayout的效果,当然了,大家也可以选择直接去看Drawerlayout的源码。相信侧滑大家肯定不陌生,网络上流传无数个版本,其实利用ViewDragHelper就能方便的写出比较不错的侧滑代码~

那么首先上个效果图:

ok,其实和DrawerLayout效果类似,当然不是完全的一致~~本篇的主要目的也是演示VDH的实战用法,大家在选择侧滑的时候还是建议使用DrawerLayout.

二、实战

(一)布局文件

首先看一下布局文件:

<com.zhy.learn.view.LeftDrawerLayout   xmlns:android="http://schemas.android.com/apk/res/android"  xmlns:tools="http://schemas.android.com/tools"                                   android:layout_width="match_parent"  android:id="@+id/id_drawerlayout"                                     android:layout_height="match_parent"      >        <!--content-->      <RelativeLayout          android:clickable="true"          android:layout_width="match_parent"          android:background="#44ff0000"          android:layout_height="match_parent">            <TextView              android:id="@+id/id_content_tv"              android:layout_width="wrap_content"              android:layout_height="wrap_content"              android:text="hello world"              android:textSize="40sp"              android:layout_centerInParent="true"/>      </RelativeLayout>        <!--menu-->      <FrameLayout          android:id="@+id/id_container_menu"          android:layout_width="match_parent"          android:layout_height="match_parent"          >      </FrameLayout>      </com.zhy.learn.view.LeftDrawerLayout>

ok,可以看到我们的LeftDrawerLayout里面一个是content,一个是menu~为了方便,我们就默认child0为content,child1为menu。

(二)LeftDrawerLayout

package com.zhy.learn.view;    import android.content.Context;  import android.support.v4.widget.ViewDragHelper;  import android.util.AttributeSet;  import android.view.MotionEvent;  import android.view.View;  import android.view.ViewGroup;    import com.zhy.util.common.L;    /** * Created by zhy on 15/5/29. */  public class LeftDrawerLayout extends ViewGroup {      private static final int MIN_DRAWER_MARGIN = 64; // dp      /** * Minimum velocity that will be detected as a fling */      private static final int MIN_FLING_VELOCITY = 400; // dips per second        /** * drawer离父容器右边的最小外边距 */      private int mMinDrawerMargin;        private View mLeftMenuView;      private View mContentView;        private ViewDragHelper mHelper;      /** * drawer显示出来的占自身的百分比 */      private float mLeftMenuOnScrren;          public LeftDrawerLayout(Context context, AttributeSet attrs)      {          super(context, attrs);          //setup drawer's minMargin          final float density = getResources().getDisplayMetrics().density;          final float minVel = MIN_FLING_VELOCITY * density;          mMinDrawerMargin = (int) (MIN_DRAWER_MARGIN * density + 0.5f);            mHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback()          {              @Override              public int clampViewPositionHorizontal(View child, int left, int dx)              {                  int newLeft = Math.max(-child.getWidth(), Math.min(left, 0));                  return newLeft;              }                @Override              public boolean tryCaptureView(View child, int pointerId)              {                  L.e("tryCaptureView");                  return child == mLeftMenuView;              }                @Override              public void onEdgeDragStarted(int edgeFlags, int pointerId)              {                  L.e("onEdgeDragStarted");                  mHelper.captureChildView(mLeftMenuView, pointerId);              }                @Override              public void onViewReleased(View releasedChild, float xvel, float yvel)              {                  L.e("onViewReleased");                  final int childWidth = releasedChild.getWidth();                  float offset = (childWidth + releasedChild.getLeft()) * 1.0f / childWidth;                  mHelper.settleCapturedViewAt(xvel > 0 || xvel == 0 && offset > 0.5f ? 0 : -childWidth, releasedChild.getTop());                  invalidate();              }                @Override              public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy)              {                  final int childWidth = changedView.getWidth();                  float offset = (float) (childWidth + left) / childWidth;                  mLeftMenuOnScrren = offset;                  //offset can callback here                   changedView.setVisibility(offset == 0 ? View.INVISIBLE : View.VISIBLE);                  invalidate();              }                @Override              public int getViewHorizontalDragRange(View child)              {                  return mLeftMenuView == child ? child.getWidth() : 0;              }          });          //设置edge_left track          mHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);          //设置minVelocity          mHelper.setMinVelocity(minVel);      }        @Override      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)      {          int widthSize = MeasureSpec.getSize(widthMeasureSpec);          int heightSize = MeasureSpec.getSize(heightMeasureSpec);            setMeasuredDimension(widthSize, heightSize);            View leftMenuView = getChildAt(1);          MarginLayoutParams lp = (MarginLayoutParams)                  leftMenuView.getLayoutParams();            final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec,                  mMinDrawerMargin + lp.leftMargin + lp.rightMargin,                  lp.width);          final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec,                  lp.topMargin + lp.bottomMargin,                  lp.height);          leftMenuView.measure(drawerWidthSpec, drawerHeightSpec);              View contentView = getChildAt(0);          lp = (MarginLayoutParams) contentView.getLayoutParams();          final int contentWidthSpec = MeasureSpec.makeMeasureSpec(                  widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY);          final int contentHeightSpec = MeasureSpec.makeMeasureSpec(                  heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY);          contentView.measure(contentWidthSpec, contentHeightSpec);            mLeftMenuView = leftMenuView;          mContentView = contentView;          }        @Override      protected void onLayout(boolean changed, int l, int t, int r, int b)      {          View menuView = mLeftMenuView;          View contentView = mContentView;            MarginLayoutParams lp = (MarginLayoutParams) contentView.getLayoutParams();          contentView.layout(lp.leftMargin, lp.topMargin,                  lp.leftMargin + contentView.getMeasuredWidth(),                  lp.topMargin + contentView.getMeasuredHeight());            lp = (MarginLayoutParams) menuView.getLayoutParams();            final int menuWidth = menuView.getMeasuredWidth();          int childLeft = -menuWidth + (int) (menuWidth * mLeftMenuOnScrren);          menuView.layout(childLeft, lp.topMargin, childLeft + menuWidth,                  lp.topMargin + menuView.getMeasuredHeight());      }        @Override      public boolean onInterceptTouchEvent(MotionEvent ev)      {          boolean shouldInterceptTouchEvent = mHelper.shouldInterceptTouchEvent(ev);          return shouldInterceptTouchEvent;      }          @Override      public boolean onTouchEvent(MotionEvent event)      {          mHelper.processTouchEvent(event);          return true;      }          @Override      public void computeScroll()      {          if (mHelper.continueSettling(true))          {              invalidate();          }      }        public void closeDrawer()      {          View menuView = mLeftMenuView;          mLeftMenuOnScrren = 0.f;          mHelper.smoothSlideViewTo(menuView, -menuView.getWidth(), menuView.getTop());      }        public void openDrawer()      {          View menuView = mLeftMenuView;          mLeftMenuOnScrren = 1.0f;          mHelper.smoothSlideViewTo(menuView, 0, menuView.getTop());      }        @Override      protected LayoutParams generateDefaultLayoutParams()      {          return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);      }        public LayoutParams generateLayoutParams(AttributeSet attrs)      {          return new MarginLayoutParams(getContext(), attrs);      }        protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p)      {          return new MarginLayoutParams(p);      }    }

哈,很少的代码就完成了我们的侧滑的编写,目测如果使用横向的LinearLayout代码更短。构造方法中,主要就是初始化我们的mDragHelper了,接下来onMeasure和onLayout都比较简单,onLayout也只是去将我们的menu设置到屏幕的左侧以至于不可见。onInterceptTouchEventonTouchEvent中都只需要简单的调用mDragHelper的方法。那么核心代码都在我们的ViewDragHelper.Callback的实例中了。注意一点:我们LeftDrawerLayout默认的LayoutParams为MarginLayoutParams,注意复写相关方法。

接下来就把注意力放到我们的Callback中,我尽可能的按照调用的逻辑来解释各个方法:

  • onEdgeDragStarted 如果你仔细的看过上篇,那么一定知道这个方法的回调位置;因为我们的View不可见,所以我们没有办法直接通过触摸到它来把menu设置为captureView,所以我们只能设置边界检测,当MOVE时回调onEdgeDragStarted时,我们直接通过captureChildView手动捕获。

  • tryCaptureView 那么既然我们手动捕获了,为什么还需要这个方法呢?主要是因为当Drawer拉出的时候,我们通过拖拽Drawer也能进行移动菜单。

  • clampViewPositionHorizontal 主要是移动的时候去控制控制范围,可以看到我们的int newLeft = Math.max(-child.getWidth(), Math.min(left, 0));一定>=-child.getWidth() && <=0 。

  • getViewHorizontalDragRange 为什么要复写,我们上篇已经描述过,返回captureView的移动范围。

  • onViewReleased 则是释放的时候触发的,我们计算当前显示的百分比,以及加速度来决定是否显示drawer,从代码可以看出,当xvel > 0 || xvel == 0 && offset > 0.5f显示我们的菜单,其他情况隐藏。这里注意一点xvel的值只有大于我们设置的minVelocity才会出现大于0,如果小于我们设置的值则一直是0。

  • onViewPositionChanged 整个pos变化的过程中,我们计算offset保存,这里可以使用接口将offset回调出去,方便做动画。

ok,我们重写的所有方法描述完成了~那么博文主要内容也就结束了~哈~是不是 so easy ~!

呃,忘了MainActivity和Fragment的代码了~~按顺序贴

(三)LeftDrawerLayoutActivity

package com.imooc.testandroid;    import android.os.Bundle;  import android.support.v4.app.FragmentManager;  import android.support.v7.app.ActionBarActivity;  import android.view.Menu;  import android.view.MenuItem;  import android.widget.TextView;    import com.zhy.learn.view.LeftDrawerLayout;      public class LeftDrawerLayoutActivity extends ActionBarActivity {        private LeftMenuFragment mMenuFragment;      private LeftDrawerLayout mLeftDrawerLayout ;      private TextView mContentTv ;          @Override      protected void onCreate(Bundle savedInstanceState)      {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_left_drawer_layout);            mLeftDrawerLayout = (LeftDrawerLayout) findViewById(R.id.id_drawerlayout);          mContentTv = (TextView) findViewById(R.id.id_content_tv);            FragmentManager fm = getSupportFragmentManager();          mMenuFragment = (LeftMenuFragment) fm.findFragmentById(R.id.id_container_menu);          if (mMenuFragment == null)          {              fm.beginTransaction().add(R.id.id_container_menu, mMenuFragment = new LeftMenuFragment()).commit();          }            mMenuFragment.setOnMenuItemSelectedListener(new LeftMenuFragment.OnMenuItemSelectedListener()          {              @Override              public void menuItemSelected(String title)              {                  mLeftDrawerLayout.closeDrawer();                  mContentTv.setText(title);              }          });        }

可以看到drawer使用了一个Fragment ~~

(四) LeftMenuFragment

 import android.os.Bundle;  import android.support.annotation.Nullable;  import android.support.v4.app.ListFragment;  import android.view.LayoutInflater;  import android.view.View;  import android.view.ViewGroup;  import android.widget.ListView;    /** * Created by zhy on 15/4/26. */  public class LeftMenuFragment extends ListFragment {        private static final int SIZE_MENU_ITEM = 3;        private MenuItem[] mItems = new MenuItem[SIZE_MENU_ITEM];        private LeftMenuAdapter mAdapter;          private LayoutInflater mInflater;        @Override      public void onCreate(@Nullable Bundle savedInstanceState) {          super.onCreate(savedInstanceState);            mInflater = LayoutInflater.from(getActivity());            MenuItem menuItem = null;          for (int i = 0; i < SIZE_MENU_ITEM; i++) {              menuItem = new MenuItem(getResources().getStringArray(R.array.array_left_menu)[i], false, R.drawable.music_36px, R.drawable.music_36px_light);              mItems[i] = menuItem;          }      }        @Override      public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {          return super.onCreateView(inflater, container, savedInstanceState);      }        @Override      public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {          super.onViewCreated(view, savedInstanceState);          view.setBackgroundColor(0xffffffff);          setListAdapter(mAdapter = new LeftMenuAdapter(getActivity(), mItems));        }        @Override      public void onListItemClick(ListView l, View v, int position, long id) {          super.onListItemClick(l, v, position, id);            if (mMenuItemSelectedListener != null) {              mMenuItemSelectedListener.menuItemSelected(((MenuItem) getListAdapter().getItem(position)).text);          }          mAdapter.setSelected(position);      }          //选择回调的接口      public interface OnMenuItemSelectedListener {          void menuItemSelected(String title);      }      private OnMenuItemSelectedListener mMenuItemSelectedListener;        public void setOnMenuItemSelectedListener(OnMenuItemSelectedListener menuItemSelectedListener) {          this.mMenuItemSelectedListener = menuItemSelectedListener;      }  }        public class MenuItem {        public MenuItem(String text, boolean isSelected, int icon, int iconSelected) {          this.text = text;          this.isSelected = isSelected;          this.icon = icon;          this.iconSelected = iconSelected;      }        boolean isSelected;      String text;      int icon;      int iconSelected;  }        /** * Created by zhy on 15/4/26. */  public class LeftMenuAdapter extends ArrayAdapter<MenuItem> {          private LayoutInflater mInflater;        private int mSelected;          public LeftMenuAdapter(Context context, MenuItem[] objects) {          super(context, -1, objects);            mInflater = LayoutInflater.from(context);        }        @Override      public View getView(int position, View convertView, ViewGroup parent) {              if (convertView == null) {              convertView = mInflater.inflate(R.layout.item_left_menu, parent, false);          }            ImageView iv = (ImageView) convertView.findViewById(R.id.id_item_icon);          TextView title = (TextView) convertView.findViewById(R.id.id_item_title);          title.setText(getItem(position).text);          iv.setImageResource(getItem(position).icon);          convertView.setBackgroundColor(Color.TRANSPARENT);            if (position == mSelected) {              iv.setImageResource(getItem(position).iconSelected);              convertView.setBackgroundColor(getContext().getResources().getColor(R.color.state_menu_item_selected));          }            return convertView;      }        public void setSelected(int position) {          this.mSelected = position;          notifyDataSetChanged();      }      }

贴了一大串,主要就是Fragment的代码,内部有个ListView,所以还有个Adapter,这个Fragment在之前的博文中出现过,搬过来而已。item的布局文件就是一个TextView和ImageView~~不贴了~~大家自己下载源码~

源码点击下载(导入方式注意看下ReadMe)

ok,到此结束~~hava a nice day ~~


欢迎关注我的微博:
http://weibo.com/u/3165018720


群号:463081660,欢迎入群

微信公众号:hongyangAndroid
(欢迎关注,第一时间推送博文信息)

来自: http://blog.csdn.net//lmj623565791/article/details/47396187