美文网首页Android-CoordinatorLayout.……自定义控件android
Android悬浮置顶封装(FrameLayout+Recycl

Android悬浮置顶封装(FrameLayout+Recycl

作者: Sky_Blue | 来源:发表于2019-06-27 18:11 被阅读1次
    一、实际开发效果图

    默认效果

    页面默认效果.jpg
    滚动后的效果
    滚动后的效果.jpg
    二、效果实现方式
    1. CoordinatorLayout + AppBarLayout + RecyclerView
      (适用于简单的悬浮View不超过一屏的情况,头部固定,数据简单)
    2. FrameLayout + RecyclerView
      (适用于复杂的多条目布局,且悬浮条目位置受后台数据的影响而生改变)
      建议:能用1的情况,尽量不用2
      针对方式1的实现,自己去百度。下面主要讲的是方式2的实现
    三、实现效果分析
    基本布局.png

    实现思路:将要悬浮的条目创建一个新的,添加到FrameLayout里面,当RecyclerView滚动超过条目位置的时候显示出来。

    四、创建悬浮View需要的的条件:
    1. 要知道条目的位置。
    2. 要知道条目的类型。
        /**
         * 接口定义
         */
        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. 加载布局完成之后
         */
        @Override
        protected void onFinishInflate() {
            super.onFinishInflate();
            // 添加滚动监听
            addOnScrollListener();
            // 添加悬浮根布局
            addStickyLayout();
        }
    
    
        /**
         * 添加滚动监听
         */
        private void addOnScrollListener() {
            mRecyclerView = (RecyclerView) getChildAt(0);
            mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public 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) {
                    // 根据类型创建ViewHolder
                    mStickyLayout.setTag(R.id.view_position, stickPosition);
                    RecyclerView.ViewHolder viewHolder = adapter.onCreateViewHolder(mStickyLayout, stick.getStickViewType());
                    // 根据位置绑定View
                    adapter.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);
                }
            }
        }
    
    
        @Override
        protected 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();
        }
    
    
        @Override
        protected 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();
        }
    
        @Override
        protected 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();
        }
    
        @Override
        public void scrollBy(int x, int y) {
            if (mRecyclerView != null) {
                mRecyclerView.scrollBy(x, y);
            } else {
                super.scrollBy(x, y);
            }
        }
    
        @Override
        public void scrollTo(int x, int y) {
            if (mRecyclerView != null) {
                mRecyclerView.scrollTo(x, y);
            } else {
                super.scrollTo(x, y);
            }
        }
    }
    
    1. 核心代码是滚动的处理,onScrolled()方法。
    2. 用ViewHolder创建悬浮的View,给悬浮条目的Parent打个位置的Tag,就能知道要创建哪个位置的条目。
    3. 提供一些常用的方法,如顶部位置的偏移。

    adapter关键代码

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            // 如果是多条目,viewType就是布局ID
            View 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关键代码是在我自己项目通用适配器添加的,你们根据自己项目的适配器添加。

    六、总结
    1. 实际开发有一些业务细节要自己处理。
    2. Android通用的Adapter
    3. 测试源码地址:https://github.com/wenkency/CommAdapter

    相关文章

      网友评论

        本文标题:Android悬浮置顶封装(FrameLayout+Recycl

        本文链接:https://www.haomeiwen.com/subject/xaircctx.html