On top of the screen I have a Grid Recycler layout and it has height wrap content. So on start it has 1 row of items. In the middle of the screen I have a button. So I am starting adding items in recycler and number of rows increases 1 then 2 then 3 etc... So I want to know the point when my recycler view will be visually "UNDER" button because number of rows in grid is too much.
So is there a way to check if the view is covered by another view?
Assuming your item layout_height is wrap_content along recyclerview, there is a possibility recyclerview takes one item to fill up the screen or more than 100. However, you can check the accepted answer and explore other answers.
private boolean isViewOverlapping(View firstView, View secondView) {
int[] firstPosition = new int[2];
int[] secondPosition = new int[2];
firstView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
firstView.getLocationOnScreen(firstPosition);
secondView.getLocationOnScreen(secondPosition);
int r = firstView.getMeasuredWidth() + firstPosition[0];
int l = secondPosition[0];
return r >= l && (r != 0 && l != 0);
}
Related
I have two views A and B. View B is completely covered view A which means the bounding rect of view B is more than bounding rect of view A.
The best example I can give is, I have a fixed bottom tabs layout and a RecyclerView with multiple linear layouts. When I scroll, one of the linear layout will be behind the bottom tabs layout.
So, How can I programatically check If view A is completely covered by other views (other views might not be known upfront)?
Is there any possible way to identify this?
Update:
I have tried the solution mentioned here. But it didn't solve my problem. In my case, the view bounds are exactly matching and I want which view is on top.
After lot of struggle, I have found a way to identify if view is overlapped by other views in the following way.
AccessibilityNodeInfo nodeInfo = AccessibilityNodeInfo.obtain();
viewA.onInitializeAccessibilityNodeInfo(nodeInfo);
if (!nodeInfo.isVisibleToUser()) {
// View is not visible to user. This also validates if viewA is overlapped by other views
}
nodeInfo.recycle();
Use the following method it will help you to find view overlapping:-
private boolean isYourViewOverlapping(View firstView, View secondView) {
int[] firstPosition = new int[2];
int[] secondPosition = new int[2];
firstView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
firstView.getLocationOnScreen(firstPosition);
secondView.getLocationOnScreen(secondPosition);
int r = firstView.getMeasuredWidth() + firstPosition[0];
int l = secondPosition[0];
return r >= l && (r != 0 && l != 0);
}
Problem description
LinearLayoutManager.scrollToPositionWithOffset(pos, 0) works great if the sum of RecyclerView's all children's height is big than screen height. But it does not work if the sum of RecyclerView's all children's height is small than screen height.
Problem description in detail
Let's say I have an Activity and a RecyclerView as it's root view. RecyclerView's width and height are both match_parent. This RecyclerView has 3 items and the sum of these 3 child view's height is small than screen height. I want to hide first item when Activity is onCreated. User will see second item at start. If user scroll down, he still can see first item. So I call LinearLayoutManager.scrollToPositionWithOffset(1, 0). But it won't work since the sum of RecyclerView's all children's height is small than screen height.
Question
How can I make RecyclerView scroll to specific position even though the sum of RecyclerView's all children's height is small than screen height.
Following is my code according to #Haran Sivaram's answer:
Item first = new Item();
Item second = new Item();
Item third = new Item();
List<Item> list = Arrays.asList(first, second, three);
adapter.add(list);
adapter.notifyDataSetChanged();
recyclerView.post(new Runnable() {
#Override
public void run() {
int sumHeight = 0;
for (int i = 0; i < recyclerView.getChildCount(); i++) {
View view = recyclerView.getChildAt(i);
sumHeight += view.getHeight();
}
if (sumHeight < recyclerView.getHeight()) {
adapter.addItem(new VerticalSpaceViewModel(recyclerView.getHeight() - sumHeight + recyclerView.getChildAt(0).getHeight()));
}
linearLayoutManager.scrollToPositionWithOffset(1, 0);
}
});
It worked. But has some small issues.
What you need to do is to increase the height of the recycler view to a minimum height which will allow scrolling and hide your first element (screen height + height of the first item). You can achieve this by adding a dummy element as the last element and setting it's height or you could also do this using padding/margins (Have not tried this).
This also needs to be done dynamically once the view is drawn (You can do it statically if you are aware of the sizes of each item beforehand - I will not recommend this method though).
Use an onGLobalLayoutListner to get a callback once the view is drawn, do your measurements here and update the height. Now the scroll with offset should work fine.
I want to find out the position or ids related to a ListView's items: only those ones which are completely visible on the screen.
Using listview.getFirstVisibleposition and listview.getLastVisibleposition takes partial list items into account.
I followed a little bit similar approach as suggested by Rich, to suit my requirement which was to fetch completely visible items on screen when List View is scrolled every time.
This is what i did
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//Loop to get tids of all completely visible List View's item scrolled on screen
for (int listItemIndex = 0; listItemIndex <= getListView().getLastVisiblePosition() - getListView().getFirstVisiblePosition(); listItemIndex++) {
View listItem = getListView().getChildAt(listItemIndex);
TextView tvNewPostLabel = (TextView) listItem.findViewById(R.id.tvNewPostLabel);
if (tvNewPostLabel != null && tvNewPostLabel.getVisibility() == View.VISIBLE) {
int listTid = (int) tvNewPostLabel.getTag();
if (listItem.getBottom() < getListView().getHeight()) {//If List View's item is not partially visible
listItemTids.add(listTid);
}
}
}
}
I have not tried this, but here are the pieces of the framework that I believe will get you to what you're looking for (at least this is what I'd try first)
As you've stated, you should get the last visible position from the list view using ListView.getLastVisiblePosition()
You can then access the View representing this position using ListView.getChildAt(position)
You now have a reference to the view, which you can call a combination of View.getLocationOnScreen(location) and View.getHeight()
Also call View.getLocationOnScreen(location) and View.getHeight() on the ListView. y + height of the View should be less than or equal to y + height of the ListView if it is fully visible.
I want to be able to take a ListView and have a specific row be scrollable to the top of that Listview's bounds, even if the row is near the end and normally wouldn't be able to scroll that high in a normal android ListView (similar to how twitter works when you drill into a specific tweet and that tweet is always scrollable to the top even when there's nothing underneath it.)
Is there any way I can accomplish this task easily? I've tried measuring the row i want to scroll to the top and applying bottom padding to account for the extra space it would need, but that yields odd results (i presume because changing padding and such during the measure pass of a view is ill advised). Doing so before the measure pass doesn't work since the measured height of the cell in question (and any cells after it) hasn't happened yet.
Looks like you the setSelectionFromTop method of listview.
mListView.setSelectionFromTop(listItemIndex, 0);
I figured it out; its a bit complex but it seems to work mostly:
public int usedHeightForAndAfterDesiredRow() {
int totalHeight = 0;
for (int index = 0; index < rowHeights.size(); index++) {
int height = rowHeights.get(rowHeights.keyAt(index));
totalHeight += height;
}
return totalHeight;
}
#Override
public View getView(int position, View convertView, final ViewGroup parent) {
View view = super.getView(position, convertView, parent);
if (measuringLayout.getLayoutParams() == null) {
measuringLayout.setLayoutParams(new AbsListView.LayoutParams(parent.getWidth(), parent.getHeight()));
}
// measure the row ahead of time so that we know how much space will need to be added at the end
if (position >= mainRowPosition && position < getCount()-1 && rowHeights.indexOfKey(position) < 0) {
measuringLayout.addView(view, new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
measuringLayout.measure(MeasureSpec.makeMeasureSpec(parent.getWidth(), MeasureSpec.EXACTLY), MeasureSpec.UNSPECIFIED);
rowHeights.put(position, view.getMeasuredHeight());
measuringLayout.removeAllViews();
view.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
}
if (position == getCount()-1 && view.getLayoutParams().height == 0) {
// we know how much height the prior rows take, so calculate the last row with that.
int height = usedHeightForAndAfterDesiredRow();
height = Math.max(0, parent.getHeight() - height);
view.getLayoutParams().height = height;
}
return view;
}
This is in my adapter. It's a subclass of a merge adapter, but you can just put it in your code and substitute the super call with however you generate your rows.
the first if statement in getView() sets the layout params of a frame layout member var that is only intended for measuring, it has no parent view.
the second if statement calculates all the row heights for rows including and after the position of the row that I care about scrolling to the top. rowHeights is a SparseIntArray.
the last if statement assumes that there is one extra view with layout params already set at the bottom of the list of views whose sole intention is to be transparent and expand at will. the usedHeightForAndAfterDesiredRow call adds up all the precalculated heights which is subtracted from the parent view's height (with a min of 0 so we don't get negative heights). this ends up creating a view on the bottom that expands at will based on the heights of the other items, so a specific row can always scroll to the top of the list regardless of where it is in the list.
I added a linear layout in horizontal scroll view and in layout add some text views. Is it possible to get visible children in this layout.
This code get all child but i want to get visible (currently displaying) children only:
final HorizontalScrollView scroll = (HorizontalScrollView)findViewById(R.id.horizontalScrollView1);
LinearLayout linearLayout = ((LinearLayout)scroll.findViewById(R.id.linearLayout1));
int chilrenNum = linearLayout.getChildCount();
Well , after a bit of searching on SO I found this answer to listen to scroll events. Implement Scroll Event Listener in Android.
The idea is to override onScrollChanged in your ScrollView and keep track of the visible part of your scrollview in your activity.
Doing that you can easily get the visible views by a code that looks like this:
int currentPosition = lastXPosition; // lastXPosition gets updated from scroll event
int layoutWidth = linearLayout.getWidth();
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
int childWidth = layoutWidth/linearLayout.getChildCount();
int firstVisibleXPos = currentPosition - width/2; // currentPosition will be in the middle of the screen
int lastVisibleXPos = currentPosition + width/2;
int indexOfFirstVisible = firstVisibleXPos/childWidth;
int indexOfLastVisible = lastVisibleXPos/ childWidth;
All the above code assumes fixed child view sizes . if you are using variable child size you will need to get their width first and keep track of it then calculate the visiblity based on index and position in parent view.