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

当时就非常想通过自己来实现这种仿探探式的效果,然而却没什么思路。不过毋庸置疑的是,这种效果的原理肯定和 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);}@Overridepublic RecyclerView.LayoutParams generateDefaultLayoutParams() {//必须要实现的方法return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);}@Overridepublic void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {//在这个方法中进行View的布局操作.此方法会被调用多次.detachAndScrapAttachedViews(recycler);int itemCount = getItemCount();if (itemCount < 1) {return;}//top-3View的positionint 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的缓存机制中拿到一个ViewView view = recycler.getViewForPosition(position);//2:和自定义ViewGroup一样, 需要先addViewaddView(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.5freturn recyclerView.getWidth() * /*getSwipeThreshold(viewHolder)*/ 0.5f;}@Overridepublic boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {//由于不支持滑动排序, 所以不需要处理此方法return false;}@Overridepublic 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);}}@Overridepublic float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) {//滑动的比例达到多少之后, 视为滑动return 0.3f;}@Overridepublic 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 算出现在动画的比例系数fractionfloat swipeValue = (float) Math.sqrt(dX * dX + dY * dY);final float threshold = getThreshold(recyclerView, viewHolder);float fraction = swipeValue / threshold;//边界修正 最大为1if (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 {//最上层//rotateif (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() {@Overridepublic void onAnimationStart(Animation animation) {}@Overridepublic void onAnimationEnd(Animation animation) {isSwipeAnim = false;recyclerView.removeView(view);notifyListener(position,x > view.getMeasuredWidth() / 2?ItemTouchHelper.RIGHT : ItemTouchHelper.LEFT);}@Overridepublic 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}*/@Overridepublic void onSwiped(int adapterPosition, int direction) {}/*** {@inheritDoc}*/@Overridepublic 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() {@Overridepublic void removeFirstObjectInAdapter() {al.remove(0);adapter.notifyDataSetChanged();}@Overridepublic void onLeftCardExit(Object dataObject) {makeToast(MainActivity.this, "不喜欢");}@Overridepublic void onRightCardExit(Object dataObject) {makeToast(MainActivity.this, "喜欢");}@Overridepublic void onAdapterAboutToEmpty(int itemsInAdapter) {al.add(new CardMode("循环测试", 18, list.get(itemsInAdapter % imageUrls.length - 1)));adapter.notifyDataSetChanged();i++;}@Overridepublic 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
接下来,我们看看效果:

作者:徐义凯
链接:关于探探图片滑动操作

关于探探图片滑动操作相关推荐

  1. Android中实现类似探探中图片左右滑动切换效果

    偶然之间发现探探的左右滑动的图片挺好玩,试着去做了下,再到后来,看到许多大神也推出了同样仿探探效果的博客,从头到尾阅读下来,写得通俗易懂,基本上没什么问题.于是,实现仿探探效果的想法再次出现在脑海中. ...

  2. ❤️❌ 如何用vue制作一个探探滑动组件

    前言 嗨,说起探探想必各位程序汪都不陌生(毕竟妹子很多),能在上面丝滑的翻牌子,探探的的堆叠滑动组件起到了关键的作用,下面就来看看如何用vue写一个探探的堆叠组件 ? 一. 功能分析 简单使用下探探会 ...

  3. 模仿探探的左右滑动切换卡片功能

    偶然之间发现探探的左右滑动的图片挺好玩,试着去做了下,再到后来,看到许多大神也推出了同样仿探探效果的博客,从头到尾阅读下来,写得通俗易懂,基本上没什么问题.于是,实现仿探探效果的想法再次出现在脑海中. ...

  4. 水波纹+仿探探卡片滑动+飘赞动画

    Android开发UI效果 一.水波纹 二.仿探探滑动卡片 三.飘赞动画 本篇文章主要记录一下开发过程通过网上搜索和本项目需求结合最终实现效果做个记录, 本人比较赖,就不抽demo了,关键代码已贴,仿 ...

  5. 仿探探左右滑动的简单实现

    一.导入依赖 compile 'me.yuqirong:cardswipelayout:1.0.0' // recylerview依赖: compile 'com.android.support:re ...

  6. Android仿探探卡片式滑动效果实现

    第一次进入探探软件界面,就被这种通过卡片式滑动来选择"喜欢/不喜欢"的设计所吸引了.当时就非常想通过自己来实现这种仿探探式的效果,然而却没什么思路.不过毋庸置疑的是,这种效果的原理 ...

  7. 五行代码实现 炫动滑动 卡片层叠布局,仿探探、人人影视订阅界面 简单优雅:LayoutManager+ItemTouchHelper

    本篇文章已授权微信公众号 hongyangAndroid (鸿洋)独家发布 转载请标明出处: http://blog.csdn.net/zxt0601/article/details/53730908 ...

  8. ios 探探编辑页图片拖动_nuxt+vue仿微信/探探界面|nuxt聊天实例

    最近趁着空闲时间给自己充充电,捣鼓学习了下Nuxt.js框架并开发了一个聊天项目 NuxtChat . 如上图:一些演示效果片段.emmm~ 不错哟  项目介绍 基于nuxt.js+vue.js+vu ...

  9. Vue/Nuxt.js仿Tinder|探探翻牌特效|vue仿探探卡片滑动

    基于Vue.js|Nuxt.js实现探探卡片滑动切换效果 陌陌|探探社交App中拖拽滑动翻牌子效果让人印象深刻,最近在开发Nuxt项目,需要实现类似这个效果,于是经过多次调试,最终实现了,现整理作些简 ...

最新文章

  1. AI视频行为分析系统项目复盘——技术篇1:Ubuntu 18.04部署编译OpenCV+contrib、TensorFlow2.1、CUDA10.1+cuDNN7.6.5、tensorRT6.0.1等
  2. 如何成为软件工程师的团队合作者
  3. Bootstrap简介--目前最受欢迎的前端框架(一)
  4. java和python哪个好就业2020-java和python哪个未来发展比较好?
  5. python归并排序 分词_python实现归并排序,归并排序的详细分析
  6. 日均万亿条数据如何处理?爱奇艺实时计算平台这样做
  7. 计算机nit证书怎么学,计算机等级考试证书和NIT可以抵免自考中哪些课程?
  8. Python+OpenCV:直方图均衡化(Histogram Equalization)
  9. 应用安全-CMF/CMS漏洞整理
  10. ubuntu java apt-get_ubuntu apt-get 安装jdk7
  11. 不属于python第三方程序_安装 selenium 对于python而言属于一个第三方的模块
  12. 了解GDAL的图像处理/Python
  13. iftables 官方文档
  14. 虚拟声卡实现播放铃声
  15. 离散数学计算机科学与技术答案,湘潭大学计算机科学与技术刘任任版离散数学课后习题答案---第二学期--图论与组合数学...
  16. 用计算机运算符编写检索式,检索式
  17. u盘启动盘变成普通u盘
  18. 计算机无法验证签名,win7系统无法验证文件数字签名的解决方法
  19. 如何禁止更改IE的代理服务器设置(转)
  20. 基于MATLAB的电弧仿真模型(Mayr/Cassie 电弧模型)

热门文章

  1. 图形编程概念—显卡/GPU是如何工作的?
  2. java编写k线_用Java绘制K线
  3. 计算天数-本题要求编写程序计算某年某月某日是该年中的第几天
  4. 腾讯短信集成报错误:NoClassDefFoundError: org/apache/http/client/config/RequestConfig
  5. 头牌知产介绍喷雾机商标转让类别属于第几类?
  6. 联想 Linux下 装win10 双系统(免坑)
  7. 将pcap文件处理成KDD99数据集格式
  8. 「系统更新」苹果 iOS 16.4 推送正式版,本次更新了哪些内容?
  9. 关于Freesurfer提取annotation分区结构特征的命令mri_segstats
  10. 北语18秋《计算机应用基础》练习1,每日一练丨一建实务科练习!