I'm implementing a horizontal RecyclerView that snap items to center after scrolling, like Google play App horizontal lists. This is a review.
My code is below:
MainMenuAdapter mainMenuAdapter = new MainMenuAdapter(this, mDataset);
final LinearLayoutManager layoutManagaer = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
RecyclerView mainMenu = (RecyclerView) findViewById(R.id.main_menu);
mainMenu.setLayoutManager(layoutManagaer);
mainMenu.setAdapter(mainMenuAdapter);
final SnapHelper snapHelper = new LinearSnapHelper();
snapHelper.attachToRecyclerView(mainMenu);
How I can get the center item (position) after RecyclerView snapped it to center? Isn't there any listener implementation for this?
Also, when an item view be touched, I want to snap it to center. How can I do this?
if you need the View, you can call
View view = snapHelper.findSnapView(layoutManagaer);
once you have the View, you should be able to get the position on the dataset for that View. For instance using
mainMenu.getChildAdapterPosition(view)
Better to use this method:
https://medium.com/over-engineering/detecting-snap-changes-with-androids-recyclerview-snaphelper-9e9f5e95c424
Original post:
Even if you are not going to use SnapHelper you can get the central element position by RecyclerView.OnScrollListener.
Copy MiddleItemFinder class to your project.
Create callback object MiddleItemCallback.
MiddleItemFinder.MiddleItemCallback callback =
new MiddleItemFinder.MiddleItemCallback() {
#Override
public void scrollFinished(int middleElement) {
// interaction with middle item
}
};
Add new scroll listener to your RecyclerView
recyclerView.addOnScrollListener(
new MiddleItemFinder(getContext(), layoutManager,
callback, RecyclerView.SCROLL_STATE_IDLE));
The last parameter or MiddleItemFinder constructor is scrollState.
RecyclerView.SCROLL_STATE_IDLE – The RecyclerView is not currently
scrolling. Scroll finished.
RecyclerView.SCROLL_STATE_DRAGGING – The RecyclerView is currently
being dragged by outside input such as user touch input.
RecyclerView.SCROLL_STATE_SETTLING – The RecyclerView is currently
animating to a final position while not under outside control.
MiddleItemFinder.ALL_STATES – All states together.
For example, if you choose RecyclerView.SCROLL_STATE_IDLE as the last constructor parameter than in the end of all scroll the callback object will return you the middle element position.
MiddleItemFinder class:
public class MiddleItemFinder extends RecyclerView.OnScrollListener {
private
Context context;
private
LinearLayoutManager layoutManager;
private
MiddleItemCallback callback;
private
int controlState;
public
static final int ALL_STATES = 10;
public MiddleItemFinder(Context context, LinearLayoutManager layoutManager, MiddleItemCallback callback, int controlState) {
this.context = context;
this.layoutManager = layoutManager;
this.callback = callback;
this.controlState = controlState;
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (controlState == ALL_STATES || newState == controlState) {
int firstVisible = layoutManager.findFirstVisibleItemPosition();
int lastVisible = layoutManager.findLastVisibleItemPosition();
int itemsCount = lastVisible - firstVisible + 1;
int screenCenter = context.getResources().getDisplayMetrics().widthPixels / 2;
int minCenterOffset = Integer.MAX_VALUE;
int middleItemIndex = 0;
for (int index = 0; index < itemsCount; index++) {
View listItem = layoutManager.getChildAt(index);
if (listItem == null)
return;
int leftOffset = listItem.getLeft();
int rightOffset = listItem.getRight();
int centerOffset = Math.abs(leftOffset - screenCenter) + Math.abs(rightOffset - screenCenter);
if (minCenterOffset > centerOffset) {
minCenterOffset = centerOffset;
middleItemIndex = index + firstVisible;
}
}
callback.scrollFinished(middleItemIndex);
}
}
public interface MiddleItemCallback {
void scrollFinished(int middleElement);
}
}
Related
How can I snap to particular position for LinearSnapHelper() in horizontal RecyclerView? There is a function scrolltoposition for RecyclerView which scroll to that position but did not keep it in center for this snaphelper.
I am looking for something like below image. So when I set to particular position, it will keep it in center. I dont find anything related to select position for SnapHelper
i find this , but this doesn't help me.
Any help would be appreciated.
If I understand your question, you are looking for a way to jump to a position and have that position centered in the RecyclerView.
Maybe you have tried RecyclerView.scrollToPosition() but that doesn't snap to the view. You may also have tried RecyclerView.smoothScrollToPosition() and that works better but you may want to avoid all the movement if you have a lot of items and are scrolling a long way.
The reason that scrollToPosition() doesn't work is that it doesn't trigger the LinearSnapHelper which uses a scroll listener to detect when to snap. Since smoothScrollToPosition() does trigger the LinearSnapHelper, we will use scrollToPosition() to get us in the area of the target view then use smoothScrollToPosition() to get the view centered as follows:
private RecyclerView mRecycler;
private void newScrollTo(final int pos) {
RecyclerView.ViewHolder vh = mRecycler.findViewHolderForLayoutPosition(pos);
if (vh != null) {
// Target view is available, so just scroll to it.
mRecycler.smoothScrollToPosition(pos);
} else {
// Target view is not available. Scroll to it.
mRecycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
// From the documentation:
// This callback will also be called if visible item range changes after a layout
// calculation. In that case, dx and dy will be 0.This callback will also be called
// if visible item range changes after a layout calculation. In that case,
// dx and dy will be 0.
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
mRecycler.removeOnScrollListener(this);
if (dx == 0) {
newScrollTo(pos);
}
}
});
mRecycler.scrollToPosition(pos);
}
}
Sample app
MainActivity.java
public class MainActivity extends AppCompatActivity {
private final LinearLayoutManager mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
private final List<String> mItems = new ArrayList<>();
private RecyclerView mRecycler;
private final int mItemCount = 2000;
private final Handler mHandler = new Handler();
private final LinearSnapHelper mLinearSnapHelper = new LinearSnapHelper();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
for (int i = 0; i < mItemCount; i++) {
mItems.add(i + "");
}
mRecycler = findViewById(R.id.recyclerView);
final RecyclerViewAdapter adapter = new RecyclerViewAdapter(null);
adapter.setItems(mItems);
mRecycler.setLayoutManager(mLayoutManager);
mRecycler.setAdapter(adapter);
mLinearSnapHelper.attachToRecyclerView(mRecycler);
newScrollTo(1);
// fireScrollTo();
}
private int maxScrolls = mItemCount;
private void fireScrollTo() {
if (--maxScrolls > 0) {
int pos = (int) (Math.random() * mItemCount);
newScrollTo(pos);
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
fireScrollTo();
}
}, 2000);
}
}
private void newScrollTo(final int pos) {
mRecycler.smoothScrollToPosition(pos);
RecyclerView.ViewHolder vh = mRecycler.findViewHolderForLayoutPosition(pos);
if (vh != null) {
// Target view is available, so just scroll to it.
mRecycler.smoothScrollToPosition(pos);
} else {
// Target view is not available. Scroll to it.
mRecycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
// From the documentation:
// This callback will also be called if visible item range changes after a layout
// calculation. In that case, dx and dy will be 0.This callback will also be called
// if visible item range changes after a layout calculation. In that case,
// dx and dy will be 0.
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
mRecycler.removeOnScrollListener(this);
if (dx == 0) {
newScrollTo(pos);
}
}
});
mRecycler.scrollToPosition(pos);
}
}
}
activity_main.xml
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:layout_width="3px"
android:layout_height="match_parent"
android:background="#android:color/holo_red_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingStart="660px"
android:paddingEnd="660px"/>
</android.support.constraint.ConstraintLayout>
RecyclerViewAdapter.java
class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<String> mItems;
RecyclerViewAdapter(List<String> items) {
mItems = items;
}
#Override
public #NonNull
RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_1, parent, false);
view.getLayoutParams().width = 220;
view.getLayoutParams().height = 220;
// view.setPadding(220 * 3, 0, 220 * 3, 0);
((TextView) view).setGravity(Gravity.CENTER);
return new ItemViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
ItemViewHolder vh = (ItemViewHolder) holder;
String itemText = mItems.get(position);
vh.mItemTextView.setText(itemText);
int bgColor = (position % 2 == 0)
? android.R.color.holo_blue_light
: android.R.color.holo_green_light;
holder.itemView.setBackgroundColor(
holder.itemView.getContext().getResources().getColor(bgColor));
}
#Override
public int getItemCount() {
return (mItems == null) ? 0 : mItems.size();
}
#Override
public int getItemViewType(int position) {
return TYPE_ITEM;
}
static class ItemViewHolder extends RecyclerView.ViewHolder {
private TextView mItemTextView;
ItemViewHolder(View item) {
super(item);
mItemTextView = item.findViewById(android.R.id.text1);
}
}
public void setItems(List<String> items) {
mItems = items;
}
#SuppressWarnings("unused")
private final static String TAG = "RecyclerViewAdapter";
private final static int TYPE_ITEM = 1;
}
Add this where ever you want to scroll your recycler view
recyclerView.scrollToPosition(position)
recyclerView.post {
var view = recyclerView.layoutManager?.findViewByPosition(position);
if (view == null) {
// do nothing
}
var snapDistance = snapHelper.calculateDistanceToFinalSnap(recyclerView.layoutManager!!, view!!)
if (snapDistance?.get(0) != 0 || snapDistance[1] != 0) {
recyclerView.scrollBy(snapDistance?.get(0)!!, snapDistance?.get(1));
}
}
By using this LinearSnap Helper which is attached to your recycler view
var snapHelper = LinearSnapHelper()
snapHelper.attachToRecyclerView(recyclerView)
For horizontal RecyclerView, you should use PagerSnapHelper() instead of LinearSnapHelper().
Just did some research and trace the SnapHelper's source code, it turns out the solution could be very simple:
class MyPagerSnapHelper: PagerSnapHelper() {
fun smoothScrollToPosition(layoutManager: RecyclerView.LayoutManager, position: Int) {
val smoothScroller = createScroller(layoutManager) ?: return
smoothScroller.targetPosition = position
layoutManager.startSmoothScroll(smoothScroller)
}
}
And then you can pass RecyclerView's LayoutManager and target position here
snapHelper.smoothScrollToPosition(recyclerView.layoutManager!!, index)
I'm using a LinearSnapHelper to make items in my RecyclerView "snap" into place on the screen (my cards take up most of the screen, so I want them to snap into place and fill the screen on every swipe/fling/scroll).
I'm struggling with how to make the cards snap into place faster. I've tried creating a custom LinearLayoutManager (and editing the calculateSpeedPerPixel method in scrollToPosition or smoothScrollToPosition), as well as a custom RecyclerView (and editing the fling method). But nothing effects the speed that cards "snap" into place.
I suppose the issue is that I don't really understand how LinearSnapHelper "scrolls" the cards into position. It doesn't seem to use LinearLayoutManager's scrollToPosition or smoothScrollToPosition methods.
snapHelper = new LinearSnapHelper() {
#Override
public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) {
View centerView = findSnapView(layoutManager);
if (centerView == null) {
return RecyclerView.NO_POSITION;
}
int position = layoutManager.getPosition(centerView);
int targetPosition = -1;
if (layoutManager.canScrollHorizontally()) {
if (velocityX < 0) {
targetPosition = position - 1;
} else {
targetPosition = position + 1;
}
}
if (layoutManager.canScrollVertically()) {
if (velocityY > 0) {
targetPosition = position + 1;
} else {
targetPosition = position - 1;
}
}
final int firstItem = 0;
final int lastItem = layoutManager.getItemCount() - 1;
targetPosition = Math.min(lastItem, Math.max(targetPosition, firstItem));
return targetPosition;
}
};
snapHelper.attachToRecyclerView(mRecyclerView);
As 郭玉龙 mentioned, SnapHelper call RecyclerView.smoothScrollBy() method. And it use default sQuinticInterpolator.
To change speed of snap you can do next:
public class SlowdownRecyclerView extends RecyclerView {
// Change pow to control speed.
// Bigger = faster. RecyclerView default is 5.
private static final int POW = 2;
private Interpolator interpolator;
public SlowdownRecyclerView(Context context) {
super(context);
createInterpolator();
}
public SlowdownRecyclerView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
createInterpolator();
}
public SlowdownRecyclerView(Context context, #Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
createInterpolator();
}
private void createInterpolator(){
interpolator = new Interpolator() {
#Override
public float getInterpolation(float t) {
t = Math.abs(t - 1.0f);
return (float) (1.0f - Math.pow(t, POW));
}
};
}
#Override
public void smoothScrollBy(int dx, int dy) {
super.smoothScrollBy(dx, dy, interpolator);
}
Or you can implement your own interpolator.
The speed of snapping scroll is affected by RecyclerView.smoothScrollBy().
Here's the snippet of source code.
Override this function to increase or decrease the speed of snapping scroll.
I wound up doing this by adding a ScrollListener to my RecycleView, and then creating a custom LinearLayoutManager and custom smoothScrollToPosition method.
final CustomLinearLayoutManager mLayoutManager = new CustomLinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
private boolean scrollingUp;
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
scrollingUp = dy < 0;
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
int visiblePosition = scrollingUp ? mLayoutManager.findFirstVisibleItemPosition() : mLayoutManager.findLastVisibleItemPosition();
int completelyVisiblePosition = scrollingUp ? mLayoutManager
.findFirstCompletelyVisibleItemPosition() : mLayoutManager
.findLastCompletelyVisibleItemPosition();
if (visiblePosition != completelyVisiblePosition) {
recyclerView.smoothScrollToPosition(visiblePosition);
return;
}
}
});
I achieved this using a library https://github.com/rubensousa/GravitySnapHelper
you can also override findTargetSnapPosition to get pager like scroll
tweek the scrollMsPerInch to increase / decrease speed
val snapHelper : GravitySnapHelper = GravitySnapHelper(Gravity.CENTER)
// the lower the higher the speed, default is 100f
snapHelper.scrollMsPerInch = 40f
snapHelper.attachToRecyclerView(binding?.mRecyclerView)
Actually you can modify the LinearSnapHelper and SnapHelperClass by simply copy/paste the existing code the only thing you will do is to set MILLISECONDS_PER_INCH on SnapHelper as you want and then use simply use the LinearSnapHelper you created
I want to center the clicked position in the Recyclerview. I am able to scroll the Recyclerview to certain position but i want to middle that position in the screen.
I used this method to scroll to that position.
videoRecyclerview.scrollToPosition(position);
If you are using a RecyclerView and LinearLayoutManager this will work:
private void scrollToCenter(View v) {
int itemToScroll = mRecyclerView.getChildPosition(v);
int centerOfScreen = mRecyclerView.getWidth() / 2 - v.getWidth() / 2;
mLayoutManager.scrollToPositionWithOffset(itemToScroll, centerOfScreen);
}
if you use linearlayoutManager, you can use this code,
linearLayoutManager.scrollToPositionWithOffset(2, 20);
(linearLayoutManager.void scrollToPositionWithOffset (int position,
int offset))
Setting the offset to 0 should align with the top
first move scroll to your item, but whenever recyclerView scrolls it just brings the item in visible region, it is never sure that the item is in center or not, so we find the center item and then check if we are on next to center to item or behind it, here is working logic
recyclerView.smoothScrollToPosition(index);
int firstVisibleItemPosition = rvLayoutManager.findFirstVisibleItemPosition();
int lastVisibleItemPosition = rvLayoutManager.findLastVisibleItemPosition();
int centerPosition = (firstVisibleItemPosition + lastVisibleItemPosition) / 2;
if (index > centerPosition) {
recyclerView.smoothScrollToPosition(index + 1);
} else if (index < centerPosition) {
recyclerView.smoothScrollToPosition(index - 1);
}
If you need smooth scroll to centre for LieanerLayoutManager both horizontal & vertical
Copy the entire code and simply call** scrollToCenter:
public void scrollToCenter(LinearLayoutManager layoutManager, RecyclerView recyclerList, int clickPosition) {
RecyclerView.SmoothScroller smoothScroller = createSnapScroller(recyclerList, layoutManager);
if (smoothScroller != null) {
smoothScroller.setTargetPosition(clickPosition);
layoutManager.startSmoothScroll(smoothScroller);
}
}
// This number controls the speed of smooth scroll
private static final float MILLISECONDS_PER_INCH = 70f;
private final static int DIMENSION = 2;
private final static int HORIZONTAL = 0;
private final static int VERTICAL = 1;
#Nullable
private LinearSmoothScroller createSnapScroller(RecyclerView mRecyclerView, RecyclerView.LayoutManager layoutManager) {
if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
return null;
}
return new LinearSmoothScroller(mRecyclerView.getContext()) {
#Override
protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
int[] snapDistances = calculateDistanceToFinalSnap(layoutManager, targetView);
final int dx = snapDistances[HORIZONTAL];
final int dy = snapDistances[VERTICAL];
final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)));
if (time > 0) {
action.update(dx, dy, time, mDecelerateInterpolator);
}
}
#Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
}
};
}
private int[] calculateDistanceToFinalSnap(#NonNull RecyclerView.LayoutManager layoutManager, #NonNull View targetView) {
int[] out = new int[DIMENSION];
if (layoutManager.canScrollHorizontally()) {
out[HORIZONTAL] = distanceToCenter(layoutManager, targetView,
OrientationHelper.createHorizontalHelper(layoutManager));
}
if (layoutManager.canScrollVertically()) {
out[VERTICAL] = distanceToCenter(layoutManager, targetView,
OrientationHelper.createHorizontalHelper(layoutManager));
}
return out;
}
private int distanceToCenter(#NonNull RecyclerView.LayoutManager layoutManager,
#NonNull View targetView, OrientationHelper helper) {
final int childCenter = helper.getDecoratedStart(targetView)
+ (helper.getDecoratedMeasurement(targetView) / 2);
final int containerCenter;
if (layoutManager.getClipToPadding()) {
containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;
} else {
containerCenter = helper.getEnd() / 2;
}
return childCenter - containerCenter;
}
it's worked for me with this code :
layoutManager.scrollToPositionWithOffset(pos - 1,0);
//on the click callback
view.OnClickListener { callback?.onItemClicked(it)}
// code in activity or your fragment
override fun onItemClicked(view: View) {
val position = recyclerView.getChildLayoutPosition(view)
recyclerView.smoothScrollToPosition(position)
}
simplest hack ever, this was good enough for me:
videoRecyclerview.scrollToPosition(position+2);
if position+2 is within the arraylist.
I'm trying to figure some way to achieve the next kind of view. At the moment I have tried to create a Listview and just make bigger the selected item. But I cannot make the selected item always be in the middle of my view. So now I'm trying to get this with a numberpicker.
But I didn't find any way to hide the divider bar, and make different the selected item and the rest of the view. The idea is get something like in the bottom image.
I think that the ListView may be more configurable than the NumberPicker.
What you can do is use different row layouts dependind if it is the middle one or the others, so your getView(...) method would look like this:
public View getView(int position, View convertView, ViewGroup parent) {
if (position == 1) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.focused_layout, parent, false);
// Do whatever with this view
} else {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.not_focused_layout, parent, false);
// Do whatever with this view
}
return convertView;
}
This way you can customize both layouts both in XML and code. Yo can change the condition if you want the "special" item any other way.
Following is a number picker with custom display values:
final NumberPicker aNumberPicker = new NumberPicker(context);
List<Integer> ids = getIds();
aNumberPicker.setMaxValue(ids.size()-1);
aNumberPicker.setMinValue(0);
mDisplayedIds = new String[ids.size()];
for (int i = 0; i < ids.size(); i++) {
mDisplayedIds[i] = "Nombre"+String.valueOf(ids.get(i)) ;
}
aNumberPicker.setDisplayedValues(mDisplayedIds);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(50, 50);
RelativeLayout.LayoutParams numPickerParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
numPickerParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
relativeLayout.setLayoutParams(params);
relativeLayout.addView(aNumberPicker, numPickerParams);
Also, you can check out some open source library like this one AndroidPicker
You can implement this using RecyclerView with one Holder for Normal Item and one Holder for Selected Item.
Inside your RecyclerView Adapter
private static int SELECTED_ITEM_POSITION = 2;
private static int NORMAL_ITEM = 1;
private static int SELECTED_ITEM = 2;
#Override
public int getItemViewType(int position)
{
if(position == SELECTED_ITEM_POSITION)
return SELECTED_ITEM;
else
return NORMAL_ITEM;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
if(viewType == SELECTED_ITEM)
{
YourSelectedViewHolder selectedViewHolder = (YourSelectedViewHolder)layoutInflater.inflate(R.layout.selected_item_layout, parent, false);
return selectedViewHolder;
}
else //viewType == NORMAL_ITEM
{
YourNormalViewHolder normalViewHolder = (YourNormalViewHolder)layoutInflater.inflate(R.layout.normal_item_layout, parent, false);
return normalViewHolder;
}
}
I wanted to achieve a pretty similar effect on one of my project, where I wanted the middle item of my recycler view to be more prominent.
In my case, that said item is only z-translated to give an impression of focus, but the result is pretty similar to what you're describing.
I'll post my code here, in case it could help you go in the right direction :
//We're on the onCreateView in a fragment here
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//First I find the first visible element
int firstVisiblePosition = mLayoutManager.findFirstVisibleItemPosition();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (firstVisiblePosition != -1) {
int lastVisiblePosition = mLayoutManager.findLastVisibleItemPosition();
int itemHeight = mLayoutManager.getChildAt(0).getMeasuredHeight();
int itemTop = mLayoutManager.getChildAt(0).getTop();
//We use a '+' as itemTop will be negative
int delta = itemHeight + itemTop;
int currentItemToBeFocused = (delta < (itemHeight / 2)) ? 1 : 0;
//Reset the z-translation of other items to 0
for (int i = 0, last = (lastVisiblePosition - firstVisiblePosition); i <= last; ++i) {
if (mLayoutManager.getChildAt(i) != null) {
mLayoutManager.getChildAt(i).setTranslationZ(0);
}
}
//And set the z-translation of the current "centered" item
if (mLayoutManager.getChildAt(currentItemToBeFocused) != null) {
mLayoutManager.getChildAt(currentItemToBeFocused).setTranslationZ(10);
}
}
}
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
});
I wnat to show and hide the Android ActionBar when the user scrolls the screen. I found some examples, like this question in SO. But the code showed in this question and its answer makes the action hides after the list have scrolled some pixels, and I want to make this scroll like the Inbox App by Google, that is the action bar is pulled by the Layout according to the user scroll the screen up or down, in other words, I want to show/hide the action bar at the same time the user scroll down/up.
Someone face a problema like this? Some idea?
super simple:
ditch the ListView. ListView is the past. Use RecyclerView instead.
add a RecyclerView.OnScrollListener to it, to get pixel-by-pixel scroll.
use a Toolbar on your activity layout. So you can control the position of it.
call setTranslationY(val) on your Toolbar to "scroll" it with the RecyclerView.
a few links with the docs for the mentioned classes:
http://android-developers.blogspot.de/2014/10/appcompat-v21-material-design-for-pre.html
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.OnScrollListener.html
http://developer.android.com/reference/android/widget/Toolbar.html
https://developer.android.com/reference/android/view/View.html#setTranslationY(float)
I created a solution based answer of #Budius and this link
First add this class
public class HidingRecyclerViewScrollListener extends RecyclerView.OnScrollListener {
private final int UP = 0;
private final int DOWN = 1;
private final int margin = 5;
private View mView;
RecyclerView.LayoutManager mLayoutManager;
private float currentPoint = 0;
private int lastDirection = 1;
public HidingRecyclerViewScrollListener(View view, LinearLayoutManager layoutManager) {
this.mLayoutManager = layoutManager;
mView = view;
}
public HidingRecyclerViewScrollListener(View view, GridLayoutManager layoutManager) {
this.mLayoutManager = layoutManager;
mView = view;
}
public HidingRecyclerViewScrollListener(View view, StaggeredGridLayoutManager layoutManager) {
this.mLayoutManager = layoutManager;
mView = view;
}
public int getFirstVisibleItem(int[] firstVisibleItemPositions) {
int minSize = 0;
for (int i = 0; i < firstVisibleItemPositions.length; i++) {
if (i == 0) {
minSize = firstVisibleItemPositions[i];
} else if (firstVisibleItemPositions[i] < minSize) {
minSize = firstVisibleItemPositions[i];
}
}
return minSize;
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
float nextPoint = currentPoint + dy;
if (nextPoint < 0) {
nextPoint = 0;
}
if (nextPoint > viewSize()) {
nextPoint = viewSize();
}
lastDirection = nextPoint >= currentPoint ? UP : DOWN;
currentPoint = nextPoint;
mView.setTranslationY(-currentPoint);
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
if (lastDirection == UP) {
if (getFirstVisibleItem() > 0) {
currentPoint = viewSize();
}
}
if (lastDirection == DOWN) {
//Volta para origem
currentPoint = 0;
}
mView.animate().translationY(-currentPoint);
}
}
private int getFirstVisibleItem() {
int firstVisibleItem = 0;
if (mLayoutManager instanceof StaggeredGridLayoutManager) {
int[] lastVisibleItemPositions = ((StaggeredGridLayoutManager) mLayoutManager).findFirstVisibleItemPositions(null);
firstVisibleItem = getFirstVisibleItem(lastVisibleItemPositions);
} else if (mLayoutManager instanceof LinearLayoutManager) {
firstVisibleItem = ((LinearLayoutManager) mLayoutManager).findFirstVisibleItemPosition();
} else if (mLayoutManager instanceof GridLayoutManager) {
firstVisibleItem = ((GridLayoutManager) mLayoutManager).findFirstVisibleItemPosition();
}
return firstVisibleItem;
}
private int viewSize() {
return mView.getHeight() + margin;
}
}
Is simple to use, only add using addOnScrollListener of your recyclerView
recyclerView = (RecyclerView) view.findViewById(R.id.mRecyclerView );
layoutManager = new LinearLayoutManager(getContext());
recyclerView.setLayoutManager(layoutManager);
recyclerView.addOnScrollListener(new HidingRecyclerViewScrollListener(mView, layoutManager));
mView is the view you want to control
You can user this class with LinearLayoutManager, GridLayoutManager and StaggeredGridLayoutManager (but i never test with GridLayoutManager)
In RecyclerView use the code below to the list does not stay below the object you want to control, this code is simpler and better to test, but use a Hearder in RecyclerView as is said in the link above has a better result.
android:clipToPadding="false"
android:paddingTop="#dimen/outher_view_dimem"
If you using SwipeRefreshLayout use code below to show ProgressBar
swipeRefreshLayout.setProgressViewOffset(true, 0, mViewHeight + actionBarHeight);