当前位置: 首页 > 编程笔记 >

Android实现自定义滑动式抽屉菜单效果

屈博
2023-03-14
本文向大家介绍Android实现自定义滑动式抽屉菜单效果,包括了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滑动功能》

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

 类似资料:
  • 本文向大家介绍Android自定义控件简单实现侧滑菜单效果,包括了Android自定义控件简单实现侧滑菜单效果的使用技巧和注意事项,需要的朋友参考一下 侧滑菜单在很多应用中都会见到,最近QQ5.0侧滑还玩了点花样~~对于侧滑菜单,一般大家都会自定义ViewGroup,然后隐藏菜单栏,当手指滑动时,通过Scroller或者不断的改变leftMargin等实现;多少都有点复杂,完成以后还需要对滑动冲突

  • 本文向大家介绍Android利用滑动菜单框架实现滑动菜单效果,包括了Android利用滑动菜单框架实现滑动菜单效果的使用技巧和注意事项,需要的朋友参考一下 之前我向大家介绍了史上最简单的滑动菜单的实现方式,相信大家都还记得。如果忘记了其中的实现原理或者还没看过的朋友,请先去看一遍之前的文章Android仿人人客户端滑动菜单的侧滑特效实现代码,史上最简单的侧滑实现 ,因为我们今天要实现的滑动菜单框架

  • 本文向大家介绍IOS实现点击滑动抽屉效果,包括了IOS实现点击滑动抽屉效果的使用技巧和注意事项,需要的朋友参考一下 最近,看到好多Android上的抽屉效果,也忍不住想要自己写一个。在Android里面可以用SlidingDrawer,很方便的实现。IOS上面就只有自己写了。其实原理很简单就是 UIView 的移动,和一些手势的操作。 效果图: 以上就是本文的全部内容,希望对大家的学习有所帮助。

  • 嗨,我正在尝试创建一个类似gmail应用程序导航抽屉的导航抽屉。我关注开发者网站,但它只指定基本的实现。但是我需要根据我的规格定制导航。 我需要添加标题以对抽屉中的列表项进行分类 我需要一个单选按钮来选择我的一些选项 我怎么能这么做?

  • 本文向大家介绍Android组件之DrawerLayout实现抽屉菜单,包括了Android组件之DrawerLayout实现抽屉菜单的使用技巧和注意事项,需要的朋友参考一下 DrawerLayout组件同样是V4包中的组件,也是直接继承于ViewGroup类,所以这个类也是一个容器类。 抽屉菜单的摆放和布局通过android:layout_gravity属性来控制,可选值为left、right或

  • 本文向大家介绍Swift4.1转场动画实现侧滑抽屉效果,包括了Swift4.1转场动画实现侧滑抽屉效果的使用技巧和注意事项,需要的朋友参考一下 本文实现使用了Modal转场动画,原因是项目多由导航控制器和标签控制器作为基类,为了不影响导航控制器的代理,转场动画使用模态交互。 代码使用SnapKit进行布局,能够适应屏幕旋转。手势速率大于300或进度超过30%的时候直接完成动画,否则动画回滚取消,具