package info.guardianproject.securereaderinterface.widgets; import info.guardianproject.securereaderinterface.R; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.widget.ListView; public class SyncableListView extends ListView { public interface OnPullDownListener { /** * Called when the list has been pulled down to reveal the header (if * header height is set with * {@link SyncableListView#setHeaderHeight(int)}). * * @param heightVisible * Number of visible pixels. */ void onListPulledDown(int heightVisible); /** * Called when the list was "dropped" while it was expanded (if header * height > 0) */ void onListDroppedWhilePulledDown(); } private int mHeaderHeight; private boolean mHeaderEnabled = true; private int mCurrentPullDownHeight; private boolean mDragging; private float mDragStartY; private final Interpolator mDragInterpolator = new LinearInterpolator(); private OnPullDownListener mListener; private int mScaledTouchSlop; private Paint mEdgePaint; public SyncableListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(attrs); } public SyncableListView(Context context, AttributeSet attrs) { super(context, attrs); init(attrs); } public SyncableListView(Context context) { super(context); init(null); } private void init(AttributeSet attrs) { mScaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); this.setAdapter(null); mEdgePaint = new Paint(); mEdgePaint.setColor(getContext().getResources().getColor(R.color.grey_dark)); mEdgePaint.setStyle(Style.STROKE); mEdgePaint.setStrokeWidth(0); } public void setPullDownListener(OnPullDownListener listener) { mListener = listener; } public void setHeaderEnabled(boolean enabled) { mHeaderEnabled = enabled; } public void setHeaderHeight(int height) { mHeaderHeight = height; } /** * Examine a touch motion event to see if this is the start of a "pull down" operation. * @param ev * @return True if we should capture motion events for a pull down operation. */ private boolean checkPullDownStart(MotionEvent ev) { if (!mDragging && ev.getAction() == MotionEvent.ACTION_MOVE && mDragStartY != -1 && mHeaderEnabled && mHeaderHeight > 0) { if (ev.getY() > (mDragStartY + mScaledTouchSlop)) { mDragging = true; mDragStartY += mScaledTouchSlop; return true; } else if (ev.getY() < (mDragStartY - mScaledTouchSlop)) { mDragStartY = -1; // reset, no scroll, we are moving up } } return false; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { View view = this.getChildAt(0); if (view == null || view.getTop() == 0) { mDragStartY = ev.getY(); } else { mDragStartY = -1; } } else if (checkPullDownStart(ev)) { return true; } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { checkPullDownStart(ev); if (mDragging) { if (ev.getAction() == MotionEvent.ACTION_MOVE) { int pullDown = (int) Math.max(0, ev.getY() - mDragStartY); if (pullDown > mHeaderHeight) { // Adjust dragStartY so that moving upwards will start to close the panel // immediately after touch slop! if (pullDown > (mHeaderHeight + mScaledTouchSlop)) mDragStartY += (pullDown - (mHeaderHeight + mScaledTouchSlop)); pullDown = mHeaderHeight; } float fractionVisible = (float)pullDown / (float)mHeaderHeight; float outputVisible = mDragInterpolator.getInterpolation(fractionVisible); int pullDownPixels = (int) (outputVisible * mHeaderHeight); if (pullDownPixels != mCurrentPullDownHeight) { mCurrentPullDownHeight = pullDownPixels; if (mListener != null) mListener.onListPulledDown(mCurrentPullDownHeight); invalidate(); } return true; } else if (ev.getAction() == MotionEvent.ACTION_UP) { // Check if we "dropped" it when it was fully expanded if (mHeaderHeight > 0 && mCurrentPullDownHeight == mHeaderHeight && mListener != null) { mListener.onListDroppedWhilePulledDown(); } mDragging = false; mCurrentPullDownHeight = 0; if (mListener != null) mListener.onListPulledDown(0); invalidate(); return true; } } return super.onTouchEvent(ev); } @Override public int pointToPosition(int x, int y) { int position = super.pointToPosition(x, y); if (position == INVALID_POSITION && this.getAdapter() != null && this.getAdapter().getCount() > 0) position = this.getAdapter().getCount() - 1; return position; } @Override public void draw(Canvas canvas) { canvas.translate(0, mCurrentPullDownHeight); super.draw(canvas); if (mCurrentPullDownHeight != 0) { // Draw a small edge so it does not blend with the background. canvas.drawLine(0, 0, getWidth(), 0, mEdgePaint); } } }