Friday, March 25, 2011

Complete Custom List Example

Listviews are a very useful part of Android applications.  However, they are much more useful when you create a custom listview.  With a custom listview, you can extend all the standard classes to very easily show more complex information in a simple way.  In creating my application, I combined several bits of code from many different places from the web to get my listview to act the way I wanted.  I thought there might be a few people out there that might find this stuff helpful so I figured I'd share a simple example I came up with.

My example will show a list of countries.  If you click on them, it will tell you the capitol.  If you long click, it will give a few simple options.  This is obviously not that useful and overly complicated but it provides a good framework for more complex implementations.

First, I created a custom Countries class to hold each object.
1:  public class Country {  
2:       public String name;  
3:       public String capitol;  
4:  }  
Code Block 1

Well that was pretty simple.  Next, create your activity and some global variables.
1:  public class CustomListSuperDemo extends Activity {  
2:       private final String[] countryList = {  
3:            "Afghanistan:Kabul",  
4:            ...  
5:            "United States:Washington D.C.",  
6:            ...  
7:            "Zimbabwe:Harare"  
8:       };  
9:       private static List<Country> myCountries = new LinkedList<Country>();  
10:       private MovieAdapter myAdapter;  
11:       private int selectedMovieIndex;  
12:       private static final int MENU_UPP = 0;  
13:       private static final int MENU_LOW = 1;  
Code Block 2

The countryList will be used later to create all the Country objects, which are held in the myCountries list.  You can get the full list from the full code.  Everything else are used for saving information when the user clicks a list item.  Next I define the behavior or the listview adapter.
1:       private static class MovieAdapter extends BaseAdapter {  
2:            private final LayoutInflater mInflater;  
3:            public MovieAdapter(Context context) {  
4:                 mInflater = LayoutInflater.from(context);  
5:            }  
6:            public int getCount() {  
7:                 return myCountries.size();  
8:            }  
9:            public Object getItem(int position) {  
10:                 return position;  
11:            }  
12:            public long getItemId(int position) {  
13:                 return position;  
14:            }  
15:            public View getView(int position, View convertView, ViewGroup parent) {  
16:                 ViewHolder holder;  
17:                 if (convertView == null) {  
18:                      convertView = mInflater.inflate(R.layout.listitem, null);  
19:                      holder = new ViewHolder();  
20:                 } else {  
21:                      holder = (ViewHolder) convertView.getTag();  
22:                 }  
23:                 holder.name = (TextView) convertView  
24:                      .findViewById(R.id.name);  
25:                 convertView.setTag(holder);  
26:                 holder.name.setText( myCountries.get(position).name );  
27:                 return convertView;  
28:            }  
29:            static class ViewHolder {  
30:                 TextView name;  
31:            }  
32:       }  
Code Block 3

Most of that is pretty simple but the getView function is where it gets fun.  This function is used to map your list of objects to individual views (what the user sees).  You can move lines 23-24 into the if statement (after line 19).  Be careful with this though.  I had an issue when doing this with an imageView because these are cached differently.  You can randomly get images show up in the wrong list item.  I just like my stuff like I show above just to be on the safe side.

Next I set up the UI and load the list.
1:       public void onCreate(Bundle savedInstanceState){  
2:            super.onCreate(savedInstanceState);  
3:            setContentView(R.layout.main);  
4:            new loadList().execute();  
5:    }  
6:       class loadList extends AsyncTask<Void, Movie, Void> {  
7:            ProgressDialog dialog;  
8:            protected void onPreExecute(){  
9:                 dialog = ProgressDialog.show(CustomListSuperDemo.this, "",  
10:                           "Loading. Please wait...", true);  
11:            }  
12:            protected Void doInBackground(Void... unused) {  
13:                 Arrays.sort( countryList );  
14:                 for( int i=0; i< countryList.length; ++i){  
15:                      //add it to the country list  
16:                      Country newCountry = new Country();  
17:                      newCountry.name = countryList[i].split(":")[0];  
18:                      newCountry.capitol = countryList[i].split(":")[1];  
19:                      myCountries.add(newCountry);  
20:                 }  
21:                 return(null);  
22:            }  
23:            protected void onPostExecute(Void unused){  
24:                 if( myCountries.size() == 0 ){  
25:                      ((TextView)findViewById(R.id.empty)).setVisibility(View.VISIBLE);  
26:                 }else{  
27:                      ((TextView)findViewById(R.id.empty)).setVisibility(View.GONE);  
28:                      ListView lv = (ListView) findViewById(R.id.movieList);  
29:                      lv.setAdapter(new MovieAdapter(CustomListSuperDemo.this));  
30:                      lv.setDivider(new ColorDrawable(Color.DKGRAY));  
31:                      lv.setDividerHeight(2);  
32:                      lv.setOnItemClickListener(new AdapterView.OnItemClickListener(){  
33:                           public void onItemClick(AdapterView<?> av, View v, int pos, long id){  
34:                                Toast.makeText(CustomListSuperDemo.this,  
35:                                     myCountries.get(pos).capitol + " is my capitol.", Toast.LENGTH_SHORT).show();  
36:                           }  
37:                      });  
38:                      lv.setOnCreateContextMenuListener(new OnCreateContextMenuListener(){  
39:                           public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo){  
40:                             AdapterView.AdapterContextMenuInfo info =  
41:                               (AdapterView.AdapterContextMenuInfo) menuInfo;  
42:                             selectedMovieIndex = info.position;  
43:                             menu.setHeaderTitle("Options");  
44:                             menu.add(0, MENU_UPP, 0, "To Upper");  
45:                             menu.add(0, MENU_LOW, 0, "To Lower");  
46:                           }  
47:                      });  
48:                      myAdapter = (MovieAdapter)lv.getAdapter();  
49:                 }  
50:                 try{  
51:                      dialog.dismiss();  
52:                 }catch(Exception e){}  
53:            }  
54:       }  
Code Block 4

In onCreate, I load the UI and call an instance of AsyncTask to load the data.  This is useful when object data takes a while to load (load from database or download from web).  Once this is done, I set up the display and add the click events.  This is where I use the two MENU constants.  When the user longClicks, they are used to create the buttons.  These are also use one more time below.  I also save a reference to myAdapter and the selected position to make things easier later on.

Only one bit of code left: processing the long click.
1:       public boolean onContextItemSelected(MenuItem item) {  
2:            new processClick().execute(item.getItemId());  
3:            return true;  
4:       }  
5:       class processClick extends AsyncTask<Integer, Void, Void> {  
6:            ProgressDialog dialog;  
7:            protected void onPreExecute(){  
8:                 dialog = ProgressDialog.show(CustomListSuperDemo.this, "",  
9:                           "Loading. Please wait...", true);  
10:            }  
11:            protected Void doInBackground(Integer... tmpbtnID){  
12:                 try {  
13:                      Thread.sleep(1000);  
14:                 } catch (InterruptedException e){  
15:                      e.printStackTrace();  
16:                 }  
17:                 switch(tmpbtnID[0]){  
18:                 case MENU_UPP:  
19:                      myCountries.get(selectedMovieIndex).name =  
20:                           myCountries.get(selectedMovieIndex).name.toUpperCase();  
21:                      break;  
22:                 case MENU_LOW:  
23:                      myCountries.get(selectedMovieIndex).name =  
24:                           myCountries.get(selectedMovieIndex).name.toLowerCase();  
25:                      break;  
26:                 }  
27:                 return(null);  
28:            }  
29:            protected void onPostExecute(Void unused){  
30:                 myAdapter.notifyDataSetChanged();  
31:                 dialog.dismiss();  
32:            }  
33:       }  
Code Block 5

I process the click in another AsyncTask because some actions may take a while.  Once I'm all done, I notify the saved adapter that it should update.

If anyone needs something explained a little more, just let me know.  You can get the full code here: Custom ListView Example.zip

Sunday, March 20, 2011

I Wanna See That: v1.3 coming soon

I am working on one last function for v1.3.  Keep an eye out for it to hit the market tomorrow.  Updates in version 1.3 include:

  1. Ability to check off (watch) movies
  2. Update movie info
You can now long-click a movie from the home screen to get new options.

In the next few versions, I will be adding user preferences including sorting capability based on release date, alphabetical, or rating.  I also want to add the capability to view movie lists such as watched, unwatched, or all.  I will also try to take care of some of the random errors I see come in via error reports.  I think things should get pretty interesting in the coming weeks.

On a side note, I am still seeing some error come in from version 1.0, which any user that jumped on board that early knows, didn't work at all.  I think I got another 1 star review because of this.  I'm not sure how people are still getting it but please make sure you are on the most current version before commenting that it doesn't work.  As always, if you notice any error please let me know and I will work to resolve them.

Sunday, March 13, 2011

My first android app: I Wanna See That

Alpha version released to Android Market.
Get it here: market.android.com/details?id=com.I_Wanna_See_That
Or scan this:

This app is for every person who sees a movie preview and says "Hey, that looks good.  I want to see it."  Just open the app, search for the movie, and add it to your to-watch list.  It doesn't have to be a new release.  You can also manage a list of movies that you would like to catch up on.  This is the only app on the market now specifically designed to keep track of a to-watch list.

This is an alpha version of the app.  It is not very pretty yet and not all functionality is ready.  It is mostly for testing.

Thanks to those who try it out and help me test.

For the alpha version only basic functionality is added: view saved movies, search for new movies, and view movie info.

Here are some ideas I have been working on and would like to eventually add to the app:

  1. The ability to check movies off in the list
  2. Come up with a good-looking theme for the app (or several and let the user pick)
  3. Update movie info if it was incomplete when the user saved it
  4. Several movie lists managed by user (ie Romantic, With Kids, Action, etc)
  5. Alerts every week for movies coming out
  6. View local movie times (and maybe even buy tickets)
Feel free to make your own suggestions and help make the app better.

Saturday, March 5, 2011

Intro

This blog is dedicated to the many DIY projects I start and sometimes finish in my own free time.  Projects are usually technology related but also include home improvement.

These are some of my past projects:

  • Set up a home web server with Railo and Postgres.
  • Custom circuit design and pcb population
  • Home automation
  • Installing wood floors
  • Tiling and countertops
As you can see, the projects cover a wide range.  I will be using this blog to record some of what I do for my projects.  If I revisit any of my past projects, I will try my best to write down what I've done but feel free to ask any questions about them and I'll try to answer.  Otherwise, I'll just write about whatever project I'm doing at the time.

Some of the stuff you will be seeing in the near future
  • Android applications
    • How to get started
    • How to get it out there for people to download/buy
    • How to add ads to your applications
  • Designing and installing a yard shed
More coming soon!