Saturday, April 26, 2014

GridView for Multiple Screen Sizes



For the OpenStax College app, I needed to display the book covers in a GridView which sounds like an easy task, but it was not.  The main problem was that the book covers are rectangular and they could not be cropped.  As with all things Android, the grid needed to deal with different screen sizes correctly.  The code I will detail below adjusts the number of items in the grid based on screen size and orientation (landscape vs. portrait). I was helped tremendously by this post.

Layouts for the grid involve two XML files gridview.xml and gridcell.xml

gridview.xml

<framelayout android:background="@color/beige" android:layout_height="match_parent" android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android">
    <gridview android:columnwidth="200dp" android:gravity="center" android:horizontalspacing="10dp" android:id="@+id/gridView" android:layout_height="match_parent" android:layout_width="match_parent" android:numcolumns="2" android:padding="5dp" android:stretchmode="columnWidth" android:verticalspacing="10dp">

</gridview></framelayout>

gridcell.xml

<framelayout android:gravity="center" android:layout_height="match_parent" android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android">
 
    <org .openstaxcollege.android.views.oscimageview="" android:id="@+id/grid_item_image" android:layout_gravity="center" android:layout_height="200dp" android:layout_margin="5dp" android:layout_width="200dp" android:scaletype="fitCenter" android:src="@drawable/biology_lg">
    </org>
 
</framelayout>

gridcell.xml references a custom ImageView class, OSCImageView. This class gives some control over how the image is displayed in the onMeasure() method.

public class OSCImageView extends ImageView 
{
 public OSCImageView(Context context) 
 {
        super(context);
    }

    public OSCImageView(Context context, AttributeSet attrs) 
    {
        super(context, attrs);
    }

    public OSCImageView(Context context, AttributeSet attrs, int defStyle) 
    {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth()); 
    }

}

There are two inner classes in my opening activity which work as part of the GridView. The first, BookCover, is a simple javabean to hold the info about each cover.

private class Bookcover
{
     
  final String name;
  final int drawableId;

  Bookcover(String name, int drawableId) 
  {
     this.name = name;
     this.drawableId = drawableId;
  }
        
    
}

I originally displayed the title of each book with the cover, but decided to do away with the title for now. I left the name field since we may have covers in the future that do not display the title as well as the current covers do.

The other inner class is ImageAdaptor which holds all of the covers and displays them in the GridView.

    
class ImageAdapter extends BaseAdapter 
    {
     private Context context;
     
     List<bookcover> bookcovers = new ArrayList<bookcover>();
     
     public ImageAdapter(Context c)
     {
      context = c;

            bookcovers.add(new Bookcover("",R.drawable.physics_lg));
            bookcovers.add(new Bookcover("",R.drawable.sociology_lg));
            bookcovers.add(new Bookcover("", R.drawable.biology_lg));
            bookcovers.add(new Bookcover("",R.drawable.concepts_biology_lg));
            bookcovers.add(new Bookcover("",R.drawable.anatomy_lg));
            bookcovers.add(new Bookcover("",R.drawable.statistics_lg));
            bookcovers.add(new Bookcover("",R.drawable.precalculus_lg));
            bookcovers.add(new Bookcover("",R.drawable.psychology_lg));
            bookcovers.add(new Bookcover("",R.drawable.econ_lg));
            bookcovers.add(new Bookcover("",R.drawable.chemistry_lg));
            bookcovers.add(new Bookcover("",R.drawable.history_lg));
            bookcovers.add(new Bookcover("",R.drawable.macro_econ_lg));
            bookcovers.add(new Bookcover("",R.drawable.micro_econ_lg));
      
     }

     /* (non-Javadoc)
      * @see android.widget.Adapter#getCount()
      */
     @Override
     public int getCount() 
     {
      return bookcovers.size();
     }

     /* (non-Javadoc)
      * @see android.widget.Adapter#getItem(int)
      */
     @Override
     public Object getItem(int position) 
     {
      return bookcovers.get(position);
     }

     /* (non-Javadoc)
      * @see android.widget.Adapter#getItemId(int)
      */
     @Override
     public long getItemId(int position) 
     {
      return 0;
     }

     /* (non-Javadoc)
      * @see android.widget.Adapter#getView(int, android.view.View, android.view.ViewGroup)
      */
     @Override
     public View getView(int position, View convertView, ViewGroup parent) 
     {

      View v = convertView;
            ImageView picture;
            //TextView name;

            if(v == null) {
             
                v = LayoutInflater.from(context).inflate(R.layout.gridcell, parent, false);
                v.setTag(R.id.grid_item_image, v.findViewById(R.id.grid_item_image));
                //v.setTag(R.id.text, v.findViewById(R.id.text));
            }

            picture = (ImageView)v.getTag(R.id.grid_item_image);
            //name = (TextView)v.getTag(R.id.text);

            Bookcover item = (Bookcover)getItem(position);

            picture.setImageResource(item.drawableId);
            //name.setText(item.name);

            return v;
     }

    }

In the Activity onCreate() method, The GridView is setup for orientation and screen size.

        
GridView gridView = (GridView) findViewById(R.id.gridView);
        int orient = getResources().getConfiguration().orientation;
        Display d = getWindowManager().getDefaultDisplay();
        boolean isTablet = OSCUtil.isTabletDevice(this);
        if(orient == Configuration.ORIENTATION_LANDSCAPE && isTablet)
        {
            if(OSCUtil.isXLarge(this))
            {
                gridView.setNumColumns(5);
            }
            else
            {
             gridView.setNumColumns(4);
            }
        }
        else if(orient == Configuration.ORIENTATION_LANDSCAPE)
        {
         gridView.setNumColumns(3);
        }
        else if(orient == Configuration.ORIENTATION_PORTRAIT && isTablet)
        {

            if(OSCUtil.isXLarge(this))
            {
                gridView.setNumColumns(4);
            }
            else
            {
                gridView.setNumColumns(3);
            }
        }
        
        gridView.setAdapter(new ImageAdapter(this));
        gridView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView parent, View v,int position, long id) {
             if(position > 5)
             {
              Toast.makeText(LandingActivity.this, getString(R.string.coming_soon),  Toast.LENGTH_SHORT).show();
              return;
             }
             Content c = content.get(position);
             ContentCache.setObject(getString(R.string.webcontent), c);
             startActivity(new Intent(getApplicationContext(), WebViewActivity.class));
                
            }
        });


The number of columns is adjusted based on the screen size and orientation. Code to determine if the device is a tablet or not was covered in this post. When a book is selected in the Grid, if it is one of the first 5, the Content object is used to send the user to the book URL. If the selected cover is not in the first 5, a Toast is displayed. I'm not sure why I coded the URL as a separate object and it could probably be refactored to have the URL in the BookCover object. The Content object is most likely a leftover from one of the many things I tried that did not work.

The code for the OpenStax College app is available on Github. The App is available in Google Play.

No comments:

Post a Comment