Android-自定义滑动菜单(抽屉效果)

在Andoird使用Android自带的那些组件,像SlidingDrawer和DrawerLayout都是抽屉效果的菜单,但是在项目很多要实现的功能都收到Android这些自带组件的限制,导致很难完成项目的需求,自定义的组件,各方面都在自己的控制之下,从而根据需求做出调整。想要实现好的效果,基本上都的基于Android的OnTouch事件自己实现响应的功能。

首先,给大家先看一下整体的效果:

滑动的加速度效果都是有的,具体的体验,只能安装后才能查看。

接下来,看代码:

代码从MainActivity延伸出了2个类:MainController和MainView,MainController来处理控制层、MainView来操作展示层。

主要代码:

MainActivity的代码:

package com.example.wz;

import com.example.wz.controller.MainController;
import com.example.wz.util.MyLog;
import com.example.wz.view.MainView;

import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;

public class MainActivity extends Activity {

    public MyLog log = new MyLog(this, true);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        log.e("欢迎你加入测试项目.");
        link();
    }

    public MainController mainController;
    public MainView mainView;

    private void link() {
        this.mainController = new MainController(this);
        this.mainView = new MainView(this);

        this.mainController.thisView = this.mainView;
        this.mainView.thisController = this.mainController;

        this.mainView.initViews();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        return mainController.onTouchEvent(event);
    }
}

MainController的代码:

package com.example.wz.controller;

import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;

import com.example.wz.MainActivity;
import com.example.wz.util.MyLog;
import com.example.wz.util.OpenLooper;
import com.example.wz.util.OpenLooper.LoopCallback;
import com.example.wz.view.MainView;

public class MainController {

    public MyLog log = new MyLog(this, true);

    public MainActivity mainActivity;
    public MainController thisController;
    public MainView thisView;

    public GestureDetector mGesture;

    public MainController(MainActivity mainActivity) {
        this.mainActivity = mainActivity;
        this.thisController = this;

        mGesture = new GestureDetector(mainActivity, new GestureListener());
        openLooper = new OpenLooper();
        openLooper.createOpenLooper();
        loopCallback = new ListLoopCallback(openLooper);
        openLooper.loopCallback = loopCallback;
    }

    public class TouchStatus {
        public int None = 4, Down = 1, Horizontal = 2, Vertical = 3, Up = 4;// LongPress = 5
        public int state = None;
    }

    public TouchStatus touchStatus = new TouchStatus();

    public class BodyStatus {
        public int Fixed = 0, Dragging = 1, Homing = 2, FlingHoming = 3, BoundaryHoming = 4;
        public int state = Fixed;
    }

    public BodyStatus bodyStatus = new BodyStatus();

    public class DrawStatus {
        public int Closed = 0, Open = 1, GoClosing = 2, GoOpening = 3;
        public int state = Closed;
    }

    public DrawStatus drawStatus = new DrawStatus();

    public class AreaStatus {
        public int A = 0, B = 1;
        public int state = A;
    }

    public AreaStatus areaStatus = new AreaStatus();

    public float touch_pre_x;
    public float touch_pre_y;

    public float currentTranslateX;

    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();

        float x = event.getX();
        float y = event.getY();

        if (action == MotionEvent.ACTION_DOWN) {
            this.touch_pre_x = x;
            this.touch_pre_y = y;

            if (touchStatus.state == touchStatus.None) {
                touchStatus.state = touchStatus.Down;
                log.e("Down ");
                if (x > thisView.maxTranslateX) {
                    areaStatus.state = areaStatus.B;
                } else if (x <= thisView.maxTranslateX) {
                    areaStatus.state = areaStatus.A;
                }
            }
        } else if (action == MotionEvent.ACTION_MOVE) {
            float Δy = (y - touch_pre_y);
            float Δx = (x - touch_pre_x);
            if (touchStatus.state == touchStatus.Down) {
                if (Δx * Δx + Δy * Δy > 400) {
                    if (Δx * Δx > Δy * Δy) {
                        touchStatus.state = touchStatus.Horizontal;
                    } else {
                        touchStatus.state = touchStatus.Vertical;
                    }
                    touch_pre_x = x;
                    touch_pre_y = y;
                    log.e("ACTION_MOVE ");
                }
            } else if (touchStatus.state == touchStatus.Horizontal) {
                currentTranslateX += Δx;
                this.touch_pre_x = x;
                this.touch_pre_y = y;
                if (currentTranslateX - thisView.maxTranslateX <= 0 && currentTranslateX >= 0) {
                    setPosition();
                }
                log.e("Horizontal");
                bodyStatus.state = bodyStatus.Dragging;
            } else if (touchStatus.state == touchStatus.Vertical) {
                log.e("Vertical");
                bodyStatus.state = bodyStatus.Dragging;
            }
        } else if (action == MotionEvent.ACTION_UP) {
            log.e("ACTION_UP");
            if (bodyStatus.state == bodyStatus.Dragging) {
                if (touchStatus.state == touchStatus.Horizontal) {
                    bodyStatus.state = bodyStatus.Homing;
                    openLooper.start();
                } else if (touchStatus.state == touchStatus.Vertical) {
                    if (drawStatus.state == drawStatus.Open && areaStatus.state == areaStatus.B) {
                        bodyStatus.state = bodyStatus.Homing;
                        drawStatus.state = drawStatus.GoClosing;
                        openLooper.start();
                    }
                }
            } else if (touchStatus.state == touchStatus.Down && areaStatus.state == areaStatus.B) {
                bodyStatus.state = bodyStatus.Homing;
                drawStatus.state = drawStatus.GoClosing;
                openLooper.start();
            }
            touchStatus.state = touchStatus.Up;
        }
        mGesture.onTouchEvent(event);
        return true;
    }

    class GestureListener extends SimpleOnGestureListener {

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            if (velocityX * velocityX + velocityY * velocityY > 250000) {
                if (velocityX * velocityX > velocityY * velocityY) {
                    log.e("velocityX--" + velocityX);
                    if (drawStatus.state == drawStatus.Closed && velocityX < 0) {
                    } else if (drawStatus.state == drawStatus.Open && velocityX > 0) {
                    } else {
                        dxSpeed = velocityX;
                        bodyStatus.state = bodyStatus.FlingHoming;
                        openLooper.start();
                    }
                } else {
                    log.e("velocityY");
                }
            }
            return true;
        }

        public void onLongPress(MotionEvent event) {
        }

        public boolean onDoubleTap(MotionEvent event) {
            return false;
        }

        public boolean onDoubleTapEvent(MotionEvent event) {
            return false;
        }

        public boolean onSingleTapUp(MotionEvent event) {
            return false;
        }

        @Override
        public boolean onSingleTapConfirmed(MotionEvent event) {
            return false;
        }

        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            return false;
        }
    }

    public void setPosition() {
        thisView.v1.setTranslationX(currentTranslateX - thisView.maxTranslateX);
        thisView.v2.setTranslationX(Math.abs(currentTranslateX));
    }

    float transleteSpeed = 3f;
    OpenLooper openLooper = null;
    LoopCallback loopCallback = null;

    public class ListLoopCallback extends LoopCallback {
        public ListLoopCallback(OpenLooper openLooper) {
            openLooper.super();
        }

        @Override
        public void loop(double ellapsedMillis) {
            if (bodyStatus.state == bodyStatus.Homing) {
                hommingView((float) ellapsedMillis);
            } else if (bodyStatus.state == bodyStatus.FlingHoming) {
                flingHomingView((float) ellapsedMillis);
            }
        }
    }

    public float ratio = 0.0008f;

    public void flingHomingView(float ellapsedMillis) {
        float distance = (float) ellapsedMillis * transleteSpeed;
        boolean isStop = false;
        if (drawStatus.state == drawStatus.Closed) {
            drawStatus.state = drawStatus.GoOpening;
        } else if (drawStatus.state == drawStatus.Open) {
            drawStatus.state = drawStatus.GoClosing;
        }
        if (drawStatus.state == drawStatus.GoClosing) {
            this.currentTranslateX -= distance;
            if (this.currentTranslateX <= 0) {
                this.currentTranslateX = 0;
                drawStatus.state = drawStatus.Closed;
                isStop = true;
                log.e("-------------1");
            }
        } else if (drawStatus.state == drawStatus.GoOpening) {
            this.currentTranslateX += distance;
            if (this.currentTranslateX >= thisView.maxTranslateX) {
                this.currentTranslateX = thisView.maxTranslateX;
                drawStatus.state = drawStatus.Open;
                isStop = true;
                log.e("-------------2");
            }
        }
        setPosition();
        if (isStop) {
            openLooper.stop();
        }
    }

    public float dxSpeed;

    public void dampenSpeed(long deltaMillis) {

        if (this.dxSpeed != 0.0f) {
            this.dxSpeed *= (1.0f - 0.002f * deltaMillis);
            if (Math.abs(this.dxSpeed) < 50f)
                this.dxSpeed = 0.0f;
        }
    }

    public void hommingView(float ellapsedMillis) {
        float distance = (float) ellapsedMillis * transleteSpeed;
        boolean isStop = false;
        if (drawStatus.state == drawStatus.Closed && this.currentTranslateX < thisView.maxTranslateX / 5) {
            this.currentTranslateX -= distance;
            if (this.currentTranslateX <= 0) {
                this.currentTranslateX = 0;
                drawStatus.state = drawStatus.Closed;
                isStop = true;
            }
        } else if (drawStatus.state == drawStatus.Closed && this.currentTranslateX >= thisView.maxTranslateX / 5) {
            this.currentTranslateX += distance;
            if (this.currentTranslateX >= thisView.maxTranslateX) {
                this.currentTranslateX = thisView.maxTranslateX;
                drawStatus.state = drawStatus.Open;
                isStop = true;
            }
        } else if (drawStatus.state == drawStatus.Open && this.currentTranslateX < thisView.maxTranslateX / 5 * 4) {
            this.currentTranslateX -= distance;
            if (this.currentTranslateX <= 0) {
                this.currentTranslateX = 0;
                drawStatus.state = drawStatus.Closed;
                isStop = true;
            }
        } else if (drawStatus.state == drawStatus.Open && this.currentTranslateX >= thisView.maxTranslateX / 5 * 4) {
            this.currentTranslateX += distance;
            if (this.currentTranslateX >= thisView.maxTranslateX) {
                this.currentTranslateX = thisView.maxTranslateX;
                drawStatus.state = drawStatus.Open;
                isStop = true;
            }
        } else if (drawStatus.state == drawStatus.GoClosing) {
            this.currentTranslateX -= distance;
            if (this.currentTranslateX <= 0) {
                this.currentTranslateX = 0;
                drawStatus.state = drawStatus.Closed;
                isStop = true;
            }
        }
        setPosition();
        if (isStop) {
            openLooper.stop();
            log.e("looper stop...");
        }
    }

}

MainView的代码:

package com.example.wz.view;

import android.graphics.Color;
import android.util.DisplayMetrics;
import android.view.ViewGroup.LayoutParams;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.example.wz.MainActivity;
import com.example.wz.R;
import com.example.wz.controller.MainController;
import com.example.wz.util.MyLog;

public class MainView {

    public MyLog log = new MyLog(this, true);

    public MainActivity mainActivity;
    public MainController thisController;
    public MainView thisView;

    public MainView(MainActivity mainActivity) {
        this.mainActivity = mainActivity;
        this.thisView = this;
    }

    public DisplayMetrics displayMetrics;

    public float screenWidth;
    public float screenHeight;
    public float density;

    public float maxTranslateX;

    public RelativeLayout maxView;
    public RelativeLayout v1;
    public RelativeLayout v2;

    public void initViews() {
        this.displayMetrics = new DisplayMetrics();
        this.mainActivity.getWindowManager().getDefaultDisplay().getMetrics(this.displayMetrics);
        this.screenHeight = this.displayMetrics.heightPixels;
        this.screenWidth = this.displayMetrics.widthPixels;
        this.density = this.displayMetrics.density;
        this.maxTranslateX = this.screenWidth * 0.8f;
        this.mainActivity.setContentView(R.layout.activity_main);
        this.maxView = (RelativeLayout) this.mainActivity.findViewById(R.id.maxView);
        v1 = new RelativeLayout(mainActivity);
        v1.setBackgroundColor(Color.RED);
        RelativeLayout.LayoutParams params1 = new RelativeLayout.LayoutParams((int) this.maxTranslateX, LayoutParams.MATCH_PARENT);
        this.maxView.addView(v1, params1);
        TextView t1 = new TextView(mainActivity);
        t1.setText("left menu bar");
        t1.setTextColor(Color.WHITE);
        v1.addView(t1);
        v1.setTranslationX(0 - this.maxTranslateX);
        v2 = new RelativeLayout(mainActivity);
        v2.setBackgroundColor(Color.parseColor("#0099cd"));
        RelativeLayout.LayoutParams params2 = new RelativeLayout.LayoutParams((int) this.screenWidth, LayoutParams.MATCH_PARENT);
        this.maxView.addView(v2, params2);
        v2.setTranslationX(0);
        TextView t2 = new TextView(mainActivity);
        t2.setText("body content");
        t2.setTextColor(Color.WHITE);
        v2.addView(t2);
    }
}

日志管理类MyLog:

package com.example.wz.util;

import android.util.Log;

public class MyLog {

    public static boolean isGlobalTurnOn = true;

    public boolean isTurnOn = true;
    public String tag = null;

    public MyLog(String tag, boolean isTurnOn) {
        this.tag = tag;
        this.isTurnOn = isTurnOn;
    }

    public MyLog(Object clazz, boolean isTurnOn) {
        this.tag = clazz.getClass().getSimpleName();
        this.isTurnOn = isTurnOn;
    }

    public void v(String message) {
        this.v(this.tag, message);
    }

    public void d(String message) {
        this.d(this.tag, message);
    }

    public void i(String message) {
        this.i(this.tag, message);
    }

    public void w(String message) {
        this.w(this.tag, message);
    }

    public void e(String message) {
        this.e(this.tag, message);
    }

    public void v(String tag, String message) {
        if (isTurnOn && isGlobalTurnOn) {
            Log.v(tag, message);
        }
    }

    public void d(String tag, String message) {
        if (isTurnOn && isGlobalTurnOn) {
            Log.d(tag, message);
        }
    }

    public void i(String tag, String message) {
        if (isTurnOn && isGlobalTurnOn) {
            Log.i(tag, message);
        }
    }

    public void w(String tag, String message) {
        if (isTurnOn && isGlobalTurnOn) {
            Log.w(tag, message);
        }
    }

    public void e(String tag, String message) {
        if (isTurnOn && isGlobalTurnOn) {
            Log.e(tag, message);
        }
    }

}

实现动画效果的核心类OpenLooper:

package com.example.wz.util;

import android.annotation.TargetApi;
import android.os.Build;
import android.os.Handler;
import android.os.SystemClock;
import android.view.Choreographer;

public class OpenLooper {

    public LegacyAndroidSpringLooper legacyAndroidSpringLooper = null;
    public ChoreographerAndroidSpringLooper choreographerAndroidSpringLooper = null;
    public LoopCallback loopCallback = null;

    public void createOpenLooper() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            choreographerAndroidSpringLooper = new ChoreographerAndroidSpringLooper();
        } else {
            legacyAndroidSpringLooper = new LegacyAndroidSpringLooper();
        }
    }

    public void start() {
        if (choreographerAndroidSpringLooper != null) {
            choreographerAndroidSpringLooper.start();
        } else if (legacyAndroidSpringLooper != null) {
            legacyAndroidSpringLooper.start();
        }
    }

    public void stop() {
        if (choreographerAndroidSpringLooper != null) {
            choreographerAndroidSpringLooper.stop();
        } else if (legacyAndroidSpringLooper != null) {
            legacyAndroidSpringLooper.stop();
        }
    }

    public class LoopCallback {

        public void loop(double ellapsedMillis) {

        }
    }

    public void loop(double ellapsedMillis) {
        if (this.loopCallback != null) {
            this.loopCallback.loop(ellapsedMillis);
        }
    }

    public class LegacyAndroidSpringLooper {

        public Handler mHandler;
        public Runnable mLooperRunnable;
        public boolean mStarted;
        public long mLastTime;

        public LegacyAndroidSpringLooper() {
            initialize(new Handler());
        }

        public void initialize(Handler handler) {
            mHandler = handler;
            mLooperRunnable = new Runnable() {
                @Override
                public void run() {
                    if (!mStarted) {
                        return;
                    }
                    long currentTime = SystemClock.uptimeMillis();
                    loop(currentTime - mLastTime);
                    mHandler.post(mLooperRunnable);
                }
            };
        }

        public void start() {
            if (mStarted) {
                return;
            }
            mStarted = true;
            mLastTime = SystemClock.uptimeMillis();
            mHandler.removeCallbacks(mLooperRunnable);
            mHandler.post(mLooperRunnable);
        }

        public void stop() {
            mStarted = false;
            mHandler.removeCallbacks(mLooperRunnable);
        }
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public class ChoreographerAndroidSpringLooper {

        public Choreographer mChoreographer;
        public Choreographer.FrameCallback mFrameCallback;
        public boolean mStarted;
        public long mLastTime;

        public ChoreographerAndroidSpringLooper() {
            initialize(Choreographer.getInstance());
        }

        public void initialize(Choreographer choreographer) {
            mChoreographer = choreographer;
            mFrameCallback = new Choreographer.FrameCallback() {
                @Override
                public void doFrame(long frameTimeNanos) {
                    if (!mStarted) {
                        return;
                    }
                    long currentTime = SystemClock.uptimeMillis();
                    loop(currentTime - mLastTime);
                    mLastTime = currentTime;
                    mChoreographer.postFrameCallback(mFrameCallback);
                }
            };
        }

        public void start() {
            if (mStarted) {
                return;
            }
            mStarted = true;
            mLastTime = SystemClock.uptimeMillis();
            mChoreographer.removeFrameCallback(mFrameCallback);
            mChoreographer.postFrameCallback(mFrameCallback);
        }

        public void stop() {
            mStarted = false;
            mChoreographer.removeFrameCallback(mFrameCallback);
        }
    }
}

如有转载请著名来自http://blog.csdn.net/qxs965266509

源代码下载链接:http://download.csdn.net/detail/qxs965266509/8621503

时间: 2025-01-02 15:50:03

Android-自定义滑动菜单(抽屉效果)的相关文章

Android双向滑动菜单完全解析,教你如何一分钟实现双向滑动特效

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9671609 记得在很早之前,我写了一篇关于Android滑动菜单的文章,其中有一个朋友在评论中留言,希望我可以帮他将这个滑动菜单改成双向滑动的方式.当时也没想花太多时间,简单修改了一下就发给了他,结果没想到后来却有一大批的朋友都来问我要这份双向滑动菜单的代码.由于这份代码写得很不用心,我发了部分朋友之后实在不忍心继续发下去了,于是决定专门写一篇文章来介绍更好的Android双向滑

【转】Android双向滑动菜单完全解析,教你如何一分钟实现双向滑动特效

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9671609 记得在很早之前,我写了一篇关于Android滑动菜单的文章,其中有一个朋友在评论中留言,希望我可以帮他将这个滑动菜单改成双向滑动的方式.当时也没想花太多时间,简单修改了一下就发给了他,结果没想到后来却有一大批的朋友都来问我要这份双向滑动菜单的代码.由于这份代码写得很不用心,我发了部分朋友之后实在不忍心继续发下去了,于是决定专门写一篇文章来介绍更好的Android双向滑

Android Studio 使用ViewPager + Fragment实现滑动菜单Tab效果 --简易版

描述: 之前有做过一个记账本APP,拿来练手的,做的很简单,是用Eclipse开发的: 最近想把这个APP重新完善一下,添加了一些新的功能,并选用Android Studio来开发: APP已经完善了一部分,现在就想把已经做好的功能整理一下,记录下来. 效果图: 可以手动滑动菜单 也可以通过点击头部菜单进行切换           具体实现的代码: 前台代码(activity_main.xml): 1 <?xml version="1.0" encoding="utf-

Android 3D滑动菜单完全解析,实现推拉门式的立体特效

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/10471245 在上一篇文章中,我们学习了Camera的基本用法,并借助它们编写了一个例子,实现了类似于API Demos里的图片中轴旋转功能.不过那个例子的核心代码是来自于API Demos中带有的Rotate3dAnimation这个类,是它帮助我们完成了所有的三维旋转操作,所有Matrix和Camera相关的代码也是封装在这个类中. 这样说来的话,大家心里会不会痒痒的呢?虽然

android自定义ViewPager之——3D效果应用

今天在github里看到一个3D效果的ViewPager,感觉做出来的ViewPager滑动的时候效果十分的炫,就check out下来研究了一下如何实现的,以及如何使用,将整个ViewPager稍加修改后(主要是处理了一下与项目中其它控滑动控件的事件冲突)后,应用到了自己现在项目中.感觉这个效果真的非常的不错,现在把自己写的一个Demo分享出来. 下面是这个ViewPager嵌入到项目中的效果图: 修改以后,在切换ViewPager时会有立体感,会为自己的应用增色不少.下面把使用的Demo发出

Android自定义上下文菜单

今天自定义了一个简单的Android菜单控件.实现方式是:PopupWindow和ListView. 现在来给大家分享一下源码: SHContextMenu.java 核心代码部分:主要是对PopupWindow和ListView的初始化,为ListView设置数据源,以及封装了菜单的显示和隐藏的方法.还有提供了菜单的点击回调. import android.app.Activity; import android.content.Context; import android.graphics

Android 自定义ScrollView(具有反弹效果的ScrollView,能够兼容横向的滑动)

package com.itau.jingdong.widgets; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.animation.TranslateAnimation; import and

自定义滑动菜单SlidingMenu

最近没事就模仿scdn客户端的左右侧滑效果,自定义了一个SlindingMenu,虽然github上已经有了相当成熟的SlindingMenu开源框架,但本博客旨在帮助更多同学理解SlidingMenu的原理,使使用起来更得心应手. 1.分析 首先对其实现进行分析,SlindingMenu的布局需要有两部分,一个是菜单(menu)的布局,一个是内容(content)的布局.两个布局横向排列,菜单布局在左,内容布局在右.初始化的时候将菜单布局向左偏移,以至于能够完全隐藏,这样内容布局就会完全显示在

ViewDragHelper实践之仿Android官方侧滑菜单NavigationDrawer效果

相信经常使用移动应用的用户都很熟悉侧滑菜单栏, 下拉, 下弹, 上弹等应用场景, 几乎主流的移动应用无论IOS 还是Android都能看到. 2.3以前的时候, 很多第三方比如SlidingMenu, MenuDrawer, ActionbarSherlock等等都很大程度的丰富和深化了这种交互理念.能让小小的屏幕, 容纳更多的交互接口. 也是这种趋势, Android官方在v4终于推出了DrawerLayout. 表示对侧滑的重视与肯定. 唠叨到这了. 去看了DrawerLayout的源码和官