Skip to content
This repository has been archived by the owner on Jun 9, 2020. It is now read-only.

Account for divider height when switching pinned headers #38

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion example/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
android:layout_height="wrap_content"
android:headerDividersEnabled="false"
android:footerDividersEnabled="false"
android:divider="@null"
android:divider="#FFF"
android:dividerHeight="10dp"
/>
15 changes: 15 additions & 0 deletions example/res/menu/main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,20 @@
android:showAsAction="never"
android:title="@string/action_showHeaderAndFooter"
android:checkable="true"/>

<item
android:id="@+id/action_scroll_13"
android:showAsAction="never"
android:title="Scroll to item 13 (B)"/>

<item
android:id="@+id/action_scroll_13_50"
android:showAsAction="never"
android:title="Colliding scroll to item 13 (B)"/>

<item
android:id="@+id/action_scroll_20"
android:showAsAction="never"
android:title="Scroll to item 20 (B-6)"/>

</menu>
8 changes: 8 additions & 0 deletions example/src/com/hb/examples/PinnedSectionListActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,14 @@ public boolean onOptionsItemSelected(MenuItem item) {
item.setChecked(hasHeaderAndFooter);
initializeHeaderAndFooter();
break;
case R.id.action_scroll_13:
getListView().setSelectionFromTop(13,0);
break;
case R.id.action_scroll_13_50:
getListView().setSelectionFromTop(13, 50);
break;
case R.id.action_scroll_20:
getListView().setSelection(20);
}
return true;
}
Expand Down
127 changes: 111 additions & 16 deletions library/src/com/hb/views/PinnedSectionListView.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCoun
if (sectionView.getTop() == getPaddingTop()) { // view sticks to the top, no need for pinned shadow
destroyPinnedShadow();
} else { // section doesn't stick to the top, make sure we have a pinned shadow
ensureShadowForPosition(firstVisibleItem, firstVisibleItem, visibleItemCount);
ensureShadowForFirstItem(firstVisibleItem, firstVisibleItem, visibleItemCount);
}

} else { // section is not at the first visible position
Expand All @@ -124,17 +124,23 @@ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCoun
destroyPinnedShadow();
}
}
};

}
};

private Runnable recreatePinnedShadow = new Runnable() {
@Override
public void run() {
recreatePinnedShadow();
}
};

/** Default change observer. */
private final DataSetObserver mDataSetObserver = new DataSetObserver() {
@Override public void onChanged() {
recreatePinnedShadow();
post(recreatePinnedShadow);
};
@Override public void onInvalidated() {
recreatePinnedShadow();
post(recreatePinnedShadow);
}
};

Expand Down Expand Up @@ -199,6 +205,7 @@ void createPinnedShadow(int position) {
LayoutParams layoutParams = (LayoutParams) pinnedView.getLayoutParams();
if (layoutParams == null) { // create default layout params
layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
pinnedView.setLayoutParams(layoutParams);
}

int heightMode = MeasureSpec.getMode(layoutParams.height);
Expand Down Expand Up @@ -234,15 +241,59 @@ void destroyPinnedShadow() {
}
}

/** Makes sure we have an actual pinned shadow for given position. */
void ensureShadowForPosition(int sectionPosition, int firstVisibleItem, int visibleItemCount) {
if (visibleItemCount < 2) { // no need for creating shadow at all, we have a single visible item
/**
* Makes sure we have a pinned header for the first position.
*/
void ensureShadowForFirstItem(int sectionPosition, int firstVisibleItem, int visibleItemCount) {
// if the first item is a section, only recreate if getTop() < 0

View sectionView = getChildAt(0);

// when scrolling downwards, invalidate header iff sectionView's top exceeds view boundaries
if (mPinnedSection != null && mPinnedSection.position != sectionPosition
&& sectionView.getTop() <= getPaddingTop()) {
destroyPinnedShadow();
}
// when scrolling upwards, invalidate header as soon as sectionView leaves the building
else if (mPinnedSection != null && mPinnedSection.position == sectionPosition
&& sectionView.getTop() > getPaddingTop()) {
destroyPinnedShadow();
return;
}

if (mPinnedSection != null
&& mPinnedSection.position != sectionPosition) { // invalidate shadow, if required
// create header based on the view of the current section position
if (mPinnedSection == null && sectionView.getTop() <= getPaddingTop()) {
createPinnedShadow(sectionPosition);
}
// create header based on the view of the previous section position
else if (mPinnedSection == null && sectionView.getTop() > getPaddingTop()) {
int prevSection = findPreviousVisibleSectionPosition(sectionPosition);
if (prevSection > -1) {
createPinnedShadow(prevSection);
}
}

if (mPinnedSection != null && sectionView.getTop() > getPaddingTop()) {
final int bottom = mPinnedSection.view.getBottom() + getPaddingTop();
mSectionsDistanceY = sectionView.getTop() - bottom;
if (mSectionsDistanceY < 0) {
// next section overlaps pinned shadow, move it up
mTranslateY = mSectionsDistanceY;
} else {
// next section does not overlap with pinned, stick to top
mTranslateY = 0;
}
} else {
mTranslateY = 0;
mSectionsDistanceY = Integer.MAX_VALUE;
}

}

/** Makes sure we have an actual pinned shadow for given position. */
void ensureShadowForPosition(int sectionPosition, int firstVisibleItem, int visibleItemCount) {

if (mPinnedSection != null && mPinnedSection.position != sectionPosition) {
// invalidate shadow, if required
destroyPinnedShadow();
}

Expand Down Expand Up @@ -272,7 +323,16 @@ void ensureShadowForPosition(int sectionPosition, int firstVisibleItem, int visi
mSectionsDistanceY = Integer.MAX_VALUE;
}
}
}

int findPreviousVisibleSectionPosition(int fromPosition) {
ListAdapter adapter = getAdapter();
for (int childIndex = fromPosition - 1; childIndex >= 0; childIndex--) {
int viewType = adapter.getItemViewType(childIndex);
if (isItemViewTypePinned(adapter, viewType))
return childIndex;
}
return -1;
}

int findFirstVisibleSectionPosition(int firstVisibleItem, int visibleItemCount) {
Expand Down Expand Up @@ -331,11 +391,8 @@ public void setOnScrollListener(OnScrollListener listener) {
@Override
public void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(state);
post(new Runnable() {
@Override public void run() { // restore pinned view after configuration change
recreatePinnedShadow();
}
});
// restore pinned view after configuration change
post(recreatePinnedShadow);
}

@Override
Expand Down Expand Up @@ -510,4 +567,42 @@ public static boolean isItemViewTypePinned(ListAdapter adapter, int viewType) {
return ((PinnedSectionListAdapter) adapter).isItemViewTypePinned(viewType);
}

/**
* Sets the selected item and positions the selection y pixels from the top edge of the
* ListView, or bottom edge of the pinned view iff it exists. (If in touch mode, the item will
* not be selected but it will still be positioned appropriately.)
*
* @param position Index (starting at 0) of the data item to be selected.
* @param y The distance from the top edge of the ListView (plus padding) that the item will be
* positioned.
* @param adjustForHeader If true, will additionally scroll down so first item will be below header
*/
public void setSelectionFromTop(final int position, final int y, boolean adjustForHeader) {
setSelectionFromTop(position, y);

if (adjustForHeader) {
post(new Runnable() {
@Override
public void run() {
// do additional scrolling if a pinned view is displayed
int pinnedOffset = (mPinnedSection == null ? 0 : mPinnedSection.view.getBottom() + getDividerHeight());
if (pinnedOffset > 0) {
PinnedSectionListView.super.setSelectionFromTop(position, y + pinnedOffset);
}
}
});
}
}

/**
* Sets the currently selected item. If in touch mode, the item will not be selected but it will
* still be positioned appropriately. If the specified selection position is less than 0, then
* the item at position 0 will be selected.
*
* @param position Index (starting at 0) of the data item to be selected.
*/
@Override
public void setSelection(int position) {
setSelectionFromTop(position, 0);
}
}