android+recyclerview悬停动画,【Android】RecyclerView:打造悬浮效果

悬浮效果

先看个效果

b335b620af39?appinstall=0

效果

这是一个City列表,每个City都有所属的Province,需要在滑动的时候,将对应的Province悬浮在顶部。悬浮顶部的Province需要根据列表的滑动而适当改变位置,实现“顶上去”的效果。

实现思路:

利用RecyclerView.ItemDecoration绘制Province(就像绘制分割线一样)

同一组的City,只绘制一个Province

计算偏移,将当前Province固定在顶部

根据列表滑动,实现偏移效果

ItemDecoration

既然是利用RecyclerView.ItemDecoration实现的悬浮效果,那么有必要了解下它。

ItemDecoration字面意思:Item的装饰。是的!是装饰!不只是画分割线。

其实ItemDecoration的功能非常强大,而我们平时只是用它来实现分割线的效果(至少我是这样)。因此,可能很多同学认为ItemDecoration就是用来绘制分割线的。其实不然,ItemDecoration的功能远不止是分割线的绘制。

先看下RecyclerView.ItemDecoration的源码(部分):

public static abstract class ItemDecoration {

...

public void onDraw(Canvas c, RecyclerView parent, State state) {

onDraw(c, parent);

}

public void onDrawOver(Canvas c, RecyclerView parent, State state) {

onDrawOver(c, parent);

}

public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {

getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),

parent);

}

}

里面是我们常用的三个方法:

getItemOffsets:通过Rect为每个Item设置偏移,用于绘制Decoration。

onDraw:通过该方法,在Canvas上绘制内容,在绘制Item之前调用。(如果没有通过getItemOffsets设置偏移的话,Item的内容会将其覆盖)

onDrawOver:通过该方法,在Canvas上绘制内容,在Item之后调用。(画的内容会覆盖在item的上层)

RecyclerView 的背景、onDraw绘制的内容、Item、onDrawOver绘制的内容,各层级关系如下:

b335b620af39?appinstall=0

层级关系

绘制分割线

先看看一般的分割线绘制。

定义分割线高度

private int mHeight = 5; //分割线高度

通过getItemOffsets预留空间

@Override

public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {

super.getItemOffsets(outRect, view, parent, state);

int position = parent.getChildAdapterPosition(view);

if (position != 0) {

//第一个item预留空间

outRect.top = mHeight;

}

}

然后在onDraw中绘制

@Override

public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {

super.onDraw(c, parent, state);

final int left = parent.getLeft();

final int right = parent.getRight();

final int childCount = parent.getChildCount();

for (int i = 0; i < childCount; i++) {

final View childView = parent.getChildAt(i);

final int bottom = childView.getTop();

final int top = bottom - mHeight;

c.drawRect(left, top, right, bottom, mPaint);

}

}

获取当前RecyclerView的Item数量,遍历每个Item。在对应的位置绘制一个高度为mHeight的矩形 ,从而实现分割线的效果。

b335b620af39?appinstall=0

效果

(详情代码见底部链接)

打造悬浮效果

这是一个城市列表,根据省份分组,相同的城市只会显示一个省份。滚动城市列表时,省份会悬浮在顶部。效果如下:

b335b620af39?appinstall=0

效果1

实现

由于需要悬浮效果,所以需要在onDrawOver中绘制分组。

定义一个interface,根据position通过接口方法getGroupName获取当前省名(由Activity实现)

public interface GroupListener {

String getGroupName(int position);

}

创建StickyDecoration继承RecyclerView.ItemDecoration

定义isFirstInGroup方法。根据前一个省份,判断当前是否为新的省份

//判断是不是组中的第一个位置

//根据前一个组名,判断当前是否为新的组

private boolean isFirstInGroup(int pos) {

if (pos == 0) {

return true;

} else {

String prevGroupId = getGroupName(pos - 1);

String groupId = getGroupName(pos);

return !TextUtils.equals(prevGroupId, groupId);

}

}

通过position,对比上一个省份名称,判断当前省是否为第一个

重写getItemOffsets方法,为悬浮栏预留空间

@Override

public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {

super.getItemOffsets(outRect, view, parent, state);

int pos = parent.getChildAdapterPosition(view);

String groupId = getGroupName(pos);

if (groupId == null) return;

//只有是同一组的第一个才显示悬浮栏

if (pos == 0 || isFirstInGroup(pos)) {

outRect.top = mGroupHeight;

}

}

只有第一个Item或者新的省份才为悬浮栏预留空间

重写onDrawOver方法(重点)

@Override

public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {

super.onDrawOver(c, parent, state);

final int itemCount = state.getItemCount();

final int childCount = parent.getChildCount();

final int left = parent.getLeft() + parent.getPaddingLeft();

final int right = parent.getRight() - parent.getPaddingRight();

String preGroupName; //标记上一个item对应的Group

String currentGroupName = null; //当前item对应的Group

for (int i = 0; i < childCount; i++) {

View view = parent.getChildAt(i);

int position = parent.getChildAdapterPosition(view);

preGroupName = currentGroupName;

currentGroupName = getGroupName(position);

if (currentGroupName == null || TextUtils.equals(currentGroupName, preGroupName))

continue;

int viewBottom = view.getBottom();

float top = Math.max(mGroupHeight, view.getTop());//top 决定当前顶部第一个悬浮Group的位置

if (position + 1 < itemCount) {

//获取下个GroupName

String nextGroupName = getGroupName(position + 1);

//下一组的第一个View接近头部

if (!currentGroupName.equals(nextGroupName) && viewBottom < top) {

top = viewBottom;

}

}

//根据top绘制group

c.drawRect(left, top - mGroupHeight, right, top, mGroutPaint);

Paint.FontMetrics fm = mTextPaint.getFontMetrics();

//文字竖直居中显示

float baseLine = top - (mGroupHeight - (fm.bottom - fm.top)) / 2 - fm.bottom;

c.drawText(currentGroupName, left + mLeftMargin, baseLine, mTextPaint);

}

}

通过变量preGroupId和currentGroupId来保存当前分组名和上一个分组名。当前Item与上一个Item为同一个分组时,跳过该Item的绘制。

其中代码:

float top = Math.max(mGroupHeight, view.getTop());

根据当前Item的位置确定绘制分组的位置。top将在mGroupHeight和view.getTop()中取最大值,也就是说top将不会小于mGroupHeight,这样就能实现吸顶效果。

其中代码:

if (!currentGroupName.equals(nextGroupName) && viewBottom < top) {

top = viewBottom;

}

当下个分组的顶部(当前Item的底部viewBottom可近似认为下个Item的顶部)距离RecyclerView顶部小于top时,偏移当前分组位置。实现下一组上滑时候,当前分组上移;上一组下滑的时候,当前分组下移。

最后计算baseLine,并绘制背景和文字。

到目前为止,一个带有悬浮功能的列表就实现了。

(详细代码见底部链接)

b335b620af39?appinstall=0

--

进阶

当我们利用ItemDecoration实现文字的悬浮的时候,是不是还可以搞点事情~ ~我有个大胆的想法

只有文字的悬浮怎么行!我还希望可以再来个icon?再来几个TextView?来个自定义布局?那就来个自定义布局吧。

实现

实现的原理跟上面一样,由于需要自定义布局,所以需要在接口中添加一个获取View的方法。

定义PowerGroupListener

public interface PowerGroupListener {

String getGroupName(int position);

View getGroupView(int position);

}

相比之前,多了个getGroupView方法,用来获取View。

在onDrawOver中绘制

@Override

public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {

...

for (int i = 0; i < childCount; i++) {

...

//根据position获取View

View groupView = mGroupListener.getGroupView(position);

if (groupView == null) return;

ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mGroupHeight);

groupView.setLayoutParams(layoutParams);

groupView.setDrawingCacheEnabled(true);

groupView.measure(

View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),

View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));

//指定高度、宽度的groupView

groupView.layout(0, 0, right, mGroupHeight);

groupView.buildDrawingCache();

Bitmap bitmap = groupView.getDrawingCache();

c.drawBitmap(bitmap, left, top - mGroupHeight, null);

}

}

在原来的基础上做了点修改,通过接口的getGroupView方法获取需要绘制的分组View,将得到的View绘制到指定位置。

效果:

b335b620af39?appinstall=0

效果2

(详细代码见底部链接)

源码

已封装成库,欢迎来提Issues

repositories {

jcenter()// If not already there

}

dependencies {

compile 'com.gavin.com.library:stickyDecoration:x.x.x'

}

支持

LinearLayoutManager

GridLayoutManager

点击事件

分割线

详细用法级源码请看Github

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值