// hides the headers in the list under the sticky header.
  // Makes sure the other ones are showing
  private void updateHeaderVisibilities() {
    int top = stickyHeaderTop();
    int childCount = mList.getChildCount();
    for (int i = 0; i < childCount; i++) {

      // ensure child is a wrapper view
      View child = mList.getChildAt(i);
      if (!(child instanceof WrapperView)) {
        continue;
      }

      // ensure wrapper view child has a header
      WrapperView wrapperViewChild = (WrapperView) child;
      if (!wrapperViewChild.hasHeader()) {
        continue;
      }

      // update header views visibility
      View childHeader = wrapperViewChild.mHeader;
      if (wrapperViewChild.getTop() < top) {
        if (childHeader.getVisibility() != View.INVISIBLE) {
          childHeader.setVisibility(View.INVISIBLE);
        }
      } else {
        if (childHeader.getVisibility() != View.VISIBLE) {
          childHeader.setVisibility(View.VISIBLE);
        }
      }
    }
  }
  public void setAdapter(StickyListHeadersAdapter adapter) {
    if (adapter == null) {
      mList.setAdapter(null);
      clearHeader();
      return;
    }
    if (mAdapter != null) {
      mAdapter.unregisterDataSetObserver(mDataSetObserver);
    }

    if (adapter instanceof SectionIndexer) {
      mAdapter = new SectionIndexerAdapterWrapper(getContext(), adapter);
    } else {
      mAdapter = new AdapterWrapper(getContext(), adapter);
    }
    mDataSetObserver = new AdapterWrapperDataSetObserver();
    mAdapter.registerDataSetObserver(mDataSetObserver);

    if (mOnHeaderClickListener != null) {
      mAdapter.setOnHeaderClickListener(new AdapterWrapperHeaderClickHandler());
    } else {
      mAdapter.setOnHeaderClickListener(null);
    }

    mAdapter.setDivider(mDivider, mDividerHeight);

    mList.setAdapter(mAdapter);
    clearHeader();
  }
 // hides the headers in the list under the sticky header.
 // Makes sure the other ones are showing
 private void updateHeaderVisibilities() {
   int top;
   if (mHeader != null) {
     top = mHeader.getMeasuredHeight() + (mHeaderOffset != null ? mHeaderOffset : 0);
   } else {
     top = mClippingToPadding ? mPaddingTop : 0;
   }
   int childCount = mList.getChildCount();
   for (int i = 0; i < childCount; i++) {
     View child = mList.getChildAt(i);
     if (child instanceof WrapperView) {
       WrapperView wrapperViewChild = (WrapperView) child;
       if (wrapperViewChild.hasHeader()) {
         View childHeader = wrapperViewChild.mHeader;
         if (wrapperViewChild.getTop() < top) {
           if (childHeader.getVisibility() != View.INVISIBLE) {
             childHeader.setVisibility(View.INVISIBLE);
           }
         } else {
           if (childHeader.getVisibility() != View.VISIBLE) {
             childHeader.setVisibility(View.VISIBLE);
           }
         }
       }
     }
   }
 }
  private void updateOrClearHeader(int firstVisiblePosition) {
    final int adapterCount = mAdapter == null ? 0 : mAdapter.getCount();
    if (adapterCount == 0 || !mAreHeadersSticky) {
      return;
    }

    final int headerViewCount = mList.getHeaderViewsCount();
    final int realFirstVisibleItem = firstVisiblePosition - headerViewCount;

    // It is not a mistake to call getFirstVisiblePosition() here.
    // Most of the time getFixedFirstVisibleItem() should be called
    // but that does not work great together with getChildAt()
    final boolean doesListHaveChildren = mList.getChildCount() != 0;
    final boolean isFirstViewBelowTop =
        doesListHaveChildren
            && mList.getFirstVisiblePosition() == 0
            && mList.getChildAt(0).getTop() > 0;
    final boolean isFirstVisibleItemOutsideAdapterRange =
        realFirstVisibleItem > adapterCount - 1 || realFirstVisibleItem < 0;
    if (!doesListHaveChildren || isFirstVisibleItemOutsideAdapterRange || isFirstViewBelowTop) {
      clearHeader();
      return;
    }

    updateHeader(realFirstVisibleItem);
  }
 public void setAreHeadersSticky(boolean areHeadersSticky) {
   mAreHeadersSticky = areHeadersSticky;
   if (!areHeadersSticky) {
     clearHeader();
   } else {
     updateOrClearHeader(mList.getFixedFirstVisibleItem());
   }
   // invalidating the list will trigger dispatchDraw()
   mList.invalidate();
 }
 @Override
 protected void dispatchDraw(Canvas canvas) {
   // Only draw the list here.
   // The header should be drawn right after the lists children are drawn.
   // This is done so that the header is above the list items
   // but below the list decorators (scroll bars etc).
   if (mList.getVisibility() == VISIBLE || mList.getAnimation() != null) {
     drawChild(canvas, mList, 0);
   }
 }
 @SuppressLint("NewApi")
 @TargetApi(Build.VERSION_CODES.FROYO)
 public void smoothScrollToPosition(int position) {
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
     mList.smoothScrollToPosition(position);
   } else {
     int offset = mAdapter == null ? 0 : getHeaderOverlap(position);
     mList.smoothScrollToPositionFromTop(position, offset);
   }
 }
  private void updateHeader(int headerPosition) {

    // check if there is a new header should be sticky
    if (mHeaderPosition == null || mHeaderPosition != headerPosition) {
      mHeaderPosition = headerPosition;
      final long headerId = mAdapter.getHeaderId(headerPosition);
      if (mHeaderId == null || mHeaderId != headerId) {
        mHeaderId = headerId;
        final View header = mAdapter.getHeaderView(mHeaderPosition, mHeader, this);
        if (mHeader != header) {
          if (header == null) {
            throw new NullPointerException("header may not be null");
          }
          swapHeader(header);
        }
        ensureHeaderHasCorrectLayoutParams(mHeader);
        measureHeader(mHeader);
        if (mOnStickyHeaderChangedListener != null) {
          mOnStickyHeaderChangedListener.onStickyHeaderChanged(
              this, mHeader, headerPosition, mHeaderId);
        }
        // Reset mHeaderOffset to null ensuring
        // that it will be set on the header and
        // not skipped for performance reasons.
        mHeaderOffset = null;
      }
    }

    int headerOffset = 0;

    // Calculate new header offset
    // Skip looking at the first view. it never matters because it always
    // results in a headerOffset = 0
    int headerBottom = mHeader.getMeasuredHeight() + stickyHeaderTop();
    for (int i = 0; i < mList.getChildCount(); i++) {
      final View child = mList.getChildAt(i);
      final boolean doesChildHaveHeader =
          child instanceof WrapperView && ((WrapperView) child).hasHeader();
      final boolean isChildFooter = mList.containsFooterView(child);
      if (child.getTop() >= stickyHeaderTop() && (doesChildHaveHeader || isChildFooter)) {
        headerOffset = Math.min(child.getTop() - headerBottom, 0);
        break;
      }
    }

    setHeaderOffet(headerOffset);

    if (!mIsDrawingListUnderStickyHeader) {
      mList.setTopClippingLength(mHeader.getMeasuredHeight() + mHeaderOffset);
    }

    updateHeaderVisibilities();
  }
 @Override
 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
   mList.layout(0, 0, mList.getMeasuredWidth(), getHeight());
   if (mHeader != null) {
     MarginLayoutParams lp = (MarginLayoutParams) mHeader.getLayoutParams();
     int headerTop = lp.topMargin;
     mHeader.layout(
         mPaddingLeft,
         headerTop,
         mHeader.getMeasuredWidth() + mPaddingLeft,
         headerTop + mHeader.getMeasuredHeight());
   }
 }
 @Override
 public void setOnTouchListener(final OnTouchListener l) {
   if (l != null) {
     mList.setOnTouchListener(
         new OnTouchListener() {
           @Override
           public boolean onTouch(View v, MotionEvent event) {
             return l.onTouch(StickyListHeadersListView.this, event);
           }
         });
   } else {
     mList.setOnTouchListener(null);
   }
 }
 @Override
 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
   mList.layout(0, 0, mList.getMeasuredWidth(), getHeight());
   if (mHeader != null) {
     MarginLayoutParams lp = (MarginLayoutParams) mHeader.getLayoutParams();
     int headerTop = lp.topMargin + (mClippingToPadding ? mPaddingTop : 0);
     // The left parameter must for some reason be set to 0.
     // I think it should be set to mPaddingLeft but apparently not
     mHeader.layout(
         mPaddingLeft,
         headerTop,
         mHeader.getMeasuredWidth() + mPaddingLeft,
         headerTop + mHeader.getMeasuredHeight());
   }
 }
 /**
  * @return true if the fast scroller will always show. False on pre-Honeycomb devices.
  * @see android.widget.AbsListView#isFastScrollAlwaysVisible()
  */
 @TargetApi(Build.VERSION_CODES.HONEYCOMB)
 public boolean isFastScrollAlwaysVisible() {
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
     return false;
   }
   return mList.isFastScrollAlwaysVisible();
 }
 @Override
 public void setClipToPadding(boolean clipToPadding) {
   if (mList != null) {
     mList.setClipToPadding(clipToPadding);
   }
   mClippingToPadding = clipToPadding;
 }
 @TargetApi(Build.VERSION_CODES.FROYO)
 public long[] getCheckedItemIds() {
   if (requireSdkVersion(Build.VERSION_CODES.FROYO)) {
     return mList.getCheckedItemIds();
   }
   return null;
 }
 @TargetApi(Build.VERSION_CODES.HONEYCOMB)
 public int getCheckedItemCount() {
   if (requireSdkVersion(Build.VERSION_CODES.HONEYCOMB)) {
     return mList.getCheckedItemCount();
   }
   return 0;
 }
 @TargetApi(Build.VERSION_CODES.HONEYCOMB)
 public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
   if (requireSdkVersion(Build.VERSION_CODES.HONEYCOMB)) {
     offset += mAdapter == null ? 0 : getHeaderOverlap(position);
     offset -= mClippingToPadding ? 0 : mPaddingTop;
     mList.smoothScrollToPositionFromTop(position, offset, duration);
   }
 }
 @Override
 @TargetApi(Build.VERSION_CODES.GINGERBREAD)
 public int getOverScrollMode() {
   if (requireSdkVersion(Build.VERSION_CODES.GINGERBREAD)) {
     return mList.getOverScrollMode();
   }
   return 0;
 }
 @Override
 public Parcelable onSaveInstanceState() {
   Parcelable superState = super.onSaveInstanceState();
   if (superState != BaseSavedState.EMPTY_STATE) {
     throw new IllegalStateException(
         "Handling non empty state of parent class is not implemented");
   }
   return mList.onSaveInstanceState();
 }
 @Override
 @TargetApi(Build.VERSION_CODES.GINGERBREAD)
 public void setOverScrollMode(int mode) {
   if (requireSdkVersion(Build.VERSION_CODES.GINGERBREAD)) {
     if (mList != null) {
       mList.setOverScrollMode(mode);
     }
   }
 }
  @Override
  public void setPadding(int left, int top, int right, int bottom) {
    mPaddingLeft = left;
    mPaddingTop = top;
    mPaddingRight = right;
    mPaddingBottom = bottom;

    if (mList != null) {
      mList.setPadding(left, top, right, bottom);
    }
    super.setPadding(0, 0, 0, 0);
    requestLayout();
  }
  // Reset values tied the header. also remove header form layout
  // This is called in response to the data set or the adapter changing
  private void clearHeader() {
    if (mHeader != null) {
      removeView(mHeader);
      mHeader = null;
      mHeaderId = null;
      mHeaderPosition = null;
      mHeaderOffset = null;

      // reset the top clipping length
      mList.setTopClippingLength(0);
      updateHeaderVisibilities();
    }
  }
  @Override
  public boolean dispatchTouchEvent(MotionEvent ev) {
    int action = ev.getAction() & MotionEvent.ACTION_MASK;
    if (action == MotionEvent.ACTION_DOWN) {
      mDownY = ev.getY();
      mHeaderOwnsTouch = mHeader != null && mDownY <= mHeader.getHeight() + mHeaderOffset;
    }

    boolean handled;
    if (mHeaderOwnsTouch) {
      if (mHeader != null && Math.abs(mDownY - ev.getY()) <= mTouchSlop) {
        handled = mHeader.dispatchTouchEvent(ev);
      } else {
        if (mHeader != null) {
          MotionEvent cancelEvent = MotionEvent.obtain(ev);
          cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
          mHeader.dispatchTouchEvent(cancelEvent);
          cancelEvent.recycle();
        }

        MotionEvent downEvent =
            MotionEvent.obtain(
                ev.getDownTime(),
                ev.getEventTime(),
                ev.getAction(),
                ev.getX(),
                mDownY,
                ev.getMetaState());
        downEvent.setAction(MotionEvent.ACTION_DOWN);
        handled = mList.dispatchTouchEvent(downEvent);
        downEvent.recycle();
        mHeaderOwnsTouch = false;
      }
    } else {
      handled = mList.dispatchTouchEvent(ev);
    }

    return handled;
  }
 public int getPositionForView(View view) {
   return mList.getPositionForView(view);
 }
 public void setFastScrollEnabled(boolean fastScrollEnabled) {
   mList.setFastScrollEnabled(fastScrollEnabled);
 }
  @TargetApi(Build.VERSION_CODES.HONEYCOMB)
  public StickyListHeadersListView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);

    // Initialize the list
    mList = new WrapperViewList(context);
    mDivider = mList.getDivider();
    mDividerHeight = mList.getDividerHeight();

    // null out divider, dividers are handled by adapter so they look good
    // with headers
    mList.setDivider(null);
    mList.setDividerHeight(0);

    mList.setLifeCycleListener(new WrapperViewListLifeCycleListener());
    mList.setOnScrollListener(new WrapperListScrollListener());
    addView(mList);

    if (attrs != null) {
      TypedArray a =
          context
              .getTheme()
              .obtainStyledAttributes(attrs, R.styleable.StickyListHeadersListView, 0, 0);

      try {
        // Android attributes
        if (a.hasValue(R.styleable.StickyListHeadersListView_android_padding)) {
          int padding =
              a.getDimensionPixelSize(R.styleable.StickyListHeadersListView_android_padding, 0);
          mPaddingLeft = padding;
          mPaddingTop = padding;
          mPaddingRight = padding;
          mPaddingBottom = padding;
        } else {
          mPaddingLeft =
              a.getDimensionPixelSize(R.styleable.StickyListHeadersListView_android_paddingLeft, 0);
          mPaddingTop =
              a.getDimensionPixelSize(R.styleable.StickyListHeadersListView_android_paddingTop, 0);
          mPaddingRight =
              a.getDimensionPixelSize(
                  R.styleable.StickyListHeadersListView_android_paddingRight, 0);
          mPaddingBottom =
              a.getDimensionPixelSize(
                  R.styleable.StickyListHeadersListView_android_paddingBottom, 0);
        }
        setPadding(mPaddingLeft, mPaddingTop, mPaddingRight, mPaddingBottom);

        // Set clip to padding on the list and reset value to default on
        // wrapper
        mClippingToPadding =
            a.getBoolean(R.styleable.StickyListHeadersListView_android_clipToPadding, true);
        super.setClipToPadding(true);
        mList.setClipToPadding(mClippingToPadding);

        // ListView attributes
        mList.setFadingEdgeLength(
            a.getDimensionPixelSize(
                R.styleable.StickyListHeadersListView_android_fadingEdgeLength,
                mList.getVerticalFadingEdgeLength()));
        final int fadingEdge =
            a.getInt(R.styleable.StickyListHeadersListView_android_requiresFadingEdge, 0);
        if (fadingEdge == 0x00001000) {
          mList.setVerticalFadingEdgeEnabled(false);
          mList.setHorizontalFadingEdgeEnabled(true);
        } else if (fadingEdge == 0x00002000) {
          mList.setVerticalFadingEdgeEnabled(true);
          mList.setHorizontalFadingEdgeEnabled(false);
        } else {
          mList.setVerticalFadingEdgeEnabled(false);
          mList.setHorizontalFadingEdgeEnabled(false);
        }
        mList.setCacheColorHint(
            a.getColor(
                R.styleable.StickyListHeadersListView_android_cacheColorHint,
                mList.getCacheColorHint()));
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
          mList.setChoiceMode(
              a.getInt(
                  R.styleable.StickyListHeadersListView_android_choiceMode, mList.getChoiceMode()));
        }
        mList.setDrawSelectorOnTop(
            a.getBoolean(R.styleable.StickyListHeadersListView_android_drawSelectorOnTop, false));
        mList.setFastScrollEnabled(
            a.getBoolean(
                R.styleable.StickyListHeadersListView_android_fastScrollEnabled,
                mList.isFastScrollEnabled()));
        mList.setScrollBarStyle(
            a.getInt(R.styleable.StickyListHeadersListView_android_scrollbarStyle, 0));
        final Drawable selector =
            a.getDrawable(R.styleable.StickyListHeadersListView_android_listSelector);
        if (selector != null) {
          mList.setSelector(selector);
        }
        mList.setScrollingCacheEnabled(
            a.getBoolean(
                R.styleable.StickyListHeadersListView_android_scrollingCache,
                mList.isScrollingCacheEnabled()));
        final Drawable divider =
            a.getDrawable(R.styleable.StickyListHeadersListView_android_divider);
        if (divider != null) {
          mDivider = divider;
        }
        mDividerHeight =
            a.getDimensionPixelSize(
                R.styleable.StickyListHeadersListView_android_dividerHeight, mDividerHeight);

        // StickyListHeaders attributes
        mAreHeadersSticky =
            a.getBoolean(R.styleable.StickyListHeadersListView_hasStickyHeaders, true);
        mIsDrawingListUnderStickyHeader =
            a.getBoolean(
                R.styleable.StickyListHeadersListView_isDrawingListUnderStickyHeader, true);

      } finally {
        a.recycle();
      }
    }
  }
 public void invalidateViews() {
   mList.invalidateViews();
 }
 public boolean showContextMenu() {
   return mList.showContextMenu();
 }
 public void setOnCreateContextMenuListener(OnCreateContextMenuListener l) {
   mList.setOnCreateContextMenuListener(l);
 }
 public long getItemIdAtPosition(int position) {
   return mList.getItemIdAtPosition(position);
 }
 public Object getItemAtPosition(int position) {
   return mList.getItemAtPosition(position);
 }