Created
November 12, 2014 15:38
-
-
Save JohNan/df776dc4926a1676cc05 to your computer and use it in GitHub Desktop.
A simple RecyclerView where a Quick Return view can be added either at the top or the bottom. Inspired by https://github.com/LarsWerkman/QuickReturnListView
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
android:layout_width="fill_parent" | |
android:layout_height="fill_parent"> | |
<com.example.ui.widgets.QuickReturnRecyclerView | |
android:id="@+id/list" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:layout_marginBottom="0dp" /> | |
<android.support.v7.widget.Toolbar | |
android:id="@+id/BottomToolbar" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:layout_gravity="bottom" | |
android:background="#f2ffffff" | |
android:minHeight="?attr/actionBarSize"" /> | |
</FrameLayout> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context; | |
import android.os.Build; | |
import android.support.v7.widget.RecyclerView; | |
import android.util.AttributeSet; | |
import android.view.Gravity; | |
import android.view.View; | |
import android.view.ViewGroup; | |
import android.view.animation.TranslateAnimation; | |
import android.widget.FrameLayout; | |
/** | |
* Add view with BottomToolBarRecyclerView.setReturningView to be used as a QuickReturnView | |
* when the user scrolls down the content. | |
* | |
* Created by johnen on 14-11-11. | |
*/ | |
public class QuickReturnRecyclerView extends RecyclerView { | |
private static final String TAG = QuickReturnRecyclerView.class.getName(); | |
private View mReturningView; | |
private static final int STATE_ONSCREEN = 0; | |
private static final int STATE_OFFSCREEN = 1; | |
private static final int STATE_RETURNING = 2; | |
private int mState = STATE_ONSCREEN; | |
private int mMinRawY = 0; | |
private int mReturningViewHeight; | |
private int mGravity = Gravity.BOTTOM; | |
public QuickReturnRecyclerView(Context context) { | |
super(context); | |
init(); | |
} | |
public QuickReturnRecyclerView(Context context, AttributeSet attrs) { | |
super(context, attrs); | |
init(); | |
} | |
public QuickReturnRecyclerView(Context context, AttributeSet attrs, int defStyle) { | |
super(context, attrs, defStyle); | |
init(); | |
} | |
private void init(){ | |
} | |
/** | |
* The view that should be showed/hidden when scrolling the content. | |
* Make sure to set the gravity on the this view to either Gravity.Bottom or | |
* Gravity.TOP and to put it preferable in a FrameLayout. | |
* @param view Any kind of view | |
*/ | |
public void setReturningView(View view) { | |
mReturningView = view; | |
try { | |
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mReturningView.getLayoutParams(); | |
mGravity = params.gravity; | |
} catch (ClassCastException e) { | |
throw new RuntimeException("The return view need to be put in a FrameLayout"); | |
} | |
measureView(mReturningView); | |
mReturningViewHeight = mReturningView.getMeasuredHeight(); | |
setOnScrollListener(new RecyclerScrollListener()); | |
} | |
private void measureView(View child) { | |
ViewGroup.LayoutParams p = child.getLayoutParams(); | |
if (p == null) { | |
p = new ViewGroup.LayoutParams( | |
ViewGroup.LayoutParams.FILL_PARENT, | |
ViewGroup.LayoutParams.WRAP_CONTENT); | |
} | |
int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0, p.width); | |
int lpHeight = p.height; | |
int childHeightSpec; | |
if (lpHeight > 0) { | |
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); | |
} else { | |
childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); | |
} | |
child.measure(childWidthSpec, childHeightSpec); | |
} | |
private class RecyclerScrollListener extends OnScrollListener { | |
private int mScrolledY; | |
@Override | |
public void onScrolled(RecyclerView recyclerView, int dx, int dy) { | |
if(mGravity == Gravity.BOTTOM) | |
mScrolledY += dy; | |
else if(mGravity == Gravity.TOP) | |
mScrolledY -= dy; | |
if(mReturningView == null) | |
return; | |
int translationY = 0; | |
int rawY = mScrolledY; | |
switch (mState) { | |
case STATE_OFFSCREEN: | |
if(mGravity == Gravity.BOTTOM) { | |
if (rawY >= mMinRawY) { | |
mMinRawY = rawY; | |
} else { | |
mState = STATE_RETURNING; | |
} | |
} else if(mGravity == Gravity.TOP) { | |
if (rawY <= mMinRawY) { | |
mMinRawY = rawY; | |
} else { | |
mState = STATE_RETURNING; | |
} | |
} | |
translationY = rawY; | |
break; | |
case STATE_ONSCREEN: | |
if(mGravity == Gravity.BOTTOM) { | |
if (rawY > mReturningViewHeight) { | |
mState = STATE_OFFSCREEN; | |
mMinRawY = rawY; | |
} | |
} else if(mGravity == Gravity.TOP) { | |
if (rawY < -mReturningViewHeight) { | |
mState = STATE_OFFSCREEN; | |
mMinRawY = rawY; | |
} | |
} | |
translationY = rawY; | |
break; | |
case STATE_RETURNING: | |
if(mGravity == Gravity.BOTTOM) { | |
translationY = (rawY - mMinRawY) + mReturningViewHeight; | |
if (translationY < 0) { | |
translationY = 0; | |
mMinRawY = rawY + mReturningViewHeight; | |
} | |
if (rawY == 0) { | |
mState = STATE_ONSCREEN; | |
translationY = 0; | |
} | |
if (translationY > mReturningViewHeight) { | |
mState = STATE_OFFSCREEN; | |
mMinRawY = rawY; | |
} | |
} else if(mGravity == Gravity.TOP) { | |
translationY = (rawY + Math.abs(mMinRawY)) - mReturningViewHeight; | |
if (translationY > 0) { | |
translationY = 0; | |
mMinRawY = rawY - mReturningViewHeight; | |
} | |
if (rawY == 0) { | |
mState = STATE_ONSCREEN; | |
translationY = 0; | |
} | |
if (translationY < -mReturningViewHeight) { | |
mState = STATE_OFFSCREEN; | |
mMinRawY = rawY; | |
} | |
} | |
break; | |
} | |
/** 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); | |
mReturningView.startAnimation(anim); | |
} else { | |
mReturningView.setTranslationY(translationY); | |
} | |
} | |
} | |
} |
A classic discrepancy between code and comment:
Line 178 states "this can be used if the build is below honeycomb" but
line 179 states "SDK_INT <= HONEYCOMB" (less or EQUAL)
So, I guess < would be what you want??
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for sharing your work.
This is a good adaptation but you missed something. You did not take over-scrolling into account.
Add this to onScrolled to fix that (It will stutter a bit for the top item, but it works flawlessly otherwise)