Android滑动菜单特效实现,仿人人客户端侧滑效果,史上最简单的侧滑实现
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/8714621
人人客户端有一个特效还是挺吸引人的,在主界面手指向右滑动,就可以将菜单展示出来,而主界面会被隐藏大部分,但是仍有左侧的一小部分同菜单一起展示。
据说人人客户端的这个特效是从facebook客户端模仿来的,至于facebook是不是又从其它地方模仿来的就不得而知了。好,今天我们就一起来实现这个效果,总之我第一次看到这个特效是在人人客户端看到的,我也就主观性地认为我是在模仿人人客户端的特效了。
虽然现在网上类似这种效果的实现也非常多,可是我发现实现方案大都非常复杂,并不容易理解。但其实这种效果并不难实现,因此我今天给大家带来的也是史上最简单的滑动菜单实现方案。
首先还是讲一下实现原理。在一个Activity的布局中需要有两部分,一个是菜单(menu)的布局,一个是内容(content)的布局。两个布局横向排列,菜单布局在左,内容布局在右。初始化的时候将菜单布局向左偏移,以至于能够完全隐藏,这样内容布局就会完全显示在Activity中。然后通过监听手指滑动事件,来改变菜单布局的左偏移距离,从而控制菜单布局的显示和隐藏。原理图如下:
将菜单布局的左偏移值改成0时,效果图如下:
好,我们开始用代码来实现。首先在Eclipse中新建一个Android项目,项目名就叫做RenRenSlideMenuDemo。然后写一下布局文件,创建或打开layout目录下的activity_main.xml文件,加入如下代码:
- <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="horizontal"
- tools:context=".MainActivity">
- <LinearLayout
- android:id="@+id/menu"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:background="@drawable/menu">
- </LinearLayout>
- <LinearLayout
- android:id="@+id/content"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:background="@drawable/content">
- </LinearLayout>
- </LinearLayout>
创建或打开MainActivity,这个类仍然是程序的主Activity,也是这次demo唯一的Activity,在里面加入如下代码:
- publicclassMainActivityextendsActivityimplementsOnTouchListener{
- /**
- *滚动显示和隐藏menu时,手指滑动需要达到的速度。
- */
- publicstaticfinalintSNAP_VELOCITY=200;
- /**
- *屏幕宽度值。
- */
- privateintscreenWidth;
- /**
- *menu最多可以滑动到的左边缘。值由menu布局的宽度来定,marginLeft到达此值之后,不能再减少。
- */
- privateintleftEdge;
- /**
- *menu最多可以滑动到的右边缘。值恒为0,即marginLeft到达0之后,不能增加。
- */
- privateintrightEdge=0;
- /**
- *menu完全显示时,留给content的宽度值。
- */
- privateintmenuPadding=80;
- /**
- *主内容的布局。
- */
- privateViewcontent;
- /**
- *menu的布局。
- */
- privateViewmenu;
- /**
- *menu布局的参数,通过此参数来更改leftMargin的值。
- */
- privateLinearLayout.LayoutParamsmenuParams;
- /**
- *记录手指按下时的横坐标。
- */
- privatefloatxDown;
- /**
- *记录手指移动时的横坐标。
- */
- privatefloatxMove;
- /**
- *记录手机抬起时的横坐标。
- */
- privatefloatxUp;
- /**
- *menu当前是显示还是隐藏。只有完全显示或隐藏menu时才会更改此值,滑动过程中此值无效。
- */
- privatebooleanisMenuVisible;
- /**
- *用于计算手指滑动的速度。
- */
- privateVelocityTrackermVelocityTracker;
- @Override
- protectedvoidonCreate(BundlesavedInstanceState){
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- initValues();
- content.setOnTouchListener(this);
- }
- /**
- *初始化一些关键性数据。包括获取屏幕的宽度,给content布局重新设置宽度,给menu布局重新设置宽度和偏移距离等。
- */
- privatevoidinitValues(){
- WindowManagerwindow=(WindowManager)getSystemService(Context.WINDOW_SERVICE);
- screenWidth=window.getDefaultDisplay().getWidth();
- content=findViewById(R.id.content);
- menu=findViewById(R.id.menu);
- menuParams=(LinearLayout.LayoutParams)menu.getLayoutParams();
- //将menu的宽度设置为屏幕宽度减去menuPadding
- menuParams.width=screenWidth-menuPadding;
- //左边缘的值赋值为menu宽度的负数
- leftEdge=-menuParams.width;
- //menu的leftMargin设置为左边缘的值,这样初始化时menu就变为不可见
- menuParams.leftMargin=leftEdge;
- //将content的宽度设置为屏幕宽度
- content.getLayoutParams().width=screenWidth;
- }
- @Override
- publicbooleanonTouch(Viewv,MotionEventevent){
- createVelocityTracker(event);
- switch(event.getAction()){
- caseMotionEvent.ACTION_DOWN:
- //手指按下时,记录按下时的横坐标
- xDown=event.getRawX();
- break;
- caseMotionEvent.ACTION_MOVE:
- //手指移动时,对比按下时的横坐标,计算出移动的距离,来调整menu的leftMargin值,从而显示和隐藏menu
- xMove=event.getRawX();
- intdistanceX=(int)(xMove-xDown);
- if(isMenuVisible){
- menuParams.leftMargin=distanceX;
- }else{
- menuParams.leftMargin=leftEdge+distanceX;
- }
- if(menuParams.leftMargin<leftEdge){
- menuParams.leftMargin=leftEdge;
- }elseif(menuParams.leftMargin>rightEdge){
- menuParams.leftMargin=rightEdge;
- }
- menu.setLayoutParams(menuParams);
- break;
- caseMotionEvent.ACTION_UP:
- //手指抬起时,进行判断当前手势的意图,从而决定是滚动到menu界面,还是滚动到content界面
- xUp=event.getRawX();
- if(wantToShowMenu()){
- if(shouldScrollToMenu()){
- scrollToMenu();
- }else{
- scrollToContent();
- }
- }elseif(wantToShowContent()){
- if(shouldScrollToContent()){
- scrollToContent();
- }else{
- scrollToMenu();
- }
- }
- recycleVelocityTracker();
- break;
- }
- returntrue;
- }
- /**
- *判断当前手势的意图是不是想显示content。如果手指移动的距离是负数,且当前menu是可见的,则认为当前手势是想要显示content。
- *
- *@return当前手势想显示content返回true,否则返回false。
- */
- privatebooleanwantToShowContent(){
- returnxUp-xDown<0&&isMenuVisible;
- }
- /**
- *判断当前手势的意图是不是想显示menu。如果手指移动的距离是正数,且当前menu是不可见的,则认为当前手势是想要显示menu。
- *
- *@return当前手势想显示menu返回true,否则返回false。
- */
- privatebooleanwantToShowMenu(){
- returnxUp-xDown>0&&!isMenuVisible;
- }
- /**
- *判断是否应该滚动将menu展示出来。如果手指移动距离大于屏幕的1/2,或者手指移动速度大于SNAP_VELOCITY,
- *就认为应该滚动将menu展示出来。
- *
- *@return如果应该滚动将menu展示出来返回true,否则返回false。
- */
- privatebooleanshouldScrollToMenu(){
- returnxUp-xDown>screenWidth/2||getScrollVelocity()>SNAP_VELOCITY;
- }
- /**
- *判断是否应该滚动将content展示出来。如果手指移动距离加上menuPadding大于屏幕的1/2,
- *或者手指移动速度大于SNAP_VELOCITY,就认为应该滚动将content展示出来。
- *
- *@return如果应该滚动将content展示出来返回true,否则返回false。
- */
- privatebooleanshouldScrollToContent(){
- returnxDown-xUp+menuPadding>screenWidth/2||getScrollVelocity()>SNAP_VELOCITY;
- }
- /**
- *将屏幕滚动到menu界面,滚动速度设定为30.
- */
- privatevoidscrollToMenu(){
- newScrollTask().execute(30);
- }
- /**
- *将屏幕滚动到content界面,滚动速度设定为-30.
- */
- privatevoidscrollToContent(){
- newScrollTask().execute(-30);
- }
- /**
- *创建VelocityTracker对象,并将触摸content界面的滑动事件加入到VelocityTracker当中。
- *
- *@paramevent
- *content界面的滑动事件
- */
- privatevoidcreateVelocityTracker(MotionEventevent){
- if(mVelocityTracker==null){
- mVelocityTracker=VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(event);
- }
- /**
- *获取手指在content界面滑动的速度。
- *
- *@return滑动速度,以每秒钟移动了多少像素值为单位。
- */
- privateintgetScrollVelocity(){
- mVelocityTracker.computeCurrentVelocity(1000);
- intvelocity=(int)mVelocityTracker.getXVelocity();
- returnMath.abs(velocity);
- }
- /**
- *回收VelocityTracker对象。
- */
- privatevoidrecycleVelocityTracker(){
- mVelocityTracker.recycle();
- mVelocityTracker=null;
- }
- classScrollTaskextendsAsyncTask<Integer,Integer,Integer>{
- @Override
- protectedIntegerdoInBackground(Integer...speed){
- intleftMargin=menuParams.leftMargin;
- //根据传入的速度来滚动界面,当滚动到达左边界或右边界时,跳出循环。
- while(true){
- leftMargin=leftMargin+speed[0];
- if(leftMargin>rightEdge){
- leftMargin=rightEdge;
- break;
- }
- if(leftMargin<leftEdge){
- leftMargin=leftEdge;
- break;
- }
- publishProgress(leftMargin);
- //为了要有滚动效果产生,每次循环使线程睡眠20毫秒,这样肉眼才能够看到滚动动画。
- sleep(20);
- }
- if(speed[0]>0){
- isMenuVisible=true;
- }else{
- isMenuVisible=false;
- }
- returnleftMargin;
- }
- @Override
- protectedvoidonProgressUpdate(Integer...leftMargin){
- menuParams.leftMargin=leftMargin[0];
- menu.setLayoutParams(menuParams);
- }
- @Override
- protectedvoidonPostExecute(IntegerleftMargin){
- menuParams.leftMargin=leftMargin;
- menu.setLayoutParams(menuParams);
- }
- }
- /**
- *使当前线程睡眠指定的毫秒数。
- *
- *@parammillis
- *指定当前线程睡眠多久,以毫秒为单位
- */
- privatevoidsleep(longmillis){
- try{
- Thread.sleep(millis);
- }catch(InterruptedExceptione){
- e.printStackTrace();
- }
- }
- }
全部的代码都在这里了,我们可以看到,加上注释总共才两百多行的代码就能实现滑动菜单的特效。下面我来对以上代码解释一下,首先初始化的时候调用initValues方法,在这里面将内容布局的宽度设定为屏幕的宽度,菜单布局的宽度设定为屏幕的宽度减去menuPadding值,这样可以保证在菜单布局展示的时候,仍有一部分内容布局可以看到。如果不在初始化的时候重定义两个布局宽度,就会按照layout文件里面声明的一样,两个布局都是fill_parent,这样就无法实现滑动菜单的效果了。然后将菜单布局的左偏移量设置为负的菜单布局的宽度,这样菜单布局就会被完全隐藏,只有内容布局会显示在界面上。
之后给内容布局注册监听事件,这样当手指在内容布局上滑动的时候就会触发onTouch事件。在onTouch事件里面,根据手指滑动的距离会改变菜单布局的左偏移量,从而控制菜单布局的显示和隐藏。当手指离开屏幕的时候,会判断应该滑动到菜单布局还是内容布局,判断依据是根据手指滑动的距离或者滑动的速度,细节可以看代码中的注释。
最后还是给出AndroidManifest.xml的代码,都是自动生成的,非常简单:
- <?xmlversion="1.0"encoding="utf-8"?>
- <manifestxmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.renrenslidemenudemo"
- android:versionCode="1"
- android:versionName="1.0">
- <uses-sdk
- android:minSdkVersion="8"
- android:targetSdkVersion="8"/>
- <application
- android:allowBackup="true"
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name"
- android:theme="@android:style/Theme.NoTitleBar">
- <activity
- android:name="com.example.renrenslidemenudemo.MainActivity"
- android:label="@string/app_name">
- <intent-filter>
- <actionandroid:name="android.intent.action.MAIN"/>
- <categoryandroid:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- </application>
- </manifest>
而当菜单布局完全展示的时候,效果如下图:
今天大家看到了史上最简单的滑动菜单实现方案,确实是非常简单。那么有朋友也许会问了,在一个Activity当中这样实现滑动菜单是很简单,可是如果我的应用程序有好多个Activity都需要滑动菜单,每个Activity里都这么实现一遍,也变得复杂了。没错,当前的这个解决方案只适用于单个Activity中,如果是想在多个Activity中都实现滑动菜单的效果,请参考我的另一篇文章Android滑动菜单框架完全解析,教你如何一分钟实现滑动菜单特效。
有对双向滑动菜单感兴趣的朋友请转阅Android双向滑动菜单完全解析,教你如何一分钟实现双向滑动特效。