Android 自定义LayoutManager

实现自定义LayoutManager主要的4个步骤: 

  1. 指定默认的LayoutParams
  2. 计算每个ItemView的位置 
  3. 添加滑动事件
  4. 实现缓存

其中,主要在onLayoutChildern() 这个回调方法中实现主要功能。

指定默认的 LayoutParams

        当你继承LayoutManager之后,必须要重写generateDefaultLayoutParams()方法

        这个方法指定了每一个子view默认的LayoutParams,并且这个LayoutParams会在你调用getViewForPosition()返回子view前应用到这个子view。

// 重写generateDefaultLayoutParams()方法@Overridepublic RecyclerView.LayoutParams generateDefaultLayoutParams() {    return new    RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,            ViewGroup.LayoutParams.WRAP_CONTENT);}

 

2.1 实现简单的LayoutManager

       在这段代码中,我们先调用detachAndScrapAttachedViews(recycler);将所有的ItemView标记为Scrap状态,然后在挨个取出来,计算他们应该布局到什么位置,并用成员变量totalHeight记录总高度,最后依次调用layoutDecorated()将ItemView布局上去。

@Overridepublic void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {    if (getItemCount() <= 0 || state.isPreLayout()) {        return;    }// 先把所有的View先从RecyclerView中detach掉,然后标记为"Scrap"状态,表示    // 这些View处于可被重用状态(非显示中)。// 实际就是把View放到了Recycler中的一个集合中。    super.onLayoutChildren(recycler, state);    detachAndScrapAttachedViews(recycler);/* 这个方法主要用于计算并保存每个ItemView的位置 */    calculateChildrenSite(recycler);    recycleAndFillView(recycler, state);} private void calculateChildrenSite(RecyclerView.Recycler recycler) {    totalHeight = 0;    for (int i = 0; i < getItemCount(); i++) {        // 遍历Recycler中保存的View取出来        View view = recycler.getViewForPosition(i);        addView(view); // 因为刚刚进行了detach操作,所以现在可以重新添加        measureChildWithMargins(view, 0, 0); // 通知测量view的margin值        int width = getDecoratedMeasuredWidth(view);         // 计算view实际大小,包括了ItemDecorator中设置的偏移量。        int height = getDecoratedMeasuredHeight(view);        Rect mTmpRect = new Rect();        // 调用这个方法能够调整ItemView的大小,以除去ItemDecorator。        calculateItemDecorationsForChild(view, mTmpRect);        // 调用这句我们指定了该View的显示区域,并将View显示上去,此时所有区域都用于显示View,        //包括ItemDecorator设置的距离。        layoutDecorated(view, 0, totalHeight, width, totalHeight + height);        totalHeight += height;    }}

2.2 两列式的LayoutManager

       有了上例的基础,我们只需要稍作调整,直接看下面代码,注意注释部分。

private void calculateChildrenSite(RecyclerView.Recycler recycler) {    totalHeight = 0;    for (int i = 0; i < getItemCount(); i++) {        View view = recycler.getViewForPosition(i);        addView(view);        //我们自己指定ItemView的尺寸。        measureChildWithMargins(view, DisplayUtils.getScreenWidth() / 2, 0);        int width = getDecoratedMeasuredWidth(view);        int height = getDecoratedMeasuredHeight(view);        Rect mTmpRect = new Rect();        calculateItemDecorationsForChild(view, mTmpRect);        if (i % 2 == 0) { //当i能被2整除时,是左,否则是右。            //左            layoutDecoratedWithMargins(view, 0, totalHeight, DisplayUtils.getScreenWidth() / 2,                    totalHeight + height);        } else {            //右,需要换行            layoutDecoratedWithMargins(view, DisplayUtils.getScreenWidth() / 2, totalHeight,                    DisplayUtils.getScreenWidth(), totalHeight + height);            totalHeight = totalHeight + height;            LogUtils.e(i + "->" + totalHeight);        }    }}


处理滑动

        滑动事件主要涉及到4个方法需要重写,我们直接来看代码:

@Overridepublic boolean canScrollVertically() {    // 返回true表示可以纵向滑动    return true;} @Overridepublic int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {    //每次滑动时先释放掉所有的View,因为后面调用recycleAndFillView()时会重新addView()。    detachAndScrapAttachedViews(recycler);    // 列表向下滚动dy为正,列表向上滚动dy为负,这点与Android坐标系保持一致。    // 实际要滑动的距离    int travel = dy;     LogUtils.e("dy = " + dy);    // 如果滑动到最顶部    if (verticalScrollOffset + dy < 0) {        travel = -verticalScrollOffset;    } else if (verticalScrollOffset + dy > totalHeight - getVerticalSpace()) {// 如果滑动到最底部        travel = totalHeight - getVerticalSpace() - verticalScrollOffset;    }    // 调用该方法通知view在y方向上移动指定距离    offsetChildrenVertical(-travel);    recycleAndFillView(recycler, state); //回收并显示View    // 将竖直方向的偏移量+travel    verticalScrollOffset += travel;    return travel;} private int getVerticalSpace() {    // 计算RecyclerView的可用高度,除去上下Padding值    return getHeight() - getPaddingBottom() - getPaddingTop();} @Overridepublic boolean canScrollHorizontally() {    // 返回true表示可以横向滑动    return super.canScrollHorizontally();} @Overridepublic int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {    // 在这个方法中处理水平滑动    return super.scrollHorizontallyBy(dx, recycler, state);}

实现缓存

      缓存最主要的就是先把每个ItemView的位置信息保存起来,然后在滑动过程中通过判断每个ItemView的位置是否和当前RecyclerView应该显示的区域有重合,若有就显示它,若没有就移除并回收。

/** * 将滑出屏幕的Items回收到Recycle缓存中 */Rect childRect = new Rect();for (int i = 0; i < getChildCount(); i++) {    //这个方法获取的是RecyclerView中的View,注意区别Recycler中的View    //这获取的是实际的View    View child = getChildAt(i);    //下面几个方法能够获取每个View占用的空间的位置信息,包括ItemDecorator    childRect.left = getDecoratedLeft(child);    childRect.top = getDecoratedTop(child);    childRect.right = getDecoratedRight(child);    childRect.bottom = getDecoratedBottom(child);    //如果Item没有在显示区域,就说明需要回收    if (!Rect.intersects(displayRect, childRect)) {    //移除并回收掉滑出屏幕的View    removeAndRecycleView(child, recycler);    itemStates.put(i, false); //更新该View的状态为未依附    }    }     //重新显示需要出现在屏幕的子View    for (int i = 0; i < getItemCount(); i++) {    //判断ItemView的位置和当前显示区域是否重合    if (Rect.intersects(displayRect, allItemRects.get(i))) {    //获得Recycler中缓存的View    View itemView = recycler.getViewForPosition(i);    measureChildWithMargins(itemView, DisplayUtils.getScreenWidth() / 2, 0);    //添加View到RecyclerView上    addView(itemView);    //取出先前存好的ItemView的位置矩形    Rect rect = allItemRects.get(i);    //将这个item布局出来    layoutDecoratedWithMargins(itemView,    rect.left,    rect.top - verticalScrollOffset,  //因为现在是复用View,所以想要显示在    rect.right,    rect.bottom - verticalScrollOffset);    itemStates.put(i, true); //更新该View的状态为依附    }    }    LogUtils.e("itemCount = " + getChildCount());    }

参考学习:

https://blog.csdn.net/qq_31370269/article/details/52932315

 

更多相关文章

  1. 【Android(安卓)界面效果43】Android(安卓)LayoutInflater的infl
  2. Android开发之MediaPlayer使用
  3. Android从零开始(十五)
  4. Android(安卓)strings.xml中空格符的标识方法附xml特殊字符表示
  5. Android(安卓)编码规范 | 代码风格指南
  6. Android(安卓)Cavans 基础应用
  7. RemoteViews 中暗藏的坑
  8. Android中简单实现夜间模式
  9. Appium(七):Appium API(一) 应用操作

随机推荐

  1. 阅读《Android(安卓)从入门到精通》(33)—
  2. Android(安卓)播放 Gif 图片控件
  3. Android实现图片 高斯模糊,以及图片镜像
  4. 彻底解决Android(安卓)studio中文乱码问
  5. 创建android SDK开发环境
  6. Android(安卓)Animations动画使用详解
  7. [置顶] android:autoLink
  8. android平台开发下 密钥管理
  9. android 网络状态
  10. 暂时只会这种导航,实时显示自己的位置,,求其