Android使用gridlayoutmanager在recyclerview中的最后一个元素下添加间距

时间:2015-06-16 11:19:24

标签: android android-recyclerview gridlayoutmanager

我正在尝试使用RecyclerViewGridLayoutManager的最后一个元素行下添加间距。为此,我使用自定义ItemDecoration使用底部填充,其最后一个元素如下:

public class SpaceItemDecoration extends RecyclerView.ItemDecoration {
private int space;
private int bottomSpace = 0;

public SpaceItemDecoration(int space, int bottomSpace) {
    this.space = space;
    this.bottomSpace = bottomSpace;
}

public SpaceItemDecoration(int space) {
    this.space = space;
    this.bottomSpace = 0;
}

@Override
public void getItemOffsets(Rect outRect, View view,
                           RecyclerView parent, RecyclerView.State state) {

    int childCount = parent.getChildCount();
    final int itemPosition = parent.getChildAdapterPosition(view);
    final int itemCount = state.getItemCount();

    outRect.left = space;
    outRect.right = space;
    outRect.bottom = space;
    outRect.top = space;

    if (itemCount > 0 && itemPosition == itemCount - 1) {
        outRect.bottom = bottomSpace;
    }
}
}

但是这个方法的问题在于它搞砸了最后一行网格中的元素高度。我猜GridLayoutManager根据左边的间距改变元素的高度。实现这一目标的正确方法是什么?

这适用于LinearLayoutManager。如果GridLayoutManager有问题。

如果您在底部有一个FAB,并且需要在最后一行中滚动FAB以上的项目以便它们可见,那么它非常有用。

8 个答案:

答案 0 :(得分:322)

只需添加填充并设置android:clipToPadding="false"

即可
<RecyclerView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="8dp"
    android:clipToPadding="false" />

感谢这个精彩的answer

答案 1 :(得分:9)

解决这个问题的方法是重写GridLayoutManager的SpanSizeLookup。

您必须对活动或片段中的GridlayoutManager进行更改,以便为RecylerView充气。

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //your code 
    recyclerView.addItemDecoration(new PhotoGridMarginDecoration(context));

    // SPAN_COUNT is the number of columns in the Grid View
    GridLayoutManager gridLayoutManager = new GridLayoutManager(context, SPAN_COUNT);

    // With the help of this method you can set span for every type of view
    gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
        @Override
        public int getSpanSize(int position) {
            if (list.get(position).getType() == TYPE_HEADER) {
                // Will consume the whole width
                return gridLayoutManager.getSpanCount();
            } else if (list.get(position).getType() == TYPE_CONTENT) {
                // will consume only one part of the SPAN_COUNT
                return 1;
            } else if(list.get(position).getType() == TYPE_FOOTER) {
                // Will consume the whole width
                // Will take care of spaces to be left,
                // if the number of views in a row is not equal to 4
                return gridLayoutManager.getSpanCount();
            }
            return gridLayoutManager.getSpanCount();
        }
    });
    recyclerView.setLayoutManager(gridLayoutManager);
}

答案 2 :(得分:6)

我有类似的问题,并在堆栈溢出时回复了另一个线程。为了帮助落入此页面的其他人,我将在此处重新发布。
阅读所有其他答复后,我发现recyclerview布局xml中的更改按预期可用于我的recycler视图:

        android:paddingBottom="127px"
        android:clipToPadding="false"
        android:scrollbarStyle="outsideOverlay"  

完整的布局如下:

<android.support.v7.widget.RecyclerView
        android:id="@+id/library_list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="160px"
        android:layout_marginEnd="160px"
        tools:listitem="@layout/library_list_item" />  

有关之前和之后的效果,请参见androidblog.us上的链接: Adding Space to End of Android Recylerview
让我知道它如何为您工作。

大卫

答案 3 :(得分:2)

您可以做的是在您的recyclerview中添加一个空页脚。你的填充将是你的页脚大小。

SELECT 
    table.[field1], 
    table.[Name], 
    Count(table.[Name]) AS [CountOfName]
FROM 
    table
GROUP BY 
    table.[field1], 
    table.[Name]
HAVING 
    (((table.[Name]) Like "*") AND ((Count(table.[Name]))>1));

答案 4 :(得分:1)

仅在最后一项的情况下,才应在“ Recycler View”中使用“装饰”作为底边距

recyclerView.addItemDecoration(MemberItemDecoration())

public class MemberItemDecoration extends RecyclerView.ItemDecoration {

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        // only for the last one
        if (parent.getChildAdapterPosition(view) == parent.getAdapter().getItemCount() - 1) {
            outRect.bottom = 50/* set your margin here */;
        }
    }
}

答案 5 :(得分:0)

对于这样的事情,建议使用ItemDecoration解决它们的意思。

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?libraries=geometry,places&sensor=true"></script>

<script>
function initialize() {
   alert("ok");
    var mapOptions = {
        zoom: 14,
        center: new google.maps.LatLng(24.4799425,73.0934957),
        mapTypeId: google.maps.MapTypeId.ROADMAP
    };

    var map = new google.maps.Map(document.getElementById('map-canvas'),  mapOptions);
    var arr = new Array();
    var polygons = [];
    var bounds = new google.maps.LatLngBounds();
    var coordinates = [];
    var marker = new google.maps.Marker;
    var getBounds;

    // downloadUrl("subdivision-coordinates.php", function(data) {
        //var xmlString = $('#xml_values').val();
        var xmlString ="<subdivisions><subdivision name='Madivala'><coord lat='12.920496072962534' lng='77.6198673248291' id='6' /><coord lat='12.92409332232521' lng='77.62896537780762' id='6' /><coord lat='12.927439554274295' lng='77.6209831237793' id='6' /><coord lat='12.923340413955895' lng='77.61634826660156' id='6' /><coord lat='12.92066338803538' lng='77.61960983276367' id='6' /></subdivision><subdivision name='Sarjapur'><coord lat='12.91706608927013' lng='77.62372970581055' id='7' /><coord lat='12.9158948645401' lng='77.6389217376709' id='7' /><coord lat='12.919659495917085' lng='77.64299869537354' id='7' /><coord lat='12.92409332232521' lng='77.64793395996094' id='7' /><coord lat='12.92480440036714' lng='77.63424396514893' id='7' /><coord lat='12.920998017844783' lng='77.62089729309082' id='7' /></subdivision><subdivision name='Domulur'><coord lat='12.967758119178917' lng='77.63282775878906' id='8' /><coord lat='12.962237705403682' lng='77.65179634094238' id='8' /><coord lat='12.950025446093244' lng='77.64226913452148' id='8' /><coord lat='12.963826927981351' lng='77.6308536529541' id='8' /><coord lat='12.967507193936736' lng='77.63299942016602' id='8' /></subdivision><subdivision name='Indira Nagar'><coord lat='12.982227713276773' lng='77.63505935668945' id='9' /><coord lat='12.985154985380868' lng='77.64518737792969' id='9' /><coord lat='12.965834352522322' lng='77.64904975891113' id='9' /><coord lat='12.965834352522322' lng='77.63694763183594' id='9' /><coord lat='12.982060439543622' lng='77.63497352600098' id='9' /></subdivision><subdivision name='Mariyapan Palya'><coord lat='12.97846402705198' lng='77.54322052001953' id='10' /><coord lat='12.9792167688559' lng='77.56536483764648' id='10' /><coord lat='12.95671716919877' lng='77.56914138793945' id='10' /><coord lat='12.956298941771568' lng='77.54373550415039' id='10' /></subdivision></subdivisions>";
        var xml = xmlParse(xmlString);
        var subdivision = xml.getElementsByTagName("subdivision");
        for (var i = 0; i < subdivision.length; i++) {
            arr = [];
            var coordinates = xml.documentElement.getElementsByTagName("subdivision")[i].getElementsByTagName("coord");
            //console.log("coordinates="+xml.documentElement.getElementsByTagName("subdivision")[i].getElementsByTagName("coord"));


            for (var j=0; j < coordinates.length; j++) {
              arr.push(new google.maps.LatLng(
                    parseFloat(coordinates[j].getAttribute("lat")),
                    parseFloat(coordinates[j].getAttribute("lng"))
              ));

              var idd=coordinates[j].getAttribute("id");  
              bounds.extend(arr[arr.length-1]);

            }

            polygons.push(new google.maps.Polygon({
                id: idd,
                paths: arr,
                strokeColor: '#BA55D3',
                strokeOpacity: 0.8,
                strokeWeight: 2,
                fillColor: '#DA70D6',
                fillOpacity: 0.35
            }));
            polygons[polygons.length-1].setMap(map);

            polygons[polygons.length-1].addListener('click', function (event) {
                //alert(this.id);
                  var service_name=$('input[name="map_service"]:checked').val();
                  $('#add_to_qh').val(this.id);
                  $('#loc_edit_page').attr('href',"locality_service_edit.php?geoId="+this.id+"&service="+service_name);
                  $("#myModal").modal();
            });
            polygons[polygons.length-1].addListener('rightclick', function (event) {

                console.log("Need to delete the clicked polygon");
            });


        }

  map.fitBounds(bounds);

          google.maps.event.addListener(map, 'idle', function() {
            zoomLevel = map.getZoom();
              center=map.getCenter();

              var bounds1 = map.getBounds();

              var min_lat = bounds1.getSouthWest().lat();
              var min_lng = bounds1.getSouthWest().lng();

              var max_lat = bounds1.getNorthEast().lat();
              var max_lng = bounds1.getNorthEast().lng();

              var nw = new google.maps.LatLng(min_lat, max_lng);
              var se = new google.maps.LatLng(max_lat, min_lng);

              var area22=google.maps.geometry.spherical.computeArea([bounds1.getNorthEast(),se, bounds1.getSouthWest(), nw]);
              //Area chage meter2 to meter 
              area22 = area22/1000000;


              var min_area = area22-((area22*20)/100);
              var max_area = area22+((area22*20)/100);

              $('#area').val(area22);

              $('#min_area').val(min_area);
              $('#max_area').val(max_area);

              $('#min_lat').val(min_lat);
              $('#max_lat').val(max_lat);
              $('#min_lng').val(min_lng);
              $('#max_lng').val(max_lng);

              //console.log("Zoommmmmm   my area="+area+" Min Area="+min_area+ " Max Area="+max_area);
           });


}

/**
 * Parses the given XML string and returns the parsed document in a
 * DOM data structure. This function will return an empty DOM node if
 * XML parsing is not supported in this browser.
 * @param {string} str XML string.
 * @return {Element|Document} DOM.
 */
function xmlParse(str) {
  if (typeof ActiveXObject != 'undefined' && typeof GetObject != 'undefined') {
    var doc = new ActiveXObject('Microsoft.XMLDOM');
    doc.loadXML(str);
    return doc;
  }

  if (typeof DOMParser != 'undefined') {
    return (new DOMParser()).parseFromString(str, 'text/xml');
  }

  return createElement('div', null);
}

$(function(){
initialize();
});

</script>

     <div id="map-canvas" style="height:620px;" ></div>

how to delete the particular polygon in google maps add event listener right click

我复制了原始答案here中的一个编辑,这实际上是等间距但是它是相同的概念。

答案 6 :(得分:0)

您可以从源代码中以DividerItemDecoration.java为例,并替换

for (int i = 0; i < childCount; i++)

使用

for (int i = 0; i < childCount - 1; i++)

在drawVertical()和drawHorizo​​ntal()

答案 7 :(得分:0)

看看可能的解决方案,我想写一些笔记和一个合适的解决方案:

  1. 如果您对 RecyclerView 使用填充,它应该在大多数情况下都可以工作,但是一旦您开始使用自定义的项目装饰(快速滚动条)或处理透明导航栏,您将看到各种问题。

  2. 您总是可以为 RecyclerView 适配器创建一个 ViewType,它将成为它的页脚,占据整个跨度。这很完美,但只需要比其他解决方案多做一点工作。

  3. 此处提供的 ItemDecoration 解决方案在大多数情况下都有效,但不适用于 GridLayoutManager,因为它有时会为最后一行上方的行添加间距(此处要求添加更好的解决方案)。

我在 android-x 上发现了一些似乎可以解决它的代码,不过,以某种方式与汽车有关:

BottomOffsetDecoration

/**
 * A {@link RecyclerView.ItemDecoration} that will add a bottom offset to the last item in the
 * RecyclerView it is added to.
 *
 * @hide
 */
@RestrictTo(LIBRARY_GROUP_PREFIX)
public class BottomOffsetDecoration extends RecyclerView.ItemDecoration {
    private int mBottomOffset;

    public BottomOffsetDecoration(int bottomOffset) {
        mBottomOffset = bottomOffset;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
            RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);

        RecyclerView.Adapter adapter = parent.getAdapter();

        if (adapter == null || adapter.getItemCount() == 0) {
            return;
        }

        if (parent.getLayoutManager() instanceof GridLayoutManager) {
            if (GridLayoutManagerUtils.isOnLastRow(view, parent)) {
                outRect.bottom = mBottomOffset;
            }
        } else if (parent.getChildAdapterPosition(view) == adapter.getItemCount() - 1) {
            // Only set the offset for the last item.
            outRect.bottom = mBottomOffset;
        } else {
            outRect.bottom = 0;
        }
    }

    /** Sets the value to use for the bottom offset. */
    public void setBottomOffset(int bottomOffset) {
        mBottomOffset = bottomOffset;
    }

    /** Returns the set bottom offset. If none has been set, then 0 will be returned. */
    public int getBottomOffset() {
        return mBottomOffset;
    }
}

GridLayoutManagerUtils

/**
 * Utility class that helps navigating in GridLayoutManager.
 *
 * <p>Assumes parameter {@code RecyclerView} uses {@link GridLayoutManager}.
 *
 * <p>Assumes the orientation of {@code GridLayoutManager} is vertical.
 *
 * @hide
 */
@RestrictTo(LIBRARY_GROUP_PREFIX)
public class GridLayoutManagerUtils {
    private GridLayoutManagerUtils() {}

    /**
     * Returns the number of items in the first row of a RecyclerView that has a
     * {@link GridLayoutManager} as its {@code LayoutManager}.
     *
     * @param recyclerView RecyclerView that uses GridLayoutManager as LayoutManager.
     * @return number of items in the first row in {@code RecyclerView}.
     */
    public static int getFirstRowItemCount(RecyclerView recyclerView) {
        GridLayoutManager manager = (GridLayoutManager) recyclerView.getLayoutManager();
        int itemCount = recyclerView.getAdapter().getItemCount();
        int spanCount = manager.getSpanCount();

        int spanSum = 0;
        int numOfItems = 0;
        while (numOfItems < itemCount && spanSum < spanCount) {
            spanSum += manager.getSpanSizeLookup().getSpanSize(numOfItems);
            numOfItems++;
        }
        return numOfItems;
    }

    /**
     * Returns the span index of an item.
     */
    public static int getSpanIndex(View item) {
        GridLayoutManager.LayoutParams layoutParams =
                ((GridLayoutManager.LayoutParams) item.getLayoutParams());
        return layoutParams.getSpanIndex();
    }

    /**
     * Returns whether or not the given view is on the last row of a {@code RecyclerView} with a
     * {@link GridLayoutManager}.
     *
     * @param view The view to inspect.
     * @param parent {@link RecyclerView} that contains the given view.
     * @return {@code true} if the given view is on the last row of the {@code RecyclerView}.
     */
    public static boolean isOnLastRow(View view, RecyclerView parent) {
        return getLastItemPositionOnSameRow(view, parent) == parent.getAdapter().getItemCount() - 1;
    }

    /**
     * Returns the position of the last item that is on the same row as input {@code view}.
     *
     * @param view The view to inspect.
     * @param parent {@link RecyclerView} that contains the given view.
     */
    public static int getLastItemPositionOnSameRow(View view, RecyclerView parent) {
        GridLayoutManager layoutManager = ((GridLayoutManager) parent.getLayoutManager());

        GridLayoutManager.SpanSizeLookup spanSizeLookup = layoutManager.getSpanSizeLookup();
        int spanCount = layoutManager.getSpanCount();
        int lastItemPosition = parent.getAdapter().getItemCount() - 1;

        int currentChildPosition = parent.getChildAdapterPosition(view);
        int spanSum = getSpanIndex(view) + spanSizeLookup.getSpanSize(currentChildPosition);
        // Iterate to the end of the row starting from the current child position.
        while (currentChildPosition <= lastItemPosition && spanSum <= spanCount) {
            spanSum += spanSizeLookup.getSpanSize(currentChildPosition + 1);
            if (spanSum > spanCount) {
                return currentChildPosition;
            }
            currentChildPosition++;
        }
        return lastItemPosition;
    }
}

因此,我制作了一个 Kotlin 解决方案来解决所有问题(此处提供示例和库):

//https://androidx.de/androidx/car/widget/itemdecorators/BottomOffsetDecoration.html
class BottomOffsetDecoration(private val mBottomOffset: Int, private val layoutManagerType: LayoutManagerType) : ItemDecoration() {
    enum class LayoutManagerType {
        GRID_LAYOUT_MANAGER, LINEAR_LAYOUT_MANAGER
    }

    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
        super.getItemOffsets(outRect, view, parent, state)
        when (layoutManagerType) {
            LayoutManagerType.LINEAR_LAYOUT_MANAGER -> {
                val position = parent.getChildAdapterPosition(view)
                outRect.bottom =
                        if (state.itemCount <= 0 || position != state.itemCount - 1)
                            0
                        else
                            mBottomOffset
            }
            LayoutManagerType.GRID_LAYOUT_MANAGER -> {
                val adapter = parent.adapter
                outRect.bottom =
                        if (adapter == null || adapter.itemCount == 0 || GridLayoutManagerUtils.isOnLastRow(view, parent))
                            0
                        else
                            mBottomOffset
            }
        }
    }
}
//https://androidx.de/androidx/car/util/GridLayoutManagerUtils.html
/**
 * Utility class that helps navigating in GridLayoutManager.
 *
 *
 * Assumes parameter `RecyclerView` uses [GridLayoutManager].
 *
 *
 * Assumes the orientation of `GridLayoutManager` is vertical.
 */
object GridLayoutManagerUtils {
    /**
     * Returns whether or not the given view is on the last row of a `RecyclerView` with a
     * [GridLayoutManager].
     *
     * @param view   The view to inspect.
     * @param parent [RecyclerView] that contains the given view.
     * @return `true` if the given view is on the last row of the `RecyclerView`.
     */
    fun isOnLastRow(view: View, parent: RecyclerView): Boolean {
        return getLastItemPositionOnSameRow(view, parent) == parent.adapter!!.itemCount - 1
    }

    /**
     * Returns the position of the last item that is on the same row as input `view`.
     *
     * @param view   The view to inspect.
     * @param parent [RecyclerView] that contains the given view.
     */
    private fun getLastItemPositionOnSameRow(view: View, parent: RecyclerView): Int {
        val layoutManager = parent.layoutManager as GridLayoutManager
        val spanSizeLookup = layoutManager.spanSizeLookup
        val spanCount = layoutManager.spanCount
        val lastItemPosition = parent.adapter!!.itemCount - 1
        var currentChildPosition = parent.getChildAdapterPosition(view)
        val itemSpanIndex = (view.layoutParams as GridLayoutManager.LayoutParams).spanIndex
        var spanSum = itemSpanIndex + spanSizeLookup.getSpanSize(currentChildPosition)
        // Iterate to the end of the row starting from the current child position.
        while (currentChildPosition <= lastItemPosition && spanSum <= spanCount) {
            spanSum += spanSizeLookup.getSpanSize(currentChildPosition + 1)
            if (spanSum > spanCount)
                return currentChildPosition
            ++currentChildPosition
        }
        return lastItemPosition
    }
}