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

Android实现探探图片滑动效果

程序员文章站 2023-10-19 20:55:29
之前一段时间,在朋友的推荐下,玩了探探这一款软件,初玩的时候,就发现,这款软件与一般的社交软件如陌陌之类的大相径庭,让我耳目一新,特别是探探里关于图片滑动操作让人觉得非常新...

之前一段时间,在朋友的推荐下,玩了探探这一款软件,初玩的时候,就发现,这款软件与一般的社交软件如陌陌之类的大相径庭,让我耳目一新,特别是探探里关于图片滑动操作让人觉得非常新鲜。所以在下通过网上之前的前辈的经历加上自己的理解,也来涉涉水。下面是网上找的探探的原界面

Android实现探探图片滑动效果

当时就非常想通过自己来实现这种仿探探式的效果,然而却没什么思路。不过毋庸置疑的是,这种效果的原理肯定和 listview /recyclerview 类似,涉及到 item view 的回收和重用,否则早就因为大量的 item view 而 oom 了。
从view入手,recyclerview 是自带 item view 回收和重用功能的,而且,recyclerview 的布局方式是通过设置 layoutmanager 来实现的,这样就充分地把布局和 recyclerview 搞定了。

继承 recyclerview.layoutmanager , 显示自己管理布局, 比如最多显示4个view, 并且都是居中显示.
底部的view还需要进行缩放,平移操作.

public class overlaycardlayoutmanager extends recyclerview.layoutmanager {
  private static final string tag = "swipecard";
  public static int max_show_count = 4;
  public static float scale_gap = 0.05f;
  public static int trans_y_gap;

  public overlaycardlayoutmanager(context context) {
    //平移时, 需要用到的参考值
    trans_y_gap = (int) (20 * context.getresources().getdisplaymetrics().density);
  }

  @override
  public recyclerview.layoutparams generatedefaultlayoutparams() {
    //必须要实现的方法
    return new recyclerview.layoutparams(viewgroup.layoutparams.wrap_content, viewgroup.layoutparams.wrap_content);
  }

  @override
  public void onlayoutchildren(recyclerview.recycler recycler, recyclerview.state state) {
    //在这个方法中进行view的布局操作.此方法会被调用多次.
    detachandscrapattachedviews(recycler);
    int itemcount = getitemcount();
    if (itemcount < 1) {
      return;
    }
    //top-3view的position
    int bottomposition;
    //边界处理
    if (itemcount < max_show_count) {
      bottomposition = 0;
    } else {
      bottomposition = itemcount - max_show_count;
    }

    //从可见的最底层view开始layout,依次层叠上去
    for (int position = bottomposition; position < itemcount; position++) {
      //1:重recycler的缓存机制中拿到一个view
      view view = recycler.getviewforposition(position);
      //2:和自定义viewgroup一样, 需要先addview
      addview(view);
      //3:和自定义viewgroup一样, 也需要测量view的大小
      measurechildwithmargins(view, 0, 0);
      int widthspace = getwidth() - getdecoratedmeasuredwidth(view);
      int heightspace = getheight() - getdecoratedmeasuredheight(view);
      //4:和自定义viewgroup的onlayout一样, 需要layout view.对view进行布局 
      //我们在布局时,将childview居中处理,这里也可以改为只水平居中
      layoutdecoratedwithmargins(view, widthspace / 2, heightspace / 2,
          widthspace / 2 + getdecoratedmeasuredwidth(view),
          heightspace / 2 + getdecoratedmeasuredheight(view));
      /**
       * topview的scale 为1,translationy 0
       * 每一级scale相差0.05f,translationy相差7dp左右
       *
       * 观察人人影视的ui,拖动时,topview被拖动,scale不变,一直为1.
       * top-1view 的scale慢慢变化至1,translation也慢慢恢复0
       * top-2view的scale慢慢变化至 top-1view的scale,translation 也慢慢变化只top-1view的translation
       * top-3view的scale要变化,translation岿然不动
       */

      //第几层,举例子,count =7, 最后一个topview(6)是第0层,
      int level = itemcount - position - 1;

      //如果不需要缩放平移, 那么下面的代码可以注释掉...
      //除了顶层不需要缩小和位移
      if (level > 0 /*&& level < mshowcount - 1*/) {
        //每一层都需要x方向的缩小
        view.setscalex(1 - scale_gap * level);
        //前n层,依次向下位移和y方向的缩小
        if (level < max_show_count - 1) {
          view.settranslationy(trans_y_gap * level);
          view.setscaley(1 - scale_gap * level);
        } else {//第n层在 向下位移和y方向的缩小的成都与 n-1层保持一致
          view.settranslationy(trans_y_gap * (level - 1));
          view.setscaley(1 - scale_gap * (level - 1));
        }
      }
    }
  }
}

谷歌官方提供了一个itemtouchhelper工具类, 对滑动进行了优越封装

使用方法: new itemtouchhelper(callback).attachtorecyclerview(recyclerview);就这么简单,
接下来的操作, 都在回调callback里面进行.

public class renrencallback extends itemtouchhelper.simplecallback {

  private static final string tag = "renren";
  private static final int max_rotation = 15;
  onswipelistener mswipelistener;
  boolean isswipeanim = false;

  public renrencallback() {
    //第一个参数决定可以拖动排序的方向, 这里由于不需要拖动排序,所以传0
    //第二个参数决定可以支持滑动的方向,这里设置了上下左右都可以滑动.
    super(0, itemtouchhelper.down | itemtouchhelper.up | itemtouchhelper.left | itemtouchhelper.right);
  }

  public void setswipelistener(onswipelistener swipelistener) {
    mswipelistener = swipelistener;
  }

  //水平方向是否可以被回收掉的阈值
  public float getthreshold(recyclerview recyclerview, recyclerview.viewholder viewholder) {
    //2016 12 26 考虑 探探垂直上下方向滑动,不删除卡片,这里参照源码写死0.5f
    return recyclerview.getwidth() * /*getswipethreshold(viewholder)*/ 0.5f;
  }

  @override
  public boolean onmove(recyclerview recyclerview, recyclerview.viewholder viewholder, recyclerview.viewholder target) {
    //由于不支持滑动排序, 所以不需要处理此方法
    return false;
  }

  @override
  public void onswiped(recyclerview.viewholder viewholder, int direction) {
    //当view需要滑动的时候,会回调此方法
    //但是这个方法只是告诉你view需要滑动, 并不是对view和adapter进行额外的操作,
    //所以, 如果你需要实现滑动删除, 那么需要在此方法中remove item等.

    //我们这里需要对滑动过后的view,进行恢复操作. 
    viewholder.itemview.setrotation(0);//恢复最后一次的旋转状态
    if (mswipelistener != null) {
      mswipelistener.onswipeto(viewholder, 0);
    }
    notifylistener(viewholder.getadapterposition(), direction);
  }

  private void notifylistener(int position, int direction) {
    log.w(tag, "onswiped: " + position + " " + direction);
    if (mswipelistener != null) {
      mswipelistener.onswiped(position, direction);
    }
  }

  @override
  public float getswipethreshold(recyclerview.viewholder viewholder) {
    //滑动的比例达到多少之后, 视为滑动
    return 0.3f;
  }


  @override
  public void onchilddraw(canvas c, recyclerview recyclerview, recyclerview.viewholder viewholder, float dx, float dy, int actionstate, boolean iscurrentlyactive) {
    super.onchilddraw(c, recyclerview, viewholder, dx, dy, actionstate, iscurrentlyactive);
    //当你在滑动的过程中, 此方法一直会被回调, 就跟ontouch事件一样...
    //先根据滑动的dx dy 算出现在动画的比例系数fraction
    float swipevalue = (float) math.sqrt(dx * dx + dy * dy);
    final float threshold = getthreshold(recyclerview, viewholder);
    float fraction = swipevalue / threshold;
    //边界修正 最大为1
    if (fraction > 1) {
      fraction = 1;
    } else if (fraction < -1) {
      fraction = -1;
    }
    //对每个childview进行缩放 位移
    int childcount = recyclerview.getchildcount();
    for (int i = 0; i < childcount; i++) {
      view child = recyclerview.getchildat(i);
      //第几层,举例子,count =7, 最后一个topview(6)是第0层,
      int level = childcount - i - 1;
      if (level > 0) {
        child.setscalex(1 - scale_gap * level + fraction * scale_gap);

        if (level < max_show_count - 1) {
          child.setscaley(1 - scale_gap * level + fraction * scale_gap);
          child.settranslationy(trans_y_gap * level - fraction * trans_y_gap);
        } else {
          //child.settranslationy((float) (mtranslationygap * (level - 1) - fraction * mtranslationygap));
        }
      } else {
        //最上层
        //rotate
        if (dx < -50) {
          child.setrotation(-fraction * max_rotation);
        } else if (dx > 50) {
          child.setrotation(fraction * max_rotation);
        } else {
          child.setrotation(0);
        }

        if (mswipelistener != null) {
          recyclerview.layoutparams params = (recyclerview.layoutparams) child.getlayoutparams();
          final int adapterposition = params.getviewadapterposition();
          mswipelistener.onswipeto(recyclerview.findviewholderforadapterposition(adapterposition), dx);
        }
      }
    }
  }

  //扩展实现:点击按钮实现左滑效果
  public void toleft(recyclerview recyclerview) {
    if (check(recyclerview)) {
      animto(recyclerview, false);
    }
  }

  //扩展实现:点击按钮实现右滑效果
  public void toright(recyclerview recyclerview) {
    if (check(recyclerview)) {
      animto(recyclerview, true);
    }
  }

  private void animto(final recyclerview recyclerview, boolean right) {
    final int position = recyclerview.getadapter().getitemcount() - 1;
    final view view = recyclerview.findviewholderforadapterposition(position).itemview;

    translateanimation translateanimation = new translateanimation(animation.relative_to_self, 0,
        animation.relative_to_self, right ? 1f : -1f,
        animation.relative_to_self, 0f, animation.relative_to_self, 1.3f);
    translateanimation.setfillafter(true);
    translateanimation.setduration(300);
    translateanimation.setinterpolator(new decelerateinterpolator());
    translateanimation.setanimationlistener(new animation.animationlistener() {
      @override
      public void onanimationstart(animation animation) {

      }

      @override
      public void onanimationend(animation animation) {
        isswipeanim = false;
        recyclerview.removeview(view);
        notifylistener(position,
            x > view.getmeasuredwidth() / 2
                ?
                itemtouchhelper.right : itemtouchhelper.left);
      }

      @override
      public void onanimationrepeat(animation animation) {

      }
    });
    view.startanimation(translateanimation);
  }

  private boolean check(recyclerview recyclerview) {
    if (isswipeanim) {
      return false;
    }
    if (recyclerview == null || recyclerview.getadapter() == null) {
      return false;
    }
    if (recyclerview.getadapter().getitemcount() == 0) {
      return false;
    }
    isswipeanim = true;
    return true;
  }

  public interface onswipelistener {

    /**
     * @param direction {@link itemtouchhelper#left} / {@link itemtouchhelper#right}
     *         {@link itemtouchhelper#up} or {@link itemtouchhelper#down}).
     */
    void onswiped(int adapterposition, int direction);

    /**
     * 最上层view滑动时回调.
     *
     * @param viewholder 最上层的viewholder
     * @param offset   距离原始位置的偏移量
     */
    void onswipeto(recyclerview.viewholder viewholder, float offset);
  }

  public static class simpleswipecallback implements onswipelistener {

    /**
     * {@inheritdoc}
     */
    @override
    public void onswiped(int adapterposition, int direction) {

    }

    /**
     * {@inheritdoc}
     */
    @override
    public void onswipeto(recyclerview.viewholder viewholder, float offset) {

    }
  }
}

布局文件:卡片内容

<linearlayout  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_gravity="center_horizontal|bottom"
  android:layout_marginbottom="20dp"
  android:orientation="horizontal">

  <imageview
    android:id="@+id/left"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:layout_margin="10dp"
    android:background="@drawable/home_buttons_dislike"
    android:onclick="left" />

  <imageview
    android:id="@+id/info"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:layout_margin="10dp"

    android:background="@drawable/home_buttons_info" />

  <imageview
    android:id="@+id/right"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="10dp"
    android:background="@drawable/home_buttons_like"
    />

</linearlayout>

布局文件:点击按钮

<?xml version="1.0" encoding="utf-8"?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_gravity="center_horizontal|bottom"
  android:layout_marginbottom="20dp"
  android:orientation="horizontal">

  <imageview
    android:id="@+id/left"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:layout_margin="10dp"
    android:background="@drawable/home_buttons_dislike"
    android:onclick="left" />

  <imageview
    android:id="@+id/info"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:layout_margin="10dp"

    android:background="@drawable/home_buttons_info" />

  <imageview
    android:id="@+id/right"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="10dp"
    android:background="@drawable/home_buttons_like"
    />

</linearlayout>

监听函数:

    flingcontainer = (swipeflingadapterview) findviewbyid(r.id.frame);
   //设置适配器
    flingcontainer.setadapter(adapter);
    flingcontainer.setflinglistener(new swipeflingadapterview.onflinglistener() {
      @override
      public void removefirstobjectinadapter() {
        al.remove(0);
        adapter.notifydatasetchanged();
      }
      @override
      public void onleftcardexit(object dataobject) {
        maketoast(mainactivity.this, "不喜欢");
      }

      @override
      public void onrightcardexit(object dataobject) {
        maketoast(mainactivity.this, "喜欢");
      }

      @override
      public void onadapterabouttoempty(int itemsinadapter) {
        al.add(new cardmode("循环测试", 18, list.get(itemsinadapter % imageurls.length - 1)));
        adapter.notifydatasetchanged();
        i++;
      }

      @override
      public void onscroll(float scrollprogresspercent) {
        view view = flingcontainer.getselectedview();
        view.findviewbyid(r.id.item_swipe_right_indicator).setalpha(scrollprogresspercent < 0 ? -scrollprogresspercent : 0);
        view.findviewbyid(r.id.item_swipe_left_indicator).setalpha(scrollprogresspercent > 0 ? scrollprogresspercent : 0);
      }
    });

总结一下,在这整个代码流程中我们主要是运用了自定义 layoutmanager 以及 itemtouchhelper.callback
接下来,我们看看效果:

Android实现探探图片滑动效果

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