Android - horizontal scrolling of multiple viewable items

AndroidHorizontal ScrollingAndroid Viewpager

Android Problem Overview


EDIT: See my own answer for easy solution

IMPORTANT: Bounty is offered for clear way to modify ViewPager to satisfy the scenario outlined below. Please do not offer HorizontalScrollView - I need full Fragment lifecycle scenario covered

I need to implement horizontal scrolling of Fragments-based views in which one item is in the center and items to the right/left are partially or fully visible. ViewPager is ill suitable for the task since it's focused on displaying one item at each time.

To make it easier to understand below is a quick sketch in which items 1, 5 and 6 are outside of viewable area. And and want to make this viewable number configurable so for example in portrait view I will only show 2 (or possibly just one) items.

I'm not trying to fit say 3 items on the screen, as long as central item is shown others can be cropped. On the small screen is OK to have 1 central item and as screen grows in size multiple (cropped is OK) items should be shown

I understand that this looks like a gallery but again the items are not simple images but Fragments with a vertically scrollable list in each fragment

P.S. Found this blogpost by @Commonsware that list 3 different approaches. For my need I like #3

enter image description here

Android Solutions


Solution 1 - Android

This one has surprisingly easy answer, I'm not even sure why it wasn't posted right away. All that I needed to do to get the exact effect was to override PagerAdapter#getPageWidth method. By default it returns 1 but if you set it to 0.5 you will get 2 pages, 0.33 will give you 3, etc. Depending on width of the separator between pager items you may have to slightly decrease the value.

See the following snippet:

    @Override
    public float getPageWidth(final int position) {
        // this will have 3 pages in a single view
        return 0.32f;
    }

Solution 2 - Android

Once I wrote something similar as template. In my example I can scroll with the buttons up and down and sidewise. You could modify it a little bit to fulfill your requirements. In my example I have 4 Views arranged like this:

1 2
3 4

It looks like this. On the picture I scroll from view 1 to the right to view 2:

Android View Scrolling

The code consist of this xml:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
      <LinearLayout
        android:layout_height="350sp"
        android:layout_width="320sp">
    	<LinearLayout
    	    android:id="@+id/viewContainer"
    	    android:background="#CCCCCC"
    		android:layout_width="640sp"
    		android:layout_height="700sp">
    	</LinearLayout>
     </LinearLayout>
      <TableLayout
		    android:id="@+id/tableLayout"
			android:layout_width="320sp"
			android:layout_height="fill_parent"
			android:stretchColumns="1"
			android:gravity="bottom"
			android:layout_alignParentBottom="true">
			<TableRow
			android:background="#333333"
			android:gravity="bottom">		
		    <Button
		        android:id="@+id/btnUp"
		        android:layout_width="60sp"
		        android:layout_height="50sp"
		        android:text="Lift U"
		        />
		    <Button
		        android:layout_width="60sp"
		        android:layout_height="50sp"
		        android:visibility="invisible"
		    />
		    <Button
		        android:layout_width="60sp"
		        android:layout_height="50sp"
		        android:visibility="invisible"
		    />
		    <Button
		        android:id="@+id/btnScreenUp"
		        android:layout_width="60sp"
		        android:layout_height="50sp"
		        android:layout_gravity="right"
		        android:text="Scrn U"
		        />
		    </TableRow>
		    <TableRow
		      android:background="#444444"
		      android:layout_gravity="right">
		      <Button
		        android:id="@+id/btnDown"
		        android:layout_width="60sp"
		        android:layout_height="50sp"
		        android:text="Lift D"
		        />
		      <Button
		        android:id="@+id/btnEnter"
		        android:layout_width="60sp"
		        android:layout_height="50sp"
		        android:text="Enter"
		        />
		       <Button
		        android:id="@+id/btnScreenLeft"
		        android:layout_width="60sp"
		        android:layout_height="50sp"
		        android:layout_gravity="right"
		        android:text="Scrn L"
		        />
		       <Button
		        android:id="@+id/btnScreenDown"
		        android:layout_width="60sp"
		        android:layout_height="50sp"
		        android:layout_gravity="right"
		        android:text="Scrn D"
		        />
		       <Button
		        android:id="@+id/btnScreenRight"
		        android:layout_width="60sp"
		        android:layout_height="50sp"
		        android:layout_gravity="right"
		        android:text="Scrn R"
		        />
		    </TableRow>
	</TableLayout>
</FrameLayout>

and this Java code:

import android.app.Activity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;

public class ViewSwitcherTest extends Activity {

	private TextView view1, view2, view3, view4;
	private Button btnUp, btnEnter, btnDown, btnScreenDown, btnScreenUp, btnScreenLeft, btnScreenRight;
	private LinearLayout viewContainer;
//	private TableLayout tableLayout;
	private LinearLayout.LayoutParams layoutParams;
	private DisplayMetrics metrics = new DisplayMetrics();
	private int top = 0, left = 0;
	private float density = 1.0f;
//	private ViewSwitcher switcher;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		getWindowManager().getDefaultDisplay().getMetrics(metrics);
		density = metrics.density;
		setContentView(R.layout.main);
//		Buttons
		btnEnter = (Button)findViewById(R.id.btnEnter);
		btnUp = (Button)findViewById(R.id.btnUp);
		btnDown = (Button)findViewById(R.id.btnDown);
		btnScreenDown = (Button)findViewById(R.id.btnScreenDown);
		btnScreenUp = (Button)findViewById(R.id.btnScreenUp);
		btnScreenLeft = (Button)findViewById(R.id.btnScreenLeft);
		btnScreenRight = (Button)findViewById(R.id.btnScreenRight);
//		--------
//		tableLayout = (TableLayout)findViewById(R.id.tableLayout);
		view1 = new TextView(this);
		view1.setBackgroundResource(R.drawable.view1);
		view1.setHeight((int)(350*density));
		view1.setWidth((int)(320*density));
		view1.setGravity(Gravity.CENTER_VERTICAL|Gravity.CENTER_HORIZONTAL);
		layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
		layoutParams.setMargins(left, top, 0, 0);	
		viewContainer = (LinearLayout)findViewById(R.id.viewContainer);
		viewContainer.addView(view1, layoutParams);
		//Add 2nd view
		view2 = new TextView(this);
		view2.setBackgroundResource(R.drawable.view2);
		view2.setHeight((int)(350*density));
		view2.setWidth((int)(320*density));
		view2.setGravity(Gravity.CENTER_VERTICAL|Gravity.CENTER_HORIZONTAL);
		layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
		layoutParams.setMargins(left, top, 0, 0);
		viewContainer.addView(view2, layoutParams);	
		//Add 3rd view
		view3 = new TextView(this);
		view3.setBackgroundResource(R.drawable.view3);
		view3.setHeight((int)(350*density));
		view3.setWidth((int)(320*density));
		view3.setGravity(Gravity.CENTER_VERTICAL|Gravity.CENTER_HORIZONTAL);
		top += 350*density;
		left += 640*density*(-1);
		layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
		layoutParams.setMargins(left, top, 0, 0);
		viewContainer.addView(view3, layoutParams);		
		//add 4th view
		view4 = new TextView(this);
		view4.setBackgroundResource(R.drawable.view4);
		view4.setHeight((int)(350*density));
		view4.setWidth((int)(320*density));
		view4.setGravity(Gravity.CENTER_VERTICAL|Gravity.CENTER_HORIZONTAL);
		top += 0;
		left += 640*density;
		layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
		layoutParams.setMargins(left, top, 0, 0);
		viewContainer.addView(view4, layoutParams);		
		btnEnter.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				// Quit the application for now
				finish();
			}
		});
		btnScreenLeft.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				viewContainer.scrollBy(-10,0);
			}
		});
		btnScreenRight.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				viewContainer.scrollBy(10,0);
			}
		});
		btnScreenUp.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				viewContainer.scrollBy(0,-10);
			}
		});
		btnScreenDown.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				viewContainer.scrollBy(0,10);
			}
		});
//		view1.setOnKeyListener(new OnKeyListener() {			
//			@Override
//			public boolean onKey(View v, int keyCode, KeyEvent event) {
//				if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN)
//					viewContainer.scrollBy(0,10);
//				return true;
//			}
//		});
	}
}

The big numbers on every screen are black background images with those numbers painted on it. (I didn't post it here because you will probably modify the code anyway).

Solution 3 - Android

We are doing something pretty much exactly like you are describing using a Gallery with Fragments, and a gallery adapter extending the BaseAdapter. I would recommend going with a gallery to achieve your goal (we also use a ViewPager for the view where we do not need to see the other fragments).

Solution 4 - Android

I can think of two ways to possibly implement this.

  1. By a ViewSwitcher. Here is an excellent YouTube video showing how it works with an onGestureListener. However, I am not sure if this will work with multiple views like your picture shows.

  2. As an alternative, you could use a HorizontalScrollView. However, this often causes issues if you have one ScrollView inside of a another, but it may be worth a shot!

Let me know how it goes, Good luck!

Solution 5 - Android

In addition to MrZander's second answer take a look at this https://stackoverflow.com/a/2655740/935075

Solution 6 - Android

Check out this blog post on how to write a custom horizontal scroll view to achieve something similar. The example has only one screen visible at a time, but you should be able to easily modify it for your needs.

Solution 7 - Android

If you want to extend ViewPager just override the draw method, and setOffscreenPageLimit(int limit). But, I recommend using FragmentPageAdapter if your fragments themselves are kind of complex.

You can check the source code here or if you want to check the one in the support package you can do it here.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBostoneView Question on Stackoverflow
Solution 1 - AndroidBostoneView Answer on Stackoverflow
Solution 2 - AndroidBevorView Answer on Stackoverflow
Solution 3 - AndroidWrydayView Answer on Stackoverflow
Solution 4 - AndroidMrZanderView Answer on Stackoverflow
Solution 5 - AndroidsebatazView Answer on Stackoverflow
Solution 6 - AndroidJoelView Answer on Stackoverflow
Solution 7 - AndroidCris StringfellowView Answer on Stackoverflow