美文网首页
RecyclerView使用StaggeredGridLayou

RecyclerView使用StaggeredGridLayou

作者: 蛋西 | 来源:发表于2017-02-23 17:21 被阅读0次

    我们经常在使用App的过程中有城市的列表选择,我们会发现App在滑动的过程中,会有粘性头部一直悬浮在最上端,其中实现的方法有很多种,当然,这里我们不讨论使用ListView或者GridView控件,也不讨论RecyclerView使用LinearLayoutManager或者GridLayoutManager布局时的实现方式。本文讨论的重点是使用StaggeredGridLayoutManager布局时的粘性头部的实现方式。本文已展示本地图片库的图片作为例子来展开。

    先上一下效果图

    粘性头部

    扫描本地图片库

    public LocalPictureDateResult loadAllLocalPictures() {
            LocalPictureDateResult result = new LocalPictureDateResult(mContext);
            Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            ContentResolver mContentResolver = mContext.getContentResolver();
            Log.i(TAG, mImageUri.getPath());
            // query only jpeg and png image type files
            Cursor mCursor = mContentResolver.query(mImageUri, null,
                    MediaStore.Images.Media.MIME_TYPE + "=? or "
                            + MediaStore.Images.Media.MIME_TYPE + "=?",
                    new String[]{"image/jpeg", "image/png"}, MediaStore.Images.Media.DATE_MODIFIED + " DESC");
            if (mCursor != null) {
                while (mCursor.moveToNext()) {
                    //retrive image path
                    String path = mCursor.getString(mCursor
                            .getColumnIndex(MediaStore.Images.Media.DATA));
                    int width = mCursor.getInt(mCursor
                            .getColumnIndex(MediaStore.Images.Media.WIDTH));
                    int height = mCursor.getInt(mCursor
                            .getColumnIndex(MediaStore.Images.Media.HEIGHT));
                    long modifiedData = mCursor.getLong(mCursor.
                           getColumnIndex(MediaStore.Images.Media.DATE_MODIFIED));
                    long addedData = mCursor.getLong(mCursor.
                           getColumnIndex(MediaStore.Images.Media.DATE_ADDED));
                    if (path==null || "".equals(path) || width == 0 || height == 0 || modifiedData == 0) {
                        continue;
                    }
                    LocalPictureDetailInfo lpi = new LocalPictureDetailInfo(path, width, height, addedData * 1000, modifiedData * 1000);
                    result.add(lpi);
                }
                mCursor.close();
            }
            return result;
        }
    

    这里返回一个LocalPictureDateResult对象,里面存放了扫描出来的本地图片文件信息集合

    这里我们看下上面代码中的result.add(lpi)方法

    **LocalPictureDateResult.java** 
    
        /**
         * save the positions of title in the data collections
         */
        ArrayList mArrayTitlePos;
    
        /**
         * title collection
         */
        private HashSet<String> titleSet ;
    
        /**
         * pictures collection,include title and picture details
         */
        ArrayList<WrapLocalPictureDetailInfo> mLocalPictureInfos;
    
       /**
         * add local picture details
         *
         * @param detailInfo
         */
        public void add(LocalPictureDetailInfo detailInfo) {
            String dateTimeStr = mDateParseFilter.parse(detailInfo.getModifiedDate());
            if (!titleSet.contains(dateTimeStr)) {
                titleSet.add(dateTimeStr);
                // save title position
                mArrayTitlePos.add(mLocalPictureInfos.size());
    
                // add title object
                WrapLocalPictureDetailInfo titleInfo = new WrapLocalPictureDetailInfo(dateTimeStr, DATA_TYPE_TITLE);
                mLocalPictureInfos.add(titleInfo);
                // add content object
                WrapLocalPictureDetailInfo contentInfo = new WrapLocalPictureDetailInfo(detailInfo);
                mLocalPictureInfos.add(contentInfo);
            } else {
                mLocalPictureInfos.add(new WrapLocalPictureDetailInfo(detailInfo));
            }
        }
    

    上面这段代码,把扫描出来的本地图片集,通过图片的最后修改时间,分离出时间段名称,并且把时间段名称作为数据的一部分加入到最后的图片集合中,这样图片集合中就包含了图片数据和标题数据,并且我们也有了每个标题在图片集合中的位置,这样有利于我们后面计算和寻找标题做准备。

    主页面布局

    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/srl_root"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
    
            <android.support.v7.widget.RecyclerView
                android:id="@+id/rv_content"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
    
            </android.support.v7.widget.RecyclerView>
    
        </android.support.v4.widget.SwipeRefreshLayout>
    
        <LinearLayout
            android:id="@+id/header_one"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:visibility="invisible">
    
            <include layout="@layout/item_title_local_picture" />
        </LinearLayout>
    
    </FrameLayout>
    

    上面我们看到最外层用了个FrameLayout,用了个LinearLayout作为粘性头部,引入的头部布局和RecyclerView中的头部布局一样

    RecyclerView的普通头部制作

    头部布局文件

    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/tv_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_red_light"
        android:gravity="center_vertical"
        android:textColor="@android:color/black"
        android:textSize="20sp">
    
    </TextView>
    

    设置RecylerView的adapter和layoutmanager

        int mColumns = 3;
        // 
        mLayoutManager = new StaggeredGridLayoutManager(mColumns, StaggeredGridLayoutManager.VERTICAL);
        mAdapter = new LocalPicturesAdapter(this, mMainPresenter.getLocalPictureDatas(), mColumns);
        // set RecylerView's adapter and layoutmanager
        mContentView.setLayoutManager(mLayoutManager);
        mContentView.setAdapter(mAdapter);
        mContentView.setHasFixedSize(true);
    

    LocalPicturesAdapter类,主要根据数据中的type类型来显示不同的

    public class LocalPicturesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements View.OnClickListener {
    
        Activity mContext;
    
        List<WrapLocalPictureDetailInfo> mImagePaths;
    
        int columnNums = 1;
    
        private OnRecyclerViewItemClickListener mOnItemClickListener = null;
    
        public LocalPicturesAdapter(Activity context, List<WrapLocalPictureDetailInfo> imagePaths, int columnNums) {
            this.mContext = context;
            this.mImagePaths = imagePaths;
            this.columnNums = columnNums;
        }
    
        public void setImagePaths(List<WrapLocalPictureDetailInfo> imagePaths){
            this.mImagePaths = imagePaths;
        }
    
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            RecyclerView.ViewHolder viewHolder;
            View view;
            if (viewType == DATA_TYPE_TITLE) {
                view = mContext.getLayoutInflater().inflate(R.layout.item_title_local_picture,null);
                viewHolder = new LocalPicturesTitleHolder(view);
            } else if (viewType == DATA_TYPE_CONTENT) {
                view = mContext.getLayoutInflater().inflate(R.layout.item_content_local_picture,null);
                viewHolder = new LocalPicturesContentHolder(view);
            } else {
                view = mContext.getLayoutInflater().inflate(R.layout.item_content_local_picture,null);
                viewHolder = new LocalPicturesContentHolder(view);
            }
            if (view != null) {
                // add listener
                view.setOnClickListener(this);
            }
            return viewHolder;
        }
    
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
            if (viewHolder.getItemViewType() == DATA_TYPE_TITLE) {
                LocalPicturesTitleHolder holder = (LocalPicturesTitleHolder) viewHolder;
                holder.itemView.setTag(position);
                if (holder.mTvTitle != null) {
                    WrapLocalPictureDetailInfo pictureDetailInfo = mImagePaths.get(position);
                    // set title layoutparams
                    StaggeredGridLayoutManager.LayoutParams layoutParams = new StaggeredGridLayoutManager.LayoutParams(
                            ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                    // set title full span
                    layoutParams.setFullSpan(true);
                    holder.itemView.setLayoutParams(layoutParams);
                    holder.mTvTitle.setText(pictureDetailInfo.getDataTitle());
                }
            } else if (viewHolder.getItemViewType() == DATA_TYPE_CONTENT) {
                LocalPicturesContentHolder holder = (LocalPicturesContentHolder) viewHolder;
                holder.itemView.setTag(position);
                if (holder.mImageView != null) {
                    WrapLocalPictureDetailInfo pictureDetailInfo = mImagePaths.get(position);
                    ........
                    ........
                    ........
                    // use glide load the image into ImageView
                    Glide.with(mContext).load(new File(pictureDetailInfo.getPath())).into(holder.mImageView);
                }
            }
        }
    
        ......
        ......
        ......
    }
    

    我们通过显示普通title的方法是使用设置itemview的StaggeredGridLayoutManager.LayoutParams来实现的,并且setFUllSpan(true)用来让title显示一整行

    RecylerView滚动监听

    public RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
    
            // get the recyclerview first visable item's position,return the size same as span count
            int mFirstVisiblePosition[] = new int[mColumns];
            // get the recyclerview last visable item's position,return the size same as span count
            int mLastVisiblePosition[] = new int[mColumns];
    
            // sticky head view height
            private int mStickyHeadHeight = 0;
    
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }
    
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                if (dy == 0) {
                    // the first time show the recyclerview items
                    mLayoutManager.findFirstVisibleItemPositions(mFirstVisiblePosition);
                    int titlePosision = getMinVisablePosition(mFirstVisiblePosition);
                    if (titlePosision >= 0) {
                        // get the item object
                        WrapLocalPictureDetailInfo wrapLocalPictureDetailInfo = mAdapter.getItemObject(titlePosision);
    
                        if (wrapLocalPictureDetailInfo != null) {
                            // set the first title view text content
                            ((TextView) mHeaderOneView.getChildAt(0)).setText(wrapLocalPictureDetailInfo.getDataTitle());
                            mHeaderOneView.setVisibility(View.VISIBLE);
                            // set the current title position into view tag object
                            mHeaderOneView.setTag(titlePosision);
                            // retrive the title view height,and set into variable
                            mStickyHeadHeight = mHeaderOneView.getMeasuredHeight();
                        }
                    }
                } else if (dy != 0) {  //  pull down the recyclerview then dy<0 , pull up the recyclerview then dy>0
    
                    mLayoutManager.findFirstVisibleItemPositions(mFirstVisiblePosition);
                    int minVisablePosition = getMinVisablePosition(mFirstVisiblePosition);
    
                    mLayoutManager.findLastVisibleItemPositions(mLastVisiblePosition);
                    int maxVisablePosition = getMaxVisablePosition(mLastVisiblePosition);
    
                    if (minVisablePosition < 0) {
                        return;
                    }
                    /**
                     * get one title position before current minimum visable item position
                     */
                    int beforeFirstItemTitlePosition = mMainPresenter.findBeforeTitlePosition(minVisablePosition);
                    /**
                     * get the title position after current minimum visable item position
                     */
                    int afterFirstItemTitlePosition = mMainPresenter.findAfterTitlePosition(minVisablePosition);
    
                    // when next title item position after the current minimum visable exist,and not equals current item position
                    if (afterFirstItemTitlePosition != Integer.MIN_VALUE && afterFirstItemTitlePosition != minVisablePosition) {
                        // determine whether next title item after the current minimum visable position is visable in the recyclerview
                        if (afterFirstItemTitlePosition <= maxVisablePosition) {
                            // it means that next title item is visable in the recycleview now
                            // find that suitable next title view
                            View nextTitleView = findView(afterFirstItemTitlePosition);
                            if (nextTitleView != null) {
                                float yxis = nextTitleView.getY();
                                // if next title view is scroll into first title view's area
                                if (yxis <= mStickyHeadHeight) {
                                    // then fix the first title scroll y
                                    mHeaderOneView.scrollTo(0, (int) (mStickyHeadHeight - yxis));
                                } else {
                                    // others,next title away from the first title view
                                    // then fix first title view scroll y
                                    mHeaderOneView.scrollTo(0, 0);
                                }
                                // set visable to the header view always
                                mHeaderOneView.setVisibility(View.VISIBLE);
                            }
                        }
                    }
    
    
                    /**
                     * Determine whether need to change the title
                     */
                    // when title item before the current maxinum visable position exist,and position not equals the current header view's tag
                    if (beforeFirstItemTitlePosition != Integer.MIN_VALUE && mHeaderOneView.getTag() != null && (int) mHeaderOneView.getTag() != beforeFirstItemTitlePosition) {
                        /**
                         * it means that should change the title content
                         */
                        ((TextView) mHeaderOneView.getChildAt(0)).setText("");
                        // always show the title item that before the current maxinum visable position
                        WrapLocalPictureDetailInfo wpdi = mMainPresenter.getLocalPictureDatas().get(beforeFirstItemTitlePosition);
                        // set new title content
                        ((TextView) mHeaderOneView.getChildAt(0)).setText(wpdi.getDataTitle());
                        if (dy > 0) {
                            // if user pull up the recyclerview,fix the scroll y.Sometimes,because of the next title item may be not visable in current recyclerview,
                            // so,we should force set header view visable once again
                            mHeaderOneView.setVisibility(View.VISIBLE);
                            mHeaderOneView.scrollTo(0, 0);
                        }
                        // set title position in the tag
                        mHeaderOneView.setTag(beforeFirstItemTitlePosition);
                        // retrive the sticky head height
                        mStickyHeadHeight = mHeaderOneView.getMeasuredHeight();
                    }
                }
            }
    
            ......
            ......
            ......
        };
    

    其中最主要的原理是在RecylerView滚动的时候,判断dy的大小,查找当前RecylerView中用户可视的最小的item位置,通过之前我们初始化数据的时候,分离出的每个标题在数据集中的位置,底层使用二分查找方法,找到当前可视的最小item位置之前(下文称item之前)和之后(item之后)的标题item的位置信息。如果存在item之前的标题信息,判断和当前已经显示的粘性头部tag中存储的位置不一样,那么用新的头部信息替换当前粘性头部;如果在当前页面搜索到item之后的标题视图,获取该视图对象,判断与已经显示的粘性头部底部位置判断,如果超过了粘性头部,则把粘性头部用scrollTo方法进行向上滑动,就会出现粘性头部滑动的效果,如果没有超过,那么什么都不做

    总结

    可能看代码还是有点抽象,总结起来就是在初始化数据时,将标题也作为数据项加入到数据集合中,然后在Adapter中动态的判断是标题还是普通内容,采用不同的布局页面和布局,结合滚动时,通过当前页面最小位置的判断之前和之后标题item的位置,来控制滑动或者切换粘性头部,我把示例工程放在了github上RecyclerStaggeredStickyHeaderView,欢迎fork或者start。最后如果发现文章有什么问题或者写的不好的,欢迎留言交流,当然,也欢迎点赞~~~

    相关文章

      网友评论

          本文标题:RecyclerView使用StaggeredGridLayou

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