diff --git a/example/res/menu/main.xml b/example/res/menu/main.xml
index d227c49..8490a89 100644
--- a/example/res/menu/main.xml
+++ b/example/res/menu/main.xml
@@ -1,9 +1,15 @@
\ No newline at end of file
diff --git a/example/res/values/strings.xml b/example/res/values/strings.xml
index d8bfcec..4564915 100644
--- a/example/res/values/strings.xml
+++ b/example/res/values/strings.xml
@@ -2,6 +2,7 @@
Pinned Section Demo
- Settings
+ Fast scroll
+ Add padding
\ No newline at end of file
diff --git a/example/src/com/hb/examples/PinnedSectionListActivity.java b/example/src/com/hb/examples/PinnedSectionListActivity.java
index 83d86cc..8bae55f 100644
--- a/example/src/com/hb/examples/PinnedSectionListActivity.java
+++ b/example/src/com/hb/examples/PinnedSectionListActivity.java
@@ -16,19 +16,22 @@
package com.hb.examples;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Locale;
+import android.annotation.SuppressLint;
import android.app.ListActivity;
import android.content.Context;
import android.graphics.Color;
+import android.os.Build;
import android.os.Bundle;
import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
+import android.widget.SectionIndexer;
import android.widget.TextView;
import android.widget.Toast;
@@ -37,91 +40,200 @@
public class PinnedSectionListActivity extends ListActivity implements OnClickListener {
- private static final int[] COLORS = new int[] {
- R.color.green_light, R.color.orange_light,
- R.color.blue_light, R.color.red_light };
+ static class SimpleAdapter extends ArrayAdapter- implements PinnedSectionListAdapter {
+
+ private static final int[] COLORS = new int[] {
+ R.color.green_light, R.color.orange_light,
+ R.color.blue_light, R.color.red_light };
+
+ public SimpleAdapter(Context context, int resource, int textViewResourceId) {
+ super(context, resource, textViewResourceId);
+
+ final int sectionsNumber = 'Z' - 'A' + 1;
+ prepareSections(sectionsNumber);
+
+ int sectionPosition = 0, listPosition = 0;
+ for (char i=0; i implements PinnedSectionListAdapter {
+ }
- public MyPinnedSectionListAdapter(Context context, int resource, int textViewResourceId, List
- objects) {
- super(context, resource, textViewResourceId, objects);
- }
+ static class FastScrollAdapter extends SimpleAdapter implements SectionIndexer {
- @Override public View getView(int position, View convertView, ViewGroup parent) {
- TextView view = (TextView) super.getView(position, convertView, parent);
- view.setTextColor(Color.DKGRAY);
- view.setTag("" + position);
- if (getItem(position).type == Item.SECTION) {
- //view.setOnClickListener(PinnedSectionListActivity.this);
- view.setBackgroundColor(parent.getResources().getColor(COLORS[position % COLORS.length]));
- }
- return view;
- }
+ private Item[] sections;
- @Override public int getViewTypeCount() {
- return 2;
- }
+ public FastScrollAdapter(Context context, int resource, int textViewResourceId) {
+ super(context, resource, textViewResourceId);
+ }
- @Override public int getItemViewType(int position) {
- return getItem(position).type;
- }
+ @Override protected void prepareSections(int sectionsNumber) {
+ sections = new Item[sectionsNumber];
+ }
- @Override public boolean isItemViewTypePinned(int viewType) {
- return viewType == Item.SECTION;
- }
- }
+ @Override protected void onSectionAdded(Item section, int sectionPosition) {
+ sections[sectionPosition] = section;
+ }
+
+ @Override public Item[] getSections() {
+ return sections;
+ }
+
+ @Override public int getPositionForSection(int section) {
+ if (section >= sections.length) {
+ section = sections.length - 1;
+ }
+ return sections[section].listPosition;
+ }
+
+ @Override public int getSectionForPosition(int position) {
+ if (position >= getCount()) {
+ position = getCount() - 1;
+ }
+ return getItem(position).sectionPosition;
+ }
+
+ }
+
+ static class Item {
- private static class Item {
public static final int ITEM = 0;
public static final int SECTION = 1;
public final int type;
public final String text;
+ public int sectionPosition;
+ public int listPosition;
+
public Item(int type, String text) {
- this.type = type;
- this.text = text;
+ this.type = type;
+ this.text = text;
}
@Override public String toString() {
return text;
}
+
}
+ private boolean isFastScroll;
+ private boolean addPadding;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
- MyPinnedSectionListAdapter adapter = new MyPinnedSectionListAdapter(
- this, android.R.layout.simple_list_item_1, android.R.id.text1, prepareItems());
- setListAdapter(adapter);
+ if (savedInstanceState != null) {
+ isFastScroll = savedInstanceState.getBoolean("isFastScroll");
+ addPadding = savedInstanceState.getBoolean("addPadding");
+ }
+ initializeAdapter();
+ initializePadding();
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean("isFastScroll", isFastScroll);
+ outState.putBoolean("addPadding", addPadding);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
- Toast.makeText(this, "Item: " + position, Toast.LENGTH_SHORT).show();
+ Item item = (Item) getListAdapter().getItem(position);
+ Toast.makeText(this, "Item " + position + ": " + item.text, Toast.LENGTH_SHORT).show();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
+ menu.getItem(0).setChecked(isFastScroll);
+ menu.getItem(1).setChecked(addPadding);
return true;
}
- private static ArrayList
- prepareItems() {
- ArrayList
- result = new ArrayList
- ();
- for (int i = 0; i < 30; i++) {
- result.add(new Item(Item.SECTION, "Section " + i));
- for (int j=0; j<4; j++) {
- result.add(new Item(Item.ITEM, "Item " + j));
- }
- }
- return result;
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+
+ if (item.getItemId() == R.id.action_fastscroll) {
+ isFastScroll = !isFastScroll;
+ item.setChecked(isFastScroll);
+ initializeAdapter();
+ } else {
+ addPadding = !addPadding;
+ item.setChecked(addPadding);
+ initializePadding();
+ }
+
+ return true;
+ }
+
+ private void initializePadding() {
+ float density = getResources().getDisplayMetrics().density;
+ int padding = addPadding ? (int) (16 * density) : 0;
+ getListView().setPadding(padding, padding, padding, padding);
}
+ @SuppressLint("NewApi")
+ private void initializeAdapter() {
+ getListView().setFastScrollEnabled(isFastScroll);
+ if (isFastScroll) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ getListView().setFastScrollAlwaysVisible(true);
+ }
+ setListAdapter(new FastScrollAdapter(this, android.R.layout.simple_list_item_1, android.R.id.text1));
+ } else {
+ setListAdapter(new SimpleAdapter(this, android.R.layout.simple_list_item_1, android.R.id.text1));
+ }
+ }
+
@Override
public void onClick(View v) {
- Toast.makeText(this, "Item: " + v.getTag(), Toast.LENGTH_SHORT).show();
+ Toast.makeText(this, "Item: " + v.getTag() , Toast.LENGTH_SHORT).show();
}
-}
+}
\ No newline at end of file
diff --git a/library/src/com/hb/views/PinnedSectionListView.java b/library/src/com/hb/views/PinnedSectionListView.java
index 9416a87..5b95f65 100644
--- a/library/src/com/hb/views/PinnedSectionListView.java
+++ b/library/src/com/hb/views/PinnedSectionListView.java
@@ -2,7 +2,7 @@
* Copyright (C) 2013 Sergej Shafarenka, halfbit.de
*
* Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
+ * you may not use this file kt in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
@@ -16,7 +16,6 @@
package com.hb.views;
-import android.annotation.SuppressLint;
import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Canvas;
@@ -34,48 +33,38 @@
import android.widget.ListView;
import android.widget.SectionIndexer;
-//import com.hb.views.pinnedsection.BuildConfig;
+import com.hb.views.pinnedsection.BuildConfig;
/**
- * ListView capable to pin views at its top while the rest is still scrolled.
- * Pinned view's height should large than Body's height.
+ * ListView, which is capable to pin section views at its top while the rest is still scrolled.
*/
public class PinnedSectionListView extends ListView {
- // -- inner classes
-
- /**
- * List adapter to be implemented for being used with PinnedSectionListView
- * adapter.
- */
- public static interface PinnedSectionListAdapter extends ListAdapter {
- /**
- * This method shall return 'true' if views of given type has to be
- * pinned.
- */
- boolean isItemViewTypePinned(int viewType);
- }
-
- /** Wrapper class for pinned section view and its position in the list. */
- static class PinnedViewShadow {
- public View view;
- public int position;
- public long id;
- }
-
- // -- class fields
-
- /** Default change observer. */
- private final DataSetObserver mDataSetObserver = new DataSetObserver() {
- @Override
- public void onChanged() {
- destroyPinnedShadow();
- };
-
- @Override
- public void onInvalidated() {
- destroyPinnedShadow();
- }
+ //-- inner classes
+
+ /** List adapter to be implemented for being used with PinnedSectionListView adapter. */
+ public static interface PinnedSectionListAdapter extends ListAdapter {
+ /** This method shall return 'true' if views of given type has to be pinned. */
+ boolean isItemViewTypePinned(int viewType);
+ }
+
+ /** Wrapper class for pinned section view and its position in the list. */
+ static class PinnedViewShadow {
+ public View view;
+ public int position;
+ public long id;
+ }
+
+ //-- class fields
+
+ /** Default change observer. */
+ private final DataSetObserver mDataSetObserver = new DataSetObserver() {
+ @Override public void onChanged() {
+ recreatePinnedShadow();
+ };
+ @Override public void onInvalidated() {
+ recreatePinnedShadow();
+ }
};
// fields used for handling touch events
@@ -94,164 +83,53 @@ public void onInvalidated() {
/** shadow instance with a pinned view, can be null. */
PinnedViewShadow mPinnedShadow;
- /**
- * Pinned view Y-translation. We use it to stick pinned view to the next
- * section.
- */
+ /** Pinned view Y-translation. We use it to stick pinned view to the next section. */
int mTranslateY;
+ /** Scroll listener which does the magic */
+ private final OnScrollListener mOnScrollListener = new OnScrollListener() {
- /** Scroll listener which does the magic */
- private final OnScrollListener mOnScrollListener = new OnScrollListener() {
+ @Override public void onScrollStateChanged(AbsListView view, int scrollState) {
+ if (mDelegateOnScrollListener != null) { // delegate
+ mDelegateOnScrollListener.onScrollStateChanged(view, scrollState);
+ }
+ }
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- if (mDelegateOnScrollListener != null) { // delegate
- mDelegateOnScrollListener.onScrollStateChanged(view, scrollState);
- }
- }
-
- @SuppressLint("NewApi")
- @Override
- public void onScroll(AbsListView view, int firstVisibleItemPosition, int visibleItemCount,
- int totalItemCount) {
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (mDelegateOnScrollListener != null) { // delegate
- mDelegateOnScrollListener.onScroll(view, firstVisibleItemPosition,
- visibleItemCount, totalItemCount);
+ mDelegateOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
- // get expected adapter or fail
+ // get expected adapter or fail fast
PinnedSectionListAdapter adapter = (PinnedSectionListAdapter) view.getAdapter();
- if (adapter == null || visibleItemCount == 0)
- return; // nothing to do
-
- int firstSectionPosition = findFirstSectionPositionInScreen(firstVisibleItemPosition,
- visibleItemCount);
-
- if (firstSectionPosition == -1) { // there is no visible sections
-
- // try to find invisible view
- int currentSectionPosition = findCurrentSectionPosition(firstVisibleItemPosition);
- if (currentSectionPosition == -1)
- return; // exit here, we have no sections
- // else, we have a section to pin
-
- if (mPinnedShadow != null) {
- if (mPinnedShadow.position == currentSectionPosition) {
- // this section is already pinned
- mTranslateY = 0;
- return; // exit, as pinned section is the current one
- } else {
- // we have a pinned section, which differs from the
- // current
- destroyPinnedShadow(); // destroy old pinned view
- }
- }
-
- // create new pinned view for candidate
- createPinnedShadow(currentSectionPosition);
- return; // exit, as we have created a pinned candidate already
- }
- View firstSectionView = view
- .getChildAt(firstSectionPosition - firstVisibleItemPosition);
- int firstSectionTop = firstSectionView.getTop();
- int topBorder = getListPaddingTop();
-
- if (mPinnedShadow == null) {
-
- if (firstSectionTop < topBorder) {
- // only two sections instance > one's height we need
- // create pinned shadow
- int nextSectionPosition = findSectionPositionInScreen(firstVisibleItemPosition,
- visibleItemCount, firstSectionPosition + 1);
- boolean isCreat = true;
- if (nextSectionPosition != -1) {
- int nextSectionTop = view.getChildAt(
- nextSectionPosition - firstVisibleItemPosition).getTop();
- if (nextSectionTop - firstSectionView.getTop() <= firstSectionView
- .getHeight())
- isCreat = false;
- }
- if (isCreat)
- createPinnedShadow(firstSectionPosition);
- } else if (firstSectionTop > topBorder) {
- int pinnedSectionBottom = topBorder + firstSectionView.getHeight();
- if (firstSectionTop < pinnedSectionBottom) {
- View firstChild = getChildAt(0);
- if (!adapter.isItemViewTypePinned(adapter
- .getItemViewType(firstVisibleItemPosition))
- && firstChild.getHeight() > 0) {
- int currentSectionPosition = findCurrentSectionPosition(firstVisibleItemPosition);
- if (currentSectionPosition > -1) {
- createPinnedShadow(currentSectionPosition,
- firstSectionView.getTop() - pinnedSectionBottom);
- }
- }
- }
- }
-
- } else {
+ if (adapter == null || visibleItemCount == 0) return; // nothing to do
- if (firstSectionPosition == mPinnedShadow.position) {
- if (firstSectionTop > topBorder) {
- destroyPinnedShadow();
- int prevSectionPosition = findCurrentSectionPosition(firstSectionPosition - 1);
- if (prevSectionPosition > -1) {
- createPinnedShadow(prevSectionPosition);
- int translateY = firstSectionTop - topBorder
- - mPinnedShadow.view.getHeight();
- if (translateY > 0)
- translateY = 0;
- mTranslateY = translateY;
- }
- } else if (firstSectionTop <= topBorder) {
- // in fast scroll mode to the top, the first section top
- // is uncollect.
- mTranslateY = 0;
- }
+ final boolean isFirstVisibleItemSection =
+ adapter.isItemViewTypePinned(adapter.getItemViewType(firstVisibleItem));
- } else {
+ if (isFirstVisibleItemSection) {
+ View sectionView = getChildAt(0);
+ 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);
+ }
- int pinnedSectionBottom = topBorder + firstSectionView.getHeight();
- if (firstSectionTop < pinnedSectionBottom) {
- if (firstSectionTop < topBorder) {
- // only two sections instance > one's height we need
- // create pinned shadow
- int nextSectionPosition = findSectionPositionInScreen(
- firstVisibleItemPosition, visibleItemCount,
- firstSectionPosition + 1);
- boolean reCreat = true;
- if (nextSectionPosition != -1) {
- int nextSectionTop = view.getChildAt(
- nextSectionPosition - firstVisibleItemPosition).getTop();
- if (nextSectionTop - firstSectionView.getTop() <= firstSectionView
- .getHeight())
- reCreat = false;
- }
- destroyPinnedShadow();
- if (reCreat) {
- createPinnedShadow(firstSectionPosition);
- }
- } else {
- mTranslateY = firstSectionTop - pinnedSectionBottom;
- // in fast scroll mode to the bottom, the first
- // section top is uncollect.
- int currentSectionPosition = findCurrentSectionPosition(firstSectionPosition - 1);
- if (mPinnedShadow.position != currentSectionPosition) {
- destroyPinnedShadow();
- createPinnedShadow(currentSectionPosition, mTranslateY);
- }
- }
- } else {
- mTranslateY = 0;
- }
+ } else { // section is not at the first visible position
+ int sectionPosition = findCurrentSectionPosition(firstVisibleItem);
+ if (sectionPosition > -1) { // we have section position
+ ensureShadowForPosition(sectionPosition, firstVisibleItem, visibleItemCount);
+ } else { // there is no section for the first visible item, destroy shadow
+ destroyPinnedShadow();
}
}
- }
- };
+ };
+
+ };
- // -- class methods
+ //-- class methods
public PinnedSectionListView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -267,208 +145,214 @@ private void initView() {
setOnScrollListener(mOnScrollListener);
}
- /** Create shadow wrapper with a pinned view for a view at given position */
- private void createPinnedShadow(int position) {
- createPinnedShadow(position, 0);
- }
-
- private void createPinnedShadow(int position, int translateY) {
- if (position < 0 || mPinnedShadow != null) {
+ /** Create shadow wrapper with a pinned view for a view at given position */
+ void createPinnedShadow(int position) {
+
+ // try to recycle shadow
+ PinnedViewShadow pinnedShadow = mRecycleShadow;
+ mRecycleShadow = null;
+
+ // create new shadow, if needed
+ if (pinnedShadow == null) pinnedShadow = new PinnedViewShadow();
+ // request new view using recycled view, if such
+ View pinnedView = getAdapter().getView(position, pinnedShadow.view, PinnedSectionListView.this);
+
+ // read layout parameters
+ LayoutParams layoutParams = (LayoutParams) pinnedView.getLayoutParams();
+ if (layoutParams == null) { // create default layout params
+ layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ }
+
+ int heightMode = MeasureSpec.getMode(layoutParams.height);
+ int heightSize = MeasureSpec.getSize(layoutParams.height);
+
+ if (heightMode == MeasureSpec.UNSPECIFIED) heightMode = MeasureSpec.EXACTLY;
+
+ int maxHeight = getHeight() - getListPaddingTop() - getListPaddingBottom();
+ if (heightSize > maxHeight) heightSize = maxHeight;
+
+ // measure & layout
+ int ws = MeasureSpec.makeMeasureSpec(getWidth() - getListPaddingLeft() - getListPaddingRight(), MeasureSpec.EXACTLY);
+ int hs = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
+ pinnedView.measure(ws, hs);
+ pinnedView.layout(0, 0, pinnedView.getMeasuredWidth(), pinnedView.getMeasuredHeight());
+ mTranslateY = 0;
+
+ // initialize pinned shadow
+ pinnedShadow.view = pinnedView;
+ pinnedShadow.position = position;
+ pinnedShadow.id = getAdapter().getItemId(position);
+
+ // store pinned shadow
+ mPinnedShadow = pinnedShadow;
+ }
+
+ /** Destroy shadow wrapper for currently pinned view */
+ void destroyPinnedShadow() {
+ if (mPinnedShadow != null) {
+ // keep shadow for being recycled later
+ mRecycleShadow = mPinnedShadow;
+ mPinnedShadow = null;
+ }
+ }
+
+ /** 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
+ destroyPinnedShadow();
return;
}
- // try to recycle shadow
- PinnedViewShadow pinnedShadow = mRecycleShadow;
- mRecycleShadow = null;
-
- // create new shadow, if needed
- if (pinnedShadow == null)
- pinnedShadow = new PinnedViewShadow();
- // request new view using recycled view, if such
- View pinnedView = getAdapter().getView(position, pinnedShadow.view,
- PinnedSectionListView.this);
-
- // read layout parameters
- LayoutParams layoutParams = (LayoutParams) pinnedView.getLayoutParams();
- if (layoutParams == null) { // create default layout params
- layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
- pinnedView.setLayoutParams(layoutParams);
+ if (mPinnedShadow != null) { // invalidate shadow, if required
+ if (mPinnedShadow.position != sectionPosition) {
+ destroyPinnedShadow();
+ }
}
-
- int heightMode = MeasureSpec.getMode(layoutParams.height);
- int heightSize = MeasureSpec.getSize(layoutParams.height);
-
- if (heightMode == MeasureSpec.UNSPECIFIED)
- heightMode = MeasureSpec.EXACTLY;
-
- int maxHeight = getHeight() - getListPaddingTop() - getListPaddingBottom();
- if (heightSize > maxHeight)
- heightSize = maxHeight;
-
- // measure & layout
- int ws = MeasureSpec.makeMeasureSpec(getWidth() - getListPaddingLeft()
- - getListPaddingRight(), MeasureSpec.EXACTLY);
- int hs = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
- pinnedView.measure(ws, hs);
- pinnedView.layout(0, 0, pinnedView.getMeasuredWidth(), pinnedView.getMeasuredHeight());
-
- // initialize pinned shadow
- pinnedShadow.view = pinnedView;
- pinnedShadow.position = position;
- pinnedShadow.id = getAdapter().getItemId(position);
-
- // store pinned shadow
- mPinnedShadow = pinnedShadow;
-
- mTranslateY = translateY;
- }
-
- /** Destroy shadow wrapper for currently pinned view */
- private void destroyPinnedShadow() {
- // keep shadow for being recycled later
- // if (mPinnedShadow != null)
- // Log.d("show", "destroyPinnedShadow = " + mPinnedShadow.position);
- mRecycleShadow = mPinnedShadow;
- mPinnedShadow = null;
- }
-
- /** find first section in screen */
- private int findFirstSectionPositionInScreen(int firstVisibleItemPosition, int visibleItemCount) {
- return findSectionPositionInScreen(firstVisibleItemPosition, visibleItemCount,
- firstVisibleItemPosition);
- }
-
- /** find section from fromPosition in screen */
- private int findSectionPositionInScreen(int firstVisibleItemPosition, int visibleItemCount,
- int fromPosition) {
- PinnedSectionListAdapter adapter = (PinnedSectionListAdapter) getAdapter();
- if (fromPosition < firstVisibleItemPosition
- || fromPosition >= firstVisibleItemPosition + visibleItemCount)
- return -1;
- for (int position = fromPosition; position < firstVisibleItemPosition + visibleItemCount; position++) {
- int viewType = adapter.getItemViewType(position);
- if (adapter.isItemViewTypePinned(viewType))
- return position;
+ if (mPinnedShadow == null) { // create shadow, if empty
+ createPinnedShadow(sectionPosition);
}
- return -1;
- }
- /** find current position's section position */
- private int findCurrentSectionPosition(int fromPosition) {
- PinnedSectionListAdapter adapter = (PinnedSectionListAdapter) getAdapter();
-
- if (adapter instanceof SectionIndexer) {
- // try fast way by asking section indexer
- SectionIndexer indexer = (SectionIndexer) adapter;
- int sectionPosition = indexer.getSectionForPosition(fromPosition);
- int itemPosition = indexer.getPositionForSection(sectionPosition);
- int typeView = adapter.getItemViewType(itemPosition);
- if (adapter.isItemViewTypePinned(typeView)) {
- return itemPosition;
- } // else, no luck
+ // align shadow according to next section position, if needed
+ int nextPosition = sectionPosition + 1;
+ if (nextPosition < getCount()) {
+ int nextSectionPosition = findFirstVisibleSectionPosition(nextPosition,
+ visibleItemCount - (nextPosition - firstVisibleItem));
+ if (nextSectionPosition > -1) {
+ View nextSectionView = getChildAt(nextSectionPosition - firstVisibleItem);
+ int bottom = mPinnedShadow.view.getBottom() + getPaddingTop();
+
+ if (bottom > nextSectionView.getTop()) {
+ // next section overlaps pinned shadow, move it up
+ mTranslateY = nextSectionView.getTop() - bottom;
+ } else {
+ // next section does not overlap with pinned, stick to top
+ mTranslateY = 0;
+ }
+ } else {
+ // no other sections are visible, stick to top
+ mTranslateY = 0;
+ }
}
- // try slow way by looking through to the next section item above
- for (int position = fromPosition; position >= 0; position--) {
- int viewType = adapter.getItemViewType(position);
- if (adapter.isItemViewTypePinned(viewType))
- return position;
- }
- return -1; // no candidate found
}
- @Override
- public void setOnScrollListener(OnScrollListener listener) {
- if (listener == mOnScrollListener) {
- super.setOnScrollListener(listener);
- } else {
- mDelegateOnScrollListener = listener;
+ int findFirstVisibleSectionPosition(int firstVisibleItem, int visibleItemCount) {
+ PinnedSectionListAdapter adapter = (PinnedSectionListAdapter) getAdapter();
+ for (int childIndex = 0; childIndex < visibleItemCount; childIndex++) {
+ int position = firstVisibleItem + childIndex;
+ int viewType = adapter.getItemViewType(position);
+ if (adapter.isItemViewTypePinned(viewType)) return position;
+ }
+ return -1;
+ }
+
+ int findCurrentSectionPosition(int fromPosition) {
+ PinnedSectionListAdapter adapter = (PinnedSectionListAdapter) getAdapter();
+
+ if (adapter instanceof SectionIndexer) {
+ // try fast way by asking section indexer
+ SectionIndexer indexer = (SectionIndexer) adapter;
+ int sectionPosition = indexer.getSectionForPosition(fromPosition);
+ int itemPosition = indexer.getPositionForSection(sectionPosition);
+ int typeView = adapter.getItemViewType(itemPosition);
+ if (adapter.isItemViewTypePinned(typeView)) {
+ return itemPosition;
+ } // else, no luck
+ }
+
+ // try slow way by looking through to the next section item above
+ for (int position=fromPosition; position>=0; position--) {
+ int viewType = adapter.getItemViewType(position);
+ if (adapter.isItemViewTypePinned(viewType)) return position;
+ }
+ return -1; // no candidate found
+ }
+
+ void recreatePinnedShadow() {
+ destroyPinnedShadow();
+ ListAdapter adapter = getAdapter();
+ if (adapter != null && adapter.getCount() > 0) {
+ int firstVisiblePosition = getFirstVisiblePosition();
+ int sectionPosition = findCurrentSectionPosition(firstVisiblePosition);
+ if (sectionPosition == -1) return; // no views to pin, exit
+ ensureShadowForPosition(sectionPosition,
+ firstVisiblePosition, getLastVisiblePosition() - firstVisiblePosition);
}
- }
-
- private boolean isPinnedViewTouched(View view, float x, float y) {
- view.getHitRect(mTouchRect);
- mTouchRect.top += mTranslateY;
- mTouchRect.bottom += mTranslateY;
- return mTouchRect.contains((int) x, (int) y);
- }
-
- @Override
- public void onRestoreInstanceState(Parcelable state) {
- super.onRestoreInstanceState(state);
-
- // restore pinned view after configuration change
- post(new Runnable() {
- @Override
- public void run() {
- ListAdapter adapter = getAdapter();
- if (adapter != null && adapter.getCount() > 0) {
- // detect pinned position
- int firstVisiblePosition = getFirstVisiblePosition();
- int position = findCurrentSectionPosition(firstVisiblePosition);
- if (position == -1)
- return; // no views to pin, exit
-
- if (firstVisiblePosition == position) {
- // create pinned shadow for position
- createPinnedShadow(firstVisiblePosition);
- // adjust translation
- View childView = getChildAt(firstVisiblePosition);
- mTranslateY = childView == null ? 0 : -childView.getTop();
- } else {
- createPinnedShadow(position);
- }
- }
+ }
+
+ @Override
+ public void setOnScrollListener(OnScrollListener listener) {
+ if (listener == mOnScrollListener) {
+ super.setOnScrollListener(listener);
+ } else {
+ mDelegateOnScrollListener = listener;
+ }
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ super.onRestoreInstanceState(state);
+ post(new Runnable() {
+ @Override public void run() { // restore pinned view after configuration change
+ recreatePinnedShadow();
+ }
+ });
+ }
+
+ @Override
+ public void setAdapter(ListAdapter adapter) {
+
+ // assert adapter in debug mode
+ if (BuildConfig.DEBUG && adapter != null) {
+ if (!(adapter instanceof PinnedSectionListAdapter))
+ throw new IllegalArgumentException("Does your adapter implement PinnedSectionListAdapter?");
+ if (adapter.getViewTypeCount() < 2)
+ throw new IllegalArgumentException("Does your adapter handle at least two types" +
+ " of views in getViewTypeCount() method: items and sections?");
+ }
+
+ // unregister observer at old adapter and register on new one
+ ListAdapter oldAdapter = getAdapter();
+ if (oldAdapter != null) oldAdapter.unregisterDataSetObserver(mDataSetObserver);
+ if (adapter != null) adapter.registerDataSetObserver(mDataSetObserver);
+
+ // destroy pinned shadow, if new adapter is not same as old one
+ if (oldAdapter != adapter) destroyPinnedShadow();
+
+ super.setAdapter(adapter);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ if (mPinnedShadow != null) {
+ int parentWidth = r - l - getPaddingLeft() - getPaddingRight();
+ int shadowWidth = mPinnedShadow.view.getWidth();
+ if (parentWidth != shadowWidth) {
+ recreatePinnedShadow();
}
- });
- }
-
- @Override
- public void setAdapter(ListAdapter adapter) {
-
- // assert adapter in debug mode
- if (/* BuildConfig.DEBUG && */adapter != null) {
- if (!(adapter instanceof PinnedSectionListAdapter))
- throw new IllegalArgumentException(
- "Does your adapter implement PinnedSectionListAdapter?");
- if (adapter.getViewTypeCount() < 2)
- throw new IllegalArgumentException(
- "Does your adapter handle at least two types of views - items and sections?");
}
+ }
- // unregister observer at old adapter and register on new one
- ListAdapter currentAdapter = getAdapter();
- if (currentAdapter != null)
- currentAdapter.unregisterDataSetObserver(mDataSetObserver);
- if (adapter != null)
- adapter.registerDataSetObserver(mDataSetObserver);
-
- // destroy pinned shadow, if new adapter is not same as old one
- if (currentAdapter != adapter)
- destroyPinnedShadow();
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
- super.setAdapter(adapter);
- }
+ if (mPinnedShadow != null) {
- @Override
- protected void dispatchDraw(Canvas canvas) {
- super.dispatchDraw(canvas);
+ // prepare variables
+ int pLeft = getListPaddingLeft();
+ int pTop = getListPaddingTop();
+ View view = mPinnedShadow.view;
- if (mPinnedShadow != null) {
-
- // prepare variables
- int pLeft = getListPaddingLeft();
- int pTop = getListPaddingTop();
- View view = mPinnedShadow.view;
-
- // draw child
- canvas.save();
- canvas.clipRect(pLeft, pTop, pLeft + view.getWidth(), pTop + view.getHeight());
- canvas.translate(pLeft, pTop + mTranslateY);
- drawChild(canvas, mPinnedShadow.view, getDrawingTime());
- canvas.restore();
- }
- }
+ // draw child
+ canvas.save();
+ canvas.clipRect(pLeft, pTop, pLeft + view.getWidth(), pTop + view.getHeight());
+ canvas.translate(pLeft, pTop + mTranslateY);
+ drawChild(canvas, mPinnedShadow.view, getDrawingTime());
+ canvas.restore();
+ }
+ }
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
@@ -491,13 +375,11 @@ && isPinnedViewTouched(mPinnedShadow.view, x, y)) {
}
if (mTouchTarget != null) {
- if (isPinnedViewTouched(mTouchTarget, x, y)) { // forward event to
- // pinned view
+ if (isPinnedViewTouched(mTouchTarget, x, y)) { // forward event to pinned view
mTouchTarget.dispatchTouchEvent(ev);
}
- if (action == MotionEvent.ACTION_UP) { // perform onClick on pinned
- // view
+ if (action == MotionEvent.ACTION_UP) { // perform onClick on pinned view
super.dispatchTouchEvent(ev);
performPinnedItemClick();
clearTouchTarget();
@@ -514,8 +396,7 @@ && isPinnedViewTouched(mPinnedShadow.view, x, y)) {
mTouchTarget.dispatchTouchEvent(event);
event.recycle();
- // provide correct sequence to super class for further
- // handling
+ // provide correct sequence to super class for further handling
super.dispatchTouchEvent(mDownEvent);
super.dispatchTouchEvent(ev);
clearTouchTarget();
@@ -530,6 +411,19 @@ && isPinnedViewTouched(mPinnedShadow.view, x, y)) {
return super.dispatchTouchEvent(ev);
}
+ private boolean isPinnedViewTouched(View view, float x, float y) {
+ view.getHitRect(mTouchRect);
+
+ // by taping top or bottom padding, the list performs on click on a border item.
+ // we don't add top padding here to keep behavior consistent.
+ mTouchRect.top += mTranslateY;
+
+ mTouchRect.bottom += mTranslateY + getPaddingTop();
+ mTouchRect.left += getPaddingLeft();
+ mTouchRect.right -= getPaddingRight();
+ return mTouchRect.contains((int)x, (int)y);
+ }
+
private void clearTouchTarget() {
mTouchTarget = null;
if (mDownEvent != null) {
@@ -539,12 +433,11 @@ private void clearTouchTarget() {
}
private boolean performPinnedItemClick() {
- if (mPinnedShadow == null)
- return false;
+ if (mPinnedShadow == null) return false;
OnItemClickListener listener = getOnItemClickListener();
if (listener != null) {
- View view = mPinnedShadow.view;
+ View view = mPinnedShadow.view;
playSoundEffect(SoundEffectConstants.CLICK);
if (view != null) {
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);