[置顶] 拓展开源库PullZoomView适配瀑布流

PullZoomView是个不错的伸缩效果头部的控件,可以使用ListView、ScrollView和RecyclerView做出头部伸缩效果,但是不支持RecyclerView瀑布流布局,自己改轮子兼容.

感谢Frank-Zhu的贡献,github地址

控件的使用很简单,两个自定义属性,headerView为头部view,zoomView为后面那种有缩放的图片

    <com.ecloud.pulltozoomview.demo.PullToZoomRecyclerViewEx
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        custom:headerView="@layout/profile_head_view"
        custom:zoomView="@layout/list_head_zoom_view" />

这是demo中使用GridLayoutManager配置的RecyclerView,效果如图

 final GridLayoutManager manager = new GridLayoutManager(this, 2);
        manager.setOrientation(GridLayoutManager.VERTICAL);
        manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                return mAdapter.getItemViewType(position) == RecyclerViewHeaderAdapter.INT_TYPE_HEADER ? 2 : 1;
            }
        });
        listView.setAdapterAndLayoutManager(mAdapter, manager);

当你想换成StaggeredGridLayoutManager做瀑布流的时候,就留坑了,因为压根不支持,PullToZoomRecyclerViewEx的setAdapterAndLayoutManager参数只支持GridLayoutManager

    public void setAdapterAndLayoutManager(RecyclerView.Adapter adapter, GridLayoutManager mLayoutManager) {
        mRootView.setLayoutManager(mLayoutManager);
        mRootView.setAdapter(adapter);
        updateHeaderView();
    }

好吧,既然写不出轮子就改造轮子吧,添加一个支持StaggeredGridLayoutManager方法看看咋样?

// PullToZoomRecyclerViewEx添加方法
    public void setAdapterAndLayoutManager(RecyclerView.Adapter adapter, StaggeredGridLayoutManager mLayoutManager) {
        mRootView.setLayoutManager(mLayoutManager);
        mRootView.setAdapter(adapter);
        updateHeaderView();
    }
// PullToZoomRecyclerActivity改StaggeredGridLayoutManager
 manager = new StaggeredGridLayoutManager(2, 
 StaggeredGridLayoutManager.VERTICAL);
listView.setAdapterAndLayoutManager(mAdapter, manager);

然后到作者封装的RecyclerViewHeaderAdapter的onBindViewHolder处理头部占据两格的操作,然后看到作者try catch住了设置瀑布流头部的代码,这里已经知道作者尝试过适配瀑布流然后没适配成,try catch用来调试的,既然这样还得看看效果是咋样的?再来改进

 @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (position >= headers.size() && (position - headers.size()) < getCount()) {
            //noinspection unchecked
            onBindView((V) holder, position - headers.size());
        } else {
            try {
                final StaggeredGridLayoutManager.LayoutParams lp =  (StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams();
                lp.setFullSpan(true);
                holder.itemView.setLayoutParams(lp);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

还需要设置头部可见适配StaggeredGridLayoutManager的情况

private boolean isFirstItemVisible() {
        if (mRootView != null) {
            final RecyclerView.Adapter adapter = mRootView.getAdapter();
            RecyclerView.LayoutManager mLayoutmanager = null;
            if (mRootView.getLayoutManager() instanceof StaggeredGridLayoutManager) {
                mLayoutmanager = (StaggeredGridLayoutManager) mRootView.getLayoutManager();
            } else if (mRootView.getLayoutManager() instanceof GridLayoutManager) {
                mLayoutmanager = (GridLayoutManager) mRootView.getLayoutManager();
            }

            if (null == adapter || adapter.getItemCount() == 0) {
                return true;
            } else {
                int[] into = {0, 0};
                if (mLayoutmanager == null) return false;
                // 这里需要加上StaggeredGridLayoutManager的情况来判断头部是否可见
                if (mLayoutmanager instanceof 
                StaggeredGridLayoutManager) {
                    ((StaggeredGridLayoutManager) 
                mLayoutmanager).findFirstVisibleItemPositions(into);
                } else if (mLayoutmanager instanceof 
                GridLayoutManager) {
                    into[0] = ((GridLayoutManager) 
                    mLayoutmanager).findFirstVisibleItemPosition();
                }
                if (into.length > 0 && into.length > 0 && 
                into[0] <= 1) {
                    final View firstVisibleChild = 
                    mRootView.getChildAt(0);
                    if (firstVisibleChild != null) {
                        return firstVisibleChild.getTop() >= 
                        mRootView.getTop();
                    }
                }
            }
        }

        return false;
    }

[置顶] 拓展开源库PullZoomView适配瀑布流_第1张图片

可以看到设置头部的操作压根不起作用,而LogCat中输出捕获到的log:

java.lang.ClassCastException: android.widget.AbsListView$LayoutParams cannot be cast to
android.support.v7.widget.StaggeredGridLayoutManager$LayoutParams

可以知道头部的布局属性不是StaggeredGridLayoutManager.LayoutParams,也就说明了其Parent不是RecyclerView,是不是很疑惑呢?明明是设置在RecyclerView显示的,咋拿不到瀑布流布局属性呢?

原因就在RecyclerViewHeaderAdapter的onCreateViewHolder这里,先说明
item是作者将布局类型和ViewHolder封装起来的类

    public static class ExtraItem<V extends RecyclerView.ViewHolder> {
        public final int type;
        public final V view;

        public ExtraItem(int type, V view) {
            this.type = type;
            this.view = view;
        }

    }

PullToZoomRecyclerViewEx继承自PullToZoomBase这个类,PullToZoomBase继承自线性布局实现IPullToZoom接口,它在初始化的时候会拿自定义属性,如果配置了头部和伸缩布局的话,它会填充布局到protected修饰符的mZoomView 和mHeaderView和调用IPullToZoom的handleStyledAttributes(TypedArray a)方法供PullToZoomRecyclerViewEx去将这两个布局添加到mHeaderContainer的帧布局中,
最后将mRootView添加到线性布局中,mRootView是泛型,PullToZoomRecyclerViewEx继承PullToZoomBase的时候设置的就是recyclerview,也就是将RecyclerView放到此线性布局中

if (attrs != null) {
            LayoutInflater mLayoutInflater = LayoutInflater.from(getContext());
            //初始化状态View
            TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.PullToZoomView);

            int zoomViewResId = a.getResourceId(R.styleable.PullToZoomView_zoomView, 0);
            if (zoomViewResId > 0) {
                mZoomView = mLayoutInflater.inflate(zoomViewResId, null, false);
            }

            int headerViewResId = a.getResourceId(R.styleable.PullToZoomView_headerView, 0);
            if (headerViewResId > 0) {
                mHeaderView = mLayoutInflater.inflate(headerViewResId, null, false);
            }

            isParallax = a.getBoolean(R.styleable.PullToZoomView_isHeaderParallax, true);

            // Let the derivative classes have a go at handling attributes, then
            // recycle them...
            handleStyledAttributes(a);
            a.recycle();
        }
        addView(mRootView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);

PullToZoomRecyclerViewEx回调handleStyledAttributes方法为
mHeaderContainer添加布局,ExtraItem在这里就用上了,
new RecyclerView.ViewHolder(mHeaderContainer)说明这里直接创建了一个ViewHolder,创建后使用mAdapter.addHeaderView(mExtraItem)刷新RecyclerView的item

    @Override
    public void handleStyledAttributes(TypedArray a) {
        mHeaderContainer = new FrameLayout(getContext());
        if (mZoomView != null) {
            mHeaderContainer.addView(mZoomView);
        }
        if (mHeaderView != null) {
            mHeaderContainer.addView(mHeaderView);
        }
        RecyclerViewHeaderAdapter<RecyclerView.ViewHolder> mAdapter = (RecyclerViewHeaderAdapter<RecyclerView.ViewHolder>) mRootView.getAdapter();
        if (mAdapter != null) {
            RecyclerViewHeaderAdapter.ExtraItem mExtraItem = new RecyclerViewHeaderAdapter.ExtraItem(RecyclerViewHeaderAdapter.INT_TYPE_HEADER, new RecyclerView.ViewHolder(mHeaderContainer) {
                @Override
                public String toString() {
                    return super.toString();
                }
            });

            mAdapter.addHeaderView(mExtraItem);
        }
    }

知道以上步骤后,再来看看onCreateViewHolder,这里直接遍历headers根据类别直接返回之前ExtraItem创建的viewholder了,
然后不是INT_TYPE_HEADER和INT_TYPE_FOOTER的就属于普通的item,调用onCreateContentView给用户自己处理,这里有看出端倪了吗?没看出?对比下我们规范的onCreateViewHolder里的做法

这里写图片描述

这里的做法:

    @Override
    public final V onCreateViewHolder(ViewGroup parent, int viewType) {
        for (ExtraItem<V> item : headers)
            if (viewType == item.type)
                return item.view;

        for (ExtraItem<V> item : footers)
            if (viewType == item.type)
                return item.view;

        return onCreateContentView(parent, viewType);
    }

对比可知,这里的onCreateViewHolder在返回ExtraItem的ViewHolder时候没有用到Parent,因为在PullToZoomBase初始化的时候提前填充完view了,Parent为null,所以RecyclerView压根就不是它们的parent,所以拿到的布局属性就不会是瀑布流布局属性了,所以异常捕获是合情合理的。

PullToZoomBase的init方法里面过早填充了两个view,
private void init(Context context, AttributeSet attrs) {
     ...
     mHeaderView = mLayoutInflater.inflate(headerViewResId, null, false);
     ...
     mZoomView = mLayoutInflater.inflate(zoomViewResId, null, false);
...
}

知道了是inflate的时候参数parent导致的原因后,一切就好办了,在RecyclerViewHeaderAdapter的onCreateViewHolder,头部类型的照样调用onCreateContentView(parent, item.type, item.view.itemView)交给我们定义的RecyclerAdapterCustom去做填充,onCreateContentView添加一个type参数用于头部和普通item的处理

@Override
    public final V onCreateViewHolder(ViewGroup parent, int viewType) {
        /*for (ExtraItem<V> item : headers)
            if (viewType == item.type)
                return item.view;*/

        for (ExtraItem<V> item : footers)
            if (viewType == item.type)
                return item.view;

        for (ExtraItem<V> item : headers)
            if (viewType == item.type)
                // 这里返回item.view.itemView去给RecyclerAdapterCustom去处理
                return onCreateContentView(parent, item.type, item.view.itemView);

        return onCreateContentView(parent, viewType, null);
    }

然后在RecyclerAdapterCustom的onCreateContentView,用header_container的布局包住mHeaderContainer,这样子mHeaderContainer的parent的布局属性就是瀑布流布局属性了,PS:之前的mtextview是直接创建的,也是Parent不为RecyclerView,这里换成item_text布局做。
onBindView这里设置mtextview高度不等看下瀑布流的效果

@Override
        public ViewHolderRecyclerPullToZoom onCreateContentView(ViewGroup parent, int viewType, View itemView) {
            ViewHolderRecyclerPullToZoom viewHolderRecyclerPullToZoom = null;
            if (viewType != INT_TYPE_HEADER) {
                // 内容布局
                viewHolderRecyclerPullToZoom = new ViewHolderRecyclerPullToZoom(LayoutInflater.from(PullToZoomRecyclerActivity.this).inflate(R.layout.item_text, parent, false));
            } else {
                // 头部,这里给PullToZoomRecyclerViewEx的mHeaderContainer加一层布局,目的是让他作为recycleview的孩子,这样子才能调RecyclerViewHeaderAdapter的onBindViewHolder的else那一部分代码设置占一行
                ViewGroup view = (ViewGroup) LayoutInflater.from(PullToZoomRecyclerActivity.this).inflate(R.layout.header_container, parent, false);
                view.addView(itemView);
                viewHolderRecyclerPullToZoom = new ViewHolderRecyclerPullToZoom(view);
            }
            return viewHolderRecyclerPullToZoom;//new ViewHolderRecyclerPullToZoom(new TextView(getContext()));
        }

 @Override
        public void onBindView(ViewHolderRecyclerPullToZoom view, int position) {
            Log.e("偶滴神", "位置:" + position + " " + view.mtextview + " " + view.mtextview.getLayoutParams() + " " + ((ViewGroup) view.mtextview.getParent()).getLayoutParams());
            view.mtextview.setText(adapterData[position]);
            view.mtextview.setHeight((int) (50 + Math.random() * 100));
        }

这时再来看下效果,已经可以了,但是细心的小伙伴可以发现,往上推的时候没有上面那种图片随着缓慢往下移动的效果了,咋办呢?继续扒代码!

在PullToZoomRecyclerViewEx中,mRootView即RecyclerView设置了滚动的监听,
f 为头部的高度减去mHeaderContainer的bottom得到的值,也就是头部的底部距recyclerview顶部的距离,
然后当f大于0小于头部高度的时候,i 为据顶部距离的0.65值,mHeaderContainer.scrollTo(0, -i)即mHeaderContainer里面的内容即mHeaderView和mZoomView往Y轴正方向即往下移动,这样就形成了Recyclerview往上推的时候头部缓慢往下移动的效果。

这里就可以进行分析了,mHeaderContainer.getBottom()得到的是相对父布局的顶部的距离,现在我们在之前的onCreateContentView那里为mHeaderContainer包了层布局,所以Recyclerview往上推的时候mHeaderContainer.getBottom()是相对于父布局而不是对于RecyclerView来说,所以肯定为mHeaderHeight ,所以f 肯定等于0,对于下面的判断就无意义了,所以没有移动的效果。

 public PullToZoomRecyclerViewEx(Context context, AttributeSet attrs) {
        ...
        mRootView.setOnScrollListener(new RecyclerView.OnScrollListener() {

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                if (mZoomView != null && !isHideHeader() &&
                                       isPullToZoomEnabled()) {
                    float f = mHeaderHeight - 
                                mHeaderContainer.getBottom();
                    Log.d(TAG, "onScroll --> f = " + f);
                    if (isParallax()) {
                        if ((f > 0.0F) && (f < mHeaderHeight)) {
                            int i = (int) (0.65D * f);
                            mHeaderContainer.scrollTo(0, -i);
                        } else if (mHeaderContainer.getScrollY() 
                                                != 0) {
                            mHeaderContainer.scrollTo(0, 0);
                        }
                    }
                }

            }

           ...
        });
        ...
    }

改进相当简单,将mHeaderContainer换为mHeaderContainer.getParent()即可

   public PullToZoomRecyclerViewEx(Context context, AttributeSet attrs) {
    mRootView.setOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

            ViewGroup viewGroup = null;
            viewGroup = mHeaderContainer.getParent() != null ? (ViewGroup) 
            mHeaderContainer.getParent() : mHeaderContainer;

            if (mZoomView != null && !isHideHeader()
             && isPullToZoomEnabled()) {
                float f = mHeaderHeight - viewGroup.getBottom();
                Log.d(TAG, "onScroll --> f = " + f);
                if (isParallax()) {
                    if ((f > 0.0F) && (f < mHeaderHeight)) {
                        int i = (int) (0.65D * f);
                        viewGroup.scrollTo(0, -i);
                    } else if (viewGroup.getScrollY() != 0) {
                        viewGroup.scrollTo(0, 0);
                    }
                }
            }

        }
        ...
    });
    ...
}

最后我们来看下效果,已经完美实现了,至于之前的GridLayoutManager的兼容处理就不弄了,也就是简单的判断处理

最后提供改进后的代码下载试试吧,讲得不好请见谅,自己跑一下看下源码就很好懂了。

下载地址:PullToZoomView兼容RecyclerView瀑布流

你可能感兴趣的:(github,开源,瀑布流-PullZo)