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
的基础知识,建议大家自行学习,网上文章很多,我简单介绍一下,
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();
}
在这里我们完成了循环的操作:
-
利用当前被删除的
View
的ViewHolder
拿到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才是正确的姿势,因为很简单&快速。
下面我们来实现滑动时的动画。
滑动时动画
我们需要重写Callback
的onChildDraw()
方法,这个方法参数较多:
-
@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);
}
}
}
}
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
总结
找工作是个很辛苦的事情,而且一般周期都比较长,有时候既看个人技术,也看运气。第一次找工作,最后的结果虽然不尽如人意,不过收获远比offer大。接下来就是针对自己的不足,好好努力了。
最后为了节约大家的时间,我把我学习所用的资料和面试遇到的问题和答案都整理成了PDF文档
喜欢文章的话请关注、点赞、转发 谢谢!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
…(img-Q5xvQUmh-1711771007760)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
总结
找工作是个很辛苦的事情,而且一般周期都比较长,有时候既看个人技术,也看运气。第一次找工作,最后的结果虽然不尽如人意,不过收获远比offer大。接下来就是针对自己的不足,好好努力了。
最后为了节约大家的时间,我把我学习所用的资料和面试遇到的问题和答案都整理成了PDF文档
喜欢文章的话请关注、点赞、转发 谢谢!
[外链图片转存中…(img-A9twjRoa-1711771007760)]