相信经常使用移动应用的用户都很熟悉侧滑菜单栏, 下拉, 下弹, 上弹等应用场景, 几乎主流的移动应用无论IOS 还是Android都能看到. 2.3以前的时候, 很多第三方比如SlidingMenu, MenuDrawer, ActionbarSherlock等等都很大程度的丰富和深化了这种交互理念.能让小小的屏幕, 容纳更多的交互接口. 也是这种趋势, Android官方在v4终于推出了DrawerLayout. 表示对侧滑的重视与肯定.
唠叨到这了. 去看了DrawerLayout的源码和官方示例. 官方提供的DrawerLayout已经封装的很好,可拿来即用.其实现原理, 就是使用上篇提及的ViewDragHelper去实现.而ViewDragHelper又借助View和Scroller,去实现真正的拖曳移动效果.为了加深对ViewDragHelper的认识, 这次我也来仿照官方的NavigationDrawer示例效果,做一下.做得不好的地方,请提出.谢谢哈.
因为是仿, 所以原理是跟NavigationDrawer一样的, 图片也大部分借了官方例子的图片. NavigationDrawer的实现关键是DrawerLayout, 它利用了ViewDragHelper. 当然,不只是ViewDragHelper, 还有其他辅助类.因为NavigationDrawer是结合ActionBar去做的, 所以也使用了ActionBarDrawerToggle作为切换侧滑菜单的开关. 但其实实现类似官方的效果, 只用ViewDragHelper也是够的. 因为我们的目的就是学ViewDragHelper. 如果对ViewDragHelper不了解,可以去上篇文章或者官网去看文档先了解下相关信息.
先上个效果图(额, 手机录屏后变横屏效果了 =,=)
下面上代码:
布局文件:
activity_main.xm
DrawerLayout是官方已经封装好的View类.于是我也使用ViewDragHelper封装了类似的View, SlidingMenu.
public class SlidingMenu extends FrameLayout {
private static final String TAG = "SlidingMenu";
private ViewDragHelper mDragHelper;
private int minValue = 20; //dp
//边缘可触临界值
private int leftEdgeMinSize = minValue;
//子view左侧(LEFT)值
private int leftValue;
private View childViewA;
// private View childViewBG;
private boolean slidingMenuOpenSate = false;
//注意的几个点,
//如果mDragHelper.settleCapturedViewAt(left, top);方法去移动View,必须使用invalidate()刷新View才有效果.
public SlidingMenu(Context context)
{
this(context,null);
}
public SlidingMenu(Context context, AttributeSet attrs)
{
this(context,attrs,0);
}
public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
init();
}
private void init()
{
//为了提高兼容性,new ViewDragHelper()这个创建方法是私有的,只能通过Create()这个工厂方法去创建对象
mDragHelper = ViewDragHelper.create(this, 1.0f, new sCallBack());
int eValue = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, minValue ,
getResources().getDisplayMetrics());
leftEdgeMinSize = eValue > minValue ? eValue : minValue;
}
@Override
protected void onFinishInflate() {
childViewA = findViewById(R.id.ly_main_a);
// childViewBG = findViewById(R.id.content_frame);
}
/**
* 拖曳监听接口,要使用ViewDragHelper,必须实现该接口类
*/
private class sCallBack extends ViewDragHelper.Callback
{
//该方法必须实现
@Override
public boolean tryCaptureView(View child, int pointerId)
{
return childViewA == child;
}
@Override
public void onViewDragStateChanged(int state)
{
if(state == ViewDragHelper.STATE_IDLE)
{
//IDLE
if(childViewA.getLeft() >= 0)
{
slidingMenuOpenSate = true;
}
}
else if(state == ViewDragHelper.STATE_DRAGGING)
{
//Drag
}
else if(state == ViewDragHelper.STATE_SETTLING)
{
//Settle
}
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy)
{
if(changedView != null)
{
float alp = (float)(1 + (float)Math.abs(left)/leftValue);
if(left <= leftValue)
{
changedView.setAlpha(0.0f);
}
else
{
changedView.setAlpha(alp);
}
}
}
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {}
//手势释放子view时会回调该方法
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel)
{
if(xvel < leftValue/3)
{
closeMenu();
}
else if((xvel + leftValue/3) > 0)
{
openMenu();
}
else
{
if(releasedChild.getLeft() > (leftValue - leftValue/3))
{
openMenu();
}
else
{
closeMenu();
}
}
}
@Override
public void onEdgeTouched(int edgeFlags, int pointerId) {}
@Override
public boolean onEdgeLock(int edgeFlags) {
return false;
}
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {}
@Override
public int getOrderedChildIndex(int index) {
return index;
}
@Override
public int getViewHorizontalDragRange(View child) {
return 0;
}
@Override
public int getViewVerticalDragRange(View child)
{
return leftValue;
}
//实现水平拖曳的重要方法,返回的值是实现子view被水平拖曳移动的值
@Override
public int clampViewPositionHorizontal(View child, int left, int dx)
{
final int paddingLeft = getPaddingLeft();
//限制子view的拖曳不超出父view的左右边缘
//如果直接return left; 也是可以的.但子view的拖曳就可以滑出父view以外位置了
// final int resultLeft = Math.min(Math.max(paddingLeft,left),
// getWidth() - getChildAt(1).getWidth());
// return resultLeft;
final int resultLeft = Math.max(leftValue , Math.min(left,0));
return resultLeft;
}
//实现垂直拖曳的重要方法
@Override
public int clampViewPositionVertical(View child, int top, int dy)
{
final int paddingTop = getPaddingTop();
final int resultTop = Math.min(Math.max(paddingTop,top),
getHeight() - childViewA.getHeight());
return resultTop;
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
final int action = MotionEventCompat.getActionMasked(ev);
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP)
{
if(mDragHelper != null)
//取消或手指放开,都应当cancel()
mDragHelper.cancel();
return false;
}
return mDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev)
{
if(ev.getAction() == MotionEvent.ACTION_UP ||
ev.getAction() == MotionEvent.ACTION_CANCEL )
{
if(childViewA != null)
{
if(slidingMenuOpenSate && ev.getX() > childViewA.getWidth())
{
closeMenu();
}
}
}
if(mDragHelper != null)
{
mDragHelper.processTouchEvent(ev);
return true;
}
return false;
}
@Override
public void computeScroll()
{
if (mDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed,l,t,r,b);
leftValue = leftEdgeMinSize - childViewA.getWidth();
}
protected void openMenu()
{
if(mDragHelper != null)
{
if(mDragHelper.smoothSlideViewTo(childViewA,0,0))
{
ViewCompat.postInvalidateOnAnimation(this);
slidingMenuOpenSate = true;
}
}
}
protected void closeMenu()
{
if(mDragHelper != null)
{
if(mDragHelper.smoothSlideViewTo(childViewA,leftValue,0))
{
ViewCompat.postInvalidateOnAnimation(this);
slidingMenuOpenSate = false;
}
}
}
//获取侧滑菜单栏展开状态
public boolean getSlidingMenuOpenSate()
{
return slidingMenuOpenSate;
}
}
目的是为了熟悉ViewDragHelper这个类以及实现侧滑的效果, 所以SimpleSlidingMenu这个类并没有封装做的很复杂,思路还是能看清的吧.对于不熟悉的东西, 我原则是一般先做出来再说, 至于做得好不好,怎么优化等等,都是等先有个哪怕是粗糙的成品出来了再说. 不然胡想一大堆, 而且什么也没做成,效率太低.当然,主要的思路还是要有的嘛.
strings.xml中的引用数组:
- Mercury
- Venus
- Earth
- Mars
- Jupiter
- Saturn
- Uranus
- Neptune
然后是MainAcitvity
public class MainActivity extends Activity {
private LinearLayout linearLayout;
private ListView listView;
private SlidingMenu drawerLayout;
private String[] mPlanetTitles;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
drawerLayout = (SlidingMenu)findViewById(R.id.drawer_layout);
linearLayout = (LinearLayout)findViewById(R.id.ly_main_a);
mPlanetTitles = getResources().getStringArray(R.array.planets_array);
listView = (ListView)findViewById(R.id.listview_main);
listView.setAdapter(new ArrayAdapter(this,R.layout.drawer_list_item,mPlanetTitles));
listView.setOnItemClickListener(new DrawerItemClickListener());
}
private class DrawerItemClickListener implements ListView.OnItemClickListener
{
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id)
{
listView.setSelection(position);
selectItem(position);
}
}
private void selectItem(int position)
{
// update the main content by replacing fragments
Fragment fragment = new PlanetFragment();
Bundle args = new Bundle();
args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);
fragment.setArguments(args);
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
// update selected item and title, then close the drawer
listView.setItemChecked(position, true);
//折合侧滑菜单
drawerLayout.closeMenu();
}
public static class PlanetFragment extends Fragment
{
public static final String ARG_PLANET_NUMBER = "planet_number";
public PlanetFragment()
{
// Empty constructor required for fragment subclasses
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View rootView = inflater.inflate(R.layout.fragment_planet, container, false);
int i = getArguments().getInt(ARG_PLANET_NUMBER);
String planet = getResources().getStringArray(R.array.planets_array)[i];
int imageId = getResources().getIdentifier(planet.toLowerCase(Locale.getDefault()),
"drawable", getActivity().getPackageName());
((ImageView) rootView.findViewById(R.id.image)).setImageResource(imageId);
getActivity().setTitle(planet);
return rootView;
}
}
}
MainActivity的实现过程,跟NavigationDrawer类似, 都是利用Fragment替换某个ID的Layout. 这里要说明下, MainActivity中创建和替换PlanetFragment的相关方法源码是用了示例的源码. 毕竟不想重复造轮子嘛. 不同的是,示例直接用ListView作为侧滑的菜单, 但这样有点局限了. 于是改了用Layout,里面装ListView, 这样ListView同样也能被引用. 当然Layout里面装其他View或者Layout也是可以的.
最后回到侧滑这件事上,侧滑是一个不错的交互选择.官方也针对于此给出引导, 将应用的菜单导航放在左侧, 将功能放在右侧菜单.而侧滑流行了相当长一段时间, 现在已经有很多成熟的第三方提供. ViewDragHelper只是在自己实现的时候提供了一种选择. 至于是不是好的选择,跟具体的需求和实现有关.当然ViewDragHelper也不仅仅局限于简单的拖曳某个View什么的. 将它和ViewGroup,或者一些Layout类结合起来,能生产出有丰富拖曳滑动效果的容器.Then,这篇文章就写到这了,感谢你的阅读.^^