Droid Dev Stream
P145: Fragments p5: Communicating events

In your Fragment class, if you create listener on a particular interface, and make the parent implement that interface, you can communicate via events.

First create the interface:

public interface OnNewFragmentPressed {

void onNewFragmentPressed();

}

Then create a listener method of that interface.

public static class NewFragment extends Fragment {

    private OnNewFragmentPressed mListener;

Then in the onAttach() method of your Fragment, use the passed in Activity to make sure it implements the interface, and set the listener to that.

@Override

    public void onAttach(Activity activity) {

        super.onAttach(activity);

        try {

           mListener = (OnNewFragmentPressed) activity;

        } catch(ClassCastException e) {

           throw new ClassCastException(activity.toString() + " didn’t implement OnNewFragmentPressed");

        }

   }        

Now you can call methods of that interface, thereby interacting with your parent Activity.

P144: Fragments p4: Communicating between FragmentActivity and Fragment

In your FragmentActivity, you can call getSupportFragmentManager().findFragmentByTag(“tag”), or findFragmentById(R.id.frag), to access the child Fragment. From there you can call its methods.

Similarly, in the Fragment, you can call getActivity() to get access to the parent FragmentActivity().

P143: Fragments p3: Replacing with back stack and animation

You can replace an existing fragment, like you added one. But this time with a back stack, meaning pressing back reverts to transaction, and some simple animations.

You can, however, only delete and replace fragments which you’ve added via the FragmentManager, not ones you’ve initiated using XML.

Call this code to replace an existing Fragment you added with another.

FragmentManager fragManager = getSupportFragmentManager();

FragmentTransaction fragTransaction = fragManager.beginTransaction();

fragTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);

NewFragment f = new NewFragment();

fragTransaction.replace(R.id.frag_container1, f);

fragTransaction.addToBackStack(null);

fragTransaction.commit();

(If you’re in a Fragment, and not a FragmentActivity, replace getSupportFragmentManger wiht getFragmentManager.)

Note we’re setting a transition, TRANSIT_FRAGMENT_OPEN. And we’re added it to a back stack. So we we press back this transition is reversed.

P142: Fragments p2: adding new ones via FragmentManager

FragmentManager will allow us add, replace, fragments, control and listen on a fragment’s back stack, find a fragment by id or tag.

We’ll use it to add one. First create an empty ViewGroup in our layout file.

    <FrameLayout android:id=”@+id/frag_container1”

        android:layout_width="match_parent" 

        android:layout_height="wrap_content">

    </FrameLayout>

Then in the onCreate() method of our FragmentActivity call up a FragmentManger, call up a FragmentTransaction to add a new fragment, create a new Fragment (the same we created last tutorial), then add and commit the change.

FragmentManager fragManager = getSupportFragmentManager();

FragmentTransaction fragTransaction = fragManager.beginTransaction();

OtherFragments f = new OtherFragments();

fragTransaction.add(R.id.frag_container2, f);

fragTransaction.setBreadCrumbTitle("hello");

fragTransaction.commit();

Now our new Fragment will reside in the ViewGroup we created.

If you add it with add(fragment, string) you add a new fragment without a UI. The string is a tag that you can use to get access to it again, via the FragmentManager.

P141: Fragments p1

Create a layout file as normal. But this time with a <fragment> tag in it. It ‘name’ is a static public class we’ll shortly make.

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <fragment android:name=your.activity.namespace$OurFragment
            android:layout_width="match_parent" 
            android:layout_height="wrap_content" /> 

</LinearLayout>

Then in your ‘Activity’ change its base class to FragmentActivity.

public class OurExample extends FragmentActivity {

Now create a new public static class in this file. Note it has the same name (and be very careful you name it the same, lest badly title exceptions will come to haunt you) as the <fragment> tag above.

public static class OurFragment extends Fragment {
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
     Bundle savedInstanceState) {    
        return inflater.inflate(R.layout.fraglayout, container, false);
     }
}

Its one required method, onCreateView, will return a normal inflated layout file for its view. It has other lifecycle methods, which can be found in the docs.

P140: ActionBar p2
  • You can add action bar items like context items. First create a res/menu/menu.xml file as usual:

<?xml version="1.0" encoding="utf-8"?>

<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:id=”@+id/menu_a”

          android:title="hola"

          android:icon="@android:drawable/btn_plus"          

          android:showAsAction="ifRoom|withText" />

    <item android:id=”@+id/menu_b”

          android:icon="@android:drawable/btn_plus"

          android:title="hola1"

          android:showAsAction="ifRoom|withText" />

    <item android:id=”@+id/menu_c”

          android:icon="@android:drawable/btn_plus"

          android:title="hola1"

          android:showAsAction="ifRoom|withText" />

</menu>

Note the showAsAction. This says put it in the ActionBar if there’s room and with text if possible.

  If there are more icons that can be shown, a drop down box shows the menu item text appears.

  • Now add the methods in your activity to create the action bar.

    @Override

    public boolean onCreateOptionsMenu(Menu menu) {

       MenuInflater m = getMenuInflater();

       m.inflate(R.menu.menu, menu);

        return true;

    }

  •  onMenuItemSelected is used to grab the clicks.

 public boolean onMenuItemSelected(int featureId, MenuItem item) {

    switch (item.getItemId()) {

    }

}

  • If you call getActionBar().setHomeButtonEnabled(true); in your activity the icon in the ActionBar can be clicked. Listen to it in onMenuItemSelected with this case:

     case android.R.id.home

       Toast.makeText(this, "hiya", Toast.LENGTH_SHORT).show();

     break;

You can also give your icon a little ‘back’ icon:         getActionBar().setDisplayHomeAsUpEnabled(true);

  • In your app manifest, you can set an activity to split the action bar so its icons appear at the bottom when the screen is narrow:

            android:uiOptions="splitActionBarWhenNarrow"

P139: ActionBar p1

If you create a project targeting HoneyComb or above (I’m using 4.0.3, API Level 15), you can use the ActionBar. You’re given it by default.

You can disable it permanently: 

  • In your manifest, give your application or activity a theme=”@style: YourTheme”. 
  • Create res/values/styles.xml file
  • Use this as your theme to permanently disable the actionbar.

<resources>

        <style name="YourTheme">

         <item name="android:windowActionBar">false</item>            

        </style>

</resources>

  • If you remove the windowActionBar line, it will show as its default it on. In that case your can turn it off, temporarily, in code:

ActionBar ab = getActionBar();

if(ab.isShowing()) {

ab.hide();

} else {

ab.show();

}

  • Note that when you hide/show it, your layout goes through a new layout even. To disable that, set this in your style file: 

<resources>

        <style name="AppTheme">

        <item name="android:windowActionBarOverlay">true</item>

       </style>

</resources>

It will mean your layout does not take into account the actionbar; they overlap unless your leave room in your layout.

P138: Drawables with 9 patches

If you drawable folder you can create a filled bitmap of a 9 patch. Get a 9 patch and then set that to ‘fill’ gravity.

Then there’s another normal drawable in the same image, that’s set to repeat

<?xml version="1.0" encoding="utf-8"?>

<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

     <item>

        <bitmap

            android:gravity="fill"

            android:src="@drawable/fade" />

    </item>

    <item>

        <bitmap

            android:gravity="fill"

            android:src="@drawable/tile"

            android:tileMode="repeat" />

    </item>

</layer-list>

The tile drawable is in fact called fade.9.png.

Then tile.png is just that. Note the ‘tileMode’ is set to ‘repeat’.

P137: Application / Activity styles

You can give your application or activity a style. From there you can set the background, title bar etc. Give your application / activity a theme tag

    <application

        android:name=".Appy"

        android:label="@string/app_name"

        android:theme=@style/AppTheme" >

Then create the AppTheme in the styles.xml file in the values/ subfolder of res.

<resources>

    <style name="AppTheme" parent="android:style/Theme.Light">

        <item name="android:windowContentOverlay">@drawable/actionbar_shadow</item>

        <item name="android:windowBackground">@drawable/abgimage</item>

        <item name="android:windowTitleSize">48dp</item>

        <item name="android:windowTitleStyle"><item name="android:textColor">#000</item></item>

        <item name="android:windowTitleBackgroundStyle"><item name="android:background">#eee</item></item>

     </style>

</resources>

Now you’ve got a background image and a custom title bar.

All the android: styles can be found here: http://developer.android.com/reference/android/R.attr.html

Part 136: Making a custom Cursor Adapter

If, instead of using SimpleCursorAdapter to render your cursor to a list view, you want to modify how the list items are rendered, for example, you can create a custom cursor adapter.

Extends CursorAdapter to 1) implement the newView() method to extract your row layout XML file and 2) bindView() to fill that row with the data from the cursor.

public class YourCursorAdapter extends CursorAdapter {
    public YourCursorAdapter(Context context, Cursor c) {
       super(context, c);
    }

    @Override
    public void bindView(View view, Context context, Cursor cursor) {
       TextView tv = (TextView) view.findViewById(R.id.a_textview);
       String s = cursor.getString(cursor.getColumnIndex(“your col name”));
       tv.setText(s);
       //Any other modifications you want 
     }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        LayoutInflater infl = LayoutInflater.from(context);
        View v = infl.inflate(R.layout.your_row, parent, false);
        bindView(v, context, cursor);
        return v;
    }
}

This is then called via new YourCursorAdapter(context, your_cursor); Then you pass that to the ListView’s setAdapter() method and voila.

Part 135: Using C/C++ in Droid

The NDK allows you to use JNI to use C/C++ code in your Java classes. So knowledge of JINI issues is essential. Nevertheless, this should get you started.

First, download the NDK. I downloaded r6b, the latest.

Then, create a C file in a new ‘jni’ directory in your projects root. And place this C file, test.c, there.

#include <jni.h>

jint Java_your_package_name_Yourclass_func(JNIEnv * env, jobject this) {
        jint ret = 6;
        return ret;
}

This simply returns an int. Or a ‘jini’, in the JNI world.

Note the function name is ‘Java’, followed by your project’s package name, underscores replacing dots.

The next word is the class name you’ll be calling this function in. Finally the name of the function, ‘func’ in this case.

The arguments to the function are mandatory. The first is the JNI environment that contains various helpers. And then the object that this function will be part of. Any arguments you want to pass it will go after that.

Now make a file that will specify and build this C code. It’s called Android.mk and lives in the same directory as you C source.

   LOCAL_PATH := $(call my-dir)

   include $(CLEAR_VARS)

   LOCAL_MODULE    := denevelltest
   LOCAL_SRC_FILES := test.c

   include $(BUILD_SHARED_LIBRARY)

The first line is a macro that tells the build script that the sources will be in this directory. Then CLEAR_VARS is used to remove the previous build vars.

Then we specify a *unique* name for our library. And then the source files. Finally we build the library via BUILD_SHARED_LIBRARY.

Now go into your project’s root directory. And run the ‘ndk-build’ command there. That’ll create you some shared libraries in your project root.

/dir/of/your/ndk/install/ndk-build

(Note use ‘ndk-build’ clean to remove previous builds. Useful when you make a change.)

Now in the class that you specified in the name of your C function, ‘Yourclass’ in this instance, add this code, above the method declarations:

    static {
        System.loadLibrary(“denevelltest”);
      }
   
    native int func();

Note we’re adding the ‘denevelltest’ library we declared in Android.mk. And we’re native keywords tells Java that this method is going to come from native code.

Now in one of our java method in that class we can call func() to retrieve our int.

Part 134: Using a WebView for HTML5 apps

We can wrap our website in a WebView, which is basically the phone’s browser, but with our controls. First make a WebView in your XML layout file.

<WebView  xmlns:android=”http://schemas.android.com/apk/res/android”
    android:id=”@+id/webview”
    android:layout_width=”fill_parent”
    android:layout_height=”fill_parent”
    android:scrollbars=”none”
/>

Note that we’ve disabled the scrollbars of this View. We’ll still be able to pan around the WebView, of course.

Now let’s initialise our WebView:

mWebView = (WebView) findViewById(R.id.webview);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.setWebViewClient(new HelloWebViewClient());       
mWebView.loadUrl(“http://reddit.com”);  

We’re enabling Javascript (there are other WebView options you may find interesting, too). And loading our URL, reddit in this case for no real reason.

The important thing is the setWebViewClient. We need this to control how our app opens new links.

The WebView client is a inner class:

    private class HelloWebViewClient extends WebViewClient {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            view.loadUrl(url);
            return true;
        }
    } 

It merely overrides the url loading, so it loads it in our WebView, as opposed to a new browser on the phone.

Finally we should override the onKeyDown() method of the WebView activity. This is so when the user presses the back button, it goes back in the browser, as opposed to closing the application:

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {
            mWebView.goBack();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }     

Part 133: Writing a View to a Bitmap

You can get a Bitmap representation of a View by setting the view to use caching, and then return that Bitmap cache:

LinearLayout ll = (LinearLayout) findViewById(R.id.YOURLAYOUTITEM);        ll.setDrawingCacheEnabled(true);
ll.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
ll.buildDrawingCache();
Bitmap b1 = ll.getDrawingCache();

You get a View item, enable its drawing cache, then build it. Then getDrawingCache() will return that View as a Bitmap.

Note this differs to how you draw a Drawable to a bitmap: by calling the Drawable’s draw() method on a new Canvas(bitmap).

Part 132: Shape Drawable / SVG buttons

These allow you to make simple vector shapes, with gradients, rounded corners and strokes etc.

It’s a nice alternative to a 9patch button, as you can set the gradient in various ways, and use that drawable as the button’s background.

You place a file like this in one of your drawable directories:

<?xml version=”1.0” encoding=”utf-8”?>
<selector
    xmlns:android=”http://schemas.android.com/apk/res/android”>
    <item>       
        <shape android:shape=”rectangle”>
            <solid android:color=”#ccc”
            />
            <corners
                android:radius=”5dp” />
            <padding
                android:left=”10dp”
                android:top=”10dp”
                android:right=”10dp”
                android:bottom=”10dp” />
        </shape>
    </item>
</selector>

That just creates a rounded box with passing and a grey background. See here for a fuller list, including gradients etc.

Note the android:shape attribute in <shape>. It can be a oval, rectangle, line or ring.

You can access this as a normal drawable via getResources().getDrawable(R.drawable.its_xml_filename);

Part 131: Snake SDK sample, p5 TileView class

TILEVIEW CLASS

This is used to draw the tiles to the screen. 

It defines a mTileSize for the number, the dimensions of each tile. mX/YTileCount this is calculated in the onSizeChanged() method as the canvas width and height respectively divided by the mTileSize. The the mOffsetX/Y is calculated later as the difference between all the tiles and the canvas width. Then there’s a hash of Bitmaps used, mTileArray, and an 2d array of grid place ints, mTileGrid.

The constructors set a mTileSize from an attribute included in the XML declaration for SnakeView. And set the usual super class, View in this case, constructor. The SDK example has a slight error here, because the tileSize in the XML doesn’t have a namespace, it isn’t picked up and the default is chosen. It needs a namespace i.e. whater:tileSize=”” and xmlns:whatever=”com.example.android.snake" to go at the top of the layout.

resetTiles() sets a new mTilesArray with the length passed in, thereby resetting the Bitmaps. loadTile() takes a int key and Drawable, adding that Drawable as a Bitmap to mTileArray at key.onDraw()

setTile() takes in a tile index—set using loadTile()—and a x and y position, then it places that tile bitmap index to that place in the mTileGrid 2d array. clearTiles() resets all the grid tiles to the zeroth position in mTilesArray, which the onDraw() method will simply skip. 

onSizeChanged() is overridden to calculate how many tiles are needed based on the canvas width passed and the size of each tile. It also calculate the offsets—the difference between all the tiles and the total width/height. That’s halfed. It’ll be added to the bitmap position when drawn. Then it initialises the mTileGrid 2D array based on just calculated mX/YTileCount. Finally it clears all the tiles on the screen.

onDraw() loops through the x and y tile places, and as long as the tile grid int isn’t 0, indicating the clearTiles() method marked that as cleared and it hasn’t been subsequently filled, then draw the bitmap relating to that int.