Dynamic height viewpager for images (onMeasure() not working) - android

I have a custom ViewPager inside of an RecyclerView. I made wrap_content height of ViewPager and put onMeasure() function inside of CustomViewPager. But when I scroll down and up the RecyclerView, sometimes ViewPager is disappear. I look many SO answers but onMeasure() function not working every time for me. Method:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec); //Deneme
int height = 0;
for(int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
if(h > height) height = h;
}
if (height != 0) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
ViewPager XML:
<com.monster.vpagerapp.Utils.ViewPagerFixedPost
android:id="#+id/postsViewPager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
Also I use this in constructor of RecylerView:
setHasStableIds(true);
And use this in MainFragment
recyclerView.getRecycledViewPool().setMaxRecycledViews(1, 0); //for unnecessary refresh

Related

ViewPager in a ScrollView with dynamic page heights

I've read many other SO answers but nothing seems to be what I want. What I want is a ViewPager inside a ScrollView with the heights of each page being appropriate for the content. Some of the more accepted answers on SO seem to have to take the max height of the children in the ViewPager but that leads for empty space.
WrapContentViewPager
public class WrapContentViewPager extends ViewPager {
public WrapContentViewPager(Context context) {
super(context);
}
public WrapContentViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = 0;
View view = null;
for(int i = 0; i < getChildCount(); i++) {
view = getChildAt(i);
view.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = view.getMeasuredHeight();
if(h > height) height = h;
}
if (height != 0) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getMeasuredWidth(), measureHeight(heightMeasureSpec, view));
}
/**
* Determines the height of this view
*
* #param measureSpec A measureSpec packed into an int
* #param view the base view with already measured height
*
* #return The height of the view, honoring constraints from measureSpec
*/
private int measureHeight(int measureSpec, View view) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
// set the height from the base view if available
if (view != null) {
result = view.getMeasuredHeight();
}
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
}
The reason why this doesn't work is because if I go to the third page, which has a large height with 32 items in the list, then go back to the second page with only 3 items, there is a lot of empty space in the second page since it took the height of the third page as max.
I've tried WCViewPager library on GitHub at https://github.com/rnevet/WCViewPager and it doesn't work for me as well.
Could someone guide me to a correct solution? I am using a ViewPager with just PagerAdapter, not FragmentPagerAdapter by the way.
UPDATE
I figured out a better way to solve it.
`public class WrapContentViewPager extends ViewPager {
public WrapContentViewPager(Context context) {
super(context);
}
public WrapContentViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
// Unspecified means that the ViewPager is in a ScrollView WRAP_CONTENT.
// At Most means that the ViewPager is not in a ScrollView WRAP_CONTENT.
if (mode == MeasureSpec.UNSPECIFIED || mode == MeasureSpec.AT_MOST) {
// super has to be called in the beginning so the child views can be initialized.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int position = getCurrentItem();
View child = this.findViewWithTag("view"+position);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int height = child.getMeasuredHeight();
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
// super has to be called again so the new specs are treated as exact measurements
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}`
and in my PagerAdapter I changed the method instantiateItem
#Override
public #NonNull Object instantiateItem(#NonNull ViewGroup view, int position) {
View layout = inflater.inflate(R.layout.layout, view, false);
layout.setTag("view" + position);
}
This solution remeasures the height at the current page every time it is moved. getChildAt(getCurrentItem()) gives different results so it's not reliable.

How to make ViewPager's height wrap_content

I tried to refer[https://medium.com/#nikhil4092/how-to-have-a-height-wrapping-viewpager-when-images-have-variable-heights-on-android-60b18e55e72e] this link for making the viewpager's height wrap_content but it didn't work.I've tried several questions of stackoverflow but none of them could address my problem.When I'm giving the height as wrap_content nothing is shown
Code:
public class HeightWrappingViewPager extends ViewPager {
public HeightWrappingViewPager(Context context) {
super(context);
}
public HeightWrappingViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
// Unspecified means that the ViewPager is in a ScrollView WRAP_CONTENT.
// At Most means that the ViewPager is not in a ScrollView WRAP_CONTENT.
if (mode == MeasureSpec.UNSPECIFIED || mode == MeasureSpec.AT_MOST) {
// super has to be called in the beginning so the child views can be initialized.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
if (h > height) height = h;
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
// super has to be called again so the new specs are treated as exact measurements
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
XML:
<com.project.test.HeightWrappingViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent">
I'm not sure is it helps you.
I used the code below to images with different height.
Almost like your code, but i save height like a field.
public class MeasuredViewPager extends ViewPager {
private int mMaxHeight = 0;
public MeasuredViewPager(Context context) {
super(context);
}
public MeasuredViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
if (h > mMaxHeight) mMaxHeight = h;
}
if (mMaxHeight != 0) heightMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxHeight, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}

Gravity of a child TextView does not work with custom ViewGroup

I have the following classes:
public class MyGridView extends ViewGroup {
...
#Override
public void onSizeChanged(int xNew, int yNew, int xOld, int yOld) {
super.onSizeChanged(xNew, yNew, xOld, yOld);
// do calculations for drawing bounds of child views
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
View child;
Rect rect;
for (int i=0; i < getChildCount(); i++) {
child = getChildAt(i);
rect = getBoundsAtChildIndex(i)
child.layout(rect.left, rect.top, rect.right, rect.bottom);
}
}
...
}
AND
public class MyAdapter {
public void setMyGridView(MyGridView myGridView) {
// add a single TextView to my grid view
textView = createTextView(context);
addTextViewToGrid(textView);
container.add(textView);
}
private TextView createTextView(Context context) {
TextView textView = new TextView(context);
textView.setText("1");
textView.setTextColor(0xffffffff);
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f);
textView.setTypeface(typeface);
textView.setGravity(Gravity.CENTER | Gravity.FILL);
// textView.setGravity(Gravity.RIGHT); // other tries to see gravity
// textView.setGravity(Gravity.FILL);
return textView;
}
private void addTextViewToGrid(TextView textView) {
myGridViewPtr.addView(textView, ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
}
...
}
I have created a custom grid view and adapter, where its children draw to the screen to the correct size and location. However, their gravity, set in the createTextView() is not observed.
I can set a background image to the textView and see it fill its space in the grid.
In contrast, the text in textView always draws in its top left corner, and it always stays at the text size I set it at 12sp, rather than scale to fit using Gravity.FILL.
Preferably, the text would scale to fit and center within the textview.
EDIT
I have added the following method for onMeasure() in ViewGroup:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
doUpdateOnSizeChanged(width, height);
for (int i = 0; i < getChildCount(); i++)
getChildAt(i).measure((int) viewDim.x, (int) viewDim.y);
setMeasuredDimension(width, height);
}
The children are supposed to be the same height, width as each other. The android example code for ViewGroup is more sophisticated than I require. For instance, I am not getting the max of all children width, height.
For my onMeasure(), the child width, height in ViewDim is width, height integers that are computed in doUpdateOnSizeChanged to be less than getMeasuredWidth, getMeasuredHeight.
The result is now text aligns in the bottom-left corner of the TextView.
onMeasure needed minor corrections, basically needing to rely on MeasureSpec:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width, height;
int cWidth1, cHeight1;
width = MeasureSpec.getSize(widthMeasureSpec);
height = MeasureSpec.getSize(heightMeasureSpec);
doUpdateOnSizeChanged(width, height);
cWidth1 = (int)viewDim.x; cHeight1 = (int)viewDim.y;
measureChildren(MeasureSpec.makeMeasureSpec(cWidth1, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(cHeight1, MeasureSpec.EXACTLY));
setMeasuredDimension(width, height);
}

How to Measure child expandable list height and width

I am using expandable list view to make 3 - level hierarchy, would like to know how to set internal list height and width.
I knew we have onMeasure for this purpose but in my case it not allowing me to capture whole space of parent list view.
may be I am giving wrong value to it, here is the code which is I am using for setting height and width of child expandable list.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
widthMeasureSpec = MeasureSpec.makeMeasureSpec(960,MeasureSpec.AT_MOST);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(800,MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
Currently it appearing as follow
<ParentGroup1 >
<ChildParentGroup>
<Child1>
<Child2>
<child3>
<ParentGroup2 >
and it should appear like below.
<ParentGroup1 >
<ChildParentGroup >
<Child1 >
<Child2 >
<child3 >
<ParentGroup2 >
Please advise/suggest for the same.
Thanks for your time.
Not sure if you're still looking for an answer, but this is how I did it: pass a reference to the parent view and a height measure (in this case, I used the size of the child list) in the constructor to create the child custom list.
public CustomExpandableList(Context context, View the_parentView, int the_heightMeasure)
{
super(context);
WIDTH = the_parentView!=null?the_parentView.getWidth():LayoutParams.MATCH_PARENT;
HEIGHT = the_heightMeasure * 500;
}
EDIT: Or to make the code more consistent, you could pass the width of the parentView and height measure to the constructor instead of passing the parent view itself.
CustomExpandableList(Context the_context, int the_width, int the_heightMeasure)
use this code to calculate expandable list view dynamically:
// calculate the height of expandable listView without expanded
private void setListViewHeight(ExpandableListView expListView) {
ListAdapter listAdapter = expListView.getAdapter();
int totalHeight = 0;
for (int i = 0; i < listAdapter.getCount(); i++) {
View listItem = listAdapter.getView(i, null, expListView);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
System.out.println("i " + i);
}
ViewGroup.LayoutParams params = expListView.getLayoutParams();
params.height = totalHeight
+ (expListView.getDividerHeight() * (listAdapter.getCount() - 1));
System.out.println("params.height = " + params.height);
expListView.setLayoutParams(params);
expListView.requestLayout();
}
// calculate the height of expandable listview dynamically
private void setListViewHeight(ExpandableListView expListView, int group) {
ExpandableListAdapter listAdapter = expListView
.getExpandableListAdapter();
int totalHeight = 0;
int desiredWidth = MeasureSpec.makeMeasureSpec(expListView.getWidth(),
MeasureSpec.UNSPECIFIED);
for (int i = 0; i < listAdapter.getGroupCount(); i++) {
View groupItem = listAdapter.getGroupView(i, false, null,
expListView);
groupItem.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
totalHeight += groupItem.getMeasuredHeight();
if (((expListView.isGroupExpanded(i)) && (i == group))
|| ((!expListView.isGroupExpanded(i)) && (i == group))) {
for (int j = 0; j < listAdapter.getChildrenCount(i); j++) {
View listItem = listAdapter.getChildView(i, j, false, null,
expListView);
Log.e("Count", listAdapter.getChildrenCount(i) + "");
listItem.setLayoutParams(new ViewGroup.LayoutParams(
desiredWidth, MeasureSpec.UNSPECIFIED));
// listItem.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
listItem.measure(MeasureSpec.makeMeasureSpec(0,
MeasureSpec.UNSPECIFIED), MeasureSpec
.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
totalHeight += listItem.getMeasuredHeight();
System.out.println("totalHeight" + totalHeight);
Log.e("TEST", "gshdkfmjfy,");
}
}
}
ViewGroup.LayoutParams params = expListView.getLayoutParams();
int height = totalHeight
+ (expListView.getDividerHeight() * (listAdapter
.getGroupCount() - 1));
if (height < 10) {
height = 100;
}
params.height = height;
expListView.setLayoutParams(params);
expListView.requestLayout();
}
I succeeded in some days ago by doing this. It's a little bit more compact and without any additionnal parameter, and it works perfectly.
public static void setExpandableListViewHeightBasedOnChildren(ExpandableListView expandableListView){
ExpandableNotesListAdapter adapter = (ExpandableNotesListAdapter) expandableListView.getExpandableListAdapter();
if (adapter == null){
return;
}
int totalHeight = expandableListView.getPaddingTop() + expandableListView.getPaddingBottom();
for (int i = 0 ; i < adapter.getGroupCount() ; i++){
View groupItem = adapter.getGroupView(i, false, null, expandableListView);
groupItem.measure(View.MeasureSpec.UNSPECIFIED,View.MeasureSpec.UNSPECIFIED);
totalHeight += groupItem.getMeasuredHeight();
if (expandableListView.isGroupExpanded(i) ){
for( int j = 0 ; j < adapter.getChildrenCount(i) ; j++) {
View listItem = adapter.getChildView(i, j, false, null, expandableListView);
listItem.setLayoutParams(new LayoutParams(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED));
listItem.measure(View.MeasureSpec.makeMeasureSpec(0,
View.MeasureSpec.UNSPECIFIED), View.MeasureSpec
.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
totalHeight += listItem.getMeasuredHeight();
}
}
}
ViewGroup.LayoutParams params = expandableListView.getLayoutParams();
int height = totalHeight + expandableListView.getDividerHeight() * (adapter.getGroupCount() - 1);
if (height < 10)
height = 100;
params.height = height;
expandableListView.setLayoutParams(params);
expandableListView.requestLayout();
}
Don't forget to add this when you init your View, set your adapter, etc. :
Functions.setExpandableListViewHeightBasedOnChildren(listView);
listView.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() {
#Override
public void onGroupExpand(int groupPosition) {
Functions.setExpandableListViewHeightBasedOnChildren(listView);
}
});
Create one layout xml file for ParentGroup and ChildParentGroup , another layout xml file for Child. Now you are problem is reduced to two level hierarchy. Then In Expandable listview we have Parent view and childview methods to inflate and use the Parent and Child layouts. So in that mehods you can do whatever you want.
Simply remove the width code and it should work fine.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
heightMeasureSpec = MeasureSpec.makeMeasureSpec(999999, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
I know its late, but if anyone has the same issue. You can solve it by creating a Custom ExpandableListView and using "MeasureSpec.EXACTLY":
public class CustomExpListView extends ExpandableListView{
public CustomExpListView(Context context){
super(context);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
widthMeasureSpec = MeasureSpec.makeMeasureSpec(960, MeasureSpec.EXACTLY);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(20000, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
Hope this helps to anyone. For me its working.
Adding to muhammadSalem's answer. This is how I solved my problem by calculating the height of expandableListView's children's total height.
private fun getSubItemTotalHeight(groupPosition: Int): Int {
val children: Int = mAdapter.getChildrenCount(groupPosition)
val desiredWidth: Int = View.MeasureSpec.makeMeasureSpec(mExpandableListView.width,
View.MeasureSpec.UNSPECIFIED)
var subItemTotalHeight = 0
repeat(children) {
val child = mAdapter.getChildView(groupPosition, it, true, null, null)
child.layoutParams = ViewGroup.LayoutParams(
desiredWidth, View.MeasureSpec.UNSPECIFIED)
child.measure(View.MeasureSpec.makeMeasureSpec(0,
View.MeasureSpec.UNSPECIFIED), View.MeasureSpec
.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
subItemTotalHeight += child.measuredHeight
}
val dividerCount = children - 1
val dividerTotalCount = (dividerCount * mExpandableListView.dividerHeight).toFloat()
showToast(mExpandableListView.dividerHeight.toString())
val totalDividerPixels = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dividerTotalCount,
resources.displayMetrics
)
return subItemTotalHeight + totalDividerPixels.toInt()
}
One thing to note is that if you added a divider height for your expandableListview, you should include the calculations for it. What I did is convert the total divider height which is in dp into pixels and added it into the totalHeight. This solved the clipping issues I encountered.
Then to use it would be just :
mExpandableListView.setOnGroupExpandListener { groupPosition ->
mExpandableListView.layoutParams.height += getSubItemTotalHeight(groupPosition)
mExpandableListView.requestLayout()
}
mExpandableListView.setOnGroupCollapseListener { groupPosition ->
mExpandableListView.layoutParams.height -= getSubItemTotalHeight(groupPosition)
mExpandableListView.requestLayout()
}

Android onMeasure change View's parameters twice

I've created a new view (extends ImageView) and override onMeasure method.
Xml code:
<by.bsuir.klondike.mainCardsView
android:id="#+id/imageview1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="fitEnd"
android:baselineAlignBottom="true"
android:src="#drawable/cardsmain"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
/>
At first I overrode a method like this
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
width = parentWidth/2;
height = 210*width/498;
this.setMeasuredDimension(width, height);
this.setLayoutParams(new *ParentLayoutType*.LayoutParams(width,height));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
According to Android developer onMeasure calls twice. On it's first call I receive parentWidth == 480 and parentHeight == 320. But on second call I receive parentWidth == 240 and parentHeight == 101.
It turns out, that the size is changing twice.
I've solve this problem using bool flag and change dimension only on first call.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (flag){
flag=false;
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
width = parentWidth/2;
height = 210*width/498;
}
this.setMeasuredDimension(width, height);
}
But I think that it's a wrong way.
How can I do it right?

Categories

Resources