千家信息网

android怎么实现卡片式滑动效果

发表于:2025-11-07 作者:千家信息网编辑
千家信息网最后更新 2025年11月07日,这篇文章主要介绍"android怎么实现卡片式滑动效果",在日常操作中,相信很多人在android怎么实现卡片式滑动效果问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"a
千家信息网最后更新 2025年11月07日android怎么实现卡片式滑动效果

这篇文章主要介绍"android怎么实现卡片式滑动效果",在日常操作中,相信很多人在android怎么实现卡片式滑动效果问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"android怎么实现卡片式滑动效果"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

CardLayoutManager

创建 CardLayoutManager 并继承自 RecyclerView.LayoutManager 。需要我们自己实现 generateDefaultLayoutParams() 方法:

@Override  public RecyclerView.LayoutParams generateDefaultLayoutParams() {      return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);  }

一般情况下,像上面这样写即可。

下面这个方法就是我们的重点了。 onLayoutChildren(final RecyclerView.Recycler recycler, RecyclerView.State state) 方法就是用来实现 Item View 布局的:

@Override  public void onLayoutChildren(final RecyclerView.Recycler recycler, RecyclerView.State state) {      super.onLayoutChildren(recycler, state);      // 先移除所有view      removeAllViews();      // 在布局之前,将所有的子 View 先 Detach 掉,放入到 Scrap 缓存中      detachAndScrapAttachedViews(recycler);      int itemCount = getItemCount();      // 在这里,我们默认配置 CardConfig.DEFAULT_SHOW_ITEM = 3。即在屏幕上显示的卡片数为3      // 当数据源个数大于***显示数时      if (itemCount > CardConfig.DEFAULT_SHOW_ITEM) {          // 把数据源倒着循环,这样,第0个数据就在屏幕最上面了          for (int position = CardConfig.DEFAULT_SHOW_ITEM; position >= 0; position--) {              final View view = recycler.getViewForPosition(position);              // 将 Item View 加入到 RecyclerView 中              addView(view);              // 测量 Item View              measureChildWithMargins(view, 0, 0);              // getDecoratedMeasuredWidth(view) 可以得到 Item View 的宽度              // 所以 widthSpace 就是除了 Item View 剩余的值              int widthSpace = getWidth() - getDecoratedMeasuredWidth(view);              // 同理              int heightSpace = getHeight() - getDecoratedMeasuredHeight(view);              // 将 Item View 放入 RecyclerView 中布局              // 在这里默认布局是放在 RecyclerView 中心              layoutDecoratedWithMargins(view, widthSpace / 2, heightSpace / 2,                      widthSpace / 2 + getDecoratedMeasuredWidth(view),                      heightSpace / 2 + getDecoratedMeasuredHeight(view));              // 其实屏幕上有四张卡片,但是我们把第三张和第四张卡片重叠在一起,这样看上去就只有三张              // 第四张卡片主要是为了保持动画的连贯性              if (position == CardConfig.DEFAULT_SHOW_ITEM) {                  // 按照一定的规则缩放,并且偏移Y轴。                  // CardConfig.DEFAULT_SCALE 默认为0.1f,CardConfig.DEFAULT_TRANSLATE_Y 默认为14                  view.setScaleX(1 - (position - 1) * CardConfig.DEFAULT_SCALE);                  view.setScaleY(1 - (position - 1) * CardConfig.DEFAULT_SCALE);                  view.setTranslationY((position - 1) * view.getMeasuredHeight() / CardConfig.DEFAULT_TRANSLATE_Y);              } else if (position > 0) {                  view.setScaleX(1 - position * CardConfig.DEFAULT_SCALE);                  view.setScaleY(1 - position * CardConfig.DEFAULT_SCALE);                  view.setTranslationY(position * view.getMeasuredHeight() / CardConfig.DEFAULT_TRANSLATE_Y);              } else {                  // 设置 mTouchListener 的意义就在于我们想让处于顶层的卡片是可以随意滑动的                  // 而第二层、第三层等等的卡片是禁止滑动的                  view.setOnTouchListener(mOnTouchListener);              }          }      } else {          // 当数据源个数小于或等于***显示数时,和上面的代码差不多          for (int position = itemCount - 1; position >= 0; position--) {              final View view = recycler.getViewForPosition(position);              addView(view);              measureChildWithMargins(view, 0, 0);              int widthSpace = getWidth() - getDecoratedMeasuredWidth(view);              int heightSpace = getHeight() - getDecoratedMeasuredHeight(view);               layoutDecoratedWithMargins(view, widthSpace / 2, heightSpace / 2,                      widthSpace / 2 + getDecoratedMeasuredWidth(view),                      heightSpace / 2 + getDecoratedMeasuredHeight(view));               if (position > 0) {                  view.setScaleX(1 - position * CardConfig.DEFAULT_SCALE);                  view.setScaleY(1 - position * CardConfig.DEFAULT_SCALE);                  view.setTranslationY(position * view.getMeasuredHeight() / CardConfig.DEFAULT_TRANSLATE_Y);              } else {                  view.setOnTouchListener(mOnTouchListener);              }          }      }  }   private View.OnTouchListener mOnTouchListener = new View.OnTouchListener() {       @Override      public boolean onTouch(View v, MotionEvent event) {          RecyclerView.ViewHolder childViewHolder = mRecyclerView.getChildViewHolder(v);          // 把触摸事件交给 mItemTouchHelper,让其处理卡片滑动事件          if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {              mItemTouchHelper.startSwipe(childViewHolder);          }          return false;      }  };

总体来说,CardLayoutManager 主要就是为 Item View 布局,然后根据 position 做相对应的偏差。我们一起来看下完成的效果图:

可以看出,大致的效果已经有了。缺少的就是处理触摸滑动事件了。

OnSwipeListener

在看滑动事件的代码之前,我们先定义一个监听器。主要用于监听卡片滑动事件,代码就如下所示,注释也给出来了。应该都看得懂吧:

public interface OnSwipeListener {       /**       * 卡片还在滑动时回调       *       * @param viewHolder 该滑动卡片的viewHolder       * @param ratio      滑动进度的比例       * @param direction  卡片滑动的方向,CardConfig.SWIPING_LEFT 为向左滑,CardConfig.SWIPING_RIGHT 为向右滑,       *                   CardConfig.SWIPING_NONE 为不偏左也不偏右       */      void onSwiping(RecyclerView.ViewHolder viewHolder, float ratio, int direction);       /**       * 卡片完全滑出时回调       *       * @param viewHolder 该滑出卡片的viewHolder       * @param t          该滑出卡片的数据       * @param direction  卡片滑出的方向,CardConfig.SWIPED_LEFT 为左边滑出;CardConfig.SWIPED_RIGHT 为右边滑出       */      void onSwiped(RecyclerView.ViewHolder viewHolder, T t, int direction);       /**       * 所有的卡片全部滑出时回调       */      void onSwipedClear();   }

CardItemTouchHelperCallback

现在,我们可以回过头来看看卡片滑动了。对于 ItemTouchHelper 来处理 Item View 的触摸滑动事件相必都不陌生吧!

我们暂且命名为 CardItemTouchHelperCallback 。对于 ItemTouchHelper.Callback 而言,需要在 getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) 方法中配置 swipeFlags 和 dragFlags 。

具体的方法如下,对于 swipeFlags 只关心左右两个方向:

@Override  public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {      int dragFlags = 0;      int swipeFlags = 0;      RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();      if (layoutManager instanceof CardLayoutManager) {          swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;      }      return makeMovementFlags(dragFlags, swipeFlags);  }

还有一点需要注意,前面说过,为了防止第二层和第三层卡片也能滑动,因此我们需要设置 isItemViewSwipeEnabled() 返回 false 。

@Override  public boolean isItemViewSwipeEnabled() {      return false;  }

接下来,就是去重写 onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) 和 onSwiped(RecyclerView.ViewHolder viewHolder, int direction) 方法。但是因为在上面我们对于 dragFlags 配置的是 0 ,所以在 onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) 中直接返回 false 即可。

@Override  public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {      return false;  }

这样,我们就把目光投向 onSwiped(RecyclerView.ViewHolder viewHolder, int direction) 方法:

@Override  public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {      // 移除之前设置的 onTouchListener, 否则触摸滑动会乱了      viewHolder.itemView.setOnTouchListener(null);      // 删除相对应的数据      int layoutPosition = viewHolder.getLayoutPosition();      T remove = dataList.remove(layoutPosition);      adapter.notifyDataSetChanged();      // 卡片滑出后回调 OnSwipeListener 监听器      if (mListener != null) {          mListener.onSwiped(viewHolder, remove, direction == ItemTouchHelper.LEFT ? CardConfig.SWIPED_LEFT : CardConfig.SWIPED_RIGHT);      }      // 当没有数据时回调 OnSwipeListener 监听器      if (adapter.getItemCount() == 0) {          if (mListener != null) {              mListener.onSwipedClear();          }      }  }

写好后,我们先来看看滑动效果:

发现还是差了点什么,没错!是缺少了动画。在滑动的过程中我们可以重写 onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) 方法来添加动画:

@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);      View itemView = viewHolder.itemView;      if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {          // 得到滑动的阀值          float ratio = dX / getThreshold(recyclerView, viewHolder);          // ratio ***为 1 或 -1          if (ratio > 1) {              ratio = 1;          } else if (ratio < -1) {              ratio = -1;          }          // 默认***的旋转角度为 15 度          itemView.setRotation(ratio * CardConfig.DEFAULT_ROTATE_DEGREE);          int childCount = recyclerView.getChildCount();          // 当数据源个数大于***显示数时          if (childCount > CardConfig.DEFAULT_SHOW_ITEM) {              for (int position = 1; position < childCount - 1; position++) {                  int index = childCount - position - 1;                  View view = recyclerView.getChildAt(position);                  // 和之前 onLayoutChildren 是一个意思,不过是做相反的动画                  view.setScaleX(1 - index * CardConfig.DEFAULT_SCALE + Math.abs(ratio) * CardConfig.DEFAULT_SCALE);                  view.setScaleY(1 - index * CardConfig.DEFAULT_SCALE + Math.abs(ratio) * CardConfig.DEFAULT_SCALE);                  view.setTranslationY((index - Math.abs(ratio)) * itemView.getMeasuredHeight() / CardConfig.DEFAULT_TRANSLATE_Y);              }          } else {              // 当数据源个数小于或等于***显示数时              for (int position = 0; position < childCount - 1; position++) {                  int index = childCount - position - 1;                  View view = recyclerView.getChildAt(position);                  view.setScaleX(1 - index * CardConfig.DEFAULT_SCALE + Math.abs(ratio) * CardConfig.DEFAULT_SCALE);                  view.setScaleY(1 - index * CardConfig.DEFAULT_SCALE + Math.abs(ratio) * CardConfig.DEFAULT_SCALE);                  view.setTranslationY((index - Math.abs(ratio)) * itemView.getMeasuredHeight() / CardConfig.DEFAULT_TRANSLATE_Y);              }          }          // 回调监听器          if (mListener != null) {              if (ratio != 0) {                  mListener.onSwiping(viewHolder, ratio, ratio < 0 ? CardConfig.SWIPING_LEFT : CardConfig.SWIPING_RIGHT);              } else {                  mListener.onSwiping(viewHolder, ratio, CardConfig.SWIPING_NONE);              }          }      }  }   private float getThreshold(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {      return recyclerView.getWidth() * getSwipeThreshold(viewHolder);  }

现在我们加上动画后,来看看效果:

发现还是有问题,***层的卡片滑出去之后第二层的就莫名其妙地偏了。这正是因为 Item View 重用机制"捣鬼"。所以我们应该在 clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) 方法中重置一下:

@Override  public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {      super.clearView(recyclerView, viewHolder);      viewHolder.itemView.setRotation(0f);  }

大功告成,我们试一下效果:

到此,关于"android怎么实现卡片式滑动效果"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

卡片 方法 效果 数据 事件 就是 动画 布局 监听 卡片式 数据源 监听器 学习 个数 二层 代码 屏幕 方向 处理 配置 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 网络安全制度发行时间 数据库实验室设备管理系统功能 软件开发公司的网页开发特征 软件开发 网站开发 不同 数据库实例 用户 互联网公司发布黑科技 关于汽车网络安全的灵魂二十问 深圳掌玩网络技术 软件开发项目实施进度方案 计算机科学与软件开发证书 计算机网络安全王海晖pdf fms流媒体服务器管理 数据库的应用类型分为 网络安全向导选择连接方法 python软件开发gui 软件开发最多的计算机语言 计算机科学与技术考研网络安全吗 蓝凌软件开发是外包吗 p2p网络技术如何突破内网 基层如何确保网络安全 火影忍者怎么预约新服务器 哪些服务器可以下载网页 国外车辆网络安全的研究 软件开发岗 笔试题 安徽软件开发定制价格 ps网络技术的发展 我的世界服务器作物架怎么自动化 怎样加强网络安全心得体会 履行网络安全监督管理主要部门 万全服务器多少钱
0