Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save JeffZane/2a1afe4374f802ddab4546d76aa8ee29 to your computer and use it in GitHub Desktop.

Select an option

Save JeffZane/2a1afe4374f802ddab4546d76aa8ee29 to your computer and use it in GitHub Desktop.
A ItemDecoration to center the contents of a RecyclerView. This comes in handy when you want to build a responsive UI.
/*
* Copyright 2015 Johannes Homeier
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.twoid.ui.decoration;
import android.support.v7.widget.GridLayoutManager;
/**
* A {@link de.twoid.ui.decoration.InsetItemDecoration.SpanSizeLookupHelper} backed by a {@link android.support.v7.widget.GridLayoutManager}
*/
public class GridSpanSizeLookupHelper extends InsetItemDecoration.SpanSizeLookupHelper<GridLayoutManager> {
private GridLayoutManager.SpanSizeLookup spanSizeLookup;
public GridSpanSizeLookupHelper(GridLayoutManager layoutManager) {
super(layoutManager);
spanSizeLookup = layoutManager.getSpanSizeLookup();
spanSizeLookup.setSpanIndexCacheEnabled(true);
}
@Override
public int getSpanCount() {
return layoutManager.getSpanCount();
}
@Override
public int getSpanIndex(int position){
return spanSizeLookup.getSpanIndex(position, getSpanCount());
}
@Override
public int getSpanSize(int position) {
return spanSizeLookup.getSpanSize(position);
}
@Override
public int getOrientation() {
return layoutManager.getOrientation();
}
}
/*
* Copyright 2015 Johannes Homeier
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.twoid.ui.decoration;
import android.graphics.Rect;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.OrientationHelper;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.View;
import android.widget.ImageView;
/**
* An ItemDecoration to center the content of a RecyclerView.
* This class comes in handy when you want to build a responsive UI while still receiving touch- and scroll-events on the "borders" of the {@link android.support.v7.widget.RecyclerView}
*/
public class InsetItemDecoration extends RecyclerView.ItemDecoration {
protected SpanSizeLookupHelper spanSizeLookupHelper;
private float maxContentSize;
private float parentSize;
private boolean setup = false;
/**
* Create a new InsetItemDecoration without specifying a LayoutManager;
* The needed {@link de.twoid.ui.decoration.InsetItemDecoration.SpanSizeLookupHelper} will be generated during the first call of {@link #getItemOffsets(android.graphics.Rect, android.view.View, android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State)}
* @param maxContentSize the desired max-dimension of the content in pixel
*/
public InsetItemDecoration(float maxContentSize) {
this.maxContentSize = maxContentSize;
}
/**
* Create a new InsetItemDecoration by creating a new {@link de.twoid.ui.decoration.InsetItemDecoration.SpanSizeLookupHelper} with the passed {@link android.support.v7.widget.GridLayoutManager}
* @param layoutManager the associated GridLayoutManager
* @param maxContentSize the desired max-dimension of the content in pixel
*/
public InsetItemDecoration(GridLayoutManager layoutManager, float maxContentSize) {
this(new GridSpanSizeLookupHelper(layoutManager), maxContentSize);
}
/**
* Create a new InsetItemDecoration by creating a new {@link de.twoid.ui.decoration.InsetItemDecoration.SpanSizeLookupHelper} with the passed {@link android.support.v7.widget.LinearLayoutManager}
* @param layoutManager the associated LinearLayoutManager
* @param maxContentSize the desired max-dimension of the content in pixel
*/
public InsetItemDecoration(LinearLayoutManager layoutManager, float maxContentSize) {
this(new LinearSpanSizeLookupHelper(layoutManager), maxContentSize);
}
/**
* Create a new InsetItemDecoration by creating a new {@link de.twoid.ui.decoration.InsetItemDecoration.SpanSizeLookupHelper} with the passed {@link android.support.v7.widget.StaggeredGridLayoutManager}
* @param layoutManager the associated StaggeredGridLayoutManager
* @param maxContentSize the desired max-dimension of the content in pixel
*/
public InsetItemDecoration(StaggeredGridLayoutManager layoutManager, float maxContentSize) {
this(new StaggeredGridSpanSizeLookupHelper(layoutManager), maxContentSize);
}
/**
* Create a new InsetItemDecoration with the passed {@link de.twoid.ui.decoration.InsetItemDecoration.SpanSizeLookupHelper}
* @param spanSizeLookupHelper the SpanSizeLookupHelper to work with
* @param maxContentSize the desired max-dimension of the content in pixel
*/
public InsetItemDecoration(SpanSizeLookupHelper spanSizeLookupHelper, float maxContentSize) {
this.spanSizeLookupHelper = spanSizeLookupHelper;
this.maxContentSize = maxContentSize;
setup = true;
}
/**
* {@inheritDoc}
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if(!setup){
setup = generateSpanSizeLookupHelper(parent.getLayoutManager());
if(!setup){
return;
}
}
int position = spanSizeLookupHelper.getPosition(view);
int spanSize = spanSizeLookupHelper.getSpanSize(position);
int spanIndex = spanSizeLookupHelper.getSpanIndex(position);
if(spanSizeLookupHelper.getOrientation() == OrientationHelper.VERTICAL){
parentSize = parent.getWidth();
}else{
parentSize = parent.getHeight();
}
if(parentSize <= maxContentSize){
super.getItemOffsets(outRect, view, parent, state);
return;
}
inset(outRect, spanIndex, spanSize);
}
/**
* Generates a new {@link de.twoid.ui.decoration.InsetItemDecoration.SpanSizeLookupHelper} if the passed {@link android.support.v7.widget.RecyclerView.LayoutManager}
* is either a {@link android.support.v7.widget.LinearLayoutManager}, a {@link android.support.v7.widget.GridLayoutManager}, or a {@link android.support.v7.widget.StaggeredGridLayoutManager}
* @param layoutManager
* @return allways true;
*/
protected boolean generateSpanSizeLookupHelper(RecyclerView.LayoutManager layoutManager){
if(layoutManager instanceof LinearLayoutManager){
spanSizeLookupHelper = new LinearSpanSizeLookupHelper((LinearLayoutManager) layoutManager);
}else if(layoutManager instanceof GridLayoutManager){
spanSizeLookupHelper = new GridSpanSizeLookupHelper((GridLayoutManager) layoutManager);
}else if(layoutManager instanceof StaggeredGridLayoutManager){
spanSizeLookupHelper = new StaggeredGridSpanSizeLookupHelper((StaggeredGridLayoutManager) layoutManager);
}else{
throw new UnsupportedLayoutManagerException();
}
return true;
}
/**
* Applies a inset to the passed Rect depending on the spanIndex and spanSize;
* @param outRect the {@link android.graphics.Rect} to modify
* @param spanIndex the span-index of a view
* @param spanSize the span-size of a view
*/
protected void inset(Rect outRect, int spanIndex, int spanSize) {
final float totalSizeDifference = getTotalSizeDifference();
final int baseInset = (int) (totalSizeDifference / 2f);
if(spanSizeLookupHelper.getOrientation() == OrientationHelper.VERTICAL){
outRect.set(
(int) (baseInset - spanSizeLookupHelper.getSpanPositionDifference(spanIndex, totalSizeDifference))
, 0
, (int) -(baseInset - spanSizeLookupHelper.getSpanPositionDifference(spanIndex + spanSize, totalSizeDifference))
, 0);
}else{
outRect.set(
0
, (int) (baseInset - spanSizeLookupHelper.getSpanPositionDifference(spanIndex, totalSizeDifference))
, 0
, (int) (baseInset - spanSizeLookupHelper.getSpanPositionDifference(spanIndex + spanSize, totalSizeDifference)));
}
}
/**
* Returns the sizeDifference between the size of the parent {@link android.support.v7.widget.RecyclerView} and the passed maxContentSize
* @return 0 if maxContentSize is bigger than the size of the RecyclerView, else the difference of both sizes
*/
protected float getTotalSizeDifference(){
return Math.max(0, parentSize - maxContentSize);
}
/**
* @return the max size of the RecyclerViews content in pixels
*/
protected float getMaxContentSize() {
return maxContentSize;
}
/**
* @return the size of the RecyclerView
*/
protected float getParentSize() {
return parentSize;
}
/**
* Indicates whether the {@link de.twoid.ui.decoration.InsetItemDecoration.SpanSizeLookupHelper} has been created or not
* @return true if the SpanSizeLookupHelper got created
*/
protected boolean isSetup(){
return setup;
}
/**
* Invalidate the current {@link android.support.v7.widget.RecyclerView.LayoutManager} (and with that the {@link de.twoid.ui.decoration.InsetItemDecoration.SpanSizeLookupHelper})
* and look far a new one in the next call of {@link #getItemOffsets(android.graphics.Rect, android.view.View, android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State)}
* This has to be called when you set a new {@link android.support.v7.widget.RecyclerView.LayoutManager} to the {@link android.support.v7.widget.RecyclerView}
*/
public void invalidateLayoutManager(){
setup = false;
}
public void swapSpanSizeLookupHeler(SpanSizeLookupHelper spanSizeLookupHelper){
if(spanSizeLookupHelper == null){
setup = false;
}
this.spanSizeLookupHelper = spanSizeLookupHelper;
}
/**
* A helper-class to provide span-information to the {@link de.twoid.ui.decoration.InsetItemDecoration}
* @param <E>
*/
public static abstract class SpanSizeLookupHelper<E extends RecyclerView.LayoutManager>{
protected E layoutManager;
protected int spanCount;
protected SpanSizeLookupHelper(E layoutManager, int spanCount) {
this.layoutManager = layoutManager;
this.spanCount = spanCount;
}
/**
* @param view
* @return the position of the view in the {@link android.support.v7.widget.RecyclerView.LayoutManager}
*/
public int getPosition(View view){
return layoutManager.getPosition(view);
}
/**
* @return the span-count of the {@link android.support.v7.widget.RecyclerView.LayoutManager}
*/
public abstract int getSpanCount();
/**
* @param position
* @return the span-index for a view at the passed position
*/
public abstract int getSpanIndex(int position);
/**
* @param position
* @return the span-size for a view at the passed position
*/
public abstract int getSpanSize(int position);
/**
* Generates a offset for a span-index;
* This offset is used to inset and shrink a view according to its span-index and span-size
* @param spanIndex
* @param sizeDifference
* @return the offset for the passed spanIndex in pixels
*/
public float getSpanPositionDifference(int spanIndex, float sizeDifference){
if(sizeDifference <= 0){
return 0;
}else{
return spanIndex * sizeDifference / ((float) getSpanCount());
}
}
/**
* @return the orientation of the {@link android.support.v7.widget.RecyclerView.LayoutManager}
*/
public abstract int getOrientation();
}
public static class UnsupportedLayoutManagerException extends ClassCastException{
private static final String ADDITIONAL_MESSAGE =
InsetItemDecoration.class.getName()+" only supports "
+ LinearLayoutManager.class.getName()
+ ", " + GridLayoutManager.class.getName()
+ " and " + StaggeredGridLayoutManager.class.getName()
+ ". If you want to use a custom LayoutManager, you should create a new SpanSizeLookupHelper and pass it in the constructor";
public UnsupportedLayoutManagerException() {
super(ADDITIONAL_MESSAGE);
}
}
}
/*
* Copyright 2015 Johannes Homeier
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.twoid.ui.decoration;
import android.support.v7.widget.LinearLayoutManager;
/**
* A {@link de.twoid.ui.decoration.InsetItemDecoration.SpanSizeLookupHelper} backed by a {@link android.support.v7.widget.LinearLayoutManager}
*/
public class LinearSpanSizeLookupHelper extends InsetItemDecoration.SpanSizeLookupHelper<LinearLayoutManager> {
public LinearSpanSizeLookupHelper(LinearLayoutManager layoutManager) {
super(layoutManager);
}
@Override
public int getSpanCount() {
return 1;
}
/**
* As the {@link android.support.v7.widget.LinearLayoutManager} only has one column, this method always returns 0;
*
* {@inheritDoc}
*/
@Override
public int getSpanIndex(int position) {
return 0;
}
/**
* As the {@link android.support.v7.widget.LinearLayoutManager} only has one column, this method always returns 1;
*
* {@inheritDoc}
*/
@Override
public int getSpanSize(int position) {
return 1;
}
@Override
public int getOrientation() {
return layoutManager.getOrientation();
}
}
/*
* Copyright 2015 Johannes Homeier
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.twoid.ui.decoration;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.View;
/**
* A {@link de.twoid.ui.decoration.InsetItemDecoration.SpanSizeLookupHelper} backed by a {@link android.support.v7.widget.StaggeredGridLayoutManager}
*/
public class StaggeredGridSpanSizeLookupHelper extends InsetItemDecoration.SpanSizeLookupHelper<StaggeredGridLayoutManager> {
protected StaggeredGridSpanSizeLookupHelper(StaggeredGridLayoutManager layoutManager) {
super(layoutManager);
}
@Override
public int getSpanCount() {
return layoutManager.getSpanCount();
}
@Override
public int getSpanIndex(int position) {
StaggeredGridLayoutManager.LayoutParams params = getLayoutParams(position);
return params.getSpanIndex();
}
@Override
public int getSpanSize(int position) {
StaggeredGridLayoutManager.LayoutParams params = getLayoutParams(position);
return params.isFullSpan() ? getSpanCount() : 1;
}
@Override
public int getOrientation() {
return layoutManager.getOrientation();
}
private StaggeredGridLayoutManager.LayoutParams getLayoutParams(int position){
View view = layoutManager.getChildAt(position);
return (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment