自定義LayoutManager實現最美應用列表

RecyclerView的使用你們都很熟悉了,這裏偶帶你們來實現下面這種效果。git

EchelonLayoutManager

對的,你猜的不錯。這種效果只要操做LayoutManager就能夠實現,而後就這樣github

mRecyclerView.setLayoutManager(new EchelonLayoutManager(getContext()));
複製代碼

完了??? 對,就是so easy.緩存

關於如何自定義LayoutManager,網上有不少文章,能夠去找百度君。bash

自定義LayoutManager步驟通常是:ide

  • 指定默認的LayoutParams,通常返回 return new RecyclerView.LayoutParams(RecyclerView.LayoutParams.WRAP_CONTENT, RecyclerView.LayoutParams.WRAP_CONTENT); 就能夠了
  • 測量Item的信息
  • 處理Item回收,使用RecyclerView的二級緩存機制就能夠,多方便
  • 滑動處理邏輯

開啓擼碼模式函數

必須實現的方法ui

@Override
    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
        return new RecyclerView.LayoutParams(RecyclerView.LayoutParams.WRAP_CONTENT,
                RecyclerView.LayoutParams.WRAP_CONTENT);
    }

複製代碼

第一次加載的時候,從這裏獲取咱們須要的Item的數據,同時也要把咱們的Item排列好,關鍵函數就是layoutChild(recycler),這個後面講,主要策略是從最後一個Item加載。spa

@Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (state.getItemCount() == 0 || state.isPreLayout()) return;
        removeAndRecycleAllViews(recycler);

        mItemViewWidth = (int) (getHorizontalSpace() * 0.87f);
        mItemViewHeight = (int) (mItemViewWidth * 1.46f);
        mItemCount = getItemCount();
        //獲取到全部Item的高度和
        mScrollOffset = Math.min(Math.max(mItemViewHeight, mScrollOffset), mItemCount * mItemViewHeight);

        layoutChild(recycler);
    }
複製代碼

這裏說說layoutChild(recycler)裏面作了什麼,看註釋吧,親們code

private void layoutChild(RecyclerView.Recycler recycler) {
        if (getItemCount() == 0 ) return;
        //獲取到最後一個Item的位置
        int bottomItemPosition = (int) Math.floor(mScrollOffset / mItemViewHeight);
        //獲取到出去一個完整的Item的高度,還剩餘多少空間
         int remainSpace = getVerticalSpace() - mItemViewHeight;
        //滑動的時候能夠獲取到最後一個Item在屏幕上還顯示的高度
        int bottomItemVisibleHeight = mScrollOffset % mItemViewHeight;
        //最後一個Item顯示高度相對於自己的比例
        final float offsetPercentRelativeToItemView = bottomItemVisibleHeight * 1.0f / mItemViewHeight;
        //把咱們須要的Item添加到這個集合
        ArrayList<ItemViewInfo> layoutInfos = new ArrayList<>();
        for (int i = bottomItemPosition - 1, j = 1; i >= 0; i--, j++) {
            //計算偏移量
            double maxOffset = (getVerticalSpace() - mItemViewHeight) / 2 * Math.pow(0.8, j);
            //這個Item的top值
            int start = (int) (remainSpace - offsetPercentRelativeToItemView * maxOffset);
            //這個Item須要縮放的比例
            float scaleXY = (float) (Math.pow(mScale, j - 1) * (1 - offsetPercentRelativeToItemView * (1 - mScale)));
            float positonOffset = offsetPercentRelativeToItemView;
            //Item上面的距離佔RecyclerView可用高度的比例
            float layoutPercent = start * 1.0f / getVerticalSpace();
            ItemViewInfo info = new ItemViewInfo(start, scaleXY, positonOffset, layoutPercent);
            layoutInfos.add(0, info);
            remainSpace = (int) (remainSpace - maxOffset);
            //在添加Item的同時,計算剩餘空間是否能夠容下下一個Item,若是不能的話,就再也不添加了
            if (remainSpace <= 0) {
                info.setTop((int) (remainSpace + maxOffset));
                info.setPositionOffset(0);
                info.setLayoutPercent(info.getTop() / getVerticalSpace());
                info.setScaleXY((float) Math.pow(mScale, j - 1)); ;
                break;
            }
        }

        if (bottomItemPosition < mItemCount) {
            final int start = getVerticalSpace() - bottomItemVisibleHeight;
            layoutInfos.add(new ItemViewInfo(start, 1.0f, bottomItemVisibleHeight * 1.0f / mItemViewHeight, start * 1.0f / getVerticalSpace())
                    .setIsBottom());
        } else {
            bottomItemPosition = bottomItemPosition - 1;//99
        }
        //這裏作的是回收處理
        int layoutCount = layoutInfos.size();
        final int startPos = bottomItemPosition - (layoutCount - 1);
        final int endPos = bottomItemPosition;
        final int childCount = getChildCount();
        for (int i = childCount - 1; i >= 0; i--) {
            View childView = getChildAt(i);
            int pos = getPosition(childView);
            if (pos > endPos || pos < startPos) {
                removeAndRecycleView(childView, recycler);
            }
        }

        detachAndScrapAttachedViews(recycler);
        //這裏主要是對須要顯示的Item進行排列以及縮放
        for (int i = 0; i < layoutCount; i++) {
            View view = recycler.getViewForPosition(startPos + i);
            ItemViewInfo layoutInfo = layoutInfos.get(i);
            addView(view);
            measureChildWithExactlySize(view);
            int left = (getHorizontalSpace() - mItemViewWidth) / 2;
            layoutDecoratedWithMargins(view, left, layoutInfo.getTop(), left + mItemViewWidth, layoutInfo.getTop() + mItemViewHeight);
            view.setPivotX(view.getWidth() / 2);
            view.setPivotY(0);
            view.setScaleX(layoutInfo.getScaleXY());
            view.setScaleY(layoutInfo.getScaleXY());
        }
    }
複製代碼

下面就是滑動的處理,若是想要RecyclerView滑動的話,就要打開這個開關。cdn

@Override
    public boolean canScrollVertically() {
        return true;
    }
複製代碼

返回true,你就能夠在豎直方向上滑動,相對的也有橫向的開關,這裏只關注豎向滑動。 滑動的處理在下面這個函數裏

@Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        int pendingScrollOffset = mScrollOffset + dy;
        mScrollOffset = Math.min(Math.max(mItemViewHeight, mScrollOffset + dy), mItemCount * mItemViewHeight);
        //每次滑動都要對Item進行排列等操做
        layoutChild(recycler);
        return mScrollOffset - pendingScrollOffset + dy;
    }
複製代碼

以上就是主要的邏輯,更多詳細的請移步github : https://github.com/DingMouRen/LayoutManagerGroup

相關文章
相關標籤/搜索