Last active
August 29, 2015 14:06
-
-
Save aenain/8a3efdf7f96fff0d6807 to your computer and use it in GitHub Desktop.
Fix etsy's staggered grid view when one column is higher than the other by more than grid view height. https://github.com/etsy/AndroidStaggeredGrid/issues/66
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<LinearLayout | |
xmlns:android="http://schemas.android.com/apk/res/android" | |
android:orientation="vertical" | |
android:layout_height="wrap_content" | |
android:layout_width="match_parent"> | |
<LinearLayout | |
android:id="@+id/content" | |
style="@style/GridItem"> | |
<com.etsy.android.grid.util.DynamicHeightImageView | |
android:id="@+id/image" | |
style="@style/Image.FullWidth" /> | |
<TextView | |
android:id="@+id/title" | |
style="@style/GridItem.PrimaryHeader" /> | |
<TextView | |
android:id="@+id/description" | |
style="@style/GridItem.Text" /> | |
</LinearLayout> | |
</LinearLayout> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package no.bstcm.loyaltyapp.core.util; | |
import android.view.View; | |
import android.widget.AbsListView; | |
import com.etsy.android.grid.StaggeredGridView; | |
import no.bstcm.loyaltyapp.core.R; | |
/** | |
* Grid library has a very interesting bug related to how views are recycled. | |
* If last item is higher than grid view, then other column disappears | |
* or is weirdly moved. | |
* | |
* To fix that let's display a placeholder that ensures that columns have the same height | |
* in case the difference between them is greater than grid view height. | |
* | |
* Issues: | |
* @see https://github.com/etsy/AndroidStaggeredGrid/issues/21 | |
* @see https://github.com/etsy/AndroidStaggeredGrid/issues/66 | |
* | |
* Example: | |
* How to alter adapter to use it? | |
* | |
* public class GridAdapter extends ArrayAdapter<...> | |
* implements GridHeightPlaceholder.AdapterCallbacks { | |
* | |
* private StaggeredGridView lookupView; | |
* private final GridHeightPlaceholder placeholder; | |
* | |
* public GridAdapter(Context context, List<...> list) { | |
* super(context, R.layout.fragment_grid_item, list); | |
* placeholder = new GridHeightPlaceholder(this); | |
* } | |
* | |
* public void setLookupView(StaggeredGridView lookupView) { | |
* this.lookupView = lookupView; | |
* if (lookupView != null) { | |
* placeholder.listenToScroll(); | |
* } else { | |
* placeholder.stopListeningToScroll(); | |
* placeholder.onDetach(); | |
* } | |
* } | |
* | |
* @Override | |
* public StaggeredGridView getLookupView() { | |
* return lookupView; | |
* } | |
* | |
* @Override | |
* public int getCount() { | |
* int count = super.getCount(); | |
* if (placeholder.isDisplayed()) { | |
* count++; | |
* } | |
* return count; | |
* } | |
* | |
* @Override | |
* public View getView(int position, View convertView, ViewGroup parent) { | |
* if (convertView == null) { | |
* LayoutInflater inflater = (LayoutInflater) getContext() | |
* .getSystemService(android.app.Activity.LAYOUT_INFLATER_SERVICE); | |
* convertView = inflater.inflate(R.layout.fragment_grid_item, null); | |
* } | |
* | |
* if (position >= super.getCount()) { | |
* placeholder.bindView(convertView); | |
* } else { | |
* // regular view binding for model getItem(position) | |
* } | |
* | |
* return convertView; | |
* } | |
* | |
* // ... | |
* | |
* } | |
* | |
* Later in activity or fragment: | |
* StaggeredGridView sectionView = (StaggeredGridView) findViewById(R.id.grid); | |
* GridAdapter adapter = new GridAdapter(this, getList()); | |
* adapter.setLookupView(sectionView); | |
* sectionView.setAdapter(adapter); | |
* | |
* NOTE! | |
* | |
* It is also a good idea to cleanup when i.e. activity is destroyed. | |
* All you need to do is to call GridAdapter.setLookupView(null). | |
*/ | |
public class GridHeightPlaceholder implements AbsListView.OnScrollListener { | |
private AdapterCallbacks adapter; | |
private int height = 0, screenWidth = 0; | |
private StaggeredGridViewProxy gridView; | |
private WeakReference<View> viewRef; | |
public GridHeightPlaceholder(AdapterCallbacks adapter) { | |
this.adapter = adapter; | |
} | |
public void listenToScroll() { | |
getGridView().setOnScrollListener(this); | |
} | |
public void stopListeningToScroll() { | |
if (gridView != null) { | |
gridView.setOnScrollListener(null); | |
} | |
} | |
public void onDetach() { | |
if (gridView != null) { | |
stopListeningToScroll(); | |
gridView.onDetach(); | |
gridView = null; | |
} | |
if (viewRef != null) { | |
viewRef.clear(); | |
} | |
} | |
public boolean isDisplayed() { | |
return height > 0; | |
} | |
public void bindView(View view) { | |
View content = view.findViewById(R.id.content); | |
viewRef = new WeakReference<>(content); | |
content.getLayoutParams().height = Math.round(height / 2); | |
content.requestLayout(); | |
content.setAlpha(0); | |
} | |
@Override | |
public void onScrollStateChanged(AbsListView view, int scrollState) {} | |
@Override | |
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { | |
if (firstVisibleItem + visibleItemCount >= totalItemCount) { | |
StaggeredGridViewProxy gridView = getGridView(); | |
int[] columnHeights = new int[]{gridView.getColumnBottom(0), gridView.getColumnBottom(1)}; | |
int columnHeightDiff, newHeight; | |
View itemView = getPlaceholderView(); | |
if (itemView != null) { | |
int column = getColumn(itemView); | |
columnHeights[column] -= itemView.getHeight(); | |
} | |
columnHeightDiff = Math.abs(columnHeights[0] - columnHeights[1]); | |
if (height > columnHeightDiff || columnHeightDiff > gridView.getMeasuredHeight()) { | |
newHeight = columnHeightDiff - getGridView().getItemMargin(); | |
if (newHeight != height) { | |
height = newHeight; | |
adapter.notifyDataSetChanged(); | |
} | |
} | |
} | |
} | |
public int getColumn(View view) { | |
int[] coords = new int[2]; | |
view.getLocationInWindow(coords); | |
if (coords[0] >= Math.floor(getScreenWidth(view.getContext()) / 2)) { | |
return 1; | |
} else { | |
return 0; | |
} | |
} | |
private int getScreenWidth(Context context) { | |
if (screenWidth <= 0) { | |
DisplayMetrics metrics = new DisplayMetrics(); | |
WindowManager window = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); | |
window.getDefaultDisplay().getMetrics(metrics); | |
screenWidth = metrics.widthPixels; | |
} | |
return screenWidth; | |
} | |
private View getPlaceholderView() { | |
if (viewRef != null) { | |
return viewRef.get(); | |
} else { | |
return null; | |
} | |
} | |
private StaggeredGridViewProxy getGridView() { | |
if (gridView == null) { | |
gridView = new StaggeredGridViewProxy(adapter.getLookupView()); | |
} | |
return gridView; | |
} | |
public static interface AdapterCallbacks { | |
public void notifyDataSetChanged(); | |
public StaggeredGridView getLookupView(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package no.bstcm.loyaltyapp.core.util; | |
import android.view.View; | |
import android.widget.AbsListView; | |
import com.etsy.android.grid.StaggeredGridView; | |
import java.lang.ref.WeakReference; | |
import java.lang.reflect.Array; | |
import java.lang.reflect.Field; | |
import no.bstcm.loyaltyapp.core.R; | |
public class StaggeredGridViewProxy { | |
private final WeakReference<StaggeredGridView> targetView; | |
private Field columnBottomsField = null; | |
public StaggeredGridViewProxy(StaggeredGridView view) { | |
this.targetView = new WeakReference<StaggeredGridView>(view); | |
} | |
public void onDetach() { | |
StaggeredGridView view = targetView.get(); | |
if (view != null) { | |
view.setOnScrollListener(null); | |
targetView.clear(); | |
} | |
} | |
public int getColumnBottom(int column) { | |
if (columnBottomsField == null) { | |
try { | |
columnBottomsField = StaggeredGridView.class.getDeclaredField("mColumnBottoms"); | |
columnBottomsField.setAccessible(true); | |
} catch (NoSuchFieldException e) { | |
e.printStackTrace(); | |
return 0; | |
} | |
} | |
try { | |
if (columnBottomsField.getType().isArray()) { | |
Object array = columnBottomsField.get(targetView.get()); | |
return (int) Array.get(array, column); | |
} | |
} catch (IllegalAccessException e) { | |
e.printStackTrace(); | |
} | |
return 0; | |
} | |
public int getItemMargin() { | |
return (int) targetView.get().getResources().getDimension(R.dimen.grid_item_margin); | |
} | |
public View findViewWithTag(Object tag) { | |
return targetView.get().findViewWithTag(tag); | |
} | |
public void setOnScrollListener(AbsListView.OnScrollListener listener) { | |
targetView.get().setOnScrollListener(listener); | |
} | |
public int getMeasuredHeight() { | |
return targetView.get().getMeasuredHeight(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment