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);
 }
 }
}

源码下载:抽屉效果

更多关于滑动功能的文章,请点击专题: 《Android滑动功能》

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android侧滑菜单之DrawerLayout用法详解

    onConfigurationChanged最早的时候实现侧滑菜单功能大多时候需要通过github上一个叫做SlidingMenu的开源通过依赖包来实现,后来谷歌在v4包中添加了DrawerLayout来实现这个功能,完全可以替代SlidingMenu,这里我们来学习DrawerLayout的用法 一)创建DrawerLayout 1)在布局文件里将布局设置为DrawerLaout,而且因为是v4包中的功能,所以必须写全包名,注意第一必须先写主视图布局,然后再写抽屉里的视图,这里我们放了List

  • Android利用滑动菜单框架实现滑动菜单效果

    之前我向大家介绍了史上最简单的滑动菜单的实现方式,相信大家都还记得.如果忘记了其中的实现原理或者还没看过的朋友,请先去看一遍之前的文章Android仿人人客户端滑动菜单的侧滑特效实现代码,史上最简单的侧滑实现 ,因为我们今天要实现的滑动菜单框架也是基于同样的原理的. 之前的文章中在最后也提到了,如果是你的应用程序中有很多个Activity都需要加入滑动菜单的功能,那么每个Activity都要写上百行的代码才能实现效果,再简单的滑动菜单实现方案也没用.因此我们今天要实现一个滑动菜单的框架,然后在任

  • android实现上滑屏幕隐藏底部菜单栏的示例

    本篇文章引用github上一个仿今日头条项目,项目地址: https://github.com/iMeiji/Toutiao ,主要实现的功能是底部菜单栏随用户手势滑动而变化可见状态 布局代码 这个功能实现起来比较简单,主要利用了CoordinatorLayout的 layout_behavior 的属性.具体代码如下: <android.support.design.widget.CoordinatorLayout android:layout_width="match_parent&q

  • Android左右滑出菜单实例分析

    现在的Android应用,只要有一个什么新的创意,过不了多久,几乎所有的应用都带这个创意.这不,咱们公司最近的一个持续性的项目,想在首页加个从左滑动出来的菜单,我查阅网上资料,并自己摸索,实现了左.右两边都能滑出菜单,并且,左.右菜单中,都可以加ListView等这类需要解决GestureDetector冲突的问题(如在首页面中,含有ListView,上下滚动时,左右不动,相反,左右滑动菜单时,上下不动,听着头就大了吧!) 先上几张图,给大家瞧瞧,对整体有个了解:  一.首页布局: 复制代码 代

  • Android实现原生侧滑菜单的超简单方式

    先来看看效果图 当你点击菜单可以更改图标,例如点击happy,首页就会变一个笑脸,这个实现的过程超级简单 你需要使用ToolBar与DrawableLayout两个比较新的控件 首先要写三个xml布局文件,我这里的布局文件是使用了include标签嵌入的,代码如下 headbar_toolbar.xml <?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.Toolbar

  • Android滑动优化高仿QQ6.0侧滑菜单(滑动优化)

     推荐阅读:Android使用ViewDragHelper实现仿QQ6.0侧滑界面(一) 但是之前的实现,只是简单的可以显示和隐藏左侧的菜单,但是特别生硬,而且没有任何平滑的趋势,那么今天就来优化一下吧,加上平滑效果,而且可以根据手势滑动的方向来判断是否是显示和隐藏. 首先先来实现手势判断是否隐藏和显示 这里就要用到了一个方法了,如下: 这个是ViewDradHelper里面的方法: /** * 当view被释放的时候处理的事情(松手) * * @param releasedChild 被释放的

  • Android实现顶部导航菜单左右滑动效果

    本文给大家介绍在Android中如何实现顶部导航菜单左右滑动效果,具体内容如下 第一种解决方案: 实现原理是使用android-support-v4.jar包中ViewPager控件,在ViewPager控件中设置流布局,再在流布局中设置几项TextView,给每一个TextView设置相关参数,事件等.关于ViewPager控件可以设置全屏幕滑动效果,当然也可以实现局部滑动效果,下面介绍导航菜单. 关于导航菜单,相信大家对它并不陌生,比如在新闻客户端中就经常使用左右滑动菜单来显示不同类别的新闻

  • android RecyclerView侧滑菜单,滑动删除,长按拖拽,下拉刷新上拉加载

    本文介绍的库中的侧滑效果借鉴自SwipeMenu,并对SipwMenu的源码做了修改与Bug修复,然后才开发出的SwipeRecyclerView. 需要说明的是,本库没有对RecyclerView做大的修改,只是ItemView的封装.看起来是对RecyclerView的修改,其实仅仅是为RecyclerView添加了使用的方法API而已. 本库已经更新了三个版本了,会一直维护下去,根据小伙伴的要求,以后也会添加一些其它功能. SwipeRecyclerView将完美解决这些问题: 以下功能全

  • android自定义左侧滑出菜单效果

    这里给大家提供一个类似QQ聊天那种可以左侧滑出菜单的自定义控件.希望对大家有帮助.参考了一些网友的做法,自己整理优化了一下,用法非常简单,就一个类,不需要自己写任何的代码,只要添加上布局就能实现侧滑菜单效果,非常方便.不多说,一看就懂. 先来看看效果: 先看看实现: package com.kokjuis.travel.customView;   import android.content.Context; import android.content.res.TypedArray; impo

  • Android仿微信滑动弹出编辑、删除菜单效果、增加下拉刷新功能

    如何为不同的list item呈现不同的菜单,本文实例就为大家介绍了Android仿微信或QQ滑动弹出编辑.删除菜单效果.增加下拉刷新等功能的实现,分享给大家供大家参考,具体内容如下 效果图: 1. 下载开源项目,并将其中的liberary导入到自己的项目中: 2. 使用SwipeMenuListView代替ListView,在页面中布局: <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/swipeRefresh

随机推荐