Android使用FrameLayout+RecyclerView实现悬浮置顶封装功能
一、实际开发效果图
默认效果:

滚动后的效果:

二、效果实现方式
-
CoordinatorLayout + AppBarLayout + RecyclerView
(适用于简单的悬浮View不超过一屏的情况,头部固定,数据简单)
建议:能用1的情况,尽量不用2
针对方式1的实现,自己去百度。下面主要讲的是方式2的实现
三、实现效果分析

实现思路:将要悬浮的条目创建一个新的,添加到FrameLayout里面,当RecyclerView滚动超过条目位置的时候显示出来。
四、创建悬浮View需要的的条件:
-
要知道条目的位置。
-
要知道条目的类型。
/* 接口定义*/public interface IStick {/* 悬浮的位置*/int getStickPosition();/* 悬浮的类型*/int getStickViewType();}
五、FrameLayout + RecyclerView实现代码
/* 悬浮布局封装*/
public class StickFrameLayout extends FrameLayout {private RecyclerView mRecyclerView;// 悬浮根布局private FrameLayout mStickyLayout;// 要悬浮的布局private View mStickView;// 偏移量private int mOffset = 0;public StickFrameLayout(Context context, AttributeSet attrs) {this(context, attrs, 0);}public StickFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}/* 1. 加载布局完成之后*/@Overrideprotected void onFinishInflate() {super.onFinishInflate();// 添加滚动监听addOnScrollListener();// 添加悬浮根布局addStickyLayout();}/* 添加滚动监听*/private void addOnScrollListener() {mRecyclerView = (RecyclerView) getChildAt(0);mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {StickFrameLayout.this.onScrolled();}});}/* 滚动监听事件处理*/private void onScrolled() {RecyclerView.Adapter adapter = mRecyclerView.getAdapter();RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();if (adapter == null || layoutManager == null || adapter.getItemCount() <= 0) {return;}// 判断是不是实现了悬浮if (adapter instanceof IStick) {IStick stick = (IStick) adapter;int stickPosition = stick.getStickPosition();if (mStickView == null) {// 根据类型创建ViewHoldermStickyLayout.setTag(R.id.view_position, stickPosition);RecyclerView.ViewHolder viewHolder = adapter.onCreateViewHolder(mStickyLayout, stick.getStickViewType());// 根据位置绑定Viewadapter.onBindViewHolder(viewHolder, stickPosition);mStickView = viewHolder.itemView;mStickyLayout.addView(mStickView);}//这是是处理第一次打开时,吸顶布局已经添加到StickyLayout,但StickyLayout的高依然为0的情况。if (mStickyLayout.getChildCount() > 0 && mStickyLayout.getHeight() == 0) {mStickyLayout.requestLayout();}//设置StickyLayout显示或者隐藏。int firstVisibleItemPosition = findFirstVisibleItemPosition(mRecyclerView);View topView = layoutManager.findViewByPosition(stickPosition);// 1. 判断要不要偏移changeOffset(mOffset);// 2. 大于悬浮的位置都显示if (firstVisibleItemPosition >= stickPosition) {mStickyLayout.setVisibility(View.VISIBLE);} else if (topView != null) {// 3. 偏移大于悬浮到顶部的距离就显示boolean isShow = mOffset >= topView.getTop();if (isShow) {mStickyLayout.setVisibility(View.VISIBLE);} else {mStickyLayout.setVisibility(View.GONE);}} else {mStickyLayout.setVisibility(View.GONE);}}}/* 手动设置显示 @param visible*/public void setStickyVisibility(int visible) {if (mStickyLayout != null) {mStickyLayout.setVisibility(visible);}}/* 找第一个可见条目的位置*/private int findFirstVisibleItemPosition(RecyclerView recyclerView) {int firstVisibleItem = -1;RecyclerView.LayoutManager layout = recyclerView.getLayoutManager();if (layout != null) {if (layout instanceof GridLayoutManager) {firstVisibleItem = ((GridLayoutManager) layout).findFirstVisibleItemPosition();} else if (layout instanceof LinearLayoutManager) {firstVisibleItem = ((LinearLayoutManager) layout).findFirstVisibleItemPosition();} else if (layout instanceof StaggeredGridLayoutManager) {int[] firstPositions = new int[((StaggeredGridLayoutManager) layout).getSpanCount()];((StaggeredGridLayoutManager) layout).findFirstVisibleItemPositions(firstPositions);firstVisibleItem = getMin(firstPositions);}}return firstVisibleItem;}private int getMin(int[] arr) {int min = arr[0];for (int x = 1; x < arr.length; x++) {if (arr[x] < min)min = arr[x];}return min;}/* 添加悬浮根布局*/private void addStickyLayout() {mStickyLayout = new FrameLayout(getContext());LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT);mStickyLayout.setLayoutParams(lp);super.addView(mStickyLayout, lp);}/* 设置偏移量*/public void setStickOffset(int offset) {changeOffset(offset);}/* 改变偏移量*/private void changeOffset(int offset) {if (mOffset != offset) {if (mStickyLayout != null) {mOffset = offset;LayoutParams lp = (LayoutParams) mStickyLayout.getLayoutParams();lp.topMargin = offset;mStickyLayout.setLayoutParams(lp);}}}@Overrideprotected int computeVerticalScrollOffset() {if (mRecyclerView != null) {try {Method method = View.class.getDeclaredMethod("computeVerticalScrollOffset");method.setAccessible(true);return (int) method.invoke(mRecyclerView);} catch (Exception e) {e.printStackTrace();}}return super.computeVerticalScrollOffset();}@Overrideprotected int computeVerticalScrollRange() {if (mRecyclerView != null) {try {Method method = View.class.getDeclaredMethod("computeVerticalScrollRange");method.setAccessible(true);return (int) method.invoke(mRecyclerView);} catch (Exception e) {e.printStackTrace();}}return super.computeVerticalScrollRange();}@Overrideprotected int computeVerticalScrollExtent() {if (mRecyclerView != null) {try {Method method = View.class.getDeclaredMethod("computeVerticalScrollExtent");method.setAccessible(true);return (int) method.invoke(mRecyclerView);} catch (Exception e) {e.printStackTrace();}}return super.computeVerticalScrollExtent();}@Overridepublic void scrollBy(int x, int y) {if (mRecyclerView != null) {mRecyclerView.scrollBy(x, y);} else {super.scrollBy(x, y);}}@Overridepublic void scrollTo(int x, int y) {if (mRecyclerView != null) {mRecyclerView.scrollTo(x, y);} else {super.scrollTo(x, y);}}
}
-
核心代码是滚动的处理,onScrolled()方法。
-
用ViewHolder创建悬浮的View,给悬浮条目的Parent打个位置的Tag,就能知道要创建哪个位置的条目。
-
提供一些常用的方法,如顶部位置的偏移。
adapter关键代码:
@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {// 如果是多条目,viewType就是布局IDView view;if (mSupport != null) {Object tagPosition = parent.getTag(R.id.view_position);int layoutId = mSupport.getLayoutId(mData.get(mPosition));// 如果是滚动布局if (tagPosition != null) {int position = (int) tagPosition;layoutId = mSupport.getLayoutId(mData.get(position));}view = LayoutInflater.from(mContext).inflate(layoutId, parent, false);} else {view = LayoutInflater.from(mContext).inflate(mLayoutId, parent, false);}QuickViewHolder holder = new QuickViewHolder(view);return holder;}
注意:adapter关键代码是在我自己项目通用适配器添加的,你们根据自己项目的适配器添加。
源码地址:
https://github.com/wenkency/CommAdapter