title: How can I make a horizontal ListView in Android?
body: <blockquote>
    <p><strong>Possible Duplicate:</strong><br>
    <a href="https://stackoverflow.com/questions/3240331/horizontal-listview-in-android">Horizontal ListView in Android?</a>  </p>
  </blockquote>
  
  
  
  <p>Like many things in Android, you wouldn't think this would be such a hard problem but ohhh, by golly, would you be wrong.  And, like many things in Android, the API doesn't even provide a reasonably extensible starting point.  I'll be damned if I'm going to roll my own ListView, when all I want is to take the thing and turn it on its side.  \rant</p>
  
  <p>Okay, now that I'm done fuming, let's talk about the problem itself.  What I need is basically something exactly like the <code>Gallery</code>, but without the center-locking feature.  I don't really need <code>ListView</code>'s listSelector but it's a nice-to-have.  Mostly, I could do what I want with a <code>LinearLayout</code> inside a <code>ScrollView</code>, but I need the child views to come from a <code>ListAdapter</code> and I would really like to have a view recycler.  And I <em>really</em> don't want to write any layout code.</p>
  
  <p>I peeked into the source code for some of these classes...</p>
  
  <p><em>Gallery:</em> It looks like I could use the Gallery if I override most of the 'onXyz' methods, copy all their source code, but refrain from calling <code>scrollIntoSlots()</code>.  But I'm sure that if I do that I'll run into some member field that's inaccessible or some other unforeseen consequence.</p>
  
  <p><em>AbsSpinner:</em> Since the <code>mRecycler</code> field is package-private I doubt I'll be able to extend this class.</p>
  
  <p><em>AbsListView:</em> It looks like this class is only meant for vertical scrolling, so no help there.</p>
  
  <p><em>AdapterView:</em> I've never had to extend this class directly.  If you tell me it's easy to do, and that it's easy to roll my own <code>RecycleBin</code>, I'll be very skeptical but I'll give it a shot.</p>
  
  <p>I suppose I could possibly copy <em>both</em> <code>AbsSpinner</code> and <code>Gallery</code> to get what I want... hopefully those classes aren't using some package-private variable I can't access.  Do y'all think that's a good practice?  Does anyone have any tutorials or third-party solutions that might put me in the right direction?</p>
  
  <p><strong>Update:</strong><br>
  The only solution I've found so far is to do it all myself.  Since asking this question, I have overridden <code>AdapterView</code> and implemented my own "HorizontalListView" from scratch.  The only way to truly override the Gallery's center-locking feature is to override the private <code>scrollIntoSlots</code> method, which I believe would require generating a subclass at runtime.  If you're bold enough to do that, it's arguably the best solution, but I don't want to rely on undocumented methods that could change.</p>
  
  <p>Swathi EP below suggested that I give the <code>Gallery</code> an <code>OnTouchListener</code> and override the scroll functionality.  If you don't care about having fling support in your list, or if it's okay for the views to snap to the center at the end of the fling animation, then this <em>will</em> work for you!  However, in the end it still proves impossible to remove the center-locking feature without removing fling support.  And I ask you, what kind of list doesn't fling?</p>
  
  <p>So, alas, this did not work for me. :-(  But if you're interested in this approach, read on...</p>
  
  <p>I also had to make some additions to Swathi's code to get what I wanted.  In <code>GestureListener.onTouch</code>, in addition to delegating to the gesture detector, I also had to return true for <code>ACTION_UP</code> and <code>ACTION_CANCEL</code> events.  That successfully disabled the center-locking feature, but it also disabled flinging.  I was able to re-enable fling by having my own GestureListener delegate to the Gallery's <code>onFling</code> method.  If you want to try it out, go into your ApiDemos sample code and replace the Gallery1.java class with the following code:</p>
  
  <pre><code>import com.example.android.apis.R;
  
  import android.app.Activity;
  import android.content.Context;
  import android.content.res.TypedArray;
  import android.os.Bundle;
  import android.view.ContextMenu;
  import android.view.GestureDetector;
  import android.view.MenuItem;
  import android.view.MotionEvent;
  import android.view.View;
  import android.view.ViewGroup;
  import android.view.ContextMenu.ContextMenuInfo;
  import android.view.GestureDetector.SimpleOnGestureListener;
  import android.view.View.OnTouchListener;
  import android.widget.AdapterView;
  import android.widget.BaseAdapter;
  import android.widget.Gallery;
  import android.widget.ImageView;
  import android.widget.Toast;
  import android.widget.AdapterView.AdapterContextMenuInfo;
  import android.widget.AdapterView.OnItemClickListener;
  
  public class Gallery1 extends Activity {
  
      @Override
      public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.gallery_1);
  
          // Reference the Gallery view
          final Gallery g = (Gallery) findViewById(R.id.gallery);
  
          // Set the adapter to our custom adapter (below)
          g.setAdapter(new ImageAdapter(this));
  
          // Set a item click listener, and just Toast the clicked position
          g.setOnItemClickListener(new OnItemClickListener() {
              public void onItemClick(AdapterView parent, View v, int position, long id) {
                  Toast.makeText(Gallery1.this, "" + position, Toast.LENGTH_SHORT).show();
              }
          });
  
          // Gesture detection
          final GestureDetector gestureDetector = new GestureDetector(new MyGestureDetector(g));
          OnTouchListener gestureListener = new OnTouchListener() {
              @Override
              public boolean onTouch(View v, MotionEvent event) {
                  boolean retVal = gestureDetector.onTouchEvent(event);
                  int action = event.getAction();
                  if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
                      retVal = true;
                      onUp();
                  }
                  return retVal;
              }
  
              public void onUp() {
                  // Here I am merely copying the Gallery's onUp() method.
                  for (int i = g.getChildCount() - 1; i &gt;= 0; i--) {
                      g.getChildAt(i).setPressed(false);
                  }
                  g.setPressed(false);
              }
          };
          g.setOnTouchListener(gestureListener);
  
          // We also want to show context menu for longpressed items in the gallery
          registerForContextMenu(g);
      }
  
      @Override
      public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
          menu.add(R.string.gallery_2_text);
      }
  
      @Override
      public boolean onContextItemSelected(MenuItem item) {
          AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
          Toast.makeText(this, "Longpress: " + info.position, Toast.LENGTH_SHORT).show();
          return true;
      }
  
      public class ImageAdapter extends BaseAdapter {
          int mGalleryItemBackground;
  
          public ImageAdapter(Context c) {
              mContext = c;
              // See res/values/attrs.xml for the &lt;declare-styleable&gt; that defines
              // Gallery1.
              TypedArray a = obtainStyledAttributes(R.styleable.Gallery1);
              mGalleryItemBackground = a.getResourceId(
                      R.styleable.Gallery1_android_galleryItemBackground, 0);
              a.recycle();
          }
  
          public int getCount() {
              return mImageIds.length;
          }
  
          public Object getItem(int position) {
              return position;
          }
  
          public long getItemId(int position) {
              return position;
          }
  
          public View getView(int position, View convertView, ViewGroup parent) {
              ImageView i = new ImageView(mContext);
  
              i.setImageResource(mImageIds[position]);
              i.setScaleType(ImageView.ScaleType.FIT_XY);
              i.setLayoutParams(new Gallery.LayoutParams(136, 88));
  
              // The preferred Gallery item background
              i.setBackgroundResource(mGalleryItemBackground);
  
              return i;
          }
  
          private Context mContext;
  
          private Integer[] mImageIds = {
                  R.drawable.gallery_photo_1,
                  R.drawable.gallery_photo_2,
                  R.drawable.gallery_photo_3,
                  R.drawable.gallery_photo_4,
                  R.drawable.gallery_photo_5,
                  R.drawable.gallery_photo_6,
                  R.drawable.gallery_photo_7,
                  R.drawable.gallery_photo_8
          };
      }
  
      public class MyGestureDetector extends SimpleOnGestureListener {
  
          private Gallery gallery;
  
          public MyGestureDetector(Gallery gallery) {
              this.gallery = gallery;
          }
  
          @Override
          public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, 
                  float velocityY) {
              return gallery.onFling(e1, e2, velocityX, velocityY);
          }
      }
  
  }
  </code></pre>