您的位置:首页 > 移动开发 > Android开发

Android高手进阶篇4-实现侧滑菜单框架,一分钟集成到项目中

2013-10-14 10:39 573 查看
先来看下面的这张效果图:



上面这张效果图是百度影音的,现在在Android上很流行,最初是由facebook自己实现的,而后各大应用有跟风之势,那么这种侧滑效果是如何实现的呢?

网上现在这种侧滑菜单的例子很对,也有开源的框架sliderMenu,而且可以定义很多样式,但大部分例子,都只是实现了这种类似效果,没有实现一种可移植的框架,仅仅是单页面效果而已,而且集成起来复杂,鉴于此,我自己实现了一套侧滑菜单的框架:

1、最常用的支持左右策划

2、多个页面切换也好不费力,页面切换的逻辑已经实现好了,集成进来,只需要关注自己项目的业务逻辑

3、支持多个页面集成

4、支持退出业务逻辑

先上我自己实现的效果图:







下面说一下实现原理:

布局文件采用FrameLayout,在一个FrameLayout下有二个子布局,一个是菜单,另一个是LeftSliderLayout,而LeftSliderLayout下面可以放二个子布局:第一个是阴影布局(左边阴影),第二个是要拖动的内容。,当向右拖动LeftSliderLayout时,就显示露出菜单布局。而向左拖动LeftSliderLayout时,就覆盖菜单布局。

1.FrameLayout的布局文件local_media_fragment.xml

<?xmlversion="1.0"encoding="utf-8"?> <FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <includeandroid:id="@+id/main_layout_below"layout="@layout/main_layout_below"/> <com.zhaoxufeng.leftsliderlayout.lib.LeftSliderLayout android:id="@+id/main_slider_layout" android:layout_width="fill_parent" android:layout_height="fill_parent"> <!--ShadowChild--> <ImageView android:layout_width="15px" android:layout_height="fill_parent" android:contentDescription="@null" android:scaleType="fitXY" android:src="@drawable/main_side_shadow"/> <!--MainChild--> <includeandroid:id="@+id/main_slider_main"layout="@layout/local_media"/> </com.zhaoxufeng.leftsliderlayout.lib.LeftSliderLayout> </FrameLayout>

上面xml中main_layout_below是对应的左边菜单Menu布局文件(这个布局文件是固定的),local_media是你要的拖动布局

2、LeftSliderLayout.java代码

packagecom.zhaoxufeng.leftsliderlayout.lib; importandroid.content.Context; importandroid.graphics.Rect; importandroid.util.AttributeSet; importandroid.util.Log; importandroid.view.MotionEvent; importandroid.view.VelocityTracker; importandroid.view.View; importandroid.view.ViewConfiguration; importandroid.view.ViewGroup; importandroid.widget.Scroller; publicclassLeftSliderLayoutextendsViewGroup{ privatestaticfinalStringTAG="LeftSliderLayout"; privateScrollermScroller; privateVelocityTrackermVelocityTracker; /** *Constantvaluefortouchstate *TOUCH_STATE_REST:notouch *TOUCH_STATE_SCROLLING:scrolling */ privatestaticfinalintTOUCH_STATE_REST=0; privatestaticfinalintTOUCH_STATE_SCROLLING=1; privateintmTouchState=TOUCH_STATE_REST; /** *Distanceinpixelsatouchcanwanderbeforewethinktheuserisscrolling */ privateintmTouchSlop; /** *Valuesforsavingaxisofthelasttouchevent. */ privatefloatmLastMotionX; privatefloatmLastMotionY; /** *ValuesforVelocityTrackertocomputecurrentvelocity. *VELOCITY_UNITSindp *mVelocityUnitsinpx */ privatestaticfinalintVELOCITY_UNITS=1000; privateintmVelocityUnits; /** *Theminimumvelocityfordeterminingthedirection. *MINOR_VELOCITYindp *mMinorVelocityinpx */ privatestaticfinalfloatMINOR_VELOCITY=150.0f; privateintmMinorVelocity; /** *ThewidthofSlidingdistancefromleft. *AnditshouldbethesamewiththewidthoftheViewbelowSliderLayoutinaFrameLayout. *DOCK_WIDTHindp *mDockWidthinpx */ privatestaticfinalfloatSLIDING_WIDTH=270.0f; privateintmSlidingWidth; /** *Thedefaultvaluesofshadow. *VELOCITY_UNITSindp *mVelocityUnitsinpx */ privatestaticfinalfloatDEF_SHADOW_WIDTH=10.0f; privateintmDefShadowWidth; /** *Valueforcheckingatoucheventiscompleted. */ privatebooleanmIsTouchEventDone=false; /** *Valueforcheckingsliderisopen. */ privatebooleanmIsOpen=false; /** *Valueforsavingthelastoffsetofscroller’x-axis. */ privateintmSaveScrollX=0; /** *Valueforcheckingsliderisallowedtoslide. */ privatebooleanmEnableSlide=true; privateViewmMainChild=null; privateOnLeftSliderLayoutStateListenermListener=null; /** *InstantiatesanewLeftSliderLayout. * *@paramcontexttheassociatedContext *@paramattrsAttributeSet */ publicLeftSliderLayout(Contextcontext,AttributeSetattrs){ this(context,attrs,0); } /** *InstantiatesanewLeftSliderLayout. * *@paramcontexttheassociatedContext *@paramattrsAttributeSet *@paramdefStyleStyle */ publicLeftSliderLayout(Contextcontext,AttributeSetattrs,intdefStyle){ super(context,attrs,defStyle); mScroller=newScroller(context); mTouchSlop=ViewConfiguration.get(getContext()).getScaledTouchSlop(); /** *Convertvaluesindptovaluesinpx; */ finalfloatfDensity=getResources().getDisplayMetrics().density; mVelocityUnits=(int)(VELOCITY_UNITS*fDensity+0.5f); mMinorVelocity=(int)(MINOR_VELOCITY*fDensity+0.5f); mSlidingWidth=(int)(SLIDING_WIDTH*fDensity+0.5f); mDefShadowWidth=(int)(DEF_SHADOW_WIDTH*fDensity+0.5f); } @Override protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){ super.onMeasure(widthMeasureSpec,heightMeasureSpec); //checkMeasureModeisExactly. finalintwidthMode=MeasureSpec.getMode(widthMeasureSpec); if(widthMode!=MeasureSpec.EXACTLY){ thrownewIllegalStateException("LeftSliderLayoutonlycanmCurScreenrunatEXACTLYmode!"); } finalintheightMode=MeasureSpec.getMode(heightMeasureSpec); if(heightMode!=MeasureSpec.EXACTLY){ thrownewIllegalStateException("LeftSliderLayoutonlycanrunatEXACTLYmode!"); } //measurechildviews intnCount=getChildCount(); for(inti=2;i<nCount;i++){ removeViewAt(i); } nCount=getChildCount(); if(nCount>0){ if(nCount>1){ mMainChild=getChildAt(1); getChildAt(0).measure(widthMeasureSpec,heightMeasureSpec); }else{ mMainChild=getChildAt(0); } mMainChild.measure(widthMeasureSpec,heightMeasureSpec); } //Setthescrolledposition scrollTo(mSaveScrollX,0); } @Override protectedvoidonLayout(booleanchanged,intl,intt,intr,intb){ finalintnCount=getChildCount(); if(nCount<=0){ return; } //SetthesizeandpositionofMainChild if(mMainChild!=null){ mMainChild.layout( l, t, l+mMainChild.getMeasuredWidth(), t+mMainChild.getMeasuredHeight()); } //SetthesizeandpositionofShadowChild if(nCount>1){ intnLeftChildWidth=0; ViewleftChild=getChildAt(0); ViewGroup.LayoutParamslayoutParams=leftChild.getLayoutParams(); if(layoutParams.width==ViewGroup.LayoutParams.FILL_PARENT ||layoutParams.width==ViewGroup.LayoutParams.MATCH_PARENT){ nLeftChildWidth=mDefShadowWidth; }else{ nLeftChildWidth=layoutParams.width; } leftChild.layout( l-nLeftChildWidth, t, l, t+leftChild.getMeasuredHeight()); } } @Override publicvoidcomputeScroll(){ if(mScroller.computeScrollOffset()){ Log.d(TAG,"computeScrollexeuted:"+"x:"+mScroller.getCurrX()+"Y:"+mScroller.getCurrY()); scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); postInvalidate(); } } @Override publicbooleanonTouchEvent(MotionEventevent){ intnCurScrollX=getScrollX(); //checktouchpointisintherectangleofMainChild if(mMainChild!=null &&mTouchState!=TOUCH_STATE_SCROLLING &&mIsTouchEventDone){ Rectrect=newRect(); mMainChild.getHitRect(rect); if(!rect.contains((int)event.getX()+nCurScrollX,(int)event.getY())){ returnfalse; } } if(mVelocityTracker==null){ mVelocityTracker=VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); finalintaction=event.getAction(); finalfloatx=event.getX(); switch(action){ caseMotionEvent.ACTION_DOWN:{ if(!mScroller.isFinished()){ mScroller.abortAnimation(); } mIsTouchEventDone=false; mLastMotionX=x; break; } caseMotionEvent.ACTION_MOVE:{ //checksliderisallowedtoslide. if(!mEnableSlide){ break; } //computethex-axisoffsetfromlastpointtocurrentpoint intdeltaX=(int)(mLastMotionX-x); if(nCurScrollX+deltaX<getMinScrollX()){ deltaX=getMinScrollX()-nCurScrollX; mLastMotionX=mLastMotionX-deltaX; }elseif(nCurScrollX+deltaX>getMaxScrollX()){ deltaX=getMaxScrollX()-nCurScrollX; mLastMotionX=mLastMotionX-deltaX; }else{ mLastMotionX=x; } //Moveviewtothecurrentpoint if(deltaX!=0){ scrollBy(deltaX,0); } //Savethescrolledposition mSaveScrollX=getScrollX(); break; } caseMotionEvent.ACTION_CANCEL: caseMotionEvent.ACTION_UP:{ //checksliderisallowedtoslide. if(!mEnableSlide){ break; } finalVelocityTrackervelocityTracker=mVelocityTracker; velocityTracker.computeCurrentVelocity(mVelocityUnits); //Setopenorclosestate,whengetACTION_UPorACTION_CANCELevent. if(nCurScrollX<0){ intvelocityX=(int)velocityTracker.getXVelocity(); if(velocityX>mMinorVelocity){ scrollByWithAnim(getMinScrollX()-nCurScrollX); setState(true); } elseif(velocityX<-mMinorVelocity){ scrollByWithAnim(-nCurScrollX); setState(false); }else{ if(nCurScrollX>=getMinScrollX()/2){ scrollByWithAnim(-nCurScrollX); setState(false); }else{ scrollByWithAnim(getMinScrollX()-nCurScrollX); setState(true); } } }else{ if(nCurScrollX>0){ scrollByWithAnim(-nCurScrollX); } setState(false); } if(mVelocityTracker!=null){ mVelocityTracker.recycle(); mVelocityTracker=null; } mTouchState=TOUCH_STATE_REST; mIsTouchEventDone=true; break; } } returntrue; } @Override publicbooleanonInterceptTouchEvent(MotionEventev){ finalintaction=ev.getAction(); if(mListener!=null&&!mListener.OnLeftSliderLayoutInterceptTouch(ev)){ returnfalse; } if((action==MotionEvent.ACTION_MOVE) &&(mTouchState!=TOUCH_STATE_REST)){ returntrue; } finalfloatx=ev.getX(); finalfloaty=ev.getY(); switch(action){ caseMotionEvent.ACTION_DOWN: mLastMotionX=x; mLastMotionY=y; mTouchState=mScroller.isFinished()?TOUCH_STATE_REST:TOUCH_STATE_SCROLLING; break; caseMotionEvent.ACTION_MOVE: finalintxDiff=(int)Math.abs(mLastMotionX-x); if(xDiff>mTouchSlop){ if(Math.abs(mLastMotionY-y)/Math.abs(mLastMotionX-x)<1) mTouchState=TOUCH_STATE_SCROLLING; } break; caseMotionEvent.ACTION_CANCEL: caseMotionEvent.ACTION_UP: mTouchState=TOUCH_STATE_REST; break; } returnmTouchState!=TOUCH_STATE_REST; } /** *Withthehorizontalscrolloftheanimation * *@paramnDxx-axisoffset */ voidscrollByWithAnim(intnDx){ if(nDx==0){ return; } Log.d(TAG,"scrollByWithAnim:"+"x:"+(getScrollX()+"Y:"+Math.abs(nDx))); mScroller.startScroll(getScrollX(),0,nDx,0, Math.abs(nDx)); invalidate(); } /** *Getdistanceofthemaximumhorizontalscroll * *@returndistanceinpx */ privateintgetMaxScrollX(){ return0; } /** *Getdistanceoftheminimumhorizontalscroll *@returndistanceinpx */ privateintgetMinScrollX(){ return-mSlidingWidth; } /** *OpenLeftSlideLayout */ publicvoidopen(){ Log.d(TAG,"scrollbywidth:"+(getMinScrollX()-getScrollX())); if(mEnableSlide){ Log.d(TAG,"scrollbywidth:"+(getMinScrollX()-getScrollX())); scrollByWithAnim(getMinScrollX()-getScrollX()); setState(true); } } /** *CloseLeftSlideLayout */ publicvoidclose(){ if(mEnableSlide){ scrollByWithAnim((-1)*getScrollX()); setState(false); } } /** *DeterminewhetherLeftSlideLayoutisopen * *@returntrue-open,false-close */ publicbooleanisOpen(){ returnmIsOpen; } /** *SetstateofLeftSliderLayout * *@parambIsOpenthenewstate */ privatevoidsetState(booleanbIsOpen){ booleanbStateChanged=false; if(mIsOpen&&!bIsOpen){ bStateChanged=true; }elseif(!mIsOpen&&bIsOpen){ bStateChanged=true; } mIsOpen=bIsOpen; if(bIsOpen){ mSaveScrollX=getMaxScrollX(); }else{ mSaveScrollX=0; } if(bStateChanged&&mListener!=null){ mListener.OnLeftSliderLayoutStateChanged(bIsOpen); } } /** *enableslideactionofLeftSliderLayout * *@parambEnable */ publicvoidenableSlide(booleanbEnable){ mEnableSlide=bEnable; } /** *SetlistenertoLeftSliderLayout */ publicvoidsetOnLeftSliderLayoutListener(OnLeftSliderLayoutStateListenerlistener){ mListener=listener; } /** *LeftSliderLayoutListener * */ publicinterfaceOnLeftSliderLayoutStateListener{ /** *CalledwhenLeftSliderLayout’sstatehasbeenchanged. * *@parambIsOpenthenewstate */ publicvoidOnLeftSliderLayoutStateChanged(booleanbIsOpen); /** *CalledwhenLeftSliderLayouthasgotonInterceptTouchEvent. * *@paramevTouchEvent *@returntrue-LeftSliderLayoutneedtomanagetheInterceptTouchEvent. *false-LeftSliderLayoutdon'tneedtomanagetheInterceptTouchEvent. */ publicbooleanOnLeftSliderLayoutInterceptTouch(MotionEventev); } }

LeftSliderLayout有一个Listener。它有二个函数,一个是LeftSliderLayout的打开与关闭的状态改变;另一个是InterceptTouchEvent的回调,主要解决的是在拖动内容中有要处理左右滑动的控件与LeftSliderLayout的左右滑动的事件有冲突,当它返回true时,LeftSliderLayout会处理左右滑动,当它返回false时,就不处理左右滑动的事件。

为了实现侧滑菜单框架,故实现了一个BaseActivity,其他Activity只需要继承这个Activity就行,


3、BaseActivity.java代码

packagecom.zhaoxufeng.leftsliderlayout.example;

importandroid.app.Activity;
importandroid.content.Intent;
importandroid.os.Bundle;
importandroid.util.Log;
importandroid.view.MotionEvent;
importandroid.view.View;
importandroid.widget.*;
importcom.zhaoxufeng.leftsliderlayout.R;
importcom.zhaoxufeng.leftsliderlayout.lib.LeftSliderLayout;

importjava.util.ArrayList;
importjava.util.List;

/**
*基类Activity,SliderMenu的基础统一框架
*User:zhiwen.nan
*Date:13-10-7
*Time:下午8:31
*
*/
publicclassBaseActivityextendsActivityimplementsLeftSliderLayout.OnLeftSliderLayoutStateListener,View.OnClickListener{

privateLeftSliderLayoutleftSliderLayout;
privateImageViewmOpenButton;
privateTextViewmTitleText;
privateListViewmListView;
privateList<ListItem>mDataList;
privatelongwaitTime=2000;
privatelongtouchTime=0;
privatestaticfinalStringTAG="BaseActivity";

publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
}

@Override
protectedvoidonResume(){
super.onResume();
bindView();
initialDataList();
ListViewAdapterlistViewAdapter=newListViewAdapter(BaseActivity.this,mDataList);
mListView.setAdapter(listViewAdapter);
mListView.setOnItemClickListener(newAdapterView.OnItemClickListener(){
@Override
publicvoidonItemClick(AdapterView<?>adapterView,Viewview,inti,longl){
finish();
switch(i){
case0:
mTitleText.setText(getText(R.string.categ_local_video_list));
Intentintent=newIntent(BaseActivity.this,LocalMediaActivity.class);
startActivity(intent);
break;
case1:
mTitleText.setText(getText(R.string.cate_leida));
IntentradIntent=newIntent(BaseActivity.this,RadoActivity.class);
startActivity(radIntent);
break;
case2:
mTitleText.setText(getText(R.string.hot_viedo));
IntenthotIntent=newIntent(BaseActivity.this,HotMediaListActivity.class);
startActivity(hotIntent);
break;
case3:
mTitleText.setText(getText(R.string.cate_favrouite_list));
IntentcollectIntent=newIntent(BaseActivity.this,CollectListActivity.class);
startActivity(collectIntent);
break;
default:
leftSliderLayout.close();
break;

}
}
});
}

@Override
publicvoidonClick(Viewview){

}

@Override
publicvoidOnLeftSliderLayoutStateChanged(booleanbIsOpen){

if(bIsOpen){
//Toast.makeText(this,"LeftSliderLayoutisopen!",Toast.LENGTH_SHORT).show();
Log.d(TAG,"leftsilderisopen");
}else{
//Toast.makeText(this,"LeftSliderLayoutisclose!",Toast.LENGTH_SHORT).show();
Log.d(TAG,"leftsilderisclose");
}

}

@Override
publicbooleanOnLeftSliderLayoutInterceptTouch(MotionEventev){

returnfalse;
}

privatevoidinitialDataList(){
mDataList=newArrayList<ListItem>();
for(inti=0;i<=3;i++){
ListItemlistItem=newListItem();
listItem.setImageType(i);
mDataList.add(listItem);

}
}

privatevoidbindView(){
leftSliderLayout=(LeftSliderLayout)findViewById(R.id.main_slider_layout);
leftSliderLayout.setOnLeftSliderLayoutListener(this);
mOpenButton=(ImageView)findViewById(R.id.openButton);
mTitleText=(TextView)findViewById(R.id.titleText);
mListView=(ListView)findViewById(R.id.listTab);

mOpenButton.setOnClickListener(newView.OnClickListener(){
@Override
publicvoidonClick(Viewview){
if(leftSliderLayout.isOpen()){
leftSliderLayout.close();
}else{
leftSliderLayout.open();
}

}
});

}

publicvoidopenLeftSlider(booleanisToOpen){
if(isToOpen){
leftSliderLayout.open();
}else{
leftSliderLayout.close();
}

}

publicvoidenableSlider(booleanisEnable){
if(isEnable){
leftSliderLayout.enableSlide(true);
}else{
leftSliderLayout.enableSlide(false);
}
}

@Override
publicvoidonBackPressed(){
if(!leftSliderLayout.isOpen()){
leftSliderLayout.open();
}else{
longcurrentTime=System.currentTimeMillis();
if((currentTime-touchTime)>=waitTime){
Toast.makeText(this,"再按一次退出",Toast.LENGTH_SHORT).show();
touchTime=currentTime;
}else{
finish();
//todo
//退出业务逻辑,根据项目需求来写
}
}

}
}

关于左侧菜单的业务逻辑都在BaseActivity里处理,另外返回的逻辑也在里面处理,顶部统一的导航栏打开菜单栏业务逻辑,还有左侧菜单跳转的业务逻辑

4、LocalMediaActivity.java

packagecom.zhaoxufeng.leftsliderlayout.example;

importandroid.app.Activity;
importandroid.database.Cursor;
importandroid.os.Bundle;
importandroid.provider.Contacts;
importandroid.view.MotionEvent;
importandroid.view.View;
importandroid.view.View.OnClickListener;
importandroid.widget.*;
importcom.zhaoxufeng.leftsliderlayout.R;
importcom.zhaoxufeng.leftsliderlayout.lib.LeftSliderLayout;
importcom.zhaoxufeng.leftsliderlayout.lib.LeftSliderLayout.OnLeftSliderLayoutStateListener;

importjava.util.ArrayList;
importjava.util.List;

/**
*@authorzhiwen.nan
*@since1.0
*本地视频界面
*/
publicclassLocalMediaActivityextendsBaseActivity{

privateListViewmListView;
privateTextViewmTitleText;

@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.local_media_fragment);

mListView=(ListView)findViewById(R.id.localVideoList);

mTitleText=(TextView)findViewById(R.id.titleText);
mTitleText.setText("本地视频");

Cursorcursor=getContentResolver().query(Contacts.People.CONTENT_URI,null,null,null,null);
startManagingCursor(cursor);
ListAdapterlistAdapter=newSimpleCursorAdapter(this,android.R.layout.simple_expandable_list_item_1,
cursor,newString[]{Contacts.People.NAME},newint[]{android.R.id.text1});
mListView.setAdapter(listAdapter);

}

@Override
publicbooleanOnLeftSliderLayoutInterceptTouch(MotionEventev){

returntrue;
}

}


LocalMediaActivity是自己定义的Activty和业务逻辑界面,只需继承BaseActivity即可,其他Activity类似。

以上就是核心代码,源代码下载:

http://download.csdn.net/detail/nanzhiwen666/6394347


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐