Sunday, September 21, 2014

Migrate a Project in Github to Android Studio

Android Studio is the IDE of the future for Android development.  Unlike Eclipse, Android Studio and the Gradle build system require a default folder layout for the build system to work out of the box.  If you have stored your project in Github using the Eclipse folder layout, migrating to Android Studio can be a challenge.  This documents how I have done the migration on several projects.  The technic used preserves the Git history as you move the files to the Android Studio folder layout.

Create Empty Android Studio Project

Android Studio will not migrate a project from the Eclipse layout to the Studio format. You will need to do the migration by hand. Creating an empty project will generate some of the Gradle files you will need.  Once you create the project, close Android Studio and rename the project folder.

Create project with Github Checkout



Reopen Android Studio and select the option to Checkout From Source Control.  Enter your Github credintials if you have not already.  The result will be a project with the Eclipse folder layout that will not build and is not tied to Gradle

Move Your Files

Close Android Studio again and return to the command line. Create folders to match the structure of your empty Android Studio project. The folders you will have to create are

app/src/main/java

Use Git to move the Java source files to the app/src/main/java directory

git mv src/org app/src/main/java


Next, move the res folder

git mv res app/src/main


Finally, move AndroidManifest.xml

git mv AndroidManifest.xml app/src/main

Copy Files from Empty Project

Copy these files and directories to the same location in your Github checkout project.

  •  gradle directory
  •  gradle.properties
  •  gradlew
  •  build.gradle
  •  app/build.gradle

Migrating to Gradle Build

 Open the Github project in Android Studio.  The Gradle files will be detected and a popup will ask you if you want to migrate to a Gradle project. Click the link and the project will be updated.  If your project will still not build, you may need to add dependencies for libraries such as Appcompat in the app/build.gradle file.

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.