Android 自定义View实现抽屉效果

作者:Android_Study_OK 时间:2022-08-03 01:27:00 

Android 自定义View实现抽屉效果

说明

  1. 这个自定义View,没有处理好多点触摸问题

  2. View跟着手指移动,没有采用传统的scrollBy方法,而是通过不停地重新布局子View的方式,来使得子View产生滚动效果menuView.layout(menuLeft, 0, menuLeft + menuWidth, menuHeight);

  3. 相应的,由于没有使用scrollBy方法,就没有产生getScrollX值,所以不能通过Scroller的startScroll方法来完成手指离开后的平滑滚动效果,而是使用了Animation动画的applyTransformation方法来完成插值,从而实现动画效果

主要算法是:动画当前值=起始值+(目标值-起始值)*interpolatedTime

其中interpolatedTime是一个0.0f~1.0f的数字,系统自己插值计算好了(默认是线性变化的),当然你可以自己写插值器


/**
  * 由于上面不能使用scrollBy,那么这里就不能使用Scroller这个类来完成平滑移动了,还好我们有动画
  */
 class MyAnimation extends Animation {

private int viewCurrentLfet;
   private int viewStartLfet;
   private int viewTargetLfet;
   private int viewWidth;
   private View view;
   private int cha;

public MyAnimation(View view, int viewStartLfet, int viewTargetLfet, int viewWidth) {
     this.view = view;
     this.viewStartLfet = viewStartLfet;
     this.viewTargetLfet = viewTargetLfet;
     this.viewWidth = viewWidth;
     cha = viewTargetLfet - viewStartLfet;
     setDuration(Math.abs(cha));
   }

@Override
   protected void applyTransformation(float interpolatedTime, Transformation t) {
     super.applyTransformation(interpolatedTime, t);

viewCurrentLfet = (int) (viewStartLfet + cha * interpolatedTime);
     view.layout(viewCurrentLfet, 0, viewCurrentLfet + viewWidth, menuHeight);

}
 }

完整代码


package com.sunshine.choutidemo;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.Transformation;

/**
* Created by a on 2016/8/15.
*/
public class ChouTiView extends ViewGroup {

private View mainView;
 private View menuView;
 private int menuWidth;
 private int downX;
 private int lastX;
 private int moveX;
 private int deltaX;
 private int menuLeft;
 private int mainLeft;
 private int menuHeight;
 private int mainWidth;
 private int mainHeight;
 private int menuLeftBorder;
 private int mainLeftBorder;
 private int menuRightBorder;
 private int mainRightBorder;
 private int mMaxVelocity;
 private VelocityTracker mVelocityTracker;
 private int mPointerId;
 private float velocityX;
 private float velocityY;

public ChouTiView(Context context) {
   super(context);
   init();
 }

public ChouTiView(Context context, AttributeSet attrs) {
   super(context, attrs);
   init();
 }

private void init() {
//   0.获得此次最大速率
   mMaxVelocity = ViewConfiguration.get(getContext()).getMaximumFlingVelocity();
 }

@Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   super.onMeasure(widthMeasureSpec, heightMeasureSpec);
   mainView.measure(widthMeasureSpec, heightMeasureSpec);
   menuView.measure(widthMeasureSpec, heightMeasureSpec);
//    获得子View的正确宽度(只能获取具体的数字值),但是不能这样获取高度,因为这里match—parent为-1
   menuWidth = menuView.getLayoutParams().width;
   menuLeft = (int) (-menuWidth * 0.5);
   menuLeftBorder = (int) (-menuWidth * 0.5);
   menuRightBorder = 0;
   mainLeft = 0;
   mainLeftBorder = 0;
   mainRightBorder = menuWidth;

}

@Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
   menuHeight = b;
   mainWidth = r;
   mainHeight = b;
   mainView.layout(l, t, r, b);
   menuView.layout(menuLeft, t, menuLeft + menuWidth, b);

}

@Override
 protected void onFinishInflate() {
   super.onFinishInflate();
   mainView = getChildAt(1);
   menuView = getChildAt(0);
 }

@Override
 public boolean onTouchEvent(MotionEvent event) {
   final int action = event.getActionMasked();

acquireVelocityTracker(event); //1.向VelocityTracker添加MotionEvent
   final VelocityTracker verTracker = mVelocityTracker;
   switch (action) {

case MotionEvent.ACTION_DOWN:
       //2.求第一个触点的id, 此时可能有多个触点,但至少一个
       // 获取索引为0的手指id
       mPointerId = event.getPointerId(0);
       downX = (int) event.getX();
       lastX = downX;
       break;

case MotionEvent.ACTION_MOVE:
// 获取当前手指id所对应的索引,虽然在ACTION_DOWN的时候,我们默认选取索引为0
       // 的手指,但当有第二个手指触摸,并且先前有效的手指up之后,我们会调整有效手指

// 屏幕上可能有多个手指,我们需要保证使用的是同一个手指的移动轨迹,
       // 因此此处不能使用event.getActionIndex()来获得索引
       final int pointerIndex = event.findPointerIndex(mPointerId);

moveX = (int) event.getX(pointerIndex);
       deltaX = moveX - lastX;
//        把触摸移动引起的增量,体现在menu和main的左侧left上
       menuLeft = (int) (menuLeft + deltaX * 0.43);//让菜单移动的慢一点
       mainLeft = mainLeft + deltaX;
//        让菜单根据手指增量移动,考虑两侧边界问题(通过不停地layout实现移动效果)
//        为何不适用scrollBy,因为scrollBy移动的是外层的大View,现在需求是分别移动这个大view内的两个小View
//        scrollBy的话,会让菜单和主页面同时移动,不会产生错位效果,
//        你会想,那让小view自己scrollBy,这样也是不行的,
//        因为让小view,例如menu调用scrollBy的话,会让menu自己的边框在动,
//        看上去,是menu内部的文字在移动,但是menu并没有在外层的大View里移动
//        说的很拗口,但是真的不能用scrollBy
       if (menuLeft >= menuRightBorder) {
         menuLeft = menuRightBorder;
       } else if (menuLeft <= menuLeftBorder) {
         menuLeft = menuLeftBorder;
       }
       menuView.layout(menuLeft, 0, menuLeft + menuWidth, menuHeight);

//        让主页面根据手指增量移动,考虑两侧边界问题
       if (mainLeft >= mainRightBorder) {
         mainLeft = mainRightBorder;
       } else if (mainLeft <= mainLeftBorder) {
         mainLeft = mainLeftBorder;
       }
       mainView.layout(mainLeft, 0, mainLeft + mainWidth, mainHeight);

lastX = moveX;
       break;

case MotionEvent.ACTION_UP:
       //3.求伪瞬时速度
       verTracker.computeCurrentVelocity(1000, mMaxVelocity);
       velocityX = verTracker.getXVelocity(mPointerId);
       Log.e("qwe", velocityX + "/" + mMaxVelocity);
       if (velocityX > 1000) {
         smoothToMenu();
       } else if (velocityX < -2000) {
         smoothToMain();
       } else {
//        判断松手的位置,如果大于1/2.5的菜单宽度就打开菜单,否则打开主页面

if (mainLeft > menuWidth / 2.5) {
           Log.e("qqq", "显示菜单");
           smoothToMenu();
         } else {
           Log.e("qqq", "显示主页面");
           smoothToMain();
         }
       }
//        4.ACTION_UP释放VelocityTracker,交给其他控件使用
       releaseVelocityTracker();
       break;
     case MotionEvent.ACTION_CANCEL:

//        4.ACTION_UP释放VelocityTracker,交给其他控件使用
       releaseVelocityTracker();

case MotionEvent.ACTION_POINTER_UP:
       // 获取离开屏幕的手指的索引
       int pointerIndexLeave = event.getActionIndex();
       int pointerIdLeave = event.getPointerId(pointerIndexLeave);
       if (mPointerId == pointerIdLeave) {
         // 离开屏幕的正是目前的有效手指,此处需要重新调整,并且需要重置VelocityTracker
         int reIndex = pointerIndexLeave == 0 ? 1 : 0;
         mPointerId = event.getPointerId(reIndex);
         // 调整触摸位置,防止出现跳动
         downX = (int) event.getX(reIndex);
//          y = event.getY(reIndex);
         releaseVelocityTracker();
       }
       releaseVelocityTracker();

break;
   }

return true;
 }

private void smoothToMain() {
   MyAnimation menuAnimation = new MyAnimation(menuView, menuLeft, menuLeftBorder, menuWidth);
   MyAnimation mainAnimation = new MyAnimation(mainView, mainLeft, mainLeftBorder, mainWidth);
   AnimationSet animationSet = new AnimationSet(true);
   animationSet.addAnimation(menuAnimation);
   animationSet.addAnimation(mainAnimation);
   startAnimation(animationSet);
   //一定记得更新menu和main的左侧状态,这影响到了,再次手指触摸时候的动画,否则突变
   menuLeft = menuLeftBorder;
   mainLeft = mainLeftBorder;
 }

private void smoothToMenu() {
   MyAnimation menuAnimation = new MyAnimation(menuView, menuLeft, menuRightBorder, menuWidth);
   MyAnimation mainAnimation = new MyAnimation(mainView, mainLeft, mainRightBorder, mainWidth);
   AnimationSet animationSet = new AnimationSet(true);
   animationSet.addAnimation(menuAnimation);
   animationSet.addAnimation(mainAnimation);
   startAnimation(animationSet);
   //一定记得更新menu和main的左侧状态,这影响到了,再次手指触摸时候的动画,否则突变
   menuLeft = menuRightBorder;
   mainLeft = mainRightBorder;
 }

/**
  * @param event 向VelocityTracker添加MotionEvent
  * @see android.view.VelocityTracker#obtain()
  * @see android.view.VelocityTracker#addMovement(MotionEvent)
  */
 private void acquireVelocityTracker(final MotionEvent event) {
   if (null == mVelocityTracker) {
     mVelocityTracker = VelocityTracker.obtain();
   }
   mVelocityTracker.addMovement(event);
 }

/**
  * 释放VelocityTracker
  *
  * @see android.view.VelocityTracker#clear()
  * @see android.view.VelocityTracker#recycle()
  */
 private void releaseVelocityTracker() {
   if (null != mVelocityTracker) {
     mVelocityTracker.clear();
     mVelocityTracker.recycle();
     mVelocityTracker = null;
   }
 }

/**
  * 由于上面不能使用scrollBy,那么这里就不能使用Scroller这个类来完成平滑移动了,还好我们有动画
  */
 class MyAnimation extends Animation {

private int viewCurrentLfet;
   private int viewStartLfet;
   private int viewTargetLfet;
   private int viewWidth;
   private View view;
   private int cha;

public MyAnimation(View view, int viewStartLfet, int viewTargetLfet, int viewWidth) {
     this.view = view;
     this.viewStartLfet = viewStartLfet;
     this.viewTargetLfet = viewTargetLfet;
     this.viewWidth = viewWidth;
     cha = viewTargetLfet - viewStartLfet;
     setDuration(Math.abs(cha));
   }

@Override
   protected void applyTransformation(float interpolatedTime, Transformation t) {
     super.applyTransformation(interpolatedTime, t);

viewCurrentLfet = (int) (viewStartLfet + cha * interpolatedTime);
     view.layout(viewCurrentLfet, 0, viewCurrentLfet + viewWidth, menuHeight);

}
 }

}

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

来源:http://blog.csdn.net/android_study_ok/article/details/52280852

标签:Android,抽屉效果
0
投稿

猜你喜欢

  • Android开发手册TextInputLayout样式使用示例

    2023-10-16 11:03:12
  • 代码详解java里的“==”和“equels”区别

    2023-01-20 04:08:10
  • MVPXlistView展示上拉下拉效果

    2022-10-30 10:45:38
  • C++作用域与函数重载的实现

    2022-04-30 06:28:30
  • java Lambda表达式的使用心得

    2023-08-18 05:59:51
  • Android 获取设备屏幕大小的几种方法总结

    2022-11-30 14:29:07
  • Android SlidingDrawer 抽屉效果的实现

    2023-08-02 07:58:30
  • spring基础概念AOP与动态代理理解

    2022-01-29 20:55:13
  • 详解flutter中常用的container layout实例

    2022-07-25 07:49:53
  • android recyclerview模拟聊天界面

    2022-01-19 17:02:42
  • C#日期转换函数分享

    2021-06-30 16:48:38
  • java如何用Processing生成马赛克风格的图像

    2023-11-07 20:52:44
  • Java多线程Thread类的使用详解

    2023-11-11 13:08:11
  • Android自定义View展开菜单功能的实现

    2022-02-08 06:28:49
  • Android实现字母雨的效果

    2023-12-13 02:26:33
  • Android中mvp模式使用实例详解

    2023-12-11 19:48:04
  • java音乐播放器实现代码

    2022-08-29 08:41:22
  • C# 如何在MVC3中取消备用控制器的选择

    2023-02-16 06:48:18
  • SpringBoot通过源码探究静态资源的映射规则实现

    2022-03-26 19:05:53
  • Java基本类型与byte数组之间相互转换方法

    2023-11-16 22:49:23
  • asp之家 软件编程 m.aspxhome.com