My issue is quite complex and I have hard time finding a solution to achieve the following :
I need to have a sort of legend (a stepper) layout where a child is made of left bar, followed by a circle, followed by a right bar. Strickly speaking somethind like "-o-". So a serie of children looks like this : "-o-o-o-o-o". When the legend focuses on a child meaning that the current child is this one, this child has a bigger circle and longer bars: "--O--".
The final rendering looks therefore like this : "-o-o--O--o-o-".
Each child ("-o-") corresponds to a specific item in a database. When a child gets focuses ("--O--"), the relative item is displayed below. When the user swipes from right to left, the next item is showed, whereas a swipe from left to right will display the previous item.
When swiping, to show which child will display its corresponding item, say child will be animated to show it has focus. I use a Animator to :
increase the bars widths and the circle size of the child that is
getting the focus
to decrease these values of the previously focused child.
Displaying a legend requires to see what's before and what's next.
I believe using a ViewPager will help me achieve the following :
it has an "anchor effect", meaning a swipe will move from child to child, centering on the circle: the circle between the bars is the anchor, so to speak.
I can display multiple pages on the screen by overriding the getPageWidth() method of my custom adapter
The other solution I had in mind is the HorizontalScrollView.
If I were to use it, depending on the swipe properties (strengh, speed), the legend would indeterminately scroll without anchoring the circle.
But using the ViewPager has a big problem: each page has the same width. However, the anchored child of the legend needs to be bigger (so needs more width).
So how can I change the width dynamically ?
Unless I can customize a HorizontalScrollView to add the anchor effect by intercepting in swipe ?
There is also the CarouselLayoutManager which I think may include the effect you desire.
If you are using a ViewPager, I think the best way to do this would be with an OnPageChangeListener. You will get callbacks as the user is swiping left/right so that you can size your indicators based on the location of the user's swipe.
private int mCurrentPage = 0;
viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// positionOffset goes from 0.0 to just under 1.0 for the leftmost page
float otherOffset = 1.0 - positionOffset;
if (position == mCurrentPage) { // user is incrementing page, i.e. swiping left
// size of current page indicator based on otherOffset
// size of next page indicator based on positionOffset
} else { // user is decrementing page, i.e. swiping right
// size of current page indicator based on positionOffset
// size of previous page indicator based on otherOffset
}
}
#Override
public void onPageSelected(int position) {
mCurrentPage = position;
// ... set final sizes: page # position = 1.0 all others 0.0
}
});
Here's how to understand position and positionOffset: When a page has settled, it is centered. Let's say the current page position == 1 (i.e. page 2).
First case: User swipes left to get to the next page. Say the user swipes 10% of the distance to get to the next page. position will be 1 (leftmost displayed) and positionOffset will be 0.1 (viewport left edge is at 10% of leftmost page). So as user swipes, positionOffset will go from 0.0 to just below 1.0.
Second case: User swipes right to get to the previous page. When the user swipes 10% of the distance to the previous page, position will be 0 (leftmost displayed) and positionOffset will be 0.9 (viewport left edge is at 90% of the leftmost page). So as user swipes, positionOffset will go from just under 1.0 to 0.0.
As for the indicators themselves, I did a similar pager indicator where I used a LinearLayout with a horizontal orientation, adding a child indicator view for each page. I used pagerAdapter.getCount() to get the number of views to add.
Your indicator is more complex with the animation, but if I were to do this, I would use a plain View with ScaleDrawable backgrounds for graphics that could change size.
Related
I have some tabs in a ViewPager. Each tab except the last displays the FloatingActionButton at the bottom right corner. The last tab is supposed to display it in the bottom center, after translating it while this tab is being scrolled to. When scrolling away from this tab, the FloatingActionButton translates back into its bottom right position. I was implementing a solution, and was surprised to find that this, more or less, was all it took:
mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// One page away from last page
if (position == mAdapter.getCount() - 2) {
mFab.setTranslationX(positionOffsetPixels / -2f);
}
}
}
When I scroll to the last tab, we're translating the FAB to the left of its current position, because the argument passed to setTranslationX() is negative. Now, here's where I'm confused. When I scroll back one page, the FAB translates back to the right as desired--but how can this be? The argument to setTranslationX() is still negative. I expected the FAB to translate further left.
When you're scrolling forward from the position mAdapter.getCount() - 2, the values of positionOffsetPixels will be increasing positive numbers and positionOffsetPixels / -2f will be increasing negative numbers, so the FAB will translate to the left, as expected.
From the documentation of position:
Position index of the first page currently being displayed.
This means when scrolling back, the offset values will be relative to the page you're scrolling to, not to the page you're scrolling from.
When you're scrolling back from mAdapter.getCount() - 1, the value of position is mAdapter.getCount() - 2 (this is the position you're scrolling to) and the values of positionOffsetPixels will be decreasing positive numbers going down to 0.
0 / -2f == 0, so eventually mFab.setTranslationX(0) will be called, returning the FAB to its original position.
Although This is a solution ViewPager with previous and next page boundaries but it has limited scroll to only visible current image and does not work when i try to scroll from extreme left or right.
I want side images from left and right to be a little smaller and centre image to be on focus and larger like the image shown and with the indicator and text.
Not sure that got you right, but something similar I've done with:
pager.setClipToPadding(false);
pager.setPadding(50, 0, 50, 0);
UPD.
ViewPager indicator
In order to scale not visible fragments, try to use setOnPageChangeListener, and scale position - 1 and position + 1 fragment's view. Here how to take reference on fragment from ViewPager.
I want to scroll to specific item depending upon where user has clicked. All my items of list view are of different heights. Please see the image below.
So if user clicks on the list item 1, lets suppose, so it's bottom should be scrolled till the top of the softkeyboard.
Heights of items and keyboard vary. I have tried getTop, getHeight, getMeasuredHeight but nothing has been working so far.
Main point is to find the distance between the clicked UI and your keyboard top.
This is the distance formula. You can achieve your functionality using this. You need
Clicked Coordinates
Coordinates of your listview item
Height of your listview item
Suppose following is your ListView, that is being clicked
int[] loc = new int[2];
lvItem_button.getLocationOnScreen(loc);
Put a layout at the bottom of your layout, mark your activity as AdjustPan in Android Manifest, now it will always be at the top of keybaord, when ever the keyboard will open.
Get it's coordinates the same way.
int[] cords= new int[2];
keyboard_top_layout.getLocationOnScreen(cords);
double distance = Math.sqrt(Math.pow((cords[0] - loc[0]), 2) + Math.pow((cords[1] - loc[1]), 2));
distance -= heightOfItem; //subtract height because we want to align bottom of item to top of keyboard
distance = Math.ceil(distance);
lvItem.smoothScrollBy((int) -distance, 500); //-distance because X is increasing at bottom and decreasing at top. 500 is delay in scrolling. so it's smooth scroll
All done :)
So I've searched high and low but couldn't find an answer to my question. What I basically need is the behavior provided by Android's ViewFlipper or ViewPager but I want to display portions of the left and right views, to indicate the user there are elements to scroll, instead of having the selected view occupying the whole screen.
I would also like to add some effects to the left and side views, like dimming and scaling then down a little. Is it possible to do it with the stock ViewFlipper or ViewPager or do I have to roll out my own view group, à la cover flow (http://www.inter-fuser.com/2010/01/android-coverflow-widget.html)?
(P.S. I don't want to use the Gallery widget, that component sucks).
This is what we need to display once a view is selected (left and right views are still displayed):
Flinging left or right would transition the main view out, dimming and reducing it a little and doing the opposite with the next or previous view.
I would like to give an update to anyone who might want the same feature. A lot of progress has been made to implement this feature so far and now the view is working exactly as we need it to.
The ViewPager has a method called setPageMargin(). This method can receive a negative value which will make the fragments/views to overlap each other. To arrive at the desired layout, we first dynamically calculated the left and right margins by a percentage of the screen. This can be done statically as well but since we will be targeting a range of different screen sizes, this seems to be the best approach.
Later we set the ViewPager's page margin to 2 times the size of the side margins. This makes the views snap back together. However, at this time, there will be more than one view being displayed by the ViewPager.
All you have left to do is to either apply a transform (scale) to the views to the left and right (Android 3.0+) or add some more margins around them to shrink them to the right size (pre 3.0).
The OnPageChangeListener.onPageScrolled() can be used to track the ViewPager's scrolling. A smooth
transformation can be achieved. The code looks like this:
private OnPageChangeListener onPageChangeListener = new OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// positionOffset varies from 0 to 1 and indicates how far the view is
// from the center of the ViewPager. When a view is selected (centered), this
// value is 0.
// Fades the text in an out while the ViewPager scrolls from one view to another.
// On our final design the text views are fixed to the center of the screen and
// do not scroll along with the views.
if (positionOffset < 0.5) {
setTextViewAlpha(1 - positionOffset * 2);
} else {
setTextViewAlpha((positionOffset - 0.5f) * 2);
}
// It's surprisingly complicated to get the current object being displayed by
// a ViewPager (there's no getCurrentObject method). ScaleableFragment is just
// a custom class to allow an adapter to cache it internally and also correctly
// manage a fragment's lifecycle and restore from a saved state.
ScaleableFragment sampleFragment = (ScaleableFragment) ((ShowHeroShotImageFragmentPagerAdapter) pager
.getAdapter()).getItem(position);
// Calculates by how much the current view will be scaled down. The RATIO_SCALE
// is 0.3 in our case, which makes the side views 70% of the size of the
// center view. When centered, the scale will be 1. When
// fully scrolled, the scale will be 0.7.
float scale = 1 - (positionOffset * RATIO_SCALE);
// Just a shortcut to findViewById(R.id.image).setScale(scale);
sampleFragment.scaleImage(scale);
// Now, if not in the last element, scale the next one up (in opposite direction).
if (position + 1 < pager.getAdapter().getCount()) {
sampleFragment = (ScaleableFragment) ((ShowHeroShotImageFragmentPagerAdapter) pager.getAdapter())
.getItem(position + 1);
scale = positionOffset * RATIO_SCALE + (1 - RATIO_SCALE);
sampleFragment.scaleImage(scale);
}
}
// Once scrolling is done. Make sure the views are in the right scale (1 for center,
// 0.7 for sides). Required as the onPageScrolled() method does not guarantee it
// will interpolate to 1.0 precisely.
#Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
setTextViewAlpha(1);
ScaleableFragment sampleFragment = (ScaleableFragment) ((ShowHeroShotImageFragmentPagerAdapter) pager
.getAdapter()).getItem(pager.getCurrentItem());
sampleFragment.scaleImage(1);
sampleFragment.enableClicks();
if (pager.getCurrentItem() > 0) {
sampleFragment = (ScaleableFragment) ((ShowHeroShotImageFragmentPagerAdapter) pager.getAdapter())
.getItem(pager.getCurrentItem() - 1);
sampleFragment.scaleImage(1 - RATIO_SCALE);
sampleFragment.disableClicks();
}
if (pager.getCurrentItem() + 1 < pager.getAdapter().getCount()) {
sampleFragment = (ScaleableFragment) ((ShowHeroShotImageFragmentPagerAdapter) pager.getAdapter())
.getItem(pager.getCurrentItem() + 1);
sampleFragment.scaleImage(1 - RATIO_SCALE);
sampleFragment.disableClicks();
}
}
}
};
This is it. I didn't post the full solution but I hope this is enough to get someone else started.
P.S. On 3.0+ enable hardware acceleration. Without it, the scrolling looked choppy on a samsung galaxy tab 10.1.
I played some with it, and my solution is pretty simple. I'm posting it here in case someone is interested.
First, the layout for each fragment should allow some gaps on either side. the simplest way I found of doing this is using weights:
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.1" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.8"
android:orientation="vertical" >
<!-- fragment contents go here -->
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.1" />
This will leave a nice 10% margin on either side. Be sure not to set a background on these, since it will cover the neighbor fragments.
Now for the pager itself: you need to use negative margins, as AngraX explained, but also be sure to pre-load the subsequent fragments, as they are visible before they would be regularly. I got good results with this:
mPager = (ViewPager) findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
mPager.setPageMargin(getResources().getDisplayMetrics().widthPixels / -7);
mPager.setOffscreenPageLimit(2);
the -7 value leaves the edge of the next fragment visible, play around with your specific composition. I also recommend framing each fragment.
Well, here's a potential answer that might be close to what you're looking for, but not exactly what you're looking for: there's a project called ViewFlow that I think provides the ability to notify the user that there are more views via indicators on the screen. It also provides the ability to buffer views to the left and right of your current view, so you might be able to poke at the code to render small parts of them by basically shrinking what it views as its available screen size.
Edit: Silly me; I should read the final question before I answer. :) I don't think you can do this with a ViewFlipper or Viewpager, unfortunately.
Just use a ViewPager and play with the width of the fragments and margins. I have done so and it works fine.
This question already has answers here:
Android ViewPager padding/margin between page fragments
(7 answers)
Closed 9 years ago.
I'm using the new ViewPager-view from the Android compatibility library, and I can't figure out how to get padding between the pages so that when you're swiping between them, there's a visible border.
My ViewPager width is set to fill_parent so it takes up the entire width of the display. I can get this border between the pages by setting android:padding on the ViewPager, but that has the added (unwanted) effect of reducing the visible size of the page when it is fully displayed.
I guess what I need is to introduce a border outside the width of the view somehow, if that even makes sense...
The new Android Market does this the way I'd like, but I just can't figure out how it's accomplished.
There's currently no way to do that in the XML layout, but you can use the following functions in java:
setPageMargin(int marginPixels)
setPageMarginDrawable(drawable d)
setPageMarginDrawable(int resId)
You'll need to grab the pager from your XML layout first:
ViewPager pager = (ViewPager) findViewById(R.id.pager);
It seems that more recent releases of the support package (since revision 5) have added support for margins between pages of ViewPager via setPageMargin() and setPageMarginDrawable().
This would be a bit of work, but you could achieve the effect you want by having a border UI element that you then continue to animate off the screen at the same rate as the swipe is occuring.
E.g. say you are swiping right to left:
1) The page that comes in to view has the extra border element on it's left-hand edge by default.
2) You monitor the swipe by implementing a ViewPager.OnPageChangeListener() with a custom onPageScrolled() method. Compute the velocity of the swipe in here as well.
3) When the incoming page's left hand edge hits the left hand edge of the screen, start animating the border element off the left at the velocity you calculated.
...Thinking more about this, I think the illusion would be more effective if you animated the border element being translated left by it's width throughout the duration of the swipe. It sounds weird, but once you picture it, it will make sense :)
The source for ViewPager is available with the android v4 compatibility libs.
/**
* This method will be invoked when the current page is scrolled, either as part
* of a programmatically initiated smooth scroll or a user initiated touch scroll.
*
* #param position Position index of the first page currently being displayed.
* Page position+1 will be visible if positionOffset is nonzero.
* #param positionOffset Value from [0, 1) indicating the offset from the page at position.
* #param positionOffsetPixels Value in pixels indicating the offset from position.
*/
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);