package in.srain.cube.views; import android.content.Context; import android.content.res.TypedArray; import android.graphics.PointF; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.RelativeLayout; import in.srain.cube.R; import in.srain.cube.util.CLog; import in.srain.cube.util.Debug; /** * @author http://www.liaohuqiu.net */ public class ScrollHeaderFrame extends RelativeLayout { private static final boolean DEBUG = Debug.DEBUG_SCROLL_HEADER_FRAME; private static final String LOG_TAG = ScrollHeaderFrame.class.getName(); private int mHeaderHeight; private int mCurrentPos = 0; private int mLastPos = 0; private int mHeaderId = 0; private int mContainerId = 0; private boolean mDisabled = false; private ViewGroup mContentViewContainer; private View mHeaderContainer; private long mLastTime; private PointF mPtLastMove = new PointF(); private IScrollHeaderFrameHandler mIScrollHeaderFrameHandler; public ScrollHeaderFrame(Context context) { this(context, null); } public ScrollHeaderFrame(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ScrollHeaderFrame(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); TypedArray arr = context.obtainStyledAttributes(attrs, R.styleable.ScrollHeaderFrame, 0, 0); if (arr != null) { if (arr.hasValue(R.styleable.ScrollHeaderFrame_scrollheaderframe_header)) { mHeaderId = arr.getResourceId(R.styleable.ScrollHeaderFrame_scrollheaderframe_header, 0); } if (arr.hasValue(R.styleable.ScrollHeaderFrame_scrollheaderframe_conent_container)) { mContainerId = arr.getResourceId(R.styleable.ScrollHeaderFrame_scrollheaderframe_conent_container, 0); } if (arr.hasValue(R.styleable.ScrollHeaderFrame_scrollheaderframe_disable)) { mDisabled = arr.getBoolean(R.styleable.ScrollHeaderFrame_scrollheaderframe_disable, false); } arr.recycle(); } } public View getContentView() { return mContentViewContainer; } public View getHeaderView() { return mHeaderContainer; } public void setHandler(IScrollHeaderFrameHandler handler) { mIScrollHeaderFrameHandler = handler; CLog.d(LOG_TAG, "setHandler: %s", this); } @Override protected void onFinishInflate() { super.onFinishInflate(); mHeaderContainer = findViewById(mHeaderId); mContentViewContainer = (ViewGroup) findViewById(mContainerId); setDrawingCacheEnabled(false); setBackgroundDrawable(null); setClipChildren(false); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (DEBUG) { Log.d(LOG_TAG, String.format("onLayout: current %s, %s %s %s %s", mCurrentPos, l, t, r, b)); } int headerHeight = mHeaderContainer.getMeasuredHeight(); if (headerHeight == 0) { return; } int w = getMeasuredWidth(); int h = getMeasuredHeight(); int pos = 0; mHeaderContainer.layout(0, pos, w, pos + headerHeight); mContentViewContainer.layout(0, pos + headerHeight, w, h + headerHeight); } /** * if deltaY > 0, tryToMove the content down */ private boolean tryToMove(float deltaY) { // has reached the bottom if ((deltaY > 0 && mCurrentPos == 0)) { if (DEBUG) { Log.d(LOG_TAG, String.format("has reached the bottom")); } return false; } // has reached the top if ((deltaY < 0 && mCurrentPos == -mHeaderHeight)) { if (DEBUG) { Log.d(LOG_TAG, String.format("has reached the top")); } return false; } int to = mCurrentPos + (int) deltaY; // over top if (to < -mHeaderHeight) { if (DEBUG) { Log.d(LOG_TAG, String.format("over top")); } to = -mHeaderHeight; } // over bottom if (to > 0) { if (DEBUG) { Log.d(LOG_TAG, String.format("over bottom")); } to = 0; } return moveTo(to); } private boolean moveTo(int to) { if (DEBUG) { Log.d(LOG_TAG, String.format("moveTo: %s %s, %s", to, mCurrentPos, mHeaderHeight)); } if (mCurrentPos == to) { return false; } int y = mCurrentPos - to; mCurrentPos = to; updatePos(); return true; } private void updatePos() { int change = mCurrentPos - mLastPos; // mHeaderContainer.scrollTo(0, -change); // mContentViewContainer.scrollTo(0, -change); scrollTo(0, -change); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int w = getMeasuredWidth(); int h = getMeasuredHeight(); mHeaderHeight = mHeaderContainer.getMeasuredHeight(); if (DEBUG) { Log.d(LOG_TAG, String.format("onMeasure %s getMeasuredHeight: %s", h, mHeaderContainer.getMeasuredHeight())); } mContentViewContainer.measure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(h, MeasureSpec.AT_MOST)); } @Override public boolean dispatchTouchEvent(MotionEvent e) { if (!isEnabled() || mDisabled) { return super.dispatchTouchEvent(e); } boolean handled = super.dispatchTouchEvent(e); int action = e.getAction(); switch (action) { case MotionEvent.ACTION_UP: break; case MotionEvent.ACTION_CANCEL: break; case MotionEvent.ACTION_DOWN: mLastTime = e.getEventTime(); mPtLastMove.set(e.getX(), e.getY()); break; case MotionEvent.ACTION_MOVE: if (mHeaderHeight == 0) { break; } float deltaY = (int) (e.getY() - mPtLastMove.y); mPtLastMove.set(e.getX(), e.getY()); float speed = Math.abs(deltaY / (mLastTime - e.getEventTime())); mLastTime = e.getEventTime(); boolean moveUp = deltaY < 0; boolean canMoveUp = mCurrentPos > -mHeaderHeight; boolean moveDown = !moveUp; boolean canMoveDown = mCurrentPos < 0; if (DEBUG) { Log.d(LOG_TAG, String.format("ACTION_MOVE: %s, speed: %s, moveUp: %s, canMoveUp: %s, moveDown: %s, canMoveDown: %s", speed, deltaY, moveUp, canMoveUp, moveDown, canMoveDown)); } // disable move when header not reach top if (moveDown && mIScrollHeaderFrameHandler != null && !mIScrollHeaderFrameHandler.hasReachTop()) { return handled; } if (speed >= 5 && moveDown && mIScrollHeaderFrameHandler != null) { moveTo(0); return handled; } if (speed >= 5 && moveUp && mIScrollHeaderFrameHandler != null) { moveTo(-mHeaderHeight); return handled; } if ((moveUp && canMoveUp) || (moveDown && canMoveDown)) { tryToMove(deltaY); } break; default: break; } return handled; } }