Android 炫动滑动 卡片层叠布局,仿探探、人人影视订阅界面 简单&优雅:LayoutManager+ItemTouchHelper

public static int MAX_SHOW_COUNT;

//每一级Scale相差0.05f,translationY相差7dp左右

public static float SCALE_GAP;

public static int TRANS_Y_GAP;

public static void initConfig(Context context) {

MAX_SHOW_COUNT = 6;

SCALE_GAP = 0.05f;

TRANS_Y_GAP = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, context.getResources().getDisplayMetrics());

}

}

LayoutManager全部代码如下,布满注释,如果看不懂,建议阅读前置文章LayoutManger系列

public class OverLayCardLayoutManager extends RecyclerView.LayoutManager {

@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) {

detachAndScrapAttachedViews(recycler);

int itemCount = getItemCount();

if (itemCount >= MAX_SHOW_COUNT) {

//从可见的最底层View开始layout,依次层叠上去

for (int position = itemCount - MAX_SHOW_COUNT; position < itemCount; position++) {

View view = recycler.getViewForPosition(position);

addView(view);

measureChildWithMargins(view, 0, 0);

int widthSpace = getWidth() - getDecoratedMeasuredWidth(view);

int heightSpace = getHeight() - getDecoratedMeasuredHeight(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实现炫动滑动:

ItemTouchHelper的基础知识,建议大家自行学习,网上文章很多,我简单介绍一下,

This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView.

It works with a RecyclerView and a Callback class, which configures what type of interactions

are enabled and also receives events when user performs these actions.

Depending on which functionality you support, you should override

{@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder)} and / or

{@link Callback#onSwiped(ViewHolder, int)}.

翻译 + 总结:

这货是一个工具类,为RecyclerView扩展滑动消失(删除)和drag & drop效果的。

它需要和RecyclerView、Callback 一起工作。Callback 类里定义了 允许哪些交互,并且会接收到对应的交互事件

根据你需要哪种功能(滑动消失(删除)和drag & drop),你需要重写

Callback#onMove(RecyclerView, ViewHolder, ViewHolder)—–drag & drop

Callback#onSwiped(ViewHolder, int) 方法。 —–滑动消失(删除)

总结一下入门级用法如下,三个步骤:

  • 定义一个Callback:ItemTouchHelper.Callback callback = new ItemTouchHelper.SimpleCallback(int,int),这两个int分别代表要 监听哪几个方向上的拖拽、滑动事件。 常用:ItemTouchHelper.DOWN | ItemTouchHelper.UP | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT

  • 将Callback传给ItemTouchHelper:ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);

  • 关联ItemTouchHelper和RecyclerView:itemTouchHelper.attachToRecyclerView(mRv)

这三个步骤做完后,ItemTouchHelper就会自动帮我们完成 滑动消失(删除)和drag & drop 的功能。

滑动删除

我们本例中,需要的是滑动消失(删除) ,所以我们的Callback不需要关注onMove()方法。

且我们需要上下左右滑动都可以删除的效果。

则如下构造Callback,传入上下左右:

ItemTouchHelper.Callback callback = new ItemTouchHelper.SimpleCallback(0,

ItemTouchHelper.DOWN | ItemTouchHelper.UP | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT)

onSwiped()方法,是滑动删除动作已经发生后回调的,即,我们先滑动卡片,然后松手,此时ItemTouchHelper判断我们的手势是删除手势,会自动对这个卡片执行丢出屏幕外的动画,同时回调onSwiped()方法。

所以我们需要在其中如下写:

@Override

public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {

//★实现循环的要点

SwipeCardBean remove = mDatas.remove(viewHolder.getLayoutPosition());

mDatas.add(0, remove);

mAdapter.notifyDataSetChanged();

}

在这里我们完成了循环的操作:

  • 利用当前被删除的ViewViewHolder拿到Position

  • 删除数据集中对应Position的数据源

  • 同时将该数据源插入数据集中的首位。

  • 调用notifyDataSetChanged(),通知列表刷新

如此我们便完成了,循环列表的需求。

这里提一下为什么我们要调用notifyDataSetChanged()

看官方文档:

ItemTouchHelper moves the items’ translateX/Y properties to reposition them

即ItemTouchHelper实现的滑动删除,其实只是隐藏了这个滑动的View。并不是真的删除了。

LayoutManager实现流式布局一文第五节中,我们已经提到,notifyDataSetChanged()会回调onLayoutChildren()这个函数,而在这个函数中,我们会重新布局,即真正的移除(不再layout)滑动掉的View,同时会补充进新的最底层的View。

嗯,JavaBean也看一眼吧,没亮点:

public class SwipeCardBean {

private int postition;//位置

private String url;

private String name;

}

我们写到这里已经完成了滑动删除的功能,其实我们什么都没有写是吧,复杂的判断都由ItemTouchHelper帮我们处理掉了,例如速度、滑动距离是否到达删除阈值,删除成功移除的动画、取消删除复位的动画等等。

所以我说利用ItemTouchHelper才是正确的姿势,因为很简单&快速。

下面我们来实现滑动时的动画。

滑动时动画

我们需要重写CallbackonChildDraw()方法,这个方法参数较多:

  • @param c The canvas which RecyclerView is drawing its children

  • @param recyclerView The RecyclerView to which ItemTouchHelper is attached to

  • @param viewHolder The ViewHolder which is being interacted by the User or it was

interacted and simply animating to its original position

  • @param dX The amount of horizontal displacement caused by user’s action

  • @param dY The amount of vertical displacement caused by user’s action

  • @param actionState 是拖拽还是滑动事件 The type of interaction on the View. Is either {@link #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}.

  • @param isCurrentlyActive 事件是用户产生还是动画产生的 True if this view is currently being controlled by the user or false it is simply animating back to its original state.

对我们比较有用的有dX dX,可以判断滑动方向,以及计算滑动的比例,从而控制缩放、位移动画的程度。

本文如下编写,对View的缩放、位移,其实是对LayoutManager里的操作的逆操作,值得注意的是最后一层,即top-3View在Y轴上是保持不变的:

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

//先根据滑动的dxdy 算出现在动画的比例系数fraction

double swipValue = Math.sqrt(dX * dX + dY * dY);

double fraction = swipValue / getThreshold(viewHolder);

//边界修正 最大为1

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((float) (1 - SCALE_GAP * level + fraction * SCALE_GAP));

if (level < MAX_SHOW_COUNT - 1) {

child.setScaleY((float) (1 - SCALE_GAP * level + fraction * SCALE_GAP));

child.setTranslationY((float) (TRANS_Y_GAP * level - fraction * TRANS_Y_GAP));

}

}

}

}

getThreshold(viewHolder)函数,返回是否可以被回收掉的阈值,关于它为什么这么写,我是从源码里找到的,本末会讲解:

//水平方向是否可以被回收掉的阈值

public float getThreshold(RecyclerView.ViewHolder viewHolder) {

return mRv.getWidth() * getSwipeThreshold(viewHolder);

}

探探效果的实现

一开始文章撸到这里应该结束了,群里出来一个马小跳,告诉我探探和这略有不同,希望我一并实现。

嗯,好吧。表示没听说过探探,那我先去下载一个看看吧。

loading-install-open………

哎哟呵,十分钟过去了,我还在滑动看美女 忘记了要干什么,被女票看到胖揍了我一顿。

好的,我捂着脸继续分析。

探探和人人影视有两点不同:

  • Roate的变化:左右滑动时,顶层卡片会慢慢旋转,到阈值max大概十五度。

  • Alpha的变化:左滑时顶层卡片的删除按钮会慢慢显现,右滑时爱心按钮会慢慢显现。

感觉也是炒鸡简单,来吧。五分钟撸完吃外卖。修改点:

  • 在layout布局添加『 X 』&『 爱心 』。

  • onChildDraw()里,按比例修改TopView的Rotate & Alpha

监听方向

还有一点小不同,上滑下滑不再能删除,所以我们构造时只传入左右即可:

ItemTouchHelper.Callback callback = new ItemTouchHelper.SimpleCallback(0,

ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT)

布局添加两个按钮

onChildDraw()

在上文人人影视的基础上扩展,上文的效果,对TopView是不做任何操作的。这里只需要再对TopView做额外操作即可:

@Override

public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {

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

} else {

//探探只是第一层加了rotate & alpha的操作

//不过他区分左右

float xFraction = dX / getThreshold(viewHolder);

//边界修正 最大为1

if (xFraction > 1) {

xFraction = 1;

} else if (xFraction < -1) {

xFraction = -1;

}

//rotate

child.setRotation(xFraction * MAX_ROTATION);

//自己感受一下吧 Alpha

if (viewHolder instanceof ViewHolder) {

ViewHolder holder = (ViewHolder) viewHolder;

if (dX > 0) {

//露出左边,比心

holder.setAlpha(R.id.iv_love, xFraction);

} else {

//露出右边,滚犊子

holder.setAlpha(R.id.iv_del, -xFraction);

}

}

}

}

}

实现完后,我以为结束了,结果比我们想象的还要复杂一丢丢。因为此时删除后,notifyDataSetChanged()刷新界面,而TopView还是倾斜的,爱心、删除图标也是出现的。这显然与预期不符。所以我们需要在onSwiped()里将其复位:

@Override

public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {

//探探只是第一层加了rotate & alpha的操作

//对rotate进行复位

viewHolder.itemView.setRotation(0);

//自己感受一下吧 Alpha

if (viewHolder instanceof ViewHolder) {

ViewHolder holder = (ViewHolder) viewHolder;

holder.setAlpha(R.id.iv_love, 0);

holder.setAlpha(R.id.iv_del, 0);

}

}

Ok,大功告成。效果和文首一样,尽情去跟产品UI嘚瑟吧。

阈值的寻找之路


阈值的寻找,花费了我一些时间,因为我想做到和系统的行为保持一致。

即,当删除、喜欢图标全显,当Top-1View显示完毕时,松手 TopView会回收。

这就决定了我们的缩放、位移的阈值不能随便定,所以我们必须去源代码里找答案。

//水平方向是否可以被回收掉的阈值

public float getThreshold(RecyclerView.ViewHolder viewHolder) {

return mRv.getWidth() * getSwipeThreshold(viewHolder);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

希望本文对你有所启发,有任何面试上的建议也欢迎留言分享给大家。

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

好了~如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能点个小赞和关注下我,以后还会更新技术干货,谢谢您的支持!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

实战项目、讲解视频,并且会持续更新!**

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

希望本文对你有所启发,有任何面试上的建议也欢迎留言分享给大家。

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

[外链图片转存中…(img-d728ivue-1713151143349)]

好了~如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

[外链图片转存中…(img-680wrL6N-1713151143350)]

为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能点个小赞和关注下我,以后还会更新技术干货,谢谢您的支持!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 24
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值