package com.emilsjolander.components.stickylistheaders; import java.lang.reflect.Field; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.widget.FrameLayout; public class StickyListHeadersListViewWrapper extends FrameLayout { private static final boolean HONEYCOMB_OR__ABOVE = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; private static Field mTop; private static Field mBottom; private View header = null; private int headerBottomPosition = -1; private Drawable selector; private ViewConfiguration viewConfig; private boolean showSelector; private boolean drawSelectorOnTop = false; private Rect selectorBounds = new Rect(); private GestureDetector gestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { @Override public void onShowPress(MotionEvent e) { showSelector = true; invalidate(getRefreshedSelectorBounds()); } }); private OnTouchListener onHeaderTouchListener = new OnTouchListener() { float startY; @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { startY = event.getY(); } else if (event.getAction() == MotionEvent.ACTION_UP) { showSelector = false; invalidate(getRefreshedSelectorBounds()); } boolean isScrolling = Math.abs(startY - event.getY()) > viewConfig .getScaledTouchSlop(); if (isScrolling) { showSelector = false; invalidate(getRefreshedSelectorBounds()); } gestureDetector.onTouchEvent(event); return isScrolling; } }; private OnGlobalLayoutListener onGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if(headerChangedDuringLayout){ if(getChildCount() > 1){ removeViewAt(1); } if(header != null){ addView(header); } } headerChangedDuringLayout = false; } }; private boolean inLayout; private boolean headerChangedDuringLayout; public StickyListHeadersListViewWrapper(Context context) { this(context, null, 0); } public StickyListHeadersListViewWrapper(Context context, AttributeSet attrs) { this(context, attrs, 0); } public StickyListHeadersListViewWrapper(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); if(!HONEYCOMB_OR__ABOVE){ try { mTop = View.class.getDeclaredField("mTop"); mBottom = View.class.getDeclaredField("mBottom"); mTop.setAccessible(true); mBottom.setAccessible(true); } catch (NoSuchFieldException e) { e.printStackTrace(); } } viewConfig = ViewConfiguration.get(context); getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener); } void setHeader(View header) { if (header == this.header) { return; } if (this.header != null) { throw new IllegalStateException( "You must first remove the old header first"); } this.header = header; if (header != null) { View list = getChildAt(0); LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); params.leftMargin = list.getPaddingLeft(); params.rightMargin = list.getPaddingRight(); params.gravity = Gravity.TOP; header.setLayoutParams(params); header.setOnTouchListener(onHeaderTouchListener); if(inLayout){ headerChangedDuringLayout = true; }else{ addView(header); } } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { inLayout = true; super.onLayout(changed, left, top, right, bottom); setHeaderBottomPosition(this.headerBottomPosition); inLayout = false; } View removeHeader() { if (this.header != null) { if(inLayout){ headerChangedDuringLayout = true; }else{ removeView(this.header); } this.header.setOnTouchListener(null); } View header = this.header; this.header = null; return header; } boolean hasHeader() { return header != null; } boolean isHeader(View v) { return header == v; } int getHeaderHeight() { if (header == null) { return 0; } MarginLayoutParams params = (MarginLayoutParams) header .getLayoutParams(); int width = getMeasuredWidth() - (params == null ? 0 : (params.leftMargin + params.rightMargin)); int parentWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); int parentHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY); measureChild(header, parentWidthMeasureSpec, parentHeightMeasureSpec); return header.getMeasuredHeight(); } @SuppressLint("NewApi") void setHeaderBottomPosition(int headerBottomPosition) { if (header != null) { if (HONEYCOMB_OR__ABOVE) { header.setTranslationY(headerBottomPosition - header.getMeasuredHeight()); } else { try { mTop.set(header, headerBottomPosition - header.getMeasuredHeight()); mBottom.set(header, headerBottomPosition); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } this.headerBottomPosition = headerBottomPosition; } int getHeaderBottomPosition() { return headerBottomPosition; } public void setSelector(Drawable selector) { this.selector = selector; } @Override protected void dispatchDraw(Canvas canvas) { if (selector != null && showSelector && !drawSelectorOnTop) { drawSelector(canvas); } super.dispatchDraw(canvas); if (selector != null && showSelector && drawSelectorOnTop) { drawSelector(canvas); } } private void drawSelector(Canvas canvas) { selector.setBounds(getRefreshedSelectorBounds()); int[] selectorState = selector.getState(); selector.setState(header.getDrawableState()); selector.draw(canvas); selector.setState(selectorState); } private Rect getRefreshedSelectorBounds() { selectorBounds.left = header.getLeft(); selectorBounds.top = headerBottomPosition - header.getHeight(); selectorBounds.right = header.getRight(); selectorBounds.bottom = headerBottomPosition; return selectorBounds; } public void setDrawSelectorOnTop(boolean onTop) { this.drawSelectorOnTop = onTop; } }