I'm trying to reproduce the profile screen of the G+ Android app. In this screen, you can notice a horizontalScrollView containing tabs, which sticks at the top the screen.
The screen is made of a list, containing a HeaderView (ImageBackground, avatar, informations and tabs), and the list (cards).
I am able to make the tabs stick to the top and capture touch events, but the fling effect doesn't work anymore when the header fully disappear from the top (<=> if the firstVisible item of the listview
is not the header). I am able to touch and move the HorizontalScrollView
, it works until I try to fling.
Fling OK:
Fling KO:
Here is what I did:
1st step: subclass HorizontalScrollView
to add a ScrollChangedListener
:
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if(mOnScrollChangedListener != null) mOnScrollChangedListener.onScrollChanged(l, t, oldl, oldt);
}
2nd step:
mViewToDraw.setOnScrollChangedListener(new MyHorizontalScrollView.OnScrollChangedListener() {
@Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {
mCurrentScrollX = l;
invalidate(0, 0, getWidth(), mViewToDraw.getMeasuredHeight());
}
});
Step 3: The draw
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mViewToDraw == null) {
return;
}
canvas.translate(-mCurrentScrollX, 0);
mViewToDraw.draw(canvas);
}
Step 4: capturing the touch event
private enum TouchState {NONE, TOUCHING_HEADER}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mViewToDraw == null) {
return super.dispatchTouchEvent(ev);
}
int bottom = mViewToDraw.getMeasuredHeight();
boolean captured = false;
boolean invalidate = false;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
if (ev.getY() <= bottom) {
mTouchState = TouchState.TOUCHING_HEADER;
invalidate = true;
captured = mViewToDraw.dispatchTouchEvent(ev);
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (mTouchState == TouchState.TOUCHING_HEADER) {
mTouchState = TouchState.NONE;
invalidate = true;
captured = mViewToDraw.dispatchTouchEvent(ev);
}
break;
default:
if (mTouchState == TouchState.TOUCHING_HEADER) {
invalidate = true;
captured = mViewToDraw.dispatchTouchEvent(ev);
}
}
if (invalidate) {
mViewToDraw.invalidate();
invalidate(0, 0, getWidth(), bottom);
}
if (captured) {
return true;
}
return super.dispatchTouchEvent(ev);
}
I'm trying not to duplicate the tabs view because I'm pretty sure I can do this way. Hope I was clear enough.
EDIT: I made a small video
As you can see, it works perfectly until the header view fully disappear.
I made a blog post about that HERE