Skip to content

Instantly share code, notes, and snippets.

@frankiesardo
Last active December 14, 2015 22:49
Show Gist options
  • Star 32 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save frankiesardo/5161734 to your computer and use it in GitHub Desktop.
Save frankiesardo/5161734 to your computer and use it in GitHub Desktop.
A refactored version of LarsWerkman's QuickReturnListView. It should provide an easier to use API and an easier to understand library.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/sticky"
android:layout_width="match_parent"
android:layout_height="120dp"
android:background="#f00"
android:gravity="center"
android:textAppearance="?android:textAppearanceLarge"/>
</FrameLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<View
android:layout_width="match_parent"
android:layout_height="@dimen/top_height"
android:background="#0f0" />
<View
android:id="@+id/placeholder"
android:layout_width="match_parent"
android:layout_height="120dp" />
</LinearLayout>
public class QuickReturn {
private final static int ANIMATION_DURATION_MILLIS = 250;
private final ListView listView;
private final View quickReturnView;
private final View headerPlaceholder;
private int itemCount;
private int itemOffsetY[];
private boolean isScrollComputed;
private int calculatedHeight;
private int quickReturnHeight;
public QuickReturn(ListView listView, View quickReturnView, View headerPlaceholder) {
this.listView = listView;
this.quickReturnView = quickReturnView;
this.headerPlaceholder = headerPlaceholder;
}
public ViewTreeObserver.OnGlobalLayoutListener makeOnGlobalLayoutListener() {
return onGlobalLayoutListener;
}
private ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
quickReturnHeight = quickReturnView.getHeight();
computeScrollY();
}
};
public AbsListView.OnScrollListener makeOnScrollListener() {
return onScrollListener;
}
private AbsListView.OnScrollListener onScrollListener = new AbsListView.OnScrollListener() {
private QuickReturnState quickReturnState = QuickReturnState.ONSCREEN;
private int rawY;
private int minRawY;
private boolean isAnimationRunning;
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
int translationY = 0;
rawY = headerPlaceholder.getTop() - Math.min(calculatedHeight - listView.getHeight(), getScrollY());
switch (quickReturnState) {
case OFFSCREEN:
if (rawY <= minRawY) {
minRawY = rawY;
} else {
quickReturnState = QuickReturnState.RETURNING;
}
translationY = rawY;
break;
case ONSCREEN:
if (rawY < -quickReturnHeight) {
quickReturnState = QuickReturnState.OFFSCREEN;
minRawY = rawY;
}
translationY = rawY;
break;
case RETURNING:
if (translationY > 0) {
translationY = 0;
minRawY = rawY - quickReturnHeight;
} else if (rawY > 0) {
quickReturnState = QuickReturnState.ONSCREEN;
translationY = rawY;
} else if (translationY < -quickReturnHeight) {
quickReturnState = QuickReturnState.OFFSCREEN;
minRawY = rawY;
} else if (quickReturnView.getTranslationY() != 0
&& !isAnimationRunning) {
animateToExpanding();
}
break;
case EXPANDED:
if (rawY < minRawY - 2 && !isAnimationRunning) {
animateToOffScreen();
} else if (translationY > 0) {
translationY = 0;
minRawY = rawY - quickReturnHeight;
} else if (rawY > 0) {
quickReturnState = QuickReturnState.ONSCREEN;
translationY = rawY;
} else if (translationY < -quickReturnHeight) {
quickReturnState = QuickReturnState.ONSCREEN;
minRawY = rawY;
} else {
minRawY = rawY;
}
}
translateViewTo(translationY);
}
private void animateToOffScreen() {
TranslateAnimation anim = new TranslateAnimation(0, 0, 0, -quickReturnHeight);
startAnimationWithListener(anim, setStateOffScreenOnEnd());
}
private void animateToExpanding() {
TranslateAnimation anim = new TranslateAnimation(0, 0, -quickReturnHeight, 0);
startAnimationWithListener(anim, setStateExpandedOnEnd());
}
private void startAnimationWithListener(Animation anim, Animation.AnimationListener listener) {
isAnimationRunning = true;
anim.setFillAfter(true);
anim.setDuration(ANIMATION_DURATION_MILLIS);
anim.setAnimationListener(listener);
quickReturnView.startAnimation(anim);
}
private Animation.AnimationListener setStateOffScreenOnEnd() {
return new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
isAnimationRunning = false;
quickReturnState = QuickReturnState.OFFSCREEN;
}
};
}
private Animation.AnimationListener setStateExpandedOnEnd() {
return new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
isAnimationRunning = false;
minRawY = rawY;
quickReturnState = QuickReturnState.EXPANDED;
}
};
}
private void translateViewTo(int translationY) {
/** this can be used if the build is below honeycomb **/
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB) {
TranslateAnimation anim = new TranslateAnimation(0, 0, translationY,
translationY);
anim.setFillAfter(true);
anim.setDuration(0);
quickReturnView.startAnimation(anim);
} else {
quickReturnView.setTranslationY(translationY);
}
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
};
private static enum QuickReturnState {
ONSCREEN,
OFFSCREEN,
RETURNING,
EXPANDED
}
private void computeScrollY() {
calculatedHeight = 0;
itemCount = listView.getAdapter().getCount();
if (itemOffsetY == null) {
itemOffsetY = new int[itemCount];
}
for (int i = 0; i < itemCount; ++i) {
View view = listView.getAdapter().getView(i, null, listView);
view.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
itemOffsetY[i] = calculatedHeight;
calculatedHeight += view.getMeasuredHeight();
}
isScrollComputed = true;
}
private int getScrollY() {
if (!isScrollComputed) {
return 0;
}
int pos = listView.getFirstVisiblePosition();
View view = listView.getChildAt(0);
int nItemY = view.getTop();
int nScrollY = itemOffsetY[pos] - nItemY;
return nScrollY;
}
}
public class QuickReturnExample extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView quickReturnView = (TextView) findViewById(R.id.sticky);
View header = getLayoutInflater().inflate(R.layout.header, null);
View placeHolder = header.findViewById(R.id.placeholder);
ListView listView = (ListView) findViewById(android.R.id.list);
quickReturnView.setText("Quick Return Demo");
listView.addHeaderView(header);
String[] array = new String[20];
for (int i = 0; i < array.length; i++) {
array[i] = "Demo " + i;
}
listView.setAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, android.R.id.text1, array));
// All the magic it's here
QuickReturn quickReturn = new QuickReturn(listView, quickReturnView, placeHolder);
listView.getViewTreeObserver().addOnGlobalLayoutListener(quickReturn.makeOnGlobalLayoutListener());
listView.setOnScrollListener(quickReturn.makeOnScrollListener());
}
}
@tasomaniac
Copy link

Does this version support dynamic lists? Because in the original implementation, when the dataset is changed, there were problems.

@AlexeyFreelancer
Copy link

This version doesn't support dynamic list too :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment