package com.emilsjolander.components.stickylistheaders; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import android.content.Context; import android.graphics.Canvas; import android.graphics.Rect; import android.os.Build; import android.view.View; import android.widget.AbsListView; import android.widget.ListView; class WrapperViewList extends ListView { interface LifeCycleListener { void onDispatchDrawOccurred(Canvas canvas); } private LifeCycleListener mLifeCycleListener; private List<View> mFooterViews; private int mTopClippingLength; private Rect mSelectorRect = new Rect();// for if reflection fails private Field mSelectorPositionField; private boolean mClippingToPadding = true; public WrapperViewList(Context context) { super(context); // Use reflection to be able to change the size/position of the list // selector so it does not come under/over the header try { Field selectorRectField = AbsListView.class.getDeclaredField("mSelectorRect"); selectorRectField.setAccessible(true); mSelectorRect = (Rect) selectorRectField.get(this); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { mSelectorPositionField = AbsListView.class.getDeclaredField("mSelectorPosition"); mSelectorPositionField.setAccessible(true); } } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } @Override public boolean performItemClick(View view, int position, long id) { if (view instanceof WrapperView) { view = ((WrapperView) view).mItem; } return super.performItemClick(view, position, id); } private void positionSelectorRect() { if (!mSelectorRect.isEmpty()) { int selectorPosition = getSelectorPosition(); if (selectorPosition >= 0) { int firstVisibleItem = getFixedFirstVisibleItem(); View v = getChildAt(selectorPosition - firstVisibleItem); if (v instanceof WrapperView) { WrapperView wrapper = ((WrapperView) v); mSelectorRect.top = wrapper.getTop() + wrapper.mItemTop; } } } } private int getSelectorPosition() { if (mSelectorPositionField == null) { // not all supported andorid // version have this variable for (int i = 0; i < getChildCount(); i++) { if (getChildAt(i).getBottom() == mSelectorRect.bottom) { return i + getFixedFirstVisibleItem(); } } } else { try { return mSelectorPositionField.getInt(this); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } return -1; } @Override protected void dispatchDraw(Canvas canvas) { positionSelectorRect(); if (mTopClippingLength != 0) { canvas.save(); Rect clipping = canvas.getClipBounds(); clipping.top = mTopClippingLength; canvas.clipRect(clipping); super.dispatchDraw(canvas); canvas.restore(); } else { super.dispatchDraw(canvas); } mLifeCycleListener.onDispatchDrawOccurred(canvas); } void setLifeCycleListener(LifeCycleListener lifeCycleListener) { mLifeCycleListener = lifeCycleListener; } @Override public void addFooterView(View v) { super.addFooterView(v); if (mFooterViews == null) { mFooterViews = new ArrayList<View>(); } mFooterViews.add(v); } @Override public boolean removeFooterView(View v) { if (super.removeFooterView(v)) { mFooterViews.remove(v); return true; } return false; } boolean containsFooterView(View v) { if (mFooterViews == null) { return false; } return mFooterViews.contains(v); } void setTopClippingLength(int topClipping) { mTopClippingLength = topClipping; } int getFixedFirstVisibleItem() { int firstVisibleItem = getFirstVisiblePosition(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { return firstVisibleItem; } // first getFirstVisiblePosition() reports items // outside the view sometimes on old versions of android for (int i = 0; i < getChildCount(); i++) { if (getChildAt(i).getBottom() >= 0) { firstVisibleItem += i; break; } } // work around to fix bug with firstVisibleItem being to high // because list view does not take clipToPadding=false into account // on old versions of android if (!mClippingToPadding && getPaddingTop() > 0 && firstVisibleItem > 0) { if (getChildAt(0).getTop() > 0) { firstVisibleItem -= 1; } } return firstVisibleItem; } @Override public void setClipToPadding(boolean clipToPadding) { mClippingToPadding = clipToPadding; super.setClipToPadding(clipToPadding); } }