拓展开源库PullZoomView适配瀑布流

原创 2015年11月18日 22:50:39

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;
    }

这里写图片描述

可以看到设置头部的操作压根不起作用,而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瀑布流

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

Android自定义控件——PullZoomView

本文介绍PullZoomView的简单实现,如图: 就是通过下拉ListView或者ScrollView或者更多的View如GridView,RecycleView等等,的时候对Header有...

Android PullZoomView:PullToZoomListViewEx(1)

 Android PullZoomView:PullToZoomListViewEx(1) Android PullZoomView是github上面的一个第三方开源项目,该项目实现的功能被新...

上下拉伸图片-第三方开源-PullToZoomListViewEx 实现阻尼效果

本文要说的PullToZoomScrollViewEx 实现阻尼效果 什么是阻尼效果。简单解释就是一张图片,当你按住向下拖动,图片跟着拉升方大,当你松开时,回弹到原来的效果,在Android 上我们...

关于android.support.v7.widget.RecyclerView包导入项目引发的BUG

当我们用到RecyclerView时,在demo中或许运行的很正常,可是一挪到项目中就会出现一些还无头绪的bug。再此总结一下,我所碰到的错误。 此文是继 Android滑动展示图片(一页多图,带小圆...

PullZoomView适配瀑布流

【无限互联】iOS开发之瀑布流布局实现(UICollectionView拓展Layout)

现在的移动应用在进行图片或照片浏览是都喜欢才用瀑布流的方式来进行布局,相比于老版的等高等宽的传统照片布局方式,瀑布流显然显得更有吸引力。 其实对于瀑布流的实现原理并不是多难,像常见的应用一般都是把屏...

超炫ListView拓展的瀑布流

  • 2015-11-02 14:28
  • 5.52MB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)