Posts Tagged 'example'

Drag-Drop for Android GridView (V4)

About a year and a half ago, I built an example Android app that allows you to drag images from one spot in a GridView to another. I have had several requests to show how the drag-drop framework used in that example could be adapted to the current Android support (V4+) for drag-drop operations. I have done that work and I am making the source code available in this post.

The sample app works the same as the one described in “Drag-Drop for An Android GridView“. There is a grid displayed on the screen. An “Add Image” button allows you to add images to the screen. Those images can be dragged onto the grid. Any images in the grid can be dragged around. There is also a trash can icon on the screen. Images can be dragged there to remove them from the screen. The image below shows what the app looks like on a Nexus 7 tablet.

drag-griview-v4-05

How It Works

Usually, I do an extensive write-up to explain how my demo apps work. However, this time, I am starting with a very short description in this section and posting the source code. I will add a another longer post in the coming months.

If you read my earlier article about dragging views onto a GridView, you know that the drag-drop framework I use originates with the Android Launcher code for API 8. With a combination of interface classes for DragSource, DropTarget, a DragLayer, and DragController,  the Launcher defined a useful framework for handling drag-drop operations. How I adapted those classes from the Launcher code is described in my earlier blog post. In the newer version of my GridView demo app, I have modified the classes a little bit.

DragActivity – This is the main activity of the demo app. It sets up a grid of images, defines a listener for drag events, and connects it to the views displayed on the screen.

DragSource – A drag source is a view where drag operations start. There is a DragSource interface class that defines the methods that a view must implement to be a source.

DropTarget – A drop target is a view that accepts dragged objects. An interface class describes the methods every DropTarget must implement.

DragController – This object is the controller that does most of the work to support dragging and dropping. It receives the drag events and interacts with DragSource and DropTarget objects. It also sends information back to the activity that created it. That activity is expected to implement the DragDropPresenter interface.

DragDropPresenter – This interface defines the set of methods an activity must support in order to work with a DragController.

ImageCell – An ImageCell is a subclass of ImageView. It is used to display an image on the grid. Since objects can be moved from one spot on the grid to another, an ImageCell is both a DragSource and a DropTarget.

ImageCellAdpater – The object that is used by the GridView to place ImageCell objects on the grid.

DeleteZone – This object is a DropTarget. It allows objects to be dragged onto it for disposal. It is used to implement the trash can that you see on the screen.

In this object model, here is how an object moves on the screen. The user touches a view on the screen. The view touched signals an event, onClick, to its listener, which is the DragActivity. The activity passes the event on to its DragController object. If the view is a DragSource, the controller initiates a drag operation by asking the source for its ClipData and the view to display as the drag shadow. It then has the view start sending drag events (via a call to v.startDrag). As the user moves the view around on the screen, all the events associated with drag-drop are sent to the DragController. The DragController makes everything happen from there. When the shadow view is over a drop target, the view, if it is accepting dropped objects, is informed (via method calls to onDragEnter and onDragExit) so it can provide visual feedback to the user that a potential drop spot has been found. If the user releases the object over a drop target, the target object is informed (via onDrop) so it can perform the operation. There are also method calls made back to the DragActivity so it knows when dragging begins, when it ends, and whether it was successful.

When you study the code for DragController, you will find that there are a lot of conditions being checked. That’s because of the DragController working with two kinds of views (sources, targets) and because of the way I wanted the GridView to work. You can only put one image in a cell. The cell must be empty to accept an image. If you drag an object and drop it somewhere that does not accept drops, it goes back to the cell it started in.

Source Code

You can download the source code for this demo from  the Wglxy.com website. Click here: download zip file from wglxy.com. The zip is attached at the bottom of that page. After you import the project into Eclipse, it’s a good idea to use the Project – Clean menu item to rebuild the project.

This demo app was compiled with Android 4.2 (API 17). It works in all API levels from API 11 on up.

Potential Improvements

This sample app is an adaptation of the drag-drop framework from my earlier sample apps. The first of these (Moving Views In Android – Drag-Drop) goes back to January 2011. That one is the result of studying the Android Launcher code from API Level 8. I simplified the drag-drop framework a bit, but basically maintained its set of classes (DragSource, DropTarget, DragController, etc.).So the current example is a continuation of that framework. As described in the earlier section, you see that it has basically the same classes and interfaces.

It did not take much work to adapt to the Android drag-drop framework, which was introduced in API 11. However, it’s not clear to me that having a set of interface classes and defining View subclasses (e.g. ImageCell) is the easiest way to support drag-drop. In fact, there is a note to that effect in the “Drag and Drop” article. It says “You will probably want to use the listener in most cases. When you design UIs, you usually don’t subclass View classes …”. What I intend to try in the next month or so is another variation of this GridView example. In it, I will support dragging onto the grid, using only a listener. It will be interesting to see if that approach turns out to be easier.

Android Fragments Example

One thing I think is essential for understanding a feature in Android is a working example. The difference between a good write-up and the same write-up with source code attached is huge for me. I fully appreciate the great documentation and resources provided on the Android Developers website and on other websites. However, the working example is usually my guarantee that I will eventually understand a feature.

A case in point for me is the Fragments feature. I have been using them for about a year now so I am getting more comfortable using them in my app work. Several times in that time period, I have started apps with Fragments in Android 4+ and had to rework them so I could support older versions of Android. For me, that’s Android 2.3.3 (API 10). Fortunately, that’s easy to do because of the Android V4 Compatibility package that allows you to use Fragments in the earlier versions.

Since the need keeps coming up for me to have both Fragments and backward compatibility, I decided to build myself a simple demo app that combines both. That demo app helps me remember how to do that. Often, it is my starter kit for a new app. That’s what I am writing up and sharing in this blog post.

n7-and-lg-01

Uses for Fragments

If you have not read about Fragments, here’s my version of why they are useful. They make it easier to have UI components that you can use in different ways in your app. Two main uses: (1) handling differences between your app in landscape mode and your app in portrait; (2) handling differences in your UI when running on a tablet.

The following app screenshots illustrate the points. The first is the app on a small device in landscape. Nothing too surprising is going on. If you select a title on the left, the text for that title appears on the right. Reorient to portrait mode and you will see what’s in Figure 2. There is only room for the titles list. When you select a title, the whole screen changes. Titles are hidden and the details appear. The remaining figures show the same app on a tablet. There is more than enough screen in either orientation so the titles are always visible.

fragments-lg-02

Figure 1 – Landscape orientation, small screen

fragments-lg-01fragments-lg-01a

Figure 2 – Portrait orientation, small screen

fragments-n7-01

Figure 3 – Portrait orientation, Nexus 7 tablet, 1280 x 800

How It Works

You have to know only a few things to get this to work:

  1. How fragments can be used to display information in the two situations: standalone activity (Figure 2) and as part of a wider view (Figure 1 or Figure 3).
  2. How to structure your app to support different layouts for portrait and landscape
  3. How to structure your app to support the larger screens of tablets.
  4. How to provide backward compatibility for the Android versions prior to Android 4.

This demo example makes use of the code available with the Fragment class definition. That article explains the first two points above.

For point #3 above, I suggest reading one of my earlier blog posts, “My Dashboard User Interface on Android Tablets“. It explains how to use different layout folders to hold layout xml for different sizes of screens. Two of the most useful references in the References section “New Tools For Managing Screen Sizes”  and “Supporting Multiple Screens“.

The last point in list above has to do with using the new Fragment class in earlier versions of Android. For that, you should read about the V4 compatibility library for Android. Once I had the basic example working, I followed their instructions to make the demo example backward compatible. I did what they described in the section “Using the V4 Library APIs”.

  • Use FragmentActivity as the superclass for you own Activity classes if they are going to use Fragments.
  • When you need a FragmentManager object, call getSupportFragmentManager rather that getFragmentManager.

I did not do anything about the action bar and title bar. They are what they are in each of the Android versions. If you want to read about some options for the action bar, read my note on “Getting Started With Android V4“.

Source Code

You can download the source code for this demo from  the wglxy.com website. Click here: download zip file from wglxy.com. The zip is attached at the bottom of that page. After you import the project into Eclipse, it’s a good idea to use the Project – Clean menu item to rebuild the project.

This demo app was compiled with Android 4.2 (API 17). It works in all API levels from API 10 on up.

Conclusion

You can find examples of fragments in many places; the same goes for V4 compatibility. So, in a way, there is not a lot new here. However, if you know you want both fragments and V4 compatibility, consider using this app as a starter for your next project.

References

Fragments

V4 compatibility library for Android

Supporting Multiple Screens

Three Variations for Image Square Grids in Android

In a new Android app I am working on, I want to display a a square grid of images. I have tried  several variations of doing this using a GridView. Each of the solutions involves using a ViewTreeObserver, which is something I would not have thought of on my own. Fortunately, I found several notes on StackOverflow that pointed me in the right direction.

I’ll describe three of the variations in this blog post. The end result for each variation is good: square images on a grid. My favorite variation is the one in which the sizes of the images are determined dynamically, based on the size of the screen. It requires the least amount of  work and delivers good results.

The other two variations give you more control over the sizes of the image squares. You can choose one size for a 480 x 320 screen, another size for 800 x 480, and still another for a 1024 x 768 tablet. It relies on using resource folders with size qualifiers (examples: values-large, values-xlarge, values-sw600dp, etc.).

The main activity for the application allows you to choose the variation you want.

Variations 1-2

The first variation allows you to specify the size of the images and the containing grid. Its layout definition looks like this.

<FrameLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:background="@color/background"
 >
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/gridView"
 android:color="@color/grid_background"
 android:layout_width="@dimen/image_grid_width"
 android:layout_height="match_parent"
 android:columnWidth="@dimen/image_thumbnail_size"
 android:horizontalSpacing="@dimen/image_thumbnail_spacing"
 android:numColumns="@integer/grid_num_columns"
 android:stretchMode="none"
 android:verticalSpacing="@dimen/image_thumbnail_spacing" >
 </GridView>
</FrameLayout>

Notice that it refers to the following values:

  • image_thumbnail_size
  • image_thumbnail_spacing
  • image_grid_width
  • grid_num_columns

All of those are defined in the values resource folder inside dimens.xml.  To support the different sizes of screens, there are many of these dimens.xml files. Here is one of them. This one would work well for  480 x 320 screen.

<resources>
 <integer name="grid_num_rows">10</integer>
 <integer name="grid_num_columns">10</integer>
 <dimen name="image_thumbnail_size">30dp</dimen>
 <dimen name="image_thumbnail_spacing">1dp</dimen>
 <dimen name="image_grid_width">320dp</dimen>
</resources>

The layout file and the values for the dimensions is only half of the solution. When you use the standard GridView, you have to live with the standard GridView behavior, or find a way to work around it. A standard GridView is designed to fill up the entire width of its parent with however many columns you specify. There are lots of variations of parameters you can try, but I have not found one where you end up with the image height matching the image width.

Thanks to several people who provided answers on StackOverflow and a wonderful example on the Android Developers’ website (see References section below), I learned how to use a ViewTreeObserver to adjust the sizes of the images so the height matches the width being used in the grid. All the details of setting up the GirdView with its ImageAdapter can be seen in the full source code (see below). The most important thing is to understand the following code section that appears in the activity’s onCreate method.

// This listener is used to get the final width of the GridView.
// The column width is used to set the height
// of each view so we get nice square thumbnails.
 mGridView.getViewTreeObserver().addOnGlobalLayoutListener(
 new ViewTreeObserver.OnGlobalLayoutListener() {
 @Override public void onGlobalLayout() {
   if (mAdapter.getNumColumns() == 0) {
      final int numColumns = (int) Math.floor(
         mGridView.getWidth() / (mImageThumbSize + mImageThumbSpacing));
      if (numColumns > 0) {
         final int columnWidth =
             (mGridView.getWidth() / numColumns) - mImageThumbSpacing;
         mAdapter.setNumColumns(numColumns);
         mAdapter.setItemHeight(columnWidth);
      }
   }
 }
 });

By the time this code runs, the width of the GridView is known. That allows the width of the images in the grid to be calculated, which is then passed along in the call to the setItemHeight method.

public void setItemHeight(int height) {
  if (height == mItemHeight) {
     return;
   }
   mItemHeight = height;
   mImageViewLayoutParams = new GridView.LayoutParams(mItemHeight, mItemHeight);
   notifyDataSetChanged();
}

This method is defined inside the ImageAdapter that works to fill up the GridView. That explains why it looks so simple. You don’t see code to go through all the image views on the grid to adjust their size. What you see is changing the item height and a redefinition of some LayoutParams. The last line is a call to “notifyDataSetChanged”. That causes the adapter to redo all of its work, which includes all of the calls to its getView method. Inside that is where the image views are set up with the dimensions they should be to be squares. Check that code and you will see where it uses mImageLayoutParams.

I said earlier that there would be many of the dimens.xml files, based on an assumption that you always want the square grid to fill the screen. That might not be the case for all apps. If you wanted to have a grid that was always the same size (e.g. 320 x 320), no matter what size the screen is, you could use a definition like the one above.

To handle the landscape orientation, a second dimens.xml is defined in the values-land folder.

<resources>
 <dimen name="image_thumbnail_size">26dp</dimen>
 <dimen name="image_thumbnail_spacing">1dp</dimen>
 <dimen name="image_grid_width">270dp</dimen>
 </resources>

Note that it uses a slightly smaller grid size because it accounts for the size of the action bar  (or title bar) at the top.

My second variation came about because I wanted to see if I could simplify things by not having to define as many values. The xml for variation 2 does not include the image_grid_width. Because of that the GridView fills the entire width of the screen, which works just fine in portrait orientation, but not so good in landscape.

The images themselves are still squares, but the overall grid is not. I left this variation in this demo app because there might be some situations where expanding to fill the full width is the right thing to do.

Variation 3 – Dynamic Image Sizes

The variation I like the best is the one where the image sizes and the grid size are determined dynamically. Other than specifying the number of columns and the spacing, you do not have to do anything to end up with square images within a square grid.

Its layout file is not as complicated.

<LinearLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/frame"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content" 
 android:padding="0dp"
 android:background="@color/debug_background"
 >
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/gridView"
 android:color="@color/grid_background"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_gravity="center" 
 android:gravity="center_horizontal|center_vertical"
 android:columnWidth="@dimen/initial_image_thumbnail_size"
 android:numColumns="@integer/grid_num_columns"
 android:padding="0dp"
 android:stretchMode="none"
 android:horizontalSpacing="@dimen/image_thumbnail_spacing"
 android:verticalSpacing="@dimen/image_thumbnail_spacing" >
</GridView>
</LinearLayout>

This solution builds on the earlier ones. It still depends on there being a ViewTreeObserver. From the other variations, I understood what it could be used for. So I changed it so it would do more that adjust the height of the image views in the grid. Since the grid view height and width are known, it recalculates that view and all the image views contained in it.

mGridView.getViewTreeObserver().addOnGlobalLayoutListener(
 new ViewTreeObserver.OnGlobalLayoutListener() {
 public void onGlobalLayout() {
   // When we get here, the size of the frame view is known.
   // Use those dimensions to set the width of the columns 
   // and the image adapter size.
   if (mAdapter.getNumColumns() == 0) {
     View f = mFrameView;
     int fh = f.getHeight () 
              - f.getPaddingTop () - f.getPaddingBottom ();
     int shortestWidth = fh;
     int fw = f.getWidth () - f.getPaddingLeft () - f.getPaddingRight ();
     if (fw < shortestWidth) shortestWidth = fw;
       int usableWidth = shortestWidth 
                         - (0 + mNumColumns) * mImageThumbSpacing
                         - f.getPaddingLeft () - f.getPaddingRight ();
       usableWidth = shortestWidth - (0 + mNumColumns) * mImageThumbSpacing;
       int columnWidth = usableWidth / mNumColumns;
       mImageThumbSize = columnWidth;
       int gridWidth = shortestWidth;
       // The columnWidth used is an integer. That means that we have a
       // little unused space. Fix that up with padding if the unused 
       // space is more than half of an image.
       int estGridWidth = mNumColumns * columnWidth;
       int unusedSpace = (shortestWidth - estGridWidth);
       boolean addPadding = (unusedSpace * 2) > mImageThumbSize;
       if (addPadding) {
          // This is not a precise calculation. Pad with roughly 1/3 the unused space.
          int pad = unusedSpace / 3;
          if (pad > 0) mGridView.setPadding (pad, pad, pad, pad);
       }
       mGridView.setColumnWidth (columnWidth);
       // Now that we have made all the extra adjustments, resize the grid
       // and have it redo its view one more time.
       LayoutParams lparams = new LinearLayout.LayoutParams (gridWidth, gridWidth);
       mGridView.setLayoutParams (lparams);
       mAdapter.setNumColumns(mNumColumns);
       mAdapter.setItemHeight(mImageThumbSize);
     }
   }
 });

There is a lot going on in the code above. The key points are these:

  • The amount of space available for a GridView is known.
  • The shortest width can be determined and the grid can be sized for that value.
  • With a new shortest width, a new value for column width can be calculated.
  • Forcing the grid to redo all of its image views results in the square images needed.

This variation works everywhere that I have tried it. That includes tablets (like Xoom and Kindle Fire) and large and small screens on phones.

Source Code

You can download the source code form the wglxy.com website. Click here: download sources from wglxy.com. The zip is attached to the bottom of that page. After you import the project into Eclipse, it’s a good idea to use the Project – Clean menu item to rebuild the project.

This demo app was compiled with Android 4.0.3 (API 15). It works in all API levels from API 8 and on.

References

ViewTreeObserver – Using this class with your activity makes it possible to get the information you need to size your images and GridView to have a grid of squares.

How do you retrieve dimensions of a view? – article on Stack Overflow that describes the ViewTreeObserver onGlobalLayout method and how to use it to get the size.

Getting Layout Dimensions in Android- More discussion about getting the width of a view after layout.

Displaying Bitmaps Efficiently – Not only is this a great reference about displaying images, it has the best example I found for adjusting the height of image views in a grid. All the onGlobalLayout handlers I did are based on this example. Once again, we should all thank the writers for the Android Developers’ website.

My Dashboard Interface on Tablets – This is a blog article I did about being able to have an app adapt easily to all the different size screens on devices and tablets. It explains more about the size qualifiers that you can use on values and layout folders.

New Tools For Managing Screen Sizes – a blog article by Dianne Hackborn. It explains how to use the new size qualifiers (e.g. layout-600dp, layout-sw600dp).

Improved Drag-Drop for an Android GridView

(March 4, 2013 Updsate: This example has been revised for Android V4, See Drag-Drop for GridView V4.)

I have an improved version of my tutorial app described in “Drag-Drop for an Android GridView“. It allows a drag-drop sequence to be initiated from any touch event. The earlier version required the user to do a long click (press) before dragging would start.

There are two download locations for this demo application: (1) download from Wglxy.com; (2) download from Google Docs; It was built with API level 8. Be sure to do a “Clean” rebuild in Eclipse.

Changes

This example application, like other ones I have done related to drag-drop, was based on the Android Launcher code. The Launcher required a long click (press) before an icon on the home screen could be dragged. Several people asked how to make it work on just a regular click. I finally got around to doing that.

To do this, all I had to do was make the activity implement View.onTouchListener. The handler  checks for the ACTION_DOWN event that indicates that the user touched the screen. If it sees that, it starts a drag operation.

public boolean onTouch (View v, MotionEvent ev)
{
 // If we are configured to start only on a long click,
 // we are not going to handle any events here.
 if (mLongClickStartsDrag) return false;
 boolean handledHere = false;
 final int action = ev.getAction();
 // In the situation where a long click is not needed
 // to initiate a drag, simply start on the down event.
 if (action == MotionEvent.ACTION_DOWN) {
    handledHere = startDrag (v);
 }

 return handledHere;
}

There is no right answer for the question: what starts a drag operation? It’s really your choice: long click or one touch. So what I did in the demo application is put a new variable into the DragActivity class. It indicates whether you want long clicks to be the only way to start a drag-drop operation.

private boolean mLongClickStartsDrag = false;
    // If true, it takes a long click to start the drag operation.
    // Otherwise, any touch event starts a drag.

So you can try out both ways of starting a drag-drop operation. Just change the value of the variable from false to true.

References

Input Events - describes the different types of input events for views: click events, touch events, etc.

Discussion about the touch events - the end of the note has a succinct statement about the differences

How To Build A Simple Help Screen in Android

I released my first app to the Android Market recently. It was just complicated enough that I thought I should provide some help screens inside the app. I have taken what I did there and built a demo app, in order to illustrate how one can build a simple help screen in Android.

My simple Help screen consists of a summary page where there is an image and a brief description for each help topic. Touching the image takes you to the full help text for a topic. The figures below show what the two activities look like.

Figure 1 – Help screen

Figures 2-3 – Topic screen for topics 1 and 2

It is easy to build this kind of help screen. You need two layouts: one for the help activity and one for the topics. All of the help text comes from xml files in the resource folders. The help text can include paragraphs, text formatting (bold, italics), and even links to web pages. Even if you do not need a Help screen for your app, knowing how to have formatted Html text in an Android text view could be of use to you.

Help Activity

The demo app starts out in the HelpActivity. Its layout file is help.xml.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"
 android:background="@color/light_background"
 android:orientation="vertical" >
<ScrollView android:id="@+id/ScrollView1"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent">
<LinearLayout
 android:orientation="vertical"
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
 android:layout_weight="1"
 android:padding="6dip"
 >
<TextView
 android:id="@+id/help_page_intro"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:text="@string/help_page_intro"
 android:padding="2dip"
 android:layout_weight="1"
 />
...

</LinearLayout>
</ScrollView>
</LinearLayout>

The first thing to note is that it starts with a ScrollView that encloses a single LinearLayout. Doing that is all you need to have scrolling on the Help screen.

After the two items that support scrolling and the text view for the introduction, there are four LinearLayout views that use the horizontal orientation. Each one includes an image button and some summary text. This is what the first one looks like.

<LinearLayout
  android:orientation="horizontal"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:layout_weight="1"
  android:padding="4dip">
 <Button android:id="@+id/help_button1"
  android:layout_weight="1"
  android:layout_width="180dip"
  android:layout_height="wrap_content"
  style="@style/HelpButton.Dark"
  android:onClick="onClickHelp"
  android:text="@string/help_title_section1"
  android:drawableTop="@drawable/help_image1"/>
 <TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="@string/help_text_section1"
  android:padding="8dip"
  android:layout_weight="1"
  />
 </LinearLayout>

The image button has an image with text underneath it. The text comes from the resource string help_title_section1 (which has a value “Topics”). The drawable is defined by the help_image1 definition in the drawable folder. What makes this interesting is that it is not an image file, but an xml file.

<selector xmlns:android="http://schemas.android.com/apk/res/android">
 <item android:state_focused="true" android:state_pressed="true" 
    android:drawable="@drawable/light_bulb_pressed"/>
 <item android:state_focused="false" android:state_pressed="true"
    android:drawable="@drawable/light_bulb_pressed"/>
 <item android:state_focused="true" android:drawable="@drawable/light_bulb_selected"/>
 <item android:state_focused="false" android:state_pressed="false"
    android:drawable="@drawable/light_bulb_default"/>
</selector>

I used three distinct images so that the “selected” button looks different from the “pressed” button, and both are different from the button in its normal state. (For more on how to use these state lists, see StateListDrawable.)

Like other buttons, you can define the onClick handler in the xml file. In this case, the handler is onClickHelp. In fact, all four of the buttons use the same handler. The id of the view is used to determine which of the buttons was clicked.

The definition of onClickHelp is in HelpActivity. It looks at the id of the view to determine the resource id of the text to display in a TopicActivity. Note how it passes that argument as it starts the TopicActivity using an Intent.

public void onClickHelp (View v)
{
 int id = v.getId ();
 int textId = -1;
 switch (id) {
 case R.id.help_button1 :
   textId = R.string.topic_section1;
   break;
 case R.id.help_button2 :
   textId = R.string.topic_section2;
   break;
 case R.id.help_button3 :
   textId = R.string.topic_section3;
   break;
 case R.id.help_button4 :
   textId = R.string.topic_section4;
   break;
 default:
   break;
 }
 if (textId >= 0) startInfoActivity (textId);
 else toast ("Detailed Help for that topic is not available.", true);
}
public void startInfoActivity (int textId)
{
 if (textId >= 0) {
   Intent intent = (new Intent(this, TopicActivity.class));
   intent.putExtra (ARG_TEXT_ID, textId);
   startActivity (intent);
 }
}

Topic Activity

It may be a bit of a surprise to see how little code there is in the TopicActivity. It starts up and sets its content view. Then it reads the id of the string resource that is to be displayed on the screen. It locates the resource string and processes it as Html text and assigns that to the text view defined in the topic.xml.

protected void onCreate(Bundle savedInstanceState)
{
 super.onCreate(savedInstanceState);
 setContentView (R.layout.topic);
 // Read the arguments from the Intent object.
 Intent in = getIntent ();
 mTextResourceId = in.getIntExtra (HelpActivity.ARG_TEXT_ID, 0);
 if (mTextResourceId <= 0) mTextResourceId = R.string.no_help_available;
 TextView textView = (TextView) findViewById (R.id.topic_text);
 textView.setMovementMethod (LinkMovementMethod.getInstance());
 textView.setText (Html.fromHtml (getString (mTextResourceId)));
}

The line with the LinkMovementMethod is also important. Without that, links displayed on the screen are not active.

The Html.fromHtml method allows you to define paragraphs, bold, italics and links in the resource strings that define the help text. Figures 2-3 above showed what the formatted text looks like. The following is the string topic_section2 defined in the help_strings.xml file.

<string name="topic_section2">
 <![CDATA[
 <p>
 You can display simple forms of Html in a text view.
 You use the Html class and its fromHtml static method.
 See
 <a href="http://developer.android.com/reference/android/text/Html.html">Html
  on Android Developers</a>.
 </p>
 <p>
 As shown here, paragraphs are supported,
 as are <b>bold text</b> and <i>italics</i>.
 The preceding paragraph has a link in it.
 </p>
 <p><b>Return to the previous screen by touching the Back button.</b>
 </p>
 ]]>
 </string>

Source Code

The source code for this demo app can be downloaded from these two locations: (1) download source from Google Docs; (2) download source from Wglxy.com. The zip file at Google Docs is shared with everyone, but sometimes people outside the US have trouble downloading it. If you have problems, try the second location.

This demo app was built with Android API 10. Be sure to rebuild the project using the “Clean” command on the Build menu in Eclipse.

Integrating Help Into Your App

If you decide to you want a Help screen in your app, you can start with the code above. If you would like to have a form of context-sensitive help, you could consider doing what I did in my Gomoku League app.

In Gomoku League, I used the Help and Topic activities so I could explain how to play the game of Gomoku and how the league part of the app works. Gomoku League has a user interface based on the dashboard pattern. In the action bar for each section of the app, there is a “?” button that takes you directly to the help for that activity.

Even if you do not have an action bar at the top of the screen, you can find other ways to support context-sensitive help. The simplest is to include a Help item on the menu that you display when the user pushes the Menu button.

Android Dashboard Examples

I started looking around for Android dashboard examples and found quite a few. I used a Google search for images: android dashboard screenshot.

One thing I found interesting was the format of the search results. I had not seen this before, but I like it. You see the image and it has the link to the website where the image originated.

Dashboard examples:

Most of the items on this list were compiled  while working on “How To Build A Dashboard User Interface In Android“.

How To Build A Dashboard User Interface In Android

I have been looking into user interface designs and patterns for Android. This note describes what I have learned from building a demo app that illustrates the Dashboard user interface pattern.

The demo app has a main screen with six buttons. Each button takes you to a different section of the app. Each section has a title bar at the top. The title bar tells you where you are and provides actions that are common to all. In this case, there are two action buttons that take you to an About screen and a Search screen. For everything but the main page, there is a link back to the home screen of the app.

My example app is derived from the Schedule app that Google came out with for the 2010 Google IO conference. That app has a lot of interesting features worth studying, but what I was interested in is what it takes to do a dashboard.

The Google IO Schedule app was fully functional. I pared it down to the essential elements related to a dashboard. None of the activities do anything more than display a line or two of text. That makes it a good starting point for building your own dashboard app.

The following screenshots give you a little bit better idea of what the app looks like. The About screen and Feature 1 screen are simple placeholders for whatever the real thing turns out to be in your application. The third figure below shows what the screen looks like as a user presses the “Feature 4″ button.

Full source code for my Dashboard Demo app is available. See the Source Code section below.

How It Works – Image Buttons

Basically what you have is a layout of image buttons. When a button is clicked, the handler starts another activity. The only way back to the home activity is via the Home button that appears in all of the secondary activities.

All the layout of views is done in resources files in folder res/layouts.  I am assuming you are familiar with basic layouts, defining views within them, and connecting them to an activity so I will not cover those things here. (For more on activities, refer to “My Introduction To Android Activities“.)

One of the things I learned doing this demo was how to set up image buttons so the user gets visual feedback as the buttons are touched, selected, or clicked. There are two kinds of image buttons used: (1) the buttons in the title bar area for About and Search; (2) the buttons in the main part of the layout that are used to move to one of the main sections of the app.

Here is how a title bar button works. If you look inside any of the layout files (activity_home.xml, activity_f1.xml, etc.), you will see a LinearLayout definition for the title bar.

<LinearLayout style="@style/TitleBar">
...
</LinearLayout>

Inside of that there is an image button definition for the About screen. It indicates what image is used as the button and indicates what method gets called when the button is clicked.

<ImageButton style="@style/TitleBarAction"
            android:contentDescription="@string/description_about"
            android:src="@drawable/title_about"
            android:onClick="onClickAbout" />

What I learned from the Schedule app is that the drawable does not have to be an image. They used another xml file to define what the drawable looked like in each of the different states it could be in: default, pressed, and selected.  The following xml is in res/drawable/title_about.xml.

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_focused="true"
          android:state_pressed="true"
          android:drawable="@drawable/title_about_alt"/>
    <item android:state_focused="false"
          android:state_pressed="true"
          android:drawable="@drawable/title_about_alt"/>
    <item android:state_focused="true"
          android:drawable="@drawable/title_about_alt"/>
    <item android:state_focused="false"
          android:state_pressed="false"
          android:drawable="@drawable/title_about_default"/>
</selector>

It looks a bit complicated at first but what it is saying is that there are two images to be used: default and alt. Use alt when the image button is in focus or selected. The two images I used are these:

The second is the one shown when the user presses the button. If you have worked with web applications, you will see this is very similar to the way you do images that show up as the user moves the mouse over an image that is a linked to more information.

Each of the buttons you see for the six different sections of the app are also image buttons. For those, I used three distinct images so that the “pressed” button looks different than the button when it has focus. The three images for Feature 5 (the stars) are shown below.

Here is the xml in home_activity for the Feature 5 button.

<Button android:id="@+id/home_btn_feature1"
            style="@style/HomeButton"
            android:onClick="onClickFeature"
            android:text="@string/title_feature5"
           android:drawableTop="@drawable/home_button5" />

It refers to home_button5 as a drawable. That drawable is defined in its own xml file: home_button5.xml.

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_focused="true"
          android:state_pressed="true"
          android:drawable="@drawable/stars_pressed"/>
    <item android:state_focused="false"
          android:state_pressed="true"
          android:drawable="@drawable/stars_pressed"/>
    <item android:state_focused="true"
          android:drawable="@drawable/stars_selected"/>
    <item android:state_focused="false"
          android:state_pressed="false"
          android:drawable="@drawable/stars_default"/>
</selector>

For more on how to use these state lists, see StateListDrawable.

How It Works: Styles

The other thing new for me was the use of style definitions. In both of the button xml definitions in the preceding section, there were references to styles.

    style="@style/TitleBarAction"
and
    style="@style/HomeButton"

Styles are defined in xml files in the resource folders.  They serve the same purpose as css files in html files. They allow you to separate the design from the content.

In res/values/styles.xml, you will find the following definition.

    <style name="HomeButton">
        <item name="android:layout_gravity">center_vertical</item>
        <item name="android:layout_width">fill_parent</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:layout_weight">1</item>
        <item name="android:gravity">center_horizontal</item>
        <item name="android:textSize">@dimen/text_size_medium</item>
        <item name="android:textStyle">normal</item>
        <item name="android:textColor">@color/foreground1</item>
        <item name="android:background">@null</item>
    </style>

A good description of styles is at android.developer.com: “Applying Styles and Themes“.

How It Works: Click Handlers

If you have already built a few simple apps, building a dashboard app is not too hard. Once you get comfortable with the xml for image buttons, state lists, and styles, all you have left is implementing the onClick handlers that start the appropriate Activity.

Each activity is defined in its own class file. One thing I did was define an abstract superclass (DashboardActivity) so I would have a place to collect code common to all the activities. That means there is only one place to edit the code that starts the activities that make up the app.

It defines methods for starting the different activities. For instance, for the Search activity, it defines

public void onClickSearch (View v)
{
    startActivity (new Intent(getApplicationContext(), SearchActivity.class));
}
Returning to home is a bit different because you are in a secondary activity at the time you click the Home button. 
public void onClickHome (View v)
{
    goHome (this);
}
public void goHome(Context context) 
{
    final Intent intent = new Intent(context, HomeActivity.class);
    intent.setFlags (Intent.FLAG_ACTIVITY_CLEAR_TOP);
    context.startActivity (intent);
}

According to the Android developer documentation on Intents, the extra Intent flags result in taking the user back to the home activity after first closing the activity that was active. That seems like reasonable behavior for a dashboard app, which requires that you return home before moving to another section of the app.


Dashboard User Interface Pattern

One design pattern for user intefaces in Android is the Dashboard. Interest in that pattern is pretty high, starting with the session on Android UI Design Patterns at Google IO 2010.

After seeing that video, I really wanted to try implementing the Dashboard pattern. I searched for the Twitter app that they said would be open source, but I could never find it. Fortunately, the source code for the Schedule App is available. Studying that is what got me here to this blog article.

I like the Dashboard pattern because it matches the way I like to think about applications: web, mobile, or whatever. For websites, the things I like are:

  • Simple. Clean.
  • Make it easy for people to understand what they can do.
  • Information is the interface
  • Use plain English
  • No training required

For websites, I appreciate ones that “Don’t Make Me Think“. For me, a Dashboard is essentially that, as long as I take the time to think about what the important sections are and make those available on the home screen.

For more information about dashboards and other patterns, read “A Closer Look At Android’s Evolving UI Patterns“.

Source Code

The source code for this demo application is available in two places:  (1) download from Wglxy.com. (2) download from Google Docs.  If you find that the app does not build in Eclipse, be sure to do a clean build by using the “Clean” item on the Project menu.

This demo app was built on a MacBook running Eclipse Galileo and Android 2.2. The version listed by Eclipse for the Android Developer Tools is version 8.0.1.v201012062107-82219. The app has been tested in the Android emulator and on an LG Optimus phone.

Examples of Dashboards

I found quite a few examples of dashboards in Android apps. See my post: Android Dashboard Examples.

Update – March 2012 

This note was originally published in March 2011. You might be interested in two recent news items.

An App Based On This Work

I have applied  this dashboard pattern to an Android app. Overall, I am very happy with how it turned out. The dashboard pattern made it easier in many ways. It was so useful to have the basic structure of the app taken care of.

If you would like to have another working example of this work to study, you can try the Gomoku League app, which is a free download on the Android Market.

Dashboard XL Demo On Tablets 

If you want to see how this demo can be expanded to support both phones and tablets, see “My Dashboard User Interface On Android Tablets“.

Moving Views In Android – Part 2, Drag and Drop

This is part 2 of my investigation into how to get views on an Android screen that a user can move by touching them and dragging them. For Moving Views in Android – Part 1, I had a couple of images positioned on the screen and got the images to move on the screen. It turns out that the views themselves were not moving, just the images within the bounds of the view. In the work that I describe here, I actually have the views moving. The key was to have a custom ViewGroup to coordinate drag and drop events for all views within the layout.

The figures below show what the app looks like in the emulator. Figure 1 shows the initial state of the app. Two images and a text view are in the upper left of the screen. The other figures show the views being dragged around the screen. Red highlighting indicates which view is moving.

Figures 1-2

Dragging an image

Figures 3-4:

Dragging the second image Moving the text view

The solution feels pretty good — and it should, considering it is derived from the Android Launcher code. I had searched a long time for good touch and drag-drop examples, and never found any that were as complete as what the Android developers had done. What I like is the good object model and the clear separation of responsibilities among the classes. More on that follows in the next section.

(For more on drag-drop, see “Drag-Drop for Android GridView V4“.) 

How It Works

The Launcher is the activity that manages the workspace you see on your Android phone. Besides being able to launch apps, it allows you to change the layout of icons on the screen. You touch an icon, wait for the feedback (vibration and the change in size of the icon) that you have touched a moveable icon, and then you reposition the icon on the screen.

I made a few simplifying assumptions as I built my example activity object.

  1. The example app allows you to move views, but only on a single screen. The Launcher allows you to place app icons on many screens that you get to by scrolling left and right.
  2. The example app supports only one place where you can drop views. The Launcher supports multiple DropTargets.
  3. The example app allows views to be dropped anywhere, even on top of another view. The Launcher maintains a grid of icons and icons can be dropped only in the grid.
  4. The Launcher has animation and scaling code that is used to show a bitmap of the view being moved. The example app simply shows the bitmap.

The following classes have been adapted for my example DragView application.

DragActivity – The Launcher class was turned into my DragActivity class. It is the main activity of the app.

DragLayer – implements a custom ViewGroup, which coordinates movement of views on the screen. The Launcher DragLayer is a subclass of FrameLayout. Mine is a subclass of MyAbsoluteLayout, which is a clone of the recently deprecated AbsoluteLayout class. AbsoluteLayouts allow views to be positioned to specific absolute x and y positions.

DragController – This object is the controller that does most of the work to support dragging and dropping.

DragSource – defines where a drag operation begins.

DropTarget – defines where an object can be dropped.

DragView – This is the view that you see moving around on the screen during a drag operation. It is not the actual view you want to move, but simply a bitmap that looks like the view.

In this object model, here is how an object moves on the screen. The user touches a view on the screen. The view touched signals an event, onLongClick, to its listener, which is the DragActivity. The activity turns it over to its DragController object. The controller initiates a drag operation by first getting a bitmap copy of the view, storing it in a DragView, and displaying it on the screen. As the user moves the view, the DragLayer fields all the touch events in its onTouchEvent method. It relays those events to the DragController. It watches for the end of the touch (ACTION_UP), but in the mean time, it handles the motion events by shifting the DragView to the new touch position. When the touch finally ends, the controller checks its set of DropTargets to see which one is active and if it will accept the view being dropped there. If it does, the controller calls the onDrop method of the target. In this application, the DragLayer also implements the DropTarget interface. So the onDrop call goes to the DragLayer and it takes the view being dragged and repositions it to the drop location.

The full source code is available below. A few code sections are worth noting now.

Each view that can be moved relays its onLongClick events to the DragActivity. The connection is made in the setupViews method of DragActivity.

private void setupViews() {
...
ImageView i1 = (ImageView) findViewById (R.id.Image1);
ImageView i2 = (ImageView) findViewById (R.id.Image2);
i1.setOnLongClickListener(this);
i2.setOnLongClickListener(this);
...
}

The onLongClick handler calls on the DragController to initiate the drag sequence.

public boolean onLongClick(View v) {
    trace ("onLongClick in view: " + v);
    // Let the DragController initiate a drag-drop sequence.
    // Use the dragInfo to pass along the object being dragged.
    Object dragInfo = v;
    mDragController.startDrag (v, mDragLayer, dragInfo, DragController.DRAG_ACTION_MOVE);
    return true;
}

While the drag is happening, the DragLayer passes all touch events to its DragController. The onTouchEvent method in DragController moves the DragView and its bitmap. That creates the appearance of the view moving on the screen. The move method in DragView uses updateViewLayout, which causes the layout for the screen to be redone, using whatever new x-y positions are in effect.

void move(int touchX, int touchY) {
        // This is what was done in the Launcher code.
        WindowManager.LayoutParams lp = mLayoutParams;
        lp.x = touchX - mRegistrationX;
        lp.y = touchY - mRegistrationY;
        mWindowManager.updateViewLayout(this, lp);
}

At the end of the drag, the onDrop method of DragLayer is called. It repositions the actual view. Note that it too calls to updateViewLayout. That is what causes the DragLayer view to recalculate its layout and redraw all its views.

public void onDrop (DragSource source,
         int x, int y, int xOffset, int yOffset,
        DragView dragView, Object dragInfo)
{
    View v = (View) dragInfo;
    int w = v.getWidth ();
    int h = v.getHeight ();
    int left = x - xOffset;
    int top = y - yOffset;
    DragLayer.LayoutParams lp = new DragLayer.LayoutParams (w, h, left, top);
    this.updateViewLayout(v, lp);
}

Potential Improvements

My adaptation is not quite as clean as it should be. Roughly speaking, in MVC (model-view-controller) terms, the DragActivity is the model, the DragLayer is the view, and the DragController is the controller. It’s not clear to me that I have stayed true to MVC in this example. I think the handlers for click and long click events might be better off in the controller.

This example uses a clone of the deprecated AbsoluteLayout class. Now that I understand the Launcher code better, I see that it might be better in a real application to have the DragLayer view determine exactly where views are allowed to be dropped. That would be similar to the way it was done in the Launcher app. Its Workspace class allows drops only at empty spaces in a grid. (Note: I did something for GridViews. See “Drag-Drop for an Android GridView“.) 

If you want to see an example where views are added dynamically to the DragLayer, see “Moving Views In Android – Part 3: Drop Zones“. It also shows how to give users more visual feedback as objects are being dragged around the screen.

Acknowledgements

Thanks go to the Android development team. With all the code being open source, it makes it easy for all of us to learn from their designs. A post in the Android Developers group about doing drag and drop without AbsoluteLayout pointed me in the right direction.

Source Code

The source code is available as an Eclipse project. it is available in two places:  (1) download from Wglxy.com. (2) download from Google Docs.  Do a “clean build” in Eclipse if it reports errors immediately after importing the project.

If you find that the app does not build in Eclipse, be sure to do a clean build by using the “Clean” item on the Project menu. 

This work was done on a MacBook running Eclipse Galileo and Android 2.2 (API 8).  



Follow

Get every new post delivered to your Inbox.

Join 72 other followers