Android 自定义View实现抽屉效果

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

    }
  }

}

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

(0)

相关推荐

  • Android开发之DrawerLayout实现抽屉效果

    谷歌官方推出了一种侧滑菜单的实现方式(抽屉效果),即 DrawerLayout,这个类是在Support Library里的,需要加上android-support-v4.jar这个包. 使用注意点 1.DrawerLayout的第一个子元素必须是默认内容,即抽屉没有打开时显示的布局(如FrameLayout),后面紧跟的子元素是抽屉内容,即抽屉布局(如ListView). 2.抽屉菜单的摆放和布局通过android:layout_gravity属性来控制,可选值为left.right或star

  • Android实现图片轮播效果的两种方法

    大家在使用APP的过程中,经常会看到上部banner图片轮播的效果,那么今天我们就一起来学习一下,android中图片轮询的几种实现方法: 第一种:使用动画的方法实现:(代码繁琐) 这种发放需要:两个动画效果,一个布局,一个主类来实现,不多说了,来看代码吧: public class IamgeTrActivity extends Activity { /** Called when the activity is first created. */ public ImageView image

  • Android App中DrawerLayout抽屉效果的菜单编写实例

    抽屉效果的导航菜单 看了很多应用,觉得这种侧滑的抽屉效果的菜单很好. 不用切换到另一个页面,也不用去按菜单的硬件按钮,直接在界面上一个按钮点击,菜单就滑出来,而且感觉能放很多东西. 库的引用: 首先, DrawerLayout这个类是在Support Library里的,需要加上android-support-v4.jar这个包. 然后程序中用时在前面导入import android.support.v4.widget.DrawerLayout; 如果找不到这个类,首先用SDK Manager更

  • Android编程实现抽屉效果的方法详解

    本文实例讲述了Android编程实现抽屉效果的方法.分享给大家供大家参考,具体如下: android的UI开发确实是一件很有趣的事情,也是一件很有挑战性的事情. 本文章是将自己在开发中的项目中使用到的比较好的抽屉效果的原理以及代码整理后写上来的,以备忘记后可以查阅 抽屉效果的原理很简单,就是给其一个事件监听(动作),然后对此动作所作出的反应(开 or 关): 在编写代码的时候要注意的几点如下: 1. 打开抽屉的图标(即触发抽屉的把手),打开后的界面都是布局在<SlidingDrawer/>与&

  • Android SlidingDrawer 抽屉效果的实现

    SlidingDrawer隐藏屏外的内容,并允许用户通过handle以显示隐藏内容.它可以垂直或水平滑动,它有俩个View组成,其一是可以拖动的handle,其二是隐藏内容的View.它里面的控件必须设置布局,在布局文件中必须指定handle和content. 1.布局layou文件 复制代码 代码如下: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_w

  • Android 抽屉效果的导航菜单实现代码实例

    看了很多应用,觉得这种侧滑的抽屉效果的菜单很好. 不用切换到另一个页面,也不用去按菜单的硬件按钮,直接在界面上一个按钮点击,菜单就滑出来,而且感觉能放很多东西. 关于实现,搜索了一下,有如下两种: 1.用SlidingDrawer:http://developer.android.com/reference/android/widget/SlidingDrawer.html 但是不知道为什么这个类官方不建议再继续用了:Deprecated since API level 17 2.用Drawer

  • Android的Activity跳转动画各种效果整理

    大家使用Android的原生UI都知道,Android的Activity跳转就是很生硬的切换界面.其实Android的Activity跳转可以设置各种动画.下面给大家看看效果:  实现非常简单,用overridePendingtransition(int inId, int outId)即可实现.inId是下一界面进入效果的xml文件的id,outId是当前界面退出效果的xml文件id. 效果是用xml文件写的,首先要在res文件夹下建立anim文件夹,然后把动画效果xml文件放到里面去. 下面

  • Android Tween动画之RotateAnimation实现图片不停旋转效果实例介绍

    主要介绍Android中如何使用rotate实现图片不停旋转的效果.Android 平台提供了两类动画,一类是 Tween 动画,即通过对场景里的对象不断做图像变换(平移.缩放.旋转)产生动画效果:第二类是 Frame 动画,即顺序播放事先做好的图像,跟电影类似.本文分析 Tween动画的rotate实现旋转效果. 在新浪微博客户端中各个操作进行中时activity的右上角都会有个不停旋转的图标,类似刷新的效果,给用户以操作中的提示.这种非模态的提示方式推荐使用,那么下面就分享下如何实现这种效果

  • Android DrawerLayout实现抽屉效果实例代码

    官网:https://developer.android.com/training/implementing-navigation/nav-drawer.html 贴上主要的逻辑和布局文件: activity_main.xml <?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.DrawerLayout xmlns:android="http://schema

  • Android编程实现抽屉效果的方法示例

    本文实例讲述了Android编程实现抽屉效果的方法.分享给大家供大家参考,具体如下: 今天在手机上实现了抽屉效果,其实很简单,但是效果却很酷. 首先在layout 下设置xml布局文件 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:l

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

    在Andoird使用Android自带的那些组件,像SlidingDrawer和DrawerLayout都是抽屉效果的菜单,但是在项目很多要实现的功能都收到Android这些自带组件的限制,导致很难完成项目的需求,自定义的组件,各方面都在自己的控制之下,从而根据需求做出调整.想要实现好的效果,基本上都的基于Android的OnTouch事件自己实现响应的功能. 首先,给大家先看一下整体的效果: 滑动的加速度效果都是有的,具体的体验,只能安装后才能查看. 接下来,看代码: 代码从MainActiv

  • Android自定义控件仿QQ抽屉效果

    其实网上类似的实现已经很多了,原理也并不难,只是网上各种demo运行下来,多少都有一些问题.折腾了半天,决定自己实现一个. 首先我们看看实现效果: 对比网上各类demo,这次要实现的主要表现在以下几点: 1.侧滑显示抽屉view 2.侧滑抽屉隐藏view控件点击事件 3.单击任意item隐藏显示的抽屉view 4.滑动list隐藏显示的抽屉view 5.增加SwipeLayout点击事件和Swipe touch事件判断处理 6.优化快速划开多个抽屉隐藏view时多个SwipeLayout滑动状态

随机推荐