Android: How to implement ViewPager with scroll bar? - android

I have a challenging problem and wanted to see if the community has some comments about its implementation.
I have a ViewPager and added 20 pages in it, and each page has a WebView. – This provides vertical page flipping between multiple websites
It should work as a ViewPager by default but when I pinch to zoom, each page will shrink and the ViewPager will scroll like a List View.
I didn’t want to use a different view like a List View as I want a smooth transition and the two modes use the same web contents so want to avoid any construction/deconstruction that might affect a smooth transition.
I was able to implement this scrolling ViewPager with VelocityViewPager.
What I want to do is add a scrollbar to navigate down the pages by holding and dragging a scroller, just like a PC’s behaviour – as opposed to a ListView which only shows the scrollbar but allows no interaction. I have made a custom view that looks like a scrollbar and performs a ViewPager’s fake drag as the scrollbar is moved. The problem is I can only scroll pages ViewPager is holding - so when the ViewPager keeps the whole 20 pages, it works well but If I keep 3 pages as default to make use of page recycling, it doesn’t scroll past 3. It seems as though it doesn’t create the next pages that it usually would when scrolling with your finger.
I’m not sure why ViewPager doesn’t create next pages while fake dragging. Is there anything I have to do other than beginFakeDrag(), endFakeDrag() and fakeDragBy()?
And, Is there any way I can implement this behaviour without keeping all 20 pages in ViewPager?

Have a look into this code, this may be useful to you:
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewPagerAdapter adapter = new ViewPagerAdapter(this, imageArra);
ViewPager myPager = (ViewPager) findViewById(R.id.myfivepanelpager);
myPager.setAdapter(adapter);
myPager.setCurrentItem(0);
}
private int imageArra[] = { R.drawable.gallery_photo_1, R.drawable.gallery_photo_2,
R.drawable.gallery_photo_3, R.drawable.gallery_photo_5,
R.drawable.gallery_photo_6, R.drawable.gallery_photo_7,
R.drawable.gallery_photo_8, R.drawable.ic_launcher };
}
and ViewPager class is:
public class ViewPagerAdapter extends PagerAdapter {
Activity activity;
int imageArray[];
public ViewPagerAdapter(Activity act, int[] imgArra) {
imageArray = imgArra;
activity = act;
}
public int getCount() {
return imageArray.length;
}
public Object instantiateItem(View collection, int position) {
ImageView view = (ImageView) collection.findViewById(R.id.image);
/*
* view.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
* LayoutParams.FILL_PARENT)); view.setScaleType(ScaleType.FIT_XY);
*/
view.setBackgroundResource(imageArray[position]);
((ViewPager) collection).addView(view, 0);
return view;
}
#Override
public void destroyItem(View arg0, int arg1, Object arg2) {
((ViewPager) arg0).removeView((View) arg2);
}
#Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == ((View) arg1);
}
#Override
public Parcelable saveState() {
return null;
}
}

Related

React Native ViewPagerAndroid with multiple visible pages

I've been trying to use ViewPagerAndroid to show a carousel with "stops" at each item like so:
It appears react-native-carousel and react-native-swiper don't support these features yet on Android. I have a native ViewPager that does what I need it to do, but I'm having problems incorporating it into the React Native environment.
The native component CarouselContainer uses layout.xml and expects a ViewPager subcomponent.
<com.mycompany.ui.CarouselContainer
android:id="#+id/pager_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp">
<android.support.v4.view.ViewPager
android:id="#+id/viewpager"
android:layout_width="190dp"
android:layout_height="280dp"
android:layout_gravity="center_horizontal" />
</com.mycompany.ui.CarouselContainer >
CarouselContainer is a FrameLayout subclass that gets a ref to the ViewPager in its onFinishInflate method. It calls setPageMargin on the ViewPager and setClipChildren(false) to render multiple ViewPager pages on the screen at the same time.
I took a look at the ViewPagerAndroid implementation, and it looks like it's basically a ViewPager set up by ReactViewPagerManager. The ReactViewPagerManager exposes the ViewPager's child views via the ViewGroupManager interface.
So I copied ReactViewPagerManager in my own CarouselViewPagerManager and ReactViewPager into CarouselViewPager.
The one thing new here is CarouselViewPagerContainer. The manager creates the container and the container creates the view pager instead of the manager creating the view pager directly. Since there's no layout xml the container instantiates the pager directly instead of getting it from onFinishInflate.
For some reason, I'm getting nothing rendered on the screen when I use it. I think it's due to having the CarouselViewPagerContainer view in between but I'm not sure where to even begin. Do I need to mess with LayoutShadowNode? Do I need to add the CarouselViewPager itself to the list of child views returned by the manager?
The Native UI Components documentation only mentions SimpleViewManager. :(
// Copy of ReactViewPagerManager
public class CarouselViewPagerManager extends ViewGroupManager<CarouselViewPagerContainer> {
#Override
protected CarouselViewPagerContainer createViewInstance(ThemedReactContext reactContext) {
return new CarouselViewPagerContainer(reactContext);
}
// ...
#Override
public void addView(CarouselViewPagerContainer parent, View child, int index) {
parent.addViewToAdapter(child, index);
}
#Override
public int getChildCount(CarouselViewPagerContainer parent) {
// Should the CarouselViewPager be also counted?
return parent.getViewCountInAdapter();
}
#Override
public View getChildAt(CarouselViewPagerContainer parent, int index) {
return parent.getViewFromAdapter(index);
}
#Override
public void removeViewAt(CarouselViewPagerContainer parent, int index) {
parent.removeViewFromAdapter(index);
}
}
public class CarouselViewPagerContainer extends FrameLayout implements ViewPager.OnPageChangeListener {
private final CarouselViewPager mPager; // Copy of ReactViewPager
public CarouselViewPagerContainer(ReactContext context) {
mPager = new CarouselViewPager(context);
}
// ...
// Pass through to the CarouselViewPager
public void setCurrentItemFromJs(int item, boolean animated) {
mPager.setCurrentItemFromJs(item, animated);
}
/*package*/ void addViewToAdapter(View child, int index) {
mPager.addViewToAdapter(child, index);
}
/*package*/ void removeViewFromAdapter(int index) {
mPager.removeViewFromAdapter(index);
}
/*package*/ int getViewCountInAdapter() {
return mPager.getViewCountInAdapter();
}
/*package*/ View getViewFromAdapter(int index) {
return mPager.getViewFromAdapter(index);
}
}
If you know of a simpler way to tackle this problem I'm all ears!
I mean the only thing I'm using the FrameLayout container for is to center the first Viewpager page and occupy space so multiple pages show. :/
It's been such a struggle :(

destroyItem being called when position ==2 why? android pagerAdapter

I have a very simple PagerAdapter that for some reason, removes the views at position 0 and
1 when it is scrolled to position 2.
Specifically, when I first run the app, there are 3 views. I scroll to position 2 and position 1's view will disappear. View 0 is still there. If I scroll to view 0 and back to view 2 and again back to view 0, View 0 suddenly disappears. I do the same again, and I can actually see view 0 being instantiated and immediately destroyed.
what is going on here?
Main Activity
public class MainActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final MyPagerAdapter adapter = new MyPagerAdapter(this);
final ViewPager myPager = (ViewPager) findViewById(R.id.mypanelpager);
myPager.setAdapter(adapter);
myPager.setCurrentItem(1);
}
public class MyPagerAdapter extends PagerAdapter {
private Context ctx;
Calendar c = Calendar.getInstance();
int month = c.get(Calendar.MONTH);
int year = c.get(Calendar.YEAR);
ViewGroup collection;
public MyPagerAdapter (Context ctx){
this.ctx = ctx ;
}
#Override
public int getCount() {
return 3;
}
public Object instantiateItem(ViewGroup container, int position ){
this.collection = (ViewPager)container;
NewMonth monthObject = new NewMonth(ctx, month+position-1,2012);
View monthLayout = monthObject.newParentLayout;
collection.addView(monthLayout);
return monthLayout;
return addViewAt(position);
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
collection.removeViewAt(position);
}
#Override
public Parcelable saveState() {
return null;
}
#Override
public boolean isViewFromObject(View view, Object arg1) {
return view==arg1;
}
}
You have to maintain all tabs in memory specifying the OffscreenPageLimit to N-1, in your case put this inside the onCreate method:
myPager.setOffscreenPageLimit(2);
Checkout populate() function from ViewPager source - it has clear checks then to remove currentIndex+1 and currentIndex-1 items. Removing is done based on view sizes and it seems to be entirely internal ViewPager logic. ViewPager source is located
<android sdk folder>\extras\android\support\v4\src\java\android\support\v4\view\ViewPager.jav‌​a
.
You might debug ViewPager to know that is happening exactly, but I wouldn't suggest so until You faced some serious issue with ViewPager. What's why: if you dig into ViewPager code it might lead You to write some hackish code (even not intentionally) on it based on its internal structure and not on its public interface and documentation. So, the main idea of data encapsulation would be ruined which is definitely not good (sadly, sometimes we need to do so in Android due to lack of documentation / unclear naming / android internal issues etc., check Code Complete by Steve McConnell for more details on good encapsulation etc.).
Here the key is that position is different then index. If they give you a position there is no guarantee that your collections haven't removed other positions.
Example:
Let's say your at position 2. destroyItem might have been called for position 0 which means in your collections now position 2 is actually at index 1. So your indexes will quickly become out of sync from your positions. The same thing happens with listview children. I would recommend using a sparseArray instead.
private final SparseArray<View> views = new SparseArray<>();
public View instantiateItem(ViewGroup container, final int position) {
...
views.put(position, view);
...
}
public void destroyItem(ViewGroup container, int position, Object object) {
View view = views.get(position);
if (view != null) {
container.removeView(view);
views.remove(position);
}
}

WebViews in ViewPager are not loaded/rendered until page is shown

I use a ViewPager with many WebViews, it is for showing an ePub. My Problem is that the WebViews are just rendered/loaded (im not quite sure) wenn their corresponding page becomes visible. After this the page doesn't need to be re-rendered until it is destroyed from the ViewPagerAdapter.
The consequence is that there is always a white page for a little while. How can i pre-render the page that it scrolls smooth to next webView (which was not rendered before).
This is my PagerAdapter:
public class MagazineReaderPagerAdapter extends PagerAdapter {
private MagazineReaderActivity activity;
private EpubDocument epub;
public MagazineReaderPagerAdapter(Context ctx, EpubDocument epub)
{
activity = (MagazineReaderActivity) ctx;
this.epub = epub;
}
#Override
public int getCount() {
return epub.getContentDocuments().size();
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
ReaderWebViewMulti view = new ReaderWebViewMulti(activity);
view.loadContentDocument(epub.getContentDocuments().get(position), epub);
((ViewPager) container).addView(view, 0);
return view;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view.equals(object);
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager) container).removeView((View) object);
}
}
ReaderWebViewMulti extends from WebView ind implements a method loadContentDocument wich loads the content via loadDataWithBaseURL.
edit:
At activity-oncreate the setOffscreenPageLimit is set to 3
viewPager.setOffscreenPageLimit(3);
To point out what the problem is, i made a little video on YouTube
From second 4 you can see that every page is just rendered when it is already visible. When i go back everything is fine.
I had the same problem and fixed it by disabling hardware acceleration for the web view:
webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
See WebView inside ViewPager or ScrollView - weird rendering bug on Android 3.0+
I put some thoughts into this. Scrolling the screen 1 pixel would render the right page:
class SomeClass implements OnPageChangeListener {
private ViewPager viewPager;
...
viewPager = ...
viewPager.setOnPageChangeListener(this);
...
#Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
// viewpager finished scrolling to a page
viewPager.scrollBy(1, 0);
}
}
}
You would need to check if you are coming from right or left and according to this scroll by -1 or 1 pixels (the page where you are coming from will stay in memory, so you don't need to reload that one). Only at the the very first scroll (after opening the app) the problem described above will still persist.
Drawback: you can see the page scrolling 1 pixel, if you look real closely.
Someone with more time than me could dive into the android code to see what exactly happends when 1 pixel comes into the screen, and mimic some of this behaviour.
set this property for your application in manifest file
android:hardwareAccelerated="false"

Can I set viewpager's item width?

I hope I can show 3 items in a page of viewpager, but now I only could set viewpager's padding value and margin value, so it only show one item in a page of viewpager. How can I set the item width? I think if I can set more little width of item, the viewpager will show more items in a page.
Quick Answer
What you want is overriding getPageWidth in the PagerAdapter implementation you made for that ViewPager.
For example setting it to 0.8f will make a single page cover only 80% of the viewpagers width so that part of the next and/or previous pages are visible.
#Override
public float getPageWidth(final int position) {
return 0.8f;
}
More information at https://developer.android.com/reference/android/support/v4/view/PagerAdapter.html#getPageWidth(int)
Other remarks
Also note that the negative margins will only cause the separate pages to overlap each other. The margin is used to define the space between each page.
If no alternative pageWidth is configured (there is no setter, only the override) it will cover 100% by default making no part of the other pages visible unless dragged.
Code example
A very nice overview of what is possible can be found here https://commonsware.com/blog/2012/08/20/multiple-view-viewpager-options.html
The view pager in your layout
<android.support.v4.view.ViewPager
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v4.view.ViewPager>
Example of an inline configured Adapter implementation, this would go in the onCreate or onViewCreated methods:
// get a reference to the viewpager in your layout
ViewPager mViewPager = (ViewPager) findViewById(R.id.viewpager);
// this margin defines the space between the pages in the viewpager mViewPager.setPageMargin(context.getResources().getDimensionPixelSize(R.dimen.margin_normal));
// setting the adapter on the viewpager, this could/should be a proper Adapter implementation in it's own class file instead
mViewPager.setAdapter(new PagerAdapter() {
// just example data to show, 3 pages
String[] titles = {"Eins", "Zwei", "Drei"};
int[] layouts = {R.layout.layout1, R.layout.layout2, R.layout.layout3};
#Override
public Object instantiateItem(ViewGroup container, int position) {
LayoutInflater inflater = LayoutInflater.from(MainActivity.this);
// here you can inflate your own view and dress up
ViewGroup layout = (ViewGroup) inflater.inflate(layouts[position], container, false);
container.addView(layout);
return layout;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View)object);
}
#Override
public float getPageWidth(final int position) {
return 0.8f;
}
#Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
#Override
public int getCount() {
return layouts.length;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
});
Example code based on https://newfivefour.com/android-viewpager-simple-views.html
Have you tried setting the page margins to a negative value, see setPageMargin(int)? If I remember correctly, I read someone realizing something similar to what you're describing that way.
Alternatively, you could have a look at using a Gallery in stead, although I have to admit I'm not a big fan of them as they seem to be less flexible and more buggy.

Having vertically scrolling pages in ViewPager

This may be obvious and a completely unneeded post but I had been having quite an issue resolving a way to allow vertical scrolling functionality on pages in a VewPager and there were very few resolutions coming across google and even here. I found some claiming to resolve the issue but they seemed to be extended and convoluted. So for those who may be searching for the same issue heres the solution. With this (and you will want to make bigger strings than what I wrote here) you will be able to vertically scroll content and swipe between pages.
layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.view.ViewPager
android:id="#+id/conpageslider"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
scrolling viewpager class
public class ScrollingViewPager extends Activity{
private ViewPager pager;
private Context cxt;
private CharSequence[] pages = {"stuff", "more stuff", "other stuff"};
private PageSlider ps;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.layout);
cxt = this;
ps = new PageSlider();
pager = (ViewPager) findViewById(R.id.conpageslider);
pager.setAdapter(ps);
}
public class PageSlider extends PagerAdapter{
#Override
public int getCount() {
return pages.length;
}
#Override
public Object instantiateItem(View collection, int position) {
ScrollView sc = new ScrollView(cxt);
sc.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
sc.setFillViewport(true);
TextView tv = new TextView(cxt);
tv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
tv.setText(pages[position]);
tv.setPadding(5,5,5,5);
sc.addView(tv);
((ViewPager) collection).addView(sc);
return sc;
}
#Override
public void destroyItem(View collection, int position, Object view) {
((ViewPager) collection).removeView((ScrollView) view);
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view==((ScrollView)object);
}
}
}
I am open to critique on the code but at least here is a functioning example
I made similar one that using fragments in viewpager.
tabWigdet can be shown or not.
that example included in android-support-v4 demo application.
I achieved this by setting screenOrientation to landscape, then putting the content of the viewpager as if it is in the portrait mode.

Categories

Resources