欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android 自定义View实现抽屉效果

程序员文章站 2023-11-24 12:09:34
android 自定义view实现抽屉效果 说明 这个自定义view,没有处理好多点触摸问题 view跟着手指移动,没有采用传统的scrollby方法,而...

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


    }
  }

}

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