Recalculate size of a ListView - android

I have a ListView, let's say with 10 items. Each item's layout is of height 10dp. It gives us ListView to be of height 100dp.
Now after clicking the item, I dynamically add some content to it, which causes item's height to increase by next 10dp, which in total gives 20dp height.
The problem is that when I increase the height of the item, ListView's height remains same as it was, which causes bottom item to disappear - ListView's height is 100dp whereas total height of items now is 110dp, which in the end results in disappearing item at the end.
My question is - how can I force ListView to recalculate its size based on new sizes of its items?
I tried using invalidate(), but it didn't work:
mTestsListView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View v, int position, long id) {
RelativeLayout basicControlsLayout = (RelativeLayout)v.findViewById(R.id.basicControlsLayout);
if(basicControlsLayout != null) {
if(basicControlsLayout.getVisibility() == View.VISIBLE) {
basicControlsLayout.setVisibility(View.GONE);
} else {
basicControlsLayout.setVisibility(View.VISIBLE);
adapterView.invalidate() // doesn't work
}
}
}
});
In the code snippet above, you can see that what I really do after clicking the item is changing visibility of a hidden layout inside it from GONE to VISIBLE, which causes item's height to increase.

try LinearLayout. ListView can handle scrollable/view reuse..., but not for dynamic height.

After setting to visible you need to call
listViewAdapter.notifyDataSetChanged();

Related

How to animate only selected items in RecyclerView

I have RecyclerView and I want to animate one View inside RecyclerView item only for items which meet certain condition.
I'm using ObjectAnimator for this. Sometimes it is working fine, but sometimes animation is applied to all views. I assume there is some recycling problem with Views, but I'm not certain what can cause that.
Example:
val halfHeightCropped = halfHeight - (stopIndicatorHeight/2) //half item height minus half timeline circle height
val halfDistCurrToNext = timeDistCurrToNext/2
if (alreadyTraveled > halfDistCurrToNext){
animator = ObjectAnimator.ofFloat(indicator, "translationY", halfHeightCropped.toFloat()).apply {
duration = 0
start()
}
}
I'm an animating circle that is moving vertically through a timeline based on real-time. This circle is part of each RecyclerView item and in some items, this item is stationarily centered in item View, and some (1 at the time) is animating this circle vertically.
The example above is part of the function which is called from onBindViewHolder if the item meets the condition, otherwise, the view is reset to a default position. (layout param center vertical in an item).
when item select from recyclerView select that into adapter like
yourAdapter = new yourAdapter(new ArrayLis(),new ItemOnClick(){
#Override
public void onClick(int position){
yourAdapter.selectItem(position)
}
});
perform animate in bind() method of holder class.
if(selectedItem = getAdapterPosition){
//perform your animation for the selected item
}
your adapter should have the selectedItem variable and setter method for that.

How to show Horizontal RecyclerView first 10 items and then swipe to show another 10 items in android?

I have a Vertical recyclerView with Horizontal recyclerView.Currently i am showing 10 elements in each section (Horizontal recyclerView).. If more than 10 elements i have to first 10 elements then i have to swipe show another 10 elements.
Thanks.
Group set of 10 items into single recycler item view and repeat that item using Horizontal RecyclerView as in
You want to show just 10 items at first and swipe to show more items, The better way is to let your recyclerview's width adaptives 10 items total width, so you can custom a LayoutManager for your horizontal recyclerview, and implement it's onMeasure method to set the width of the recyclerview.
```
#Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec,int heightSpec) {
View view = recycler.getViewForPosition(0);
measureChild(view, widthSpec, heightSpec);
//int recyclerViewW = ...;
//int recyclerViewH = ...;
//setMeasuredDimension(recyclerViewW, recyclerViewH);
}
```
By the way, if the items you want to show is not fixed in size, this way is not eligible.

Android: how to determine new size of a view

I have a LinearLayout (vertical) with two child views in it. The 1st one is a ScrollView and the 2nd one is another layout with Visibility.GONE, its size is not fixed (determined by its children).
<LinearLayout vertical>
<ScrollView> ... </ScrollView>
<AnotherLayout visibility=GONE height=wrap_content> ... </AnotherLayout>
</LinearLayout>
At some point of time I want to show AnotherLayout. But, once it pops up, I also want to adjust the scrolling of my ScollView one. For this, I need to know the size of this AnotherLayout.
I'm doing something like that:
int oldHeight = scrollArea.getHeight();
linearLayout.setVisibility(VISIBLE);
int newHeight = scrollArea.getHeight();
But oldHeight and newHeight are still the same.
How can I calculate the new height?
The two dimensions are the same because visibility change took time and the line of code was run before that so it returns the same.
You can use a visibility listener to calculate the dimension after visibility change , You may use that
linearLayout.setTag(linearLayout.getVisibility());
linearLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int newVis = linearLayout.getVisibility();
if((int)linearLayout.getTag() != newVis)
{
linearLayout.setTag(linearLayout.getVisibility());
//visibility has changed
int newHeight = scrollArea.getHeight();
}
}
});

android LinearLayoutManager scrollToPositionWithOffset not work if RecyclerView is too short

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.

keeping bottom element of ListView visible when keyboard appears

I have a LinearLayout containing both a ListView and an EditText. When the On-Screen keyboard is launched by touching the EditText, the ListView resizes so that only the top few elements remain visible.
The context that the ListView is being used in has the bottom few elements being more visually relevant than the top, and so I'd like for it to resize so that the bottom remains visible, rather than the top. Any pointers?
(Incidentally, the current fix I'm using involves using smoothScrollToPosition, but the laggy scroll behaviour makes this undesirable)
I just solved a similar issue this morning, and thought I'd post my result here for the benefit of future searchers. My issue was that scrolling to the bottom wasn't helping since I was calling it before the view actually changed size. The solution? Wait until it does change size by using a GlobalLayoutListener
Steps:
1) implement the following method in the activity holding the listview
public void scrollToBottom(){
//I think this is supposed to run on the UI thread
listView.setSelection(mAdapter.getCount() - 1);
}
2) create the following class
public class OnGlobalLayoutListenerWithScrollToBottom implements OnGlobalLayoutListener{
private boolean scroll;
private OnScrollToBottomListener listener;
public interface OnScrollToBottomListener{
public void scrollToBottom();
}
public OnGlobalLayoutListenerWithScrollToBottom(OnScrollToBottomListener listener){
this.listener = listener;
}
#Override
public void onGlobalLayout() {
if(scroll){
listener.scrollToBottom();
scroll = false;
}
}
/**
* Lets the listener know to request a scroll to bottom the next time it is layed out
*/
public void scrollToBottomAtNextOpportunity(){
scroll = true;
}
};
3) In your activity, implement the interface from this class. Then, in your activity, create an instance of this OnGlobalLayoutListener and set it as the listener for your listView
//make sure your class implements OnGlobalLayoutListenerWithScrollToBottom.OnScrollToBottomListener
listViewLayoutListener = new OnGlobalLayoutListenerWithScrollToBottom(this);
listView.getViewTreeObserver().addOnGlobalLayoutListener(listViewLayoutListener);
4) In your activity, before you make changes that will affect the size of list view, such as showing and hiding other views or adding stuff to the listview, simply let the layout listener know to scroll at the next opportunity
listViewLayoutListener.scrollToBottomAtNextOpportunity();
You might achieve this with setSelectionFromTop() and by overriding onSizeChanged() in a custom listview. In your layout, you should have a RelativeLayout has a parent container and place the listview above the edittext.
By creating your own listview and overriding onSizeChange(), you will be able to get the last visible item's position before the listview resizing and get its new height, in order to finally set the "previous" position of the list with an offset of its height.
How it works: Your list will place the previous last visible item at its top and you will add the pixels to scroll it at its bottom, just above your edittext.
To override the method and display it with an offset, do as follows:
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// get last visible item's position before resizing
int lastPosition = super.getLastVisiblePosition();
// call the super method to resize the listview
super.onSizeChanged(w, h, oldw, oldh);
// after resizing, show the last visible item at the bottom of new listview's height
super.setSelectionFromTop(lastPosition, (h - lastItemHeight));
}
lastItemHeight is a little workaround, because I didn't find how to get the last item's height before that onSizeChanged is called. Then, in the case of your listview contains many types of items (without the same height), I prefer to get the selected height when an event occurs, just before the SoftKeyboard opens up.
So in the custom listview, you have this global variable:
int lastItemHeight = 0;
And in the activity (or fragment, whatever), you update this value in OnClickListener:
edittext.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// set a new Thread
listview.post(new Runnable() {
#Override
public void run() {
// get last visible position of items
int lastPosition = listview.getLastVisiblePosition() - 1;
// if the view's last child can be retrieved
if ( listview.getChildAt(lastPosition) != null ) {
// update the height of the last child in custom listview
listview.lastItemHeight = listview.getChildAt(lastPosition).getHeight();
}
}
});
}
});
Note: there is another possible solution but you have to set android:stackFromBottom="true" on the listview, which stackes its content from the bottom. Instead, this solution here can display a specific item without forcing the content to start from the bottom, with the default listview's behaviour.
Second note: (just in case) don't forget to add android:windowSoftInputMode="adjustResize" in the manifest.
You should add this attribute in the manifest for your activity and pick the correct value for it (probably you will pick "adjustResize"):
<activity android:name="package.yourActivity" android:windowSoftInputMode="adjustResize"/>

Categories

Resources