Synchronize Scroll position of ScrollView across all fragments inside ViewPager - android

So I have an activity with a view pager that inflates about 7 Fragments, and each fragment has a ScrollView inside it, I want to synchronize the scroll of all 7 scrollviews, so if the user scrolls to a position on fragment 3 and moves on to fragment 5 they should be at the exact same position, the scrollviews inflate the same children so the total height of the scrollview is constant throughout all 7 fragments.
I go some kind of system working, but its kind of a hit or miss, i get the scroll position of a list and broadcast the position and the fragment that is idle consumes the broadcast, and I save the position on the activity too so that the position can be requested in onResume of fragments that have not been inflated yet. Here is my code:
#Override
public void onResume() {
super.onResume();
mParentScrollView.setScrollY(mParentActivity.getScrollPos());
}
#Override
public void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldX, int oldY) {
mScrollListener.onScroll(y);
mParentActivity.setScrollPos(y);
if (callbackSwitch) {
Intent intent = new Intent("ACTION_SCROLL");
intent.putExtra("TRANSLATION", y);
LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent);
}
callbackSwitch = true; //Switch to stop recursive calls
}
private BroadcastReceiver mTranslationReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (!isVisible) {
int translation = intent.getIntExtra("TRANSLATION", 0);
mParentScrollView.setScrollY(translation);
callbackSwitch = false;
}
}
};
I wanted to know if there is a better and stable way of achieving something similar.
Thanks in Advance.

Not sure how elegant this solutions is, but it should do the trick. Extend ViewPager and override onMeasure(int, int) method.
#Override
protected void onMeasure(int widthMeasureSpec, int 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.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
My custom ViewPager was inside of a NestedScrollView and both of child fragments did not contained any additional ScrollViews.

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.

Android: custom views get drawn at wrong x,y coordinates

I'm trying to create a custom view, inherit from view group, and layout custom sub-views inside this view group in a customized way. Basically I'm trying to create a calendar view similar to the one in outlook, where each event takes up screen height relative to its length.
I initialize an ArrayList of View in the ViewGroup's constructor, override onMeasure, onLayout and onDraw, and everything works well, except... the rendered views all render starting at (0,0), even though I set their left and right properties to other values. Their width and height come out ok, only their top and left are wrong.
This is the code, which I abbreviated for clarity and simplicity:
public class CalendarDayViewGroup extends ViewGroup {
private Context mContext;
private int mScreenWidth = 0;
private ArrayList<Event> mEvents;
private ArrayList<View> mEventViews;
// CalendarGridPainter is a class that draws the background grid.
// this one works fine so I didn't write its actual code here.
// it just takes a Canvas and draws lines on it.
// I also tried commenting out this class and got the same result,
// so this is DEFINITELY not the problem.
private CalendarGridPainter mCalendarGridPainter;
public CalendarDayViewGroup(Context context, Date date) {
super(context);
init(date, context);
}
//... other viewGroup constructors go here...
private void init(Date date, Context context) {
mContext = context;
// the following line loads events from a database
mEvents = AppointmentsRepository.getByDateRange(date, date);
// inflate all event views
mEventViews = new ArrayList<>();
LayoutInflater inflater = LayoutInflater.from(mContext);
for (int i = 0; i < mEvents.size(); i++) {
View view = getSingleEventView(mEvents.get(i), inflater);
mEventViews.add(view);
}
// set this flag so that the onDraw event is called
this.setWillNotDraw(false);
}
private View getSingleEventView(Event event, LayoutInflater inflater) {
View view = inflater.inflate(R.layout.single_event_view, null);
// [set some properties in the view's sub-views]
return view;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
// get screen width and create a new GridPainter if needed
int screenWidth = MeasureSpec.getSize(widthMeasureSpec);
if (mScreenWidth != screenWidth)
{
mScreenWidth = screenWidth;
mCalendarGridPainter = new CalendarGridPainter(screenWidth);
}
int numChildren = mEvents.size();
for (int i = 0; i < numChildren; i++) {
View child = mEventViews.get(i);
Event event = mEvents.get(i);
// event width is the same as screen width
int specWidth = MeasureSpec.makeMeasureSpec(mScreenWidth, MeasureSpec.EXACTLY);
// event height is calculated by its length, the calculation was ommited here for simplicity
int eventHeight = 350; // actual calculation goes here...
int specHeight = MeasureSpec.makeMeasureSpec(eventHeight, MeasureSpec.EXACTLY);
child.measure(specWidth, specHeight);
}
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int numChildren = mEvents.size();
for (int i = 0; i < numChildren; i++) {
View child = mEventViews.get(i);
Event event = mEvents.get(i);
int eventLeft = 0;
int eventTop = (i + 1) * 200; // test code, make each event start 200 pixels after the previous one
int eventWidth = eventLeft + child.getMeasuredWidth();
int eventHeight = eventTop + child.getMeasuredHeight();
child.layout(eventLeft, eventTop, eventWidth, eventHeight);
}
}
#Override
protected void onDraw(Canvas canvas) {
// draw background grid
mCalendarGridPainter.paint(canvas);
// draw events
for (View view : mEventViews) {
view.draw(canvas);
}
super.onDraw(canvas);
}
}
For some reason, it seems like the way children are drawn with ViewGroups is that the ViewGroup translates the canvas to child's position then draws the child at 0,0.
But as it turns out, ViewGroup will handle all the drawing of children for you. I think if you simplify your onDraw() method you should be all set:
#Override
protected void onDraw(Canvas canvas) {
// draw background grid
mCalendarGridPainter.paint(canvas);
// draw events
super.onDraw(canvas);
}
Now that I'm looking at your code further, I noticed you are inflating your child views within the code for your ViewGroup. It would be best to do all that outside your ViewGroup, add those views using addView(), then use getChildCount() and getChildAt() to access the child views during onLayout().

How do i know, when view is drawn?

My Activity layout has View, i have get Y position this view at the beginning of the program, but if i shall insert in bottom method onCreate , i get position value = 0. How i can know, when view is drawn and get her Y position. I try get Y position when i click on button, position get good, but i have get position without using the button.
I get position:
private float cardsStartPosition = 0;
if (cardsStartPosition == 0) {
cardsStartPosition = commonCardContainer.getY();
}
Thanks for answer.
Try this method..It works fine for me.
ViewTreeObserver vto = YourView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
//do your stuff here
}
});
Add a global layout listener to your view, you will get notified when layout is done and your view has dimensions ...
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
//Here you can get your dimensions
int width = view.getWidth();
//View.removeOnGlobalLayoutListener(this);
}
});
You mention just View. So if that means it's your custom view, you can override the onLayout():
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
...;
}
This way you can observe any layout changes of your view. If you're also interested in size changes, there's also the onSizeChanged():
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
...;
}
If it's not your custom view, I'd go with the Ketan Ahir's way.

Changing the ListView item height inside the onScroll() causes a warning

I have a Listview and I am changing the ListView item height dynamically inside the onScroll callback.
The issue is that I am having the following warning:
05-29 14:41:16.935: W/View(1629): requestLayout() improperly called by android.widget.RelativeLayout{42701a80 V.E..... ......ID 0,0-768,600 #7f060133 app:id/parent} during layout: running second layout pass
And the onScroll callback is always being called, even after the user is not scrolling it, here is my code:
listview.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (adapter != null) {
for (int i = 0; i < visibleItemCount; i++) {
//the listview view
View v = adapter.getParentView(firstVisibleItem + i);
if (v != null) {
LayoutParams lp = v.getLayoutParams();
lp.height = //some height manipulation according to the view position
v.setLayoutParams(lp); //this line causes the warning
}
}
}
}
});
My goal is to change the listview items height by 1px every time depanding on the items position on the screen.
Question: how can I set the view LayoutParams inside the onScroll callback
Well the solution was posting a runnable on the looper with the setLayoutParams() call.
v.post(new Runnable() {
#Override
public void run() {
v.setLayoutParams(lp);
}
});

android- How to get the width and height of the list view dynamically

In my application i want display list view using adapter. But i want get the current height and width of the list view (means after generating the list using adapter). how to get it. can anybody help me.
thanks
Use getWidth() and getHeight() method of ListView in onWindowFocusChanged to get the width and height.
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
System.out.println("Width:" + listview.getWidth());
System.out.println("Height:" + listview.getHeight());
}
To get current width or height of any view, you need to set onLayoutChange listener
public class MainActivity extends Activity implements OnLayoutChangeListener {
private View newView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
newView = getLayoutInflater().inflate(R.layout.main_activity, null);
newView.addOnLayoutChangeListener(this);
setContentView(newView);
}
public void onLayoutChange(View v, int left, int top, int right,
int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
//Here you can get size of you ListView, e.g:
mylistView.getWidth();
}
#Override
protected void onDestroy() {
super.onDestroy();
newView.removeOnLayoutChangeListener(this);
}
}
27Nov15 - This is a good answer, I think you should release the resource too though. This might not matter so much for an Activity, but the same approach can be used by fragments where they may come and go more frequently.

Categories

Resources