Droid Dev Stream
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 125: Using a SeekBar

You can define a horizontal seek bar fairly easily. Vertical is not yet supported. There are few attempts at it on the net though.

    <SeekBar android:layout_height=”wrap_content” 
    android:indeterminate=”false”
    android:thumb=”@drawable/icon”
    android:id=”@+id/seekBar1” 
    android:layout_width=”match_parent”>
    </SeekBar>

The XML’s attributes are standard except indeterminate as true just sets the seekbar to repeat on its own. The ‘tumb’ lets you define a customisable icon. 

        SeekBar sb = (SeekBar) findViewById(R.id.seekBar1);       
        sb.setMax(max);
        sb.setProgress(curr);

The sexMax() and setProgress() methods should be self explanatory. It also takes a lister to relay its information to the program:

sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
   @Override
   public void onStopTrackingTouch(SeekBar seekBar) {
   }
   @Override
   public void onStartTrackingTouch(SeekBar seekBar) {
   }
   @Override
   public void onProgressChanged(SeekBar seekBar, int progress,
         boolean fromUser) {
      //do something with the progress value
   }
});

The onProgressChanged() method is the one you’ll probably want. Although the others are useful if you want do something on the start or the end of a seek. 

Within the onProgressChanged() method you’ll have access to the current value of the bar and whether it was programmatically changed or not.

Part 106: Using a SlidingDrawer

The Sliding Drawer is a nice widget, but with some gotchas. Here’s the XML.

Note the SlidingDrawer is referencing two element ID ‘drawbutton’ and ‘main_layout’ within it and its handle and content.

We’re also setting the ‘allowSingleTap’, ‘orientation’ and ‘animateOnClick’ attributes which should be self-explanatory.

    <SlidingDrawer
         android:layout_width=”fill_parent”
         android:layout_height=”wrap_content”  
         android:allowSingleTap=”true”
         android:animateOnClick=”true”
         android:orientation=”horizontal”
         android:content=”@+id/main_layout”
         android:handle=”@+id/drawbutton”>
        <Button
            android:layout_width=”wrap_content”
            android:layout_height=”wrap_content”   
            android:id=”@+id/drawbutton”   
            android:text=”SliderButton”/>
         <LinearLayout
             android:background=”#000”
            android:id=”@+id/main_layout”
             android:layout_width=”fill_parent”
            android:layout_height=”fill_parent”>   
            <TextView
                android:layout_width=”wrap_content”
                android:layout_height=”wrap_content”
                android:text=”Content”/>
            />       
        </LinearLayout>      
    </SlidingDrawer>

First gotcha: If this widget goes within a LinearLayout set to vertical, its children must not fill the entire height, for a SlidingDrawer set to vertical needs space at the bottom to display the button. (And vise-versa for a LinearLayout set to horizontal and a child widget with a width set to fill_parent).

Second gotcha: A LinearLayout will only pull the drawer up to the free space in the layout. If you want it to slide over the entire page, use a FrameLayout, as that allows overlap. Make sure the content of your SlidingDrawer has a background. Otherwise that which it is sliding over will still be visible. Or do as below.

Finally, SlidingDrawer has various method you can call in Java, including toggle, animateToggle, close etc. It’s most useful is a callback that can be called when it’s opened:

SlidingDrawer sd = (SlidingDrawer) findViewById(R.id.slidingdrawer);
sd.setOnDrawerOpenListener(new SlidingDrawer.OnDrawerOpenListener() {
     public void onDrawerOpened() {
         Button b = (Button) findViewById(R.id.topcontent);
         b.setVisibility(View.GONE);
     }
});

This hides the content above the SlidingDrawer in this case. This is very useful if you want to keep the LinearLayout, but then hide some of its content—as we’ve done here—when the drawer is opened. You can listen on the OnDrawerClosedListener to reappear that content. There’s even one for when the drawer is being scrolled.

Part 97: Altering your layout based on orientation change

You must set up a default layout file, let’s say that’s called main.xml in res/layout/.

<LinearLayout  xmlns:android=”http://schemas.android.com/apk/res/android”
    android:id=”@+id/main_layout”
    android:layout_width=”fill_parent”
    android:layout_height=”fill_parent”>
    <TextView
        android:layout_width=”wrap_content”
        android:layout_height=”wrap_content”
        android:text=”Portrait”/>
</LinearLayout>

Then you must create another res/layout/ directory, but this time append “-land” on the end. That stands for landscape. so now you’ve got a res/layout-land/main.xml file:

<?xml version=”1.0” encoding=”utf-8”?>
<LinearLayout  xmlns:android=”http://schemas.android.com/apk/res/android”
    android:id=”@+id/main_layout”
    android:layout_width=”fill_parent”
    android:layout_height=”fill_parent”>
    <TextView
        android:layout_width=”wrap_content”
        android:layout_height=”wrap_content”
        android:text=”Landscape”/>
</LinearLayout>

Note, this is very similar to the technique to display different layouts based on resolutions here.

This file will be called automatically by android if the screen is in the landscape mode. Otherwise our previous layout file will be called.

Note the only difference between the two is the text of the TextView. You’d obviously make other changes. If you just want to disable orientation changes place:

android:configChanges=”orientation”

In the <Activity> tag of your Activity in the AndroidManifest.xml file.

Part 93: Using a dimension XML file with different screen resolutions

If you code in a dimension in dp, sp or whatever, you’ll need to adjust that for the main screen resolutions of android.

Wikipedia has a list of resolutions with devices that use them. 480x800 is quite common at the moment. The HTC Desire and Nexus One use that.

Our default resolutions will be 480x800, and we’ll also deal with 320x480, which is used in the HTC Hero.

In our res/values/dimens.xml file put:

<?xml version=”1.0” encoding=”utf-8”?>
<resources>
    <dimen name=”my_width”>200dp</dimen>
</resources>

That gives us a default 200dp, giving it the name “half_width”. Now create the dimens.xml file for the 320x480 resolution.

Create a new folder called res/values-320x480/ and in that folder create another dimens.xml. In that file put:

<?xml version=”1.0” encoding=”utf-8”?>
<resources>
    <dimen name=”my_width”>50dp</dimen>
</resources>

Now when we have a device with 320x480 resolution it will look at res/values-320x480/dimens.xml instead of res/values/dimens.xml

We can reference this new value via

<TextView layout_width=”@dimen/my_width” />

Remember that will be 30dp in the 320x480 or else it will default to our previously defined 200dp

Part 89: Getting screen information

You can get information on the screen size using the Display class. You use the getWindowManager() Activity method to getDefaultDisplay().

        Display display = getWindowManager().getDefaultDisplay();
        display.getWidth();

You can also get the rotation of the device via getRotation(), which will return a Surface global. Surface.ROTATION_190 is as expected.

Pixel formate and refresh rate is also available.

Part 88: ViewPager aka Homescreen fling/swipe.

If you want the kind of swipe or fling you get on the homescreen, you need to use the ViewPager element. This is in the compatibility pack, if you’re not using 3.0.

You can download this in the Android SDK and ADK manager. If you may already have it. If not, check the available and installed tabs and download it.

Then, in Eclipse, load that by going to Project -> Properties -> Java Build Path -> Libraries tab.

Then click the Add External Jar. Then navigate to the android-sdk/extras/android/compatibility/v4 directory and click on the ‘android-support-v4.jar’.

This code is from http://code.google.com/p/viewpagerexample/ from Paul G.

Now the simple XML code. Note ViewPager’s required full XML name:

<?xml version=”1.0” encoding=”utf-8”?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
    android:orientation=”vertical”
    android:layout_width=”wrap_content”
    android:layout_height=”wrap_content”>
<android.support.v4.view.ViewPager
    android:layout_width=”wrap_content”
    android:layout_height=”wrap_content”
    android:id=”@+id/viewpager”/>
</LinearLayout>

Now we need to fill that ViewPager with data. We do that with an adapter. Set it up in your onCreate method:

        adapter = new MyPagerAdapter();
        pager = (ViewPager) findViewById(R.id.viewpager);
        pager.setAdapter(adapter); 

Now the MyPagerAdapter class. This extends the PagerAdapter for the ViewPager.

    private class MyPagerAdapter extends PagerAdapter{
       
        @Override
        public int getCount() {
                return 3;
        }

        @Override
        public Object instantiateItem(View collection, int position) {
                TextView tv = new TextView(getApplicationContext());
                tv.setText(“Hallo”);
                ((ViewPager) collection).addView(tv,0);
                return tv;
        }

        @Override
        public void destroyItem(View collection, int position, Object view) {
                ((ViewPager) collection).removeView((TextView) view);
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
                return view==((TextView)object);
        }

        @Override
        public void finishUpdate(View arg0) {}

        @Override
        public void restoreState(Parcelable arg0, ClassLoader arg1) {}

        @Override
        public Parcelable saveState() { return null; }

        @Override
        public void startUpdate(View arg0) { }
    }   

All these methods are required. But to get it up and running, you only need to define a few.

You only need to say how many items you have in getCount(), remove the old View in destroyItem(), which is passed the container, position and old View. And isViewFromObject is undocumented.

Note all the references to the TextView in the methods above will change if you’re returning something other than a TextView.

Finally, instantiateItem(). This is passed the ViewPager and the position it’s currently at. From this you must return the View you want to display. And add it to the passed collection, i.e Viewpager.

Also, this ViewPager takes up all the screen, from what I can tell. Or at least nothing can be placed below it—above it is okay.

Part 86: Using a ViewFlipper

A ViewFlipper allows us to flip between views, or view groups.

<ViewFlipper android:id=”@+id/ViewFlipper01”
            android:layout_width=”wrap_content”
            android:layout_height=”wrap_content”>
      <TextView android:id=”@+id/TextView01”
            android:layout_width=”fill_parent”
            android:layout_height=”wrap_content”
            android:text=”Flipper Content 1” />
      <TextView android:id=”@+id/TextView02”
            android:layout_width=”fill_parent”
            android:layout_height=”wrap_content”
            android:text=”Flipper Content 2” />
</ViewFlipper>

This is simply some TextViews wrapped in a ViewFlipper. The first ‘ViewFlipper01’ will be shown on the screen.

Now you must call the ViewFlipper’s showNext() and showPrevious() methods to switch between each.

If you wish for an animation between each flip, use ViewFlipper.setInAnimation() and setOutAnimation() before issuing showNext() etc.

vf.setInAnimation(
                     AnimationUtils.loadAnimation(
                            getApplicationContext(),
                            android.R.anim.slide_in_left));    

This loads the default android animation to slide a view in from the left.

Oddly, Android API 10 only has slide_in_left and slide_out_right. You can put these files, from Android’s repo, in your res/anim directory to use slide_in_right and slide_out_left.

You may want to swipe between each. That’s a Gesture event.

Part 82: Applying styles

Instead of setting the textColor, gravity etc on the layout XML element itself, we can use styles.

These are XML files that live in res/values/ and have this format:

<?xml version=”1.0” encoding=”utf-8”?>
<resources>
    <style name=”bottomTextStyle" parent="@android:style/TextAppearance.Medium">
        <item name=”android:layout_width”>fill_parent</item>
        <item name=”android:layout_height”>fill_parent</item>
        <item name=”android:gravity”>center</item>
        <item name=”android:textColor”>#444</item>
    </style>
</resources>

This is in the standard <resources> tag for all XML files in res/values. But it has a <style> tag within.

You give it a name, and specify and styles it should inherit from. Then the <item>s are the same name as you’d normally specify on the XML Layout element.

Then in your XML layout file, you can say:

    <TextView
           style=”@style/bottomTextStyle
           android:layout_below=”@id/imagesScroll”
           android:text=”Slide and choose a card to send.”
    />

Note the ‘style’ attribute isn’t prefixed with ‘android:’.

Part 81: HorizontalScrollView

We can get the BBC News app’s [1] horizontal scrolling layout fairly easily.

First define a HorizonalScrolView. Then a LinearLayout within set to ‘horizontal’. The elements within are then set with ‘wrap_content’.

<HorizontalScrollView  xmlns:android=”http://schemas.android.com/apk/res/android”
    android:id=”@+id/ScrollView01”
    android:layout_width=”wrap_content”
    android:layout_height=”wrap_content”>
    <LinearLayout
        android:orientation=”horizontal”
        android:layout_width=”wrap_content”
        android:layout_height=”wrap_content” >
            <TextView 
            android:layout_width=”wrap_content”
            android:layout_height=”wrap_content”
            android:text=”Item1”
            android:textSize=”76px”/>                   

    </LinearLayout>   
</HorizontalScrollView>

[1] http://static.intomobile.com/wp-content/uploads/2011/06/Android-BBC.jpg

Part 78: Gallery p2: Gallery image background

The alpha attribute in the last tutorial will not work until each ImageView of the gallery has a background image.

We can set that via passing a standard drawable or resource ID of a drawable as shown

imageView.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.alert_light_frame));

That sets the drawable to a standard android drawable.

The way outlined in the HelloGallery tutorial however says use the resource of the android attribute galleryItemBackground.

TypedArray attr = context.obtainStyledAttributes(new int[]
                                              {android.R.attr.galleryItemBackground});            bgResID = attr.getResourceId(0, 0);

This grabs the style attributes galleryItemBackground. Then the getResourceId() grabs the first, and only, resource ID in that TypedArray by passing 0. It returns 0 itself if it can’t resolve the resource ID from that attribute.

You’d then use the bgResID to set the ImageView’s background resource via setBackgroundResource().

Note, the HelloGallery tutorial uses an XML attribute file to grab the attribute galleryItemBackground. I’m putting that attribute directly in the obtainStyledAttributes() for brevity.

Part 77: Gallery p1: Setup

The gallery layout is very similar to the GridLayout widget. 

The main difference is the style attribute that we will deal with later.

<Gallery

    android:id=”@+id/gallery”

    android:layout_width=”fill_parent”
    android:layout_height=”wrap_content”
    android:animationDuration=”50”
    android:unselectedAlpha=”0.5”
/>

The animationDuration is the number of milliseconds the animation should take. unselectedAlpha is self-explanatory. 

        Gallery gallery = (Gallery) findViewById(R.id.gallery);
        gallery.setAdapter(new ImageAdapter(this));

This is the same as our other Adapter-based widgets. You can even set the standard AdapterView.OnItemClickIistener.

Our ImageAdapter() call above sets a BaseAdapter, just like our GridLayout:

    private class ImageAdapter extends BaseAdapter {
        private Context mContext;
        private Integer[] mImageIds = {
                R.drawable.icon,
                R.drawable.icon,
                R.drawable.icon
        };
        public ImageAdapter(Context c) {
            mContext = c;
        }
        public int getCount() {
            return mImageIds.length;
        }
        public Object getItem(int position) {
            return null;
        }
        public long getItemId(int position) {
            return 0;
        }
        public View getView(int position, View convertView, ViewGroup parent) {
            ImageView imageView = new ImageView(mContext);
            imageView.setImageResource(mImageIds[position]);
            imageView.setLayoutParams(new Gallery.LayoutParams(150, 150));
            imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
            return imageView;
        }
    }    

But we still haven’t set a stylable for each image. So things won’t look as they’re suppose to.

More on that next.

Part 76: AutoComplete spinner

The XML of this has a few interesting attributes:

    <AutoCompleteTextView android:id=”@+id/autocomplete_country”
        android:layout_width=”fill_parent”
        android:completionHint=”hiya”
        android:completionThreshold=”1”
        android:layout_height=”wrap_content”/>

The completeionHint attribute specifies a little text to go below the hints. The completionThreshold says how many characters to enter before hints appear.

There are others to control the height, width and horizontal offset of the drop down. showDropDown(), called after onCreate(), must be called after onCreate().stDropDownBackgroundResource() is also useful.

completionHintView seems not to work. And dropDownAnchor (where the drop down is pinned) only seems to work in Java as the method setDropDownAnchor(id). 

The code to set up the ListAdapter is the same as the ListActivity and the Spinner code previously. It takes the standard OnItemClickedListener that we’ve used before

You can set a Validator. You call performValidate() on the AutoCompleteTextView. It will return whatever is in the fixText() should isValid() say it’s incorrect:

        textView.setValidator(new Validator() {
               public boolean isValid(CharSequence text) {
                   SpannableStringBuilder a = (SpannableStringBuilder) text;
                   if(a.toString().equals(“yes”)) return false;
                   else return true;
               }
               public CharSequence fixText(CharSequence invalidText) {
                   return “NOOOO!”;
               }
        });

Part 75: Spinner

You select a spinner, or drop down, simply in XML:

    <Spinner
        android:id=”@+id/spinner11”
        android:layout_width=”fill_parent”
        android:layout_height=”wrap_content”
        android:prompt=”@string/prompt”  />     

Note the prompt must be a reference to another string. This will be title to our spinner dialogue popup. In code, you can then call the methods setPrompt(CharSequence) and setPromptId(int).

Now the code to fill the spinner with values:

        ArrayAdapter<String> adapter =
                new ArrayAdapter<String>(this, R.layout.layout_row, R.id.text1,
                        new String[] {“One”, “Two”});
       
        adapter.setDropDownViewResource(R.layout.layout_row);
        spinner.setAdapter(adapter);

We use the ArrayAdapter as we did with the ListActivity. We pass a layout with a TextView as its root node, then a then ID of that text node in the layout and finally a array string of the values.

The layout we pass is what to show when the spinner is not expanded.

We use setDropDownViewResource() to pass the TextView layout to show when we see the drop down values. We’re using the same layout here.

The default android layout and dropdown layout, of android.R.layout.simple_spinner_item and android.R.layout.simple_spinner_dropdown_item, can be used instead of custom layouts. Remeber to remove the R.id.text1 resource from the ArrayAdapter.

Finally, the code to listen in on selection:

        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
            }
            public void onNothingSelected(AdapterView<?> arg0) {}
        });

This AdapterView.OnItemSelectedListener needs the onNothingSelected() method to be defined. But it doesn’t do anything with a spinner. So ignore it. Then onItemSelected method works as the ListActivity method of the same name.

Finally, the first item in your array of selections will be selected.

Part 74: RatingBar

The rating bar has pretty self-describing attributes, except for isIndicator means if it’s editable or not by the user. And ‘rating’ is its initial rating.

    <RatingBar android:id=”@+id/ratingbar”
        android:layout_width=”wrap_content”
        android:layout_height=”wrap_content”
        android:numStars=”5”
        android:rating=”2.5”
        android:isIndicator=”false”
        android:stepSize=”0.5”/>

It takes a custom click listener:

        final RatingBar ratingbar = (RatingBar) findViewById(R.id.ratingbar);
        ratingbar.setOnRatingBarChangeListener(new OnRatingBarChangeListener() {
            public void onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser) {
                if(fromUser)
                    Toast.makeText(Tab1.this, “New Rating: ” + rating, Toast.LENGTH_SHORT).show();
            }
        });       

        ratingbar.setRating((float)4.5);

It’s onRatingChanged(), in the OneRatingBarChangeListener  innerclass, takes where the user initiated the action as its final paramter.

So above the initial setting of the value will not invoke the Toast in the onRatingChanged method.