I have following code :
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
List<Integer> data = new ArrayList();
for(int i=0; i<5; i++) {
data.add(i);
}
MyRecyclerViewAdapter adapter = new MyRecyclerViewAdapter(data);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
RecyclerView list = findViewById(R.id.list);
list.setAdapter(adapter);
list.setLayoutManager(layoutManager);
if (layoutManager.findLastCompletelyVisibleItemPosition() == adapter.getItemCount() - 1) {
}
}
findLastCompletelyVisibleItemPosition returns -1. What is the reason of that and how to fix it?
This is happening because you are trying to get last visible item during onCreate.
During onCreate, No View was measured/positioned yet. So, it is impossible to get that information right at onCreate
Edit
You can add a layout listener to be invoked after the RecyclerView is measured.
Something like:
recyclerView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
#Override
public void onLayoutChange(final View v, final int left,
final int top,
final int right,
final int bottom,
final int oldLeft,
final int oldTop,
final int oldRight,
final int oldBottom) {
int lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition();
if(lastVisibleItem > 0 && adapter.getItemCount() - 1) {
Log.d("TEST", "LastItem: " + lastVisibleItem);
// You can call this after you get your information to remove the listener
// This way, it won't be called everytime there's a re-layout
//recyclerView.removeOnLayoutChangeListener(this);
}
}
});
Related
I am using Room data base to fetch the data from the data base. Recyclerview I want to scroll to a particular position in recyclerview.
I have already tried list.setNestedScrollingEnabled(true)
StationListRepository stationListRepositoryOnlyActive = new StationListRepository(getApplicationContext());
stationListRepositoryOnlyActive.getActiveStationList().observe(this, new Observer>() {
#Override
public void onChanged(List OnlyActivestations) {
Log.d("OnlyActivestations", "" + OnlyActivestations.size());
assert OnlyActivestations != null;
if (OnlyActivestations.size() > 0 || FinalList.size() > 0) {
if (FinalList != null && FinalList.size() > 0) {
OnlyActivestations = sortList(OnlyActivestations);
FinalList.addAll(OnlyActivestations);
} else {
OnlyActivestations = sortList(OnlyActivestations);
FinalList = OnlyActivestations;
}
Log.d("FinalListSizeActive",""+FinalList.size());
detailsAdapter = new db_stationListAdapter(context, FinalList);
//recyclerView.setNestedScrollingEnabled(true);
StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL);
staggeredGridLayoutManager.scrollToPosition(10);
recyclerView.setLayoutManager(staggeredGridLayoutManager);
recyclerView.setAdapter(detailsAdapter);
detailsAdapter.notifyDataSetChanged();
runOnUiThread(new Runnable() {
#Override
public void run() {
// stopLoading();
stopAnimation();
}
});
}
}
});
I except the recycler view to auto scroll to the position 10
Try using the code below after you set the data to the adapter
recyclerView.smoothScrollToPosition(10);
or
You can add OnLayoutChangeListener to your RecyclerView.
recyclerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener(){
#Override
public void onLayoutChange(View v, int left, int top, int
right, int bottom, int oldLeft, int oldTop, int oldRight,
int oldBottom) {
if (bottom < oldBottom) {
recyclerView.postDelayed(new Runnable() {
#Override
public void run() {
recyclerView.smoothScrollToPosition(10);
}
}, 100);
}
}
});
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);
}
}
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 have created a GridView control, which inhertis from a ScrollView, the idea of this control, is that it will contain multiple Views arranged in a grid format with a given number of columns and rows.
When the view is first built, the GridView doesn't know the size of its container, so I wait until the onSizeChanged method is called, then I apply the relevant sizing.
When the below is run, it doesn't re-size the grid to show it correctly, each control is only as big as it needs to be to show the text.
When the `onSizeChanged' method is called, it has the correct size, and applies the correct size to each child view, but it doesn't affect the way the controls are drawn (i.e. they're still all bunched up on the top left of the screen).
Despite this, I have actually got it working, but it draws twice. I do this by creating a Runnable which calls ResizeList. Then calling new Handler().post(r) straight after I call BuildIt.
Although this is a solution, I just don't understand why it doesn't work in the below form.
Incidentally, if the GridView is the main View added to the Activity, it displays fine, it's only when it's subsequently added. Which is why I have the Button, which you have to press to show the grid.
Can anyone suggest why the below code doesn't work properly?
public class MainActivity extends Activity {
GridView sv;
FrameLayout flay;
#Override protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
flay=new FrameLayout(this);
this.setContentView(flay);
Button b=new Button(this);
b.setText("press me to show grid view");
b.setOnClickListener(ocl);
flay.addView(b);
}
OnClickListener ocl=new OnClickListener()
{
#Override public void onClick(View v)
{
BuildIt();
}};
private void BuildIt()
{
flay.removeAllViews(); // remove the button control
sv=new GridView(this);
for (int c1=0 ; c1<30 ; c1++)
{
TextView tv=new TextView(this);
tv.setText("Item "+c1);
tv.setGravity(android.view.Gravity.CENTER);
sv.addListItem(tv);
}
flay.addView(sv);
sv.ConstructList();
}
}
The GridView class
public class GridView extends ScrollView
{
final int rows=4;
final int cols=4;
private ArrayList<View> allViews=new ArrayList<View>();
private LinearLayout ll;
public GridView(Context context)
{
super(context);
ll=new LinearLayout(context);
ll.setOrientation(LinearLayout.VERTICAL);
this.addView(ll);
}
public void addListItem(View v)
{
allViews.add(v);
}
public void ConstructList()
{
int c1=0;
ll.removeAllViews(); // Just in case we're re-building
LinearLayout row=null;
for (View v : allViews)
{
if (c1%cols==0)
{
row=new LinearLayout(this.getContext());
ll.addView(row);
}
row.addView(v);
c1++;
}
}
private void ResizeList()
{
int useHeight=getHeight()/rows;
int useWidth=getWidth()/cols;
LinearLayout.LayoutParams lpCol=new LinearLayout.LayoutParams(useWidth, useHeight);
Log.i("log","About to set width/height="+useWidth+"/"+useHeight);
int numKids= ll.getChildCount();
for (int c1=0 ; c1<numKids ; c1++)
{
LinearLayout ll2=(LinearLayout)ll.getChildAt(c1);
for (int c2=0 ; c2<ll2.getChildCount() ; c2++) // use getChildCount rather than cols, just in case it's the last one
{
View v=ll2.getChildAt(c2);
v.setLayoutParams(lpCol);
}
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
ResizeList();
}
}
I have a function which is used to resize the child's width and height in gridView.
May be this could help you :
public static void setGridChild_Height(GridView gridView, int columns) {
ListAdapter listAdapter = gridView.getAdapter();
if (listAdapter == null) {
// pre-condition
return;
}
int totalHeight = 0;
int items = listAdapter.getCount();
int rows = 0;
for (int j = 0; j < items; j++) {
View view = gridView.getChildAt(j);
if (view != null && view.getHeight() > totalHeight) {
totalHeight = view.getHeight();
}
}
System.out.println("totalHeight -> " + totalHeight);
if (totalHeight > 0) {
for (int j = 0; j < items; j++) {
View view = gridView.getChildAt(j);
if (view != null && view.getHeight() < totalHeight) {
view.setMinimumHeight(totalHeight);
}
}
}
// View listItem = listAdapter.getView(0, null, gridView);
// listItem.measure(0, 0);
// totalHeight = listItem.getMeasuredHeight();
//
// float x = 1;
// if (items > columns) {
// x = items / columns;
// rows = (int) (x + 1);
// totalHeight *= rows;
// }
//
// ViewGroup.LayoutParams params = gridView.getLayoutParams();
// params.height = totalHeight;
// gridView.setLayoutParams(params);
}
Try to change logic as per your requirement.
Code is not tested perfectly.
It's because onSizeChanged when newly added to the view hierarchy uses it's old sizes of "0" (according to the docs: http://developer.android.com/reference/android/view/View.html#onSizeChanged(int, int, int, int))
I think what you want is to a addOnLayoutChangedListener : http://developer.android.com/reference/android/view/View.html#addOnLayoutChangeListener(android.view.View.OnLayoutChangeListener)
Using the ViewTreeObserver might be another option that will work for you: How can you tell when a layout has been drawn?
I am trying to make my own list and therefore I am extending the AdapterView class.
I have overridden the onLayout method to add children, measure them and call the layout method.
my problem is that the onLayout method gets called infinitely and items are duplicated on each call.
I have looked on the Internet and verified that my children are not changing (I have made each child returns an empty view with no dynamic content).
here is my code:
protected void onLayout(final boolean changed, final int left, final int top, final int right,
final int bottom)
{
super.onLayout(changed, left, top, right, bottom);
// If we don't have an adapter yet, do nothing and return
if(mAdapter == null)
{
return;
}
fillList();
positionItems();
}
fillList():
private void fillList()
{
int position = 0;
if(mCurrentList*mNumberItemsPerList > mAdapter.getCount() )
{
mCurrentList= 0;
return ;
}
if(mCurrentList < 0)
{
double lastList = (double)(mAdapter.getCount()/mNumberItemsPerList);
mCurrentList= (int) Math.ceil(lastList);
return ;
}
//this.removeAllViewsInLayout();
while( position+mCurrentList*mNumberItemsPerList < mAdapter.getCount() )
{
// Child view
final View child = mAdapter.getView(position+mCurrentList*mNumberItemsPerList, null, this);
// Add the child and measure its dimensions to calculate the remaining space
addAndMeasureChild(child);
position++;
}
}
addAndMeasureChild():
private void addAndMeasureChild(View child)
{
LayoutParams params = child.getLayoutParams();
if(params == null)
{
params = new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
}
//child.setLayoutParams(params);
addViewInLayout(child,-1, params);
// measure the dimensions
child.measure(MeasureSpec.EXACTLY | 250,MeasureSpec.EXACTLY | 250);
}
positionItems():
private void positionItems()
{
int left = 0;
int middleItem = mNumberItemsPerList / 2;
for(int index =0; index < getChildCount(); index++)
{
View child = getChildAt(index);
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
int bottom= (getHeight()-height)/2;
if(index < middleItem)
{
child.layout(left+15, 70+(middleItem-index)*30, left+width, 70+(middleItem-index)*30+height);
}
if(index == middleItem)
{
child.layout(left+15, 70, left+width, 70+height);
}
if(index > middleItem)
{
int diff = index -middleItem;
child.layout(left+15, 70+(middleItem-(index - (middleItem*diff)))*30, left+width, 70+(middleItem-(index - (middleItem*diff)))*30+height);
}
left += width;
}
}
and this is the getView of my child:
public View getView(final int position, final View convertView, final ViewGroup parent)
{
View view = convertView;
if (view == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.cat_item_layout, null);
}
return view;
}
I've figured out what was causing the infinite call to onLayout.
on my main layout I have added a clock with this custom view inside a relative layout.
I don't know why but the clock (which is updated every second) causes my view to call onLayout again.