Showing posts with label User Interface. Show all posts
Showing posts with label User Interface. Show all posts

Monday, June 6, 2011

0

New Editing Features in Eclipse plug-in for Android

  • Monday, June 6, 2011
  • Ric RAT
  • At the Google I/O conference a month ago, we demonstrated the next version of the Android Development Tools (ADT) plugin. Today we’re happy to announce that version 11 is done and available for download!

    ADT 11 focuses on editor improvements. First, it offers several new visual refactoring operations, such as “Extract Include” and “Extract Style,” which help automatically extract duplicated layout fragments and style attributes into reusable layouts, styles, and themes.

    Second, the visual layout editor now supports fragments, palette configurations, and improved support for custom views.

    Last, XML editing has been improved with new quick fixes, code completion in more file types and many “go to declaration” enhancements.

    ADT 11 packs a long list of new features and enhancements. Please visit our ADT page for more details. For an in-depth demo, check out the video of our Android Development Tools session at Google I/O, below.

    Please note that the visual layout editor depends on a layout rendering library that ships with each version of the platform component in the SDK. We are currently working on a number of improvements to this library as well, which we plan to release soon for all platform versions. When we release the updates, some new features in ADT 11 will be “unlocked” - such as support for ListView previewing - so keep an eye on this blog for further announcements.


    read more

    Monday, March 14, 2011

    0

    Android 3.0 Hardware Acceleration

  • Monday, March 14, 2011
  • Ric RAT
  • [This post is by Romain Guy, who likes things on your screen to move fast. —Tim Bray]

    One of the biggest changes we made to Android in this release is the addition of a new rendering pipeline so that applications can benefit from hardware accelerated 2D graphics. Hardware accelerated graphics is nothing new to the Android platform, it has always been used for windows composition or OpenGL games for instance, but with this new rendering pipeline applications can benefit from an extra boost in performance. On a Motorola Xoom device, all the standard applications like Browser and Calendar use hardware-accelerated 2D graphics.

    In this article, I will show you how to enable the hardware accelerated 2D graphics pipeline in your application and give you a few tips on how to use it properly.

    Go faster

    To enable the hardware accelerated 2D graphics, open your AndroidManifest.xml file and add the following attribute to the <application /> tag:

        android:hardwareAccelerated="true"

    If your application uses only standard widgets and drawables, this should be all you need to do. Once hardware acceleration is enabled, all drawing operations performed on a View's Canvas are performed using the GPU.

    If you have custom drawing code you might need to do a bit more, which is in part why hardware acceleration is not enabled by default. And it's why you might want to read the rest of this article, to understand some of the important details of acceleration.

    Controlling hardware acceleration

    Because of the characteristics of the new rendering pipeline, you might run into issues with your application. Problems usually manifest themselves as invisible elements, exceptions or different-looking pixels. To help you, Android gives you 4 different ways to control hardware acceleration. You can enable or disable it on the following elements:

    • Application
    • Activity
    • Window
    • View

    To enable or disable hardware acceleration at the application or activity level, use the XML attribute mentioned earlier. The following snippet enables hardware acceleration for the entire application but disables it for one activity:

        <application android:hardwareAccelerated="true">
    <activity ... />
    <activity android:hardwareAccelerated="false" />
    </application>

    If you need more fine-grained control, you can enable hardware acceleration for a given window at runtime:

        getWindow().setFlags(
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);

    Note that you currently cannot disable hardware acceleration at the window level. Finally, hardware acceleration can be disabled on individual views:

        view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

    Layer types have many other usages that will be described later.

    Am I hardware accelerated?

    It is sometimes useful for an application, or more likely a custom view, to know whether it currently is hardware accelerated. This is particularly useful if your application does a lot of custom drawing and not all operations are properly supported by the new rendering pipeline.

    There are two different ways to check whether the application is hardware accelerated:

    If you must do this check in your drawing code, it is highly recommended to use Canvas.isHardwareAccelerated() instead of View.isHardwareAccelerated(). Indeed, even when a View is attached to a hardware accelerated window, it can be drawn using a non-hardware accelerated Canvas. This happens for instance when drawing a View into a bitmap for caching purpose.

    What drawing operations are supported?

    The current hardware accelerated 2D pipeline supports the most commonly used Canvas operations, and then some. We implemented all the operations needed to render the built-in applications, all the default widgets and layouts, and common advanced visual effects (reflections, tiled textures, etc.) There are however a few operations that are currently not supported, but might be in a future version of Android:

    • Canvas
      • clipPath
      • clipRegion
      • drawPicture
      • drawPoints
      • drawPosText
      • drawTextOnPath
      • drawVertices
    • Paint
      • setLinearText
      • setMaskFilter
      • setRasterizer

    In addition, some operations behave differently when hardware acceleration enabled:

    • Canvas
      • clipRect: XOR, Difference and ReverseDifference clip modes are ignored; 3D transforms do not apply to the clip rectangle
      • drawBitmapMesh: colors array is ignored
      • drawLines: anti-aliasing is not supported
      • setDrawFilter: can be set, but ignored
    • Paint
      • setDither: ignored
      • setFilterBitmap: filtering is always on
      • setShadowLayer: works with text only
    • ComposeShader
      • A ComposeShader can only contain shaders of different types (a BitmapShader and a LinearGradientShader for instance, but not two instances of BitmapShader)
      • A ComposeShader cannot contain a ComposeShader

    If drawing code in one of your views is affected by any of the missing features or limitations, you don't have to miss out on the advantages of hardware acceleration for your overall application. Instead, consider rendering the problematic view into a bitmap or setting its layer type to LAYER_TYPE_SOFTWARE. In both cases, you will switch back to the software rendering pipeline.

    Dos and don'ts

    Switching to hardware accelerated 2D graphics is a great way to get smoother animations and faster rendering in your application but it is by no means a magic bullet. Your application should be designed and implemented to be GPU friendly. It is easier than you might think if you follow these recommendations:

    • Reduce the number of Views in your application: the more Views the system has to draw, the slower it will be. This applies to the software pipeline as well; it is one of the easiest ways to optimize your UI.
    • Avoid overdraw: always make sure that you are not drawing too many layers on top of each other. In particular, make sure to remove any Views that are completely obscured by other opaque views on top of it. If you need to draw several layers blended on top of each other consider merging them into a single one. A good rule of thumb with current hardware is to not draw more than 2.5 times the number of pixels on screen per frame (and transparent pixels in a bitmap count!)
    • Don't create render objects in draw methods: a common mistake is to create a new Paint, or a new Path, every time a rendering method is invoked. This is not only wasteful, forcing the system to run the GC more often, it also bypasses caches and optimizations in the hardware pipeline.
    • Don't modify shapes too often: complex shapes, paths and circles for instance, are rendered using texture masks. Every time you create or modify a Path, the hardware pipeline must create a new mask, which can be expensive.
    • Don't modify bitmaps too often: every time you change the content of a bitmap, it needs to be uploaded again as a GPU texture the next time you draw it.
    • Use alpha with care: when a View is made translucent using View.setAlpha(), an AlphaAnimation or an ObjectAnimator animating the “alpha” property, it is rendered in an off-screen buffer which doubles the required fill-rate. When applying alpha on very large views, consider setting the View's layer type to LAYER_TYPE_HARDWARE.

    View layers

    Since Android 1.0, Views have had the ability to render into off-screen buffers, either by using a View's drawing cache, or by using Canvas.saveLayer(). Off-screen buffers, or layers, have several interesting usages. They can be used to get better performance when animating complex Views or to apply composition effects. For instance, fade effects are implemented by using Canvas.saveLayer() to temporarily render a View into a layer and then compositing it back on screen with an opacity factor.

    Because layers are so useful, Android 3.0 gives you more control on how and when to use them. To to so, we have introduced a new API called View.setLayerType(int type, Paint p). This API takes two parameters: the type of layer you want to use and an optional Paint that describes how the layer should be composited. The paint parameter may be used to apply color filters, special blending modes or opacity to a layer. A View can use one of 3 layer types:

    • LAYER_TYPE_NONE: the View is rendered normally, and is not backed by an off-screen buffer.
    • LAYER_TYPE_HARDWARE: the View is rendered in hardware into a hardware texture if the application is hardware accelerated. If the application is not hardware accelerated, this layer type behaves the same as LAYER_TYPE_SOFTWARE.
    • LAYER_TYPE_SOFTWARE: the View is rendered in software into a bitmap

    The type of layer you will use depends on your goal:

    • Performance: use a hardware layer type to render a View into a hardware texture. Once a View is rendered into a layer, its drawing code does not have to be executed until the View calls invalidate(). Some animations, for instance alpha animations, can then be applied directly onto the layer, which is very efficient for the GPU to do.
    • Visual effects: use a hardware or software layer type and a Paint to apply special visual treatments to a View. For instance, you can draw a View in black and white using a ColorMatrixColorFilter.
    • Compatibility: use a software layer type to force a View to be rendered in software. This is an easy way to work around limitations of the hardware rendering pipeline.

    Layers and animations

    Hardware-accelerated 2D graphics help deliver a faster and smoother user experience, especially when it comes to animations. Running an animation at 60 frames per second is not always possible when animating complex views that issue a lot of drawing operations. If you are running an animation in your application and do not obtain the smooth results you want, consider enabling hardware layers on your animated views.

    When a View is backed by a hardware layer, some of its properties are handled by the way the layer is composited on screen. Setting these properties will be efficient because they do not require the view to be invalidated and redrawn. Here is the list of properties that will affect the way the layer is composited; calling the setter for any of these properties will result in optimal invalidation and no redraw of the targeted View:

    • alpha: to change the layer's opacity
    • x, y, translationX, translationY: to change the layer's position
    • scaleX, scaleY: to change the layer's size
    • rotation, rotationX, rotationY: to change the layer's orientation in 3D space
    • pivotX, pivotY: to change the layer's transformations origin

    These properties are the names used when animating a View with an ObjectAnimator. If you want to set/get these properties, call the appropriate setter or getter. For instance, to modify the alpha property, call setAlpha(). The following code snippet shows the most efficient way to rotate a View in 3D around the Y axis:

        view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    ObjectAnimator.ofFloat(view, "rotationY", 180).start();

    Since hardware layers consume video memory, it is highly recommended you enable them only for the duration of the animation. This can be achieved with animation listeners:

        view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    ObjectAnimator animator = ObjectAnimator.ofFloat(
    view, "rotationY", 180);
    animator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
    view.setLayerType(View.LAYER_TYPE_NONE, null);
    }
    });
    animator.start();

    New drawing model

    Along with hardware-accelerated 2D graphics, Android 3.0 introduces another major change in the UI toolkit’s drawing model: display lists, which are only enabled when hardware acceleration is turned on. To fully understand display lists and how they may affect your application it is important to also understand how Views are drawn.

    Whenever an application needs to update a part of its UI, it invokes invalidate() (or one of its variants) on any View whose content has changed. The invalidation messages are propagated all the way up the view hierarchy to compute the dirty region; the region of the screen that needs to be redrawn. The system then draws any View in the hierarchy that intersects with the dirty region. The drawing model is therefore made of two stages:

    1. Invalidate the hierarchy
    2. Draw the hierarchy

    There are unfortunately two drawbacks to this approach. First, this drawing model requires execution of a lot of code on every draw pass. Imagine for instance your application calls invalidate() on a button and that button sits on top of a more complex View like a MapView. When it comes time to draw, the drawing code of the MapView will be executed even though the MapView itself has not changed.

    The second issue with that approach is that it can hide bugs in your application. Since views are redrawn anytime they intersect with the dirty region, a View whose content you changed might be redrawn even though invalidate() was not called on it. When this happens, you are relying on another View getting invalidated to obtain the proper behavior. Needless to say, this behavior can change every time you modify your application ever so slightly. Remember this rule: always call invalidate() on a View whenever you modify data or state that affects this View’s drawing code. This applies only to custom code since setting standard properties, like the background color or the text in a TextView, will cause invalidate() to be called properly internally.

    Android 3.0 still relies on invalidate() to request screen updates and draw() to render views. The difference is in how the drawing code is handled internally. Rather than executing the drawing commands immediately, the UI toolkit now records them inside display lists. This means that display lists do not contain any logic, but rather the output of the view hierarchy’s drawing code. Another interesting optimization is that the system only needs to record/update display lists for views marked dirty by an invalidate() call; views that have not been invalidated can be redrawn simply by re-issuing the previously recorded display list. The new drawing model now contains 3 stages:

    1. Invalidate the hierarchy
    2. Record/update display lists
    3. Draw the display lists

    With this model, you cannot rely on a View intersecting the dirty region to have its draw() method executed anymore: to ensure that a View’s display list will be recorded, you must call invalidate(). This kind of bug becomes very obvious with hardware acceleration turned on and is easy to fix: you would see the previous content of a View after changing it.

    Using display lists also benefits animation performance. In the previous section, we saw that setting specific properties (alpha, rotation, etc.) does not require invalidating the targeted View. This optimization also applies to views with display lists (any View when your application is hardware accelerated.) Let’s imagine you want to change the opacity of a ListView embedded inside a LinearLayout, above a Button. Here is what the (simplified) display list of the LinearLayout looks like before changing the list’s opacity:

        DrawDisplayList(ListView)
    DrawDisplayList(Button)

    After invoking listView.setAlpha(0.5f) the display list now contains this:

        SaveLayerAlpha(0.5)
    DrawDisplayList(ListView)
    Restore
    DrawDisplayList(Button)

    The complex drawing code of ListView was not executed. Instead the system only updated the display list of the much simpler LinearLayout. In previous versions of Android, or in an application without hardware acceleration enabled, the drawing code of both the list and its parent would have to be executed again.

    It’s your turn

    Enabling hardware accelerated 2D graphics in your application is easy, particularly if you rely solely on standard views and drawables. Just keep in mind the few limitations and potential issues described in this document and make sure to thoroughly test your application!

    read more

    Friday, February 5, 2010

    0

    Live wallpapers

  • Friday, February 5, 2010
  • Ric RAT
  • With the introduction of live wallpapers in Android 2.1, users can now enjoy richer, animated, interactive backgrounds on their home screen. A live wallpaper is very similar to a normal Android application and has access to all the facilities of the platform: SGL (2D drawing), OpenGL (3D drawing), GPS, accelerometers, network access, etc. The live wallpapers included on Nexus One demonstrate the use of some of these APIs to create fun and interesting user experiences. For instance, the Grass wallpaper uses the phone's location to compute sunrise and sunset times in order to display the appropriate sky.

    Creating your own live wallpaper is easy, especially if you have had previous experience with SurfaceView or Canvas. To learn how to create a live wallpaper, you should check out the CubeLiveWallpaper sample provided with the Android 2.1 SDK; you will find it in the directory platforms/android-2.1/samples/CubeLiveWallpaper.

    A live wallpaper is very similar to a regular Android service. The only difference is the addition of a new method, onCreateEngine() whose goal is to create a WallpaperService.Engine. The engine is responsible for handling the lifecycle and the drawing of a wallpaper. The system provides you with a surface on which you can draw, just like you would with a SurfaceView. Drawing a wallpaper can be very expensive so you should optimize your code as much as possible to avoid using too much CPU, not only for battery life but also to avoid slowing down the rest of the system. That is also why the most important part of the lifecycle of a wallpaper is when it becomes invisible. When invisible, for instance because the user launched an application that covers the home screen, a wallpaper must stop all activity.

    The engine can also implement several methods to interact with the user or the home application. For instance, if you want your wallpaper to scroll along when the user swipes from one home screen to another, you can use onOffsetsChanged(). To react to touch events, simply implement onTouchEvent(MotionEvent). Finally, applications can send arbitrary commands to the live wallpaper. Currently, only the standard home application sends commands to the onCommand() method of the live wallpaper:

    • android.wallpaper.tap: When the user taps an empty space on the workspace. This command is interpreted by the Nexus and Water live wallpapers to make the wallpaper react to user interaction. For instance, if you tap an empty space on the Water live wallpaper, new ripples appear under your finger.
    • android.home.drop: When the user drops an icon or a widget on the workspace. This command is also interpreted by the Nexus and Water live wallpapers.

    Please note that live wallpaper is an Android 2.1 feature. To ensure that only users with devices that support this feature can download your live wallpaper, remember to add the following to your manifest before releasing to Android Market:

    • <uses-sdk android:minSdkVersion="7" />, which lets Android Market and the platform know that your application is using the Android 2.1 version.
    • <uses-feature android:name="android.software.live_wallpaper" />, which lets the Android Market and the platform know that your application is a live wallpaper.

    Many great live wallpapers are already available on Android Market and we can't wait to see more!

    read more

    Thursday, December 10, 2009

    0

    Optimize your layouts

  • Thursday, December 10, 2009
  • Ric RAT


  • Writing user interface layouts for Android applications is easy, but it can sometimes be difficult to optimize them. Most often, heavy modifications made to existing XML layouts, like shuffling views around or changing the type of a container, lead to inefficiencies that go unnoticed.

    Starting with the SDK Tools Revision 3 you can use a tool called layoutopt to automatically detect common problems. This tool is currently only available from the command line and its use is very simple - just open a terminal and launch the layoutopt command with a list of directories or XML files to analyze:


    $ layoutopt samples/
    samples/compound.xml
    7:23 The root-level <FrameLayout/> can be replaced with <merge/>
    11:21 This LinearLayout layout or its FrameLayout parent is useless samples/simple.xml
    7:7 The root-level <FrameLayout/> can be replaced with <merge/>
    samples/too_deep.xml
    -1:-1 This layout has too many nested layouts: 13 levels, it should have <= 10!
    20:81 This LinearLayout layout or its LinearLayout parent is useless
    24:79 This LinearLayout layout or its LinearLayout parent is useless
    28:77 This LinearLayout layout or its LinearLayout parent is useless
    32:75 This LinearLayout layout or its LinearLayout parent is useless
    36:73 This LinearLayout layout or its LinearLayout parent is useless
    40:71 This LinearLayout layout or its LinearLayout parent is useless
    44:69 This LinearLayout layout or its LinearLayout parent is useless
    48:67 This LinearLayout layout or its LinearLayout parent is useless
    52:65 This LinearLayout layout or its LinearLayout parent is useless
    56:63 This LinearLayout layout or its LinearLayout parent is useless
    samples/too_many.xml
    7:413 The root-level <FrameLayout/> can be replaced with <merge/>
    -1:-1 This layout has too many views: 81 views, it should have <= 80! samples/useless.xml
    7:19 The root-level <FrameLayout/> can be replaced with <merge/>
    11:17 This LinearLayout layout or its FrameLayout parent is useless
    For each analyzed file, the tool will indicate the line numbers of each tag that could potentially be optimized. In some cases, layoutopt will also offer a possible solution.

    The current version of layoutopt contains a dozen rules used to analyze your layout files and future versions will contain more. Future plans for this tool also include the ability to create and use your own analysis rules, to automatically modify the layouts with optimized XML, and to use it from within Eclipse and/or a standalone user interface.


    Windows users: to start layoutopt, open the file called layoutopt.bat in the tools directory of the SDK and on the last line, replace %jarpath% with -jar %jarpath%.
    read more

    Friday, October 23, 2009

    0

    UI framework changes in Android 1.6

  • Friday, October 23, 2009
  • Ric RAT
  • Android 1.6 introduces numerous enhancements and bug fixes in the UI framework. Today, I'd like to highlight three two improvements in particular.

    Optimized drawing

    The UI toolkit introduced in Android 1.6 is aware of which views are opaque and can use this information to avoid drawing views that the user will not be able to see. Before Android 1.6, the UI toolkit would sometimes perform unnecessary operations by drawing a window background when it was obscured by a full-screen opaque view. A workaround was available to avoid this, but the technique was limited and required work on your part. With Android 1.6, the UI toolkit determines whether a view is opaque by simply querying the opacity of the background drawable. If you know that your view is going to be opaque but that information does not depend on the background drawable, you can simply override the method called isOpaque():

    @Override
    public boolean isOpaque() {
    return true;
    }

    The value returned by isOpaque() does not have to be constant and can change at any time. For instance, the implementation of ListView in Android 1.6 indicates that a list is opaque only when the user is scrolling it.

    Updated: Our apologies—we spoke to soon about isOpaque(). It will be available in a future update to the Android platform.

    More flexible, more robust RelativeLayout

    RelativeLayout is the most versatile layout offered by the Android UI toolkit and can be successfully used to reduce the number of views created by your applications. This layout used to suffer from various bugs and limitations, sometimes making it difficult to use without having some knowledge of its implementation. To make your life easier, Android 1.6 comes with a revamped RelativeLayout. This new implementation not only fixes all known bugs in RelativeLayout (let us know when you find new ones) but also addresses its major limitation: the fact that views had to be declared in a particular order. Consider the following XML layout:

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

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="64dip"
    android:padding="6dip">

    <TextView
    android:id="@+id/band"
    android:layout_width="fill_parent"
    android:layout_height="26dip"

    android:layout_below="@+id/track"
    android:layout_alignLeft="@id/track"
    android:layout_alignParentBottom="true"

    android:gravity="top"
    android:text="The Airborne Toxic Event" />

    <TextView
    android:id="@id/track"
    android:layout_marginLeft="6dip"
    android:layout_width="fill_parent"
    android:layout_height="26dip"

    android:layout_toRightOf="@+id/artwork"

    android:textAppearance="?android:attr/textAppearanceMedium"
    android:gravity="bottom"
    android:text="Sometime Around Midnight" />

    <ImageView
    android:id="@id/artwork"
    android:layout_width="56dip"
    android:layout_height="56dip"
    android:layout_gravity="center_vertical"

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

    </RelativeLayout>

    This code builds a very simple layout—an image on the left with two lines of text stacked vertically. This XML layout is perfectly fine and contains no errors. Unfortunately, Android 1.5's RelativeLayout is incapable of rendering it correctly, as shown in the screenshot below.

    The problem is that this layout uses forward references. For instance, the "band" TextView is positioned below the "track" TextView but "track" is declared after "band" and, in Android 1.5, RelativeLayout does not know how to handle this case. Now look at the exact same layout running on Android 1.6:

    As you can see Android 1.6 is now better able to handle forward reference. The result on screen is exactly what you would expect when writing the layout.

    Easier click listeners

    Setting up a click listener on a button is very common task, but it requires quite a bit of boilerplate code:

    findViewById(R.id.myButton).setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
    // Do stuff
    }
    });

    One way to reduce the amount of boilerplate is to share a single click listener between several buttons. While this technique reduces the number of classes, it still requires a fair amount of code and it still requires giving each button an id in your XML layout file:

    View.OnClickListener handler = View.OnClickListener() {
    public void onClick(View v) {
    switch (v.getId()) {
    case R.id.myButton: // doStuff
    break;
    case R.id.myOtherButton: // doStuff
    break;
    }
    }
    }

    findViewById(R.id.myButton).setOnClickListener(handler);
    findViewById(R.id.myOtherButton).setOnClickListener(handler);

    With Android 1.6, none of this is necessary. All you have to do is declare a public method in your Activity to handle the click (the method must have one View argument):

    class MyActivity extends Activity {
    public void myClickHandler(View target) {
    // Do stuff
    }
    }

    And then reference this method from your XML layout:

    <Button android:onClick="myClickHandler" />

    This new feature reduces both the amount of Java and XML you have to write, leaving you more time to concentrate on your application.

    The Android team is committed to helping you write applications in the easiest and most efficient way possible. We hope you find these improvements useful and we're excited to see your applications on Android Market.

    read more

    Thursday, October 8, 2009

    0

    Support for additional screen resolutions and densities in Android

  • Thursday, October 8, 2009
  • Ric RAT
  • You may have heard that one of the key changes introduced in Android 1.6 is support for new screen sizes. This is one of the things that has me very excited about Android 1.6 since it means Android will start becoming available on so many more devices. However, as a developer, I know this also means a bit of additional work. That's why we've spent quite a bit of time making it as easy as possible for you to update your apps to work on these new screen sizes.

    To date, all Android devices (such as the T-Mobile G1 and Samsung I7500, among others) have had HVGA (320x480) screens. The essential change in Android 1.6 is that we've expanded support to include three different classes of screen sizes:

    • small: devices with a screen size smaller than the T-Mobile G1 or Samsung I7500, for example the recently announced HTC Tattoo
    • normal: devices with a screen size roughly the same as the G1 or I7500.
    • large: devices with a screen size larger than the G1 or I7500 (such as a tablet-style device.)

    Any given device will fall into one of those three groups. As a developer, you can control if and how your app appears to devices in each group by using a few tools we've introduced in the Android framework APIs and SDK. The documentation at the developer site describes each of these tools in detail, but here they are in a nutshell:

    • new attributes in AndroidManifest for an application to specify what kind of screens it supports,
    • framework-level support for using image drawables/layouts correctly regardless of screen size,
    • a compatibility mode for existing applications, providing a pseudo-HVGA environment, and descriptions of compatible device resolutions and minimum diagonal sizes.

    The documentation also provides a quick checklist and testing tips for developers to ensure their apps will run correctly on devices of any screen size.

    Once you've upgraded your app using Android 1.6 SDK, you'll need to make sure your app is only available to users whose phones can properly run it. To help you with that, we've also added some new tools to Android Market.

    Until the next time you upload a new version of your app to Android Market, we will assume that it works for normal-class screen sizes. This means users with normal-class and large-class screens will have access to these apps. Devices with "large" screens simply run these apps in a compatibility mode, which simulates an HVGA environment on the larger screen.

    Devices with small-class screens, however, will only be shown apps which explicitly declare (via the AndroidManifest) that they will run properly on small screens. In our studies, we found that "squeezing" an app designed for a larger screen onto a smaller screen often produces a bad result. To prevent users with small screens from getting a bad impression of your app (and reviewing it negatively!), Android Market makes sure that they can't see it until you upload a new version that declares itself compatible.

    We expect small-class screens, as well as devices with additional resolutions in Table 1 in the developer document to hit the market in time for the holiday season. Note that not all devices will be upgraded to Android 1.6 at the same time. There will be significant number of users still with Android 1.5 devices. To use the same apk to target Android 1.5 devices and Android 1.6 devices, build your apps using Android 1.5 SDK and test your apps on both Android 1.5 and 1.6 system images to make sure they continue to work well on both types of devices. If you want to target small-class devices like HTC Tattoo, please build your app using the Android 1.6 SDK. Note that if your application requires Android 1.6 features, but does not support a screen class, you need to set the appropriate attributes to false. To use optimized assets for normal-class, high density devices like WVGA, or for low density devices please use the Android 1.6 SDK.

    read more

    Wednesday, June 3, 2009

    0

    Activities and Tasks Design Guidelines

  • Wednesday, June 3, 2009
  • Ric RAT
  • For our third post in the series of Android UI, we're releasing Activity and Task Design Guidelines. This section of our guidelines aims to help you understand basic concepts of activities and tasks, how they work, and how to enrich the user experience you are creating.

    We've packed a lot into this section, which is targeted at designers and developers. You'll see examples that will illustrate how to use our core principles and mechanisms, such as multitasking, activity reuse, intents, and the back stack.

    Additionally, we are providing some best practices around our UI patterns such as notifications. For example, we'll show you how to design a notification so that it will take the user to the screen they expect. This behavior needs to be thought out, and doesn't necessarily just happen by default.

    With helpful pointers to the API's and this documentation, we look forward to building your understanding of what it means to design and develop an Android UI.

    read more

    Wednesday, May 6, 2009

    0

    Painless threading

  • Wednesday, May 6, 2009
  • Ric RAT
  • Whenever you first start an Android application, a thread called "main" is automatically created. The main thread, also called the UI thread, is very important because it is in charge of dispatching the events to the appropriate widgets and this includes the drawing events. It is also the thread you interact with Android widgets on. For instance, if you touch the a button on screen, the UI thread dispatches the touch event to the widget which in turn sets its pressed state and posts an invalidate request to the event queue. The UI thread dequeues the request and notifies the widget to redraw itself.

    This single thread model can yield poor performance in Android applications that do not consider the implications. Since everything happens on a single thread performing long operations, like network access or database queries, on this thread will block the whole user interface. No event can be dispatched, including drawing events, while the long operation is underway. From the user's perspective, the application appears hung. Even worse, if the UI thread is blocked for more than a few seconds (about 5 seconds currently) the user is presented with the infamous "application not responding" (ANR) dialog.

    If you want to see how bad this can look, write a simple application with a button that invokes Thread.sleep(2000) in its OnClickListener. The button will remain in its pressed state for about 2 seconds before going back to its normal state. When this happens, it is very easy for the user to perceive the application as slow.

    Now that you know you must avoid lengthy operations on the UI thread, you will probably use extra threads (background or worker threads) to perform these operations, and rightly so. Let's take the example of a click listener downloading an image over the network and displaying it in an ImageView:

    public void onClick(View v) {
    new Thread(new Runnable() {
    public void run() {
    Bitmap b = loadImageFromNetwork();
    mImageView.setImageBitmap(b);
    }
    }).start();
    }

    At first, this code seems to be a good solution to your problem, as it does not block the UI thread. Unfortunately, it violates the single thread model: the Android UI toolkit is not thread-safe and must always be manipulated on the UI thread. In this piece of code, the ImageView is manipulated on a worker thread, which can cause really weird problems. Tracking down and fixing such bugs can be difficult and time-consuming.

    Android offers several ways to access the UI thread from other threads. You may already be familiar with some of them but here is a comprehensive list:

    Any of these classes and methods could be used to correct our previous code example:

    public void onClick(View v) {
    new Thread(new Runnable() {
    public void run() {
    final Bitmap b = loadImageFromNetwork();
    mImageView.post(new Runnable() {
    public void run() {
    mImageView.setImageBitmap(b);
    }
    });
    }
    }).start();
    }

    Unfortunately, these classes and methods also tend to make your code more complicated and more difficult to read. It becomes even worse when your implement complex operations that require frequent UI updates. To remedy this problem, Android 1.5 offers a new utility class, called AsyncTask, that simplifies the creation of long-running tasks that need to communicate with the user interface.

    AsyncTask is also available for Android 1.0 and 1.1 under the name UserTask. It offers the exact same API and all you have to do is copy its source code in your application.

    The goal of AsyncTask is to take care of thread management for you. Our previous example can easily be rewritten with AsyncTask:

    public void onClick(View v) {
    new DownloadImageTask().execute("http://example.com/image.png");
    }

    private class DownloadImageTask extends AsyncTask {
    protected Bitmap doInBackground(String... urls) {
    return loadImageFromNetwork(urls[0]);
    }

    protected void onPostExecute(Bitmap result) {
    mImageView.setImageBitmap(result);
    }
    }

    As you can see, AsyncTask must be used by subclassing it. It is also very important to remember that an AsyncTask instance has to be created on the UI thread and can be executed only once. You can read the AsyncTask documentation for a full understanding on how to use this class, but here is a quick overview of how it works:

    In addition to the official documentation, you can read several complex examples in the source code of Shelves (ShelvesActivity.java and AddBookActivity.java) and Photostream (LoginActivity.java, PhotostreamActivity.java and ViewPhotoActivity.java). I highly recommend reading the source code of Shelves to see how to persist tasks across configuration changes and how to cancel them properly when the activity is destroyed.

    Regardless of whether or not you use AsyncTask, always remember these two rules about the single thread model: do not block the UI thread and make sure the Android UI toolkit is only accessed on the UI thread. AsyncTask just makes it easier to do both of these things.

    If you want to learn more cool techniques, come join us at Google I/O. Members of the Android team will be there to give a series of in-depth technical sessions and answer all your questions.

    read more

    Monday, May 4, 2009

    0

    Drawable mutations

  • Monday, May 4, 2009
  • Ric RAT
  • Android's drawables are extremely useful to easily build applications. A Drawable is a pluggable drawing container that is usually associated with a View. For instance, a BitmapDrawable is used to display images, a ShapeDrawable to draw shapes and gradients, etc. You can even combine them to create complex renderings.

    Drawables allow you to easily customize the rendering of the widgets without subclassing them. As a matter of fact, they are so convenient that most of the default Android apps and widgets are built using drawables; there are about 700 drawables used in the core Android framework. Because drawables are used so extensively throughout the system, Android optimizes them when they are loaded from resources. For instance, every time you create a Button, a new drawable is loaded from the framework resources (android.R.drawable.btn_default). This means all buttons across all the apps use a different drawable instance as their background. However, all these drawables share a common state, called the "constant state." The content of this state varies according to the type of drawable you are using, but it usually contains all the properties that can be defined by a resource. In the case of a button, the constant state contains a bitmap image. This way, all buttons across all applications share the same bitmap, which saves a lot of memory.

    The following diagram shows what entities are created when you assign the same image resource as the background of two different views. As you can see, two drawables are created but they both share the same constant state, hence the same bitmap:

    This state sharing feature is great to avoid wasting memory but it can cause problems when you try to modify the properties of a drawable. Imagine an application with a list of books. Each book has a star next to its name, totally opaque when the user marks the book as a favorite, and translucent when the book is not a favorite. To achieve this effect, you would probably write the following code in your list adapter's getView() method:

    Book book = ...;
    TextView listItem = ...;

    listItem.setText(book.getTitle());

    Drawable star = context.getResources().getDrawable(R.drawable.star);
    if (book.isFavorite()) {
    star.setAlpha(255); // opaque
    } else {
    star.setAlpha(70); // translucent
    }

    Unfortunately, this piece of code yields a rather strange result, all the drawables have the same opacity:

    This result is explained by the constant state. Even though we are getting a new drawable instance for each list item, the constant state remains the same and, in the case of BitmapDrawable, the opacity is part of the constant state. Thus, changing the opacity of one drawable instance changes the opacity of all the other instances. Even worse, working around this issue was not easy with Android 1.0 and 1.1.

    Android 1.5 offers a very way to solve this issue with a the new mutate() method. When you invoke this method on a drawable, the constant state of the drawable is duplicated to allow you to change any property without affecting other drawables. Note that bitmaps are still shared, even after mutating a drawable. The diagram below shows what happens when you invoke mutate() on a drawable:

    Let's update our previous piece of code to make use of mutate():

    Drawable star = context.getResources().getDrawable(R.drawable.star);
    if (book.isFavorite()) {
    star.mutate().setAlpha(255); // opaque
    } else {
    star. mutate().setAlpha(70); // translucent
    }

    For convenience, mutate() returns the drawable itself, which allows to chain method calls. It does not however create a new drawable instance. With this new piece of code, our application now behaves correctly:

    If you want to learn more cool techniques, come join us at Google I/O. Members of the Android team will be there to give a series of in-depth technical sessions and answer all your questions.

    read more

    Friday, April 24, 2009

    0

    Live folders

  • Friday, April 24, 2009
  • Ric RAT
  • Live folders have been introduced in Android 1.5 and let you display any source of data on the Home screen without forcing the user to launch an application. A live folder is simply a real-time view of a ContentProvider. As such, a live folder can be used to display all your contacts, your bookmarks, your email, your playlists, an RSS feed, etc. The possibilities are endless! Android 1.5 ships with a few stock live folders to display your contacts. For instance, the screenshot below shows the content of the live folders that displays all my contacts with a phone number:

    If a contacts sync happens in the background while I'm browsing this live folder, I will see the change happen in real-time. Live folders are not only useful but it's also very easy to modify your application to make it provider a live folder. In this article, I will show you how to add a live folder to the Shelves application. You can download its source code and modify it by following my instructions to better understand how live folders work.

    To give the user the option to create a new live folder, you first need to create a new activity with an intent filter who action is android.intent.action.CREATE_LIVE_FOLDER. To do so, simply open AndroidManifest.xml and add something similar to this:

    <activity
    android:name=".activity.BookShelfLiveFolder"
    android:label="BookShelf">
    <intent-filter>
    <action android:name="android.intent.action.CREATE_LIVE_FOLDER" />
    <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    </activity>

    The label and icon of this activity are what the user will see on the Home screen when choosing a live folder to create:

    Since you just need an intent filter, it is possible, and sometimes advised, to reuse an existing activity. In the case of Shelves, we will create a new activity, org.curiouscreature.android.shelves.activity.BookShelfLiveFolder. The role of this activity is to send an Intent result to Home containing the description of the live folder: its name, icon, display mode and content URI. The content URI is very important as it describes what ContentProvider will be used to populate the live folder. The code of the activity is very simple as you can see here:

    public class BookShelfLiveFolder extends Activity {
    public static final Uri CONTENT_URI = Uri.parse("content://shelves/live_folders/books");

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    final Intent intent = getIntent();
    final String action = intent.getAction();

    if (LiveFolders.ACTION_CREATE_LIVE_FOLDER.equals(action)) {
    setResult(RESULT_OK, createLiveFolder(this, CONTENT_URI,
    "Books", R.drawable.ic_live_folder));
    } else {
    setResult(RESULT_CANCELED);
    }

    finish();
    }

    private static Intent createLiveFolder(Context context, Uri uri, String name, int icon) {
    final Intent intent = new Intent();

    intent.setData(uri);
    intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME, name);
    intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_ICON,
    Intent.ShortcutIconResource.fromContext(context, icon));
    intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_DISPLAY_MODE, LiveFolders.DISPLAY_MODE_LIST);

    return intent;
    }
    }

    This activity, when invoked with theACTION_CREATE_LIVE_FOLDER intent, returns an intent with a URI, content://shelves/live_folders/books, and three extras to describe the live folder. There are other extras and constants you can use and you should refer to the documentation of android.provider.LiveFolders for more details. When Home receives this intent, a new live folder is created on the user's desktop, with the name and icon you provided. Then, when the user clicks on the live folder to open it, Home queries the content provider referenced by the provided URI.

    Live folders' content providers must obey specific naming rules. The Cursor returned by the query() method must have at least two columns named LiveFolders._ID and LiveFolders.NAME. The first one is the unique identifier of each item in the live folder and the second one is the name of the item. There are other column names you can use to specify an icon, a description, the intent to associate with the item (fired when the user clicks that item), etc. Again, refer to the documentation of android.provider.LiveFolders for more details.

    In our example, all we need to do is modify the existing provider in Shelves called org.curiouscreature.android.shelves.provider.BooksProvider. First, we need to modify the URI_MATCHER to recognize our content://shelves/live_folders/books content URI:

    private static final int LIVE_FOLDER_BOOKS = 4;
    // ...
    URI_MATCHER.addURI(AUTHORITY, "live_folders/books", LIVE_FOLDER_BOOKS);

    Then we need to create a new projection map for the cursor. A projection map can be used to "rename" columns. In our case, we will replace BooksStore.Book._ID, BooksStore.Book.TITLE and BooksStore.Book.AUTHORS with LiveFolders._ID, LiveFolders.TITLE and LiveFolders.DESCRIPTION:

    private static final HashMap LIVE_FOLDER_PROJECTION_MAP;
    static {
    LIVE_FOLDER_PROJECTION_MAP = new HashMap();
    LIVE_FOLDER_PROJECTION_MAP.put(LiveFolders._ID, BooksStore.Book._ID +
    " AS " + LiveFolders._ID);
    LIVE_FOLDER_PROJECTION_MAP.put(LiveFolders.NAME, BooksStore.Book.TITLE +
    " AS " + LiveFolders.NAME);
    LIVE_FOLDER_PROJECTION_MAP.put(LiveFolders.DESCRIPTION, BooksStore.Book.AUTHORS +
    " AS " + LiveFolders.DESCRIPTION);
    }

    Because we are providing a title and a description for each row, Home will automatically display each item of the live folder with two lines of text. Finally, we implement the query() method by supplying our projection map to the SQL query builder:

    public Cursor query(Uri uri, String[] projection, String selection,
    String[] selectionArgs, String sortOrder) {

    SQLiteQueryBuilder qb = new SQLiteQueryBuilder();

    switch (URI_MATCHER.match(uri)) {
    // ...
    case LIVE_FOLDER_BOOKS:
    qb.setTables("books");
    qb.setProjectionMap(LIVE_FOLDER_PROJECTION_MAP);
    break;
    default:
    throw new IllegalArgumentException("Unknown URI " + uri);
    }

    SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, BooksStore.Book.DEFAULT_SORT_ORDER);
    c.setNotificationUri(getContext().getContentResolver(), uri);

    return c;
    }

    You can now compile and deploy the application, go to the Home screen and try to add a live folder. I added a books live folder to my Home screen and when I open it, I can see the list of all of my books, with their titles and authors, and all it took was a few lines of code:

    The live folders API is extremely simple and relies only on intents and content URI. If you want to see more examples of live folders implementation, you can read the source code of the Contacts application and of the Contacts provider.

    You can also download the result of our exercise, the modified version of Shelves with live folders support.


    Learn about Android 1.5 and more at Google I/O. Members of the Android team will be there to give a series of in-depth technical sessions and to field your toughest questions.

    read more

    Tuesday, April 14, 2009

    0

    UI framework changes in Android 1.5

  • Tuesday, April 14, 2009
  • Ric RAT
  • On Monday, we released an early look at the Android 1.5 SDK. Not only does this platform update contain numerous new features, APIs, and bug fixes, but Android 1.5 also brings a new default look for the Android UI framework. After Android 1.0 and 1.1, our designers worked hard to refine and polish the appearance of the system. The screenshots below show the same activity (creating a new contact) on Android 1.1 and Android 1.5:

    You can see in this example that the buttons and checkboxes have a new appearance. Even though these changes do not affect binary nor source compatibility, they might still break the UI of your apps. As part of the UI refresh, the minimum size of some of the widgets has changed. For instance, Android 1.1 buttons have a minimum size of 44x48 pixels whereas Android 1.5 buttons now have a minimum size of 24x48 pixels. The image below compares the sizes of Android 1.1 buttons with Android 1.5 buttons:

    If you rely on the button's minimum size, then the layout of your application may not be the same in Android 1.5 as it was in Android 1.1 because of this change. This would happen for instance if you created a grid of buttons using LinearLayout and relying on the minimum size yielded by wrap_content to align the buttons properly:

    This layout could easily be fixed by using the android:layout_weight attribute or by replacing the LinearLayout containers with a TableLayout.

    This example is probably the worst-case UI issue you may encounter when running your application on Android 1.5. Other changes introduced in Android 1.5, especially bug fixes in the layout views, may also impact your application—especially if it is relying on faulty/buggy behavior of the UI framework.

    If you encounter issues when running your application on Android 1.5, please join us on the Google groups or IRC so that we and the Android community can help you fix your application.

    Happy coding!

    read more

    Monday, March 30, 2009

    0

    Android Layout Tricks #3: Optimize with stubs

  • Monday, March 30, 2009
  • Ric RAT
  • Sharing and reusing layouts is very easy with Android thanks to the <include /> tag, sometimes even too easy and you might end up with user interfaces that contain a large number of views, some of which are rarely used. Thankfully, Android offers a very special widget called ViewStub, which brings you all the benefits of the <include /> without polluting your user interface with rarely used views.

    A ViewStub is a dumb and lightweight view. It has no dimension, it does not draw anything and does not participate in the layout in any way. This means a ViewStub is very cheap to inflate and very cheap to keep in a view hierarchy. A ViewStub can be best described as a lazy include. The layout referenced by a ViewStub is inflated and added to the user interface only when you decide so.

    The following screenshot comes from the Shelves application. The main purpose of the activity shown in the screenshot is to present the user with a browsable list of books:

    The same activity is also used when the user adds or imports new books. During such an operation, Shelves shows extra bits of user interface. The screenshot below shows the progress bar and cancel button that appear at the bottom of the screen during an import:

    Because importing books is not a common operation, at least when compared to browsing the list of books, the import panel is originally represented by a ViewStub:

    When the user initiates the import process, the ViewStub is inflated and replaced by the content of the layout file it references:

    To use a ViewStub all you need is to specify an android:id attribute, to later inflate the stub, and an android:layout attribute, to reference what layout file to include and inflate. A stub lets you use a third attribute, android:inflatedId, which can be used to override the id of the root of the included file. Finally, the layout parameters specified on the stub will be applied to the roof of the included layout. Here is an example:

    <ViewStub
    android:id="@+id/stub_import"
    android:inflatedId="@+id/panel_import"

    android:layout="@layout/progress_overlay"

    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom" />

    When you are ready to inflate the stub, simply invoke the inflate() method. You can also simply change the visibility of the stub to VISIBLE or INVISIBLE and the stub will inflate. Note however that the inflate() method has the benefit of returning the root View of the inflate layout:

    ((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
    // or
    View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();

    It is very important to remember that after the stub is inflated, the stub is removed from the view hierarchy. As such, it is unnecessary to keep a long-lived reference, for instance in an class instance field, to a ViewStub.

    A ViewStub is a great compromise between ease of programming and efficiency. Instead of inflating views manually and adding them at runtime to your view hierarchy, simply use a ViewStub. It's cheap and easy. The only drawback of ViewStub is that it currently does not support the <merge /> tag.

    Happy coding!

    read more

    Thursday, March 5, 2009

    0

    Window Backgrounds & UI Speed

  • Thursday, March 5, 2009
  • Ric RAT
  • Some Android applications require to squeeze every bit of performance out of the UI toolkit and there are many ways to do so. In this article, you will discover how to speed up the drawing and the perceived startup time of your activities. Both these techniques rely on a single feature, the window's background drawable.

    The term window background is a bit misleading however. When you setup your user interface by calling setContentView() on an Activity, Android adds your views to the Activity's window. The window however does not contain only your views, but a few others created for you. The most important one is, in the current implementation used on the T-Mobile G1, the DecorView, highlighted in the view hierarchy below:

    A typical Android view hierarchy

    The DecorView is the view that actually holds the window's background drawable. Calling getWindow().setBackgroundDrawable() from your Activity changes the background of the window by changing the DecorView's background drawable. As mentioned before, this setup is very specific to the current implementation of Android and can change in a future version or even on another device.

    If you are using the standard Android themes, a default background drawable is set on your activities. The standard theme currently used on the T-Mobile G1 uses for instance a ColorDrawable. For most applications, this background drawable works just fine and can be left alone. It can however impacts your application's drawing performance. Let's take the example of an application that always draws a full screen opaque picture:

    An opaque user interface doesn't need a window background

    You can see on this screenshot that the window's background is invisible, entirely covered by an ImageView. This application is setup to redraw as fast as it can and draws at about 44 frames per second, or 22 milliseconds per frame (note: the number of frames per second used in this article were obtained on a T-Mobile G1 with my finger on the screen so as to reduce the drawing speed which would otherwise be capped at 60 fps.) An easy way to make such an application draw faster is to remove the background drawable. Since the user interface is entirely opaque, drawing the background is simply wasteful. Removing the background improves the performance quite nicely:

    Remove the background for faster drawing

    In this new version of the application, the drawing speed went up to 51 frames per second, or 19 milliseconds per frame. The difference of 3 milliseconds per is easily explained by the speed of the memory bus on the T-Mobile G1: it is exactly the time it takes to move the equivalent of a screenful of pixels on the bus. The difference could be even greater if the default background was using a more expensive drawable.

    Removing the window's background can be achieved very easily by using a custom theme. To do so, first create a file called res/values/theme.xml containing the following:

    <resources>
    <style name="Theme.NoBackground" parent="android:Theme">
    <item name="android:windowBackground">@null</item>
    </style>
    </resources>

    You then need to apply the theme to your activity by adding the attribute android:theme="@style/Theme.NoBackground" to your <activity /> or <application /> tag. This trick comes in very handy for any app that uses a MapView, a WebView or any other full screen opaque view.

    Opaque views and Android: this optimization is currently necessary because the Android UI toolkit is not smart enough to prevent the drawing of views hidden by opaque children. The main reason why this optimization was not implemented is simply because there are usually very few opaque views in Android applications. This is however something that I definitely plan on implementing as soon as possible and I can only apologize for not having been able to do this earlier.

    Using a theme to change the window's background is also a fantastic way to improve the perceived startup performance of some of your activities. This particular trick can only be applied to activities that use a custom background, like a texture or a logo. The Shelves application is a good example:

    Textured backgrounds are good candidates for window's background

    If this application simply set the wooden background in the XML layout or in onCreate() the user would see the application startup with the default theme and its dark background. The wooden texture would only appear after the inflation of the content view and the first layout/drawing pass. This causes a jarring effect and gives the user the impression that the application takes time to load (which can actually be the case.) Instead, the application defines the wooden background in a theme, picked up by the system as soon as the application starts. The user never sees the default theme and gets the impression that the application is up and running right away. To limit the memory and disk usage, the background is a tiled texture defined in res/drawable/background_shelf.xml:

    <bitmap xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@drawable/shelf_panel"
    android:tileMode="repeat" />

    This drawable is simply referenced by the theme:

    <resources>
    <style name="Theme.Shelves" parent="android:Theme">
    <item name="android:windowBackground">@drawable/background_shelf</item>
    <item name="android:windowNoTitle">true</item>
    </style>
    </resources>

    The same exact trick is used in the Google Maps application that ships with the T-Mobile G1. When the application is launched, the user immediately sees the loading tiles of MapView. This is only a trick, the theme is simply using a tiled background that looks exactly like the loading tiles of MapView.

    Sometimes the best tricks are also the simplest so the next time you create an activity with an opaque UI or a custom background, remember to change the window's background.

    Download the source code of the first example.

    Download the source code of Shelves.

    read more

    Tuesday, March 3, 2009

    0

    Android Layout Tricks #3: Optimize by merging

  • Tuesday, March 3, 2009
  • Ric RAT
  • In the previous installment of Android Layout Tricks, I showed you how to use the <include /> tag in XML layout to reuse and share your layout code. I also mentioned the <merge /> and it's now time to learn how to use it.

    The <merge /> was created for the purpose of optimizing Android layouts by reducing the number of levels in view trees. It's easier to understand the problem this tag solves by looking at an example. The following XML layout declares a layout that shows an image with its title on top of it. The structure is fairly simple; a FrameLayout is used to stack a TextView on top of an ImageView:

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

    <ImageView
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"

    android:scaleType="center"
    android:src="@drawable/golden_gate" />

    <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginBottom="20dip"
    android:layout_gravity="center_horizontal|bottom"

    android:padding="12dip"

    android:background="#AA000000"
    android:textColor="#ffffffff"

    android:text="Golden Gate" />

    </FrameLayout>

    This layout renders nicely as we expected and nothing seems wrong with this layout:



    A FrameLayout is used to overlay a title on top of an image


    Things get more interesting when you inspect the result with HierarchyViewer. If you look closely at the resulting tree you will notice that the FrameLayout defined in our XML file (highlighted in blue below) is the sole child of another FrameLayout:

    A layout with only one child of same dimensions can be removed

    Since our FrameLayout has the same dimension as its parent, by the virtue of using the fill_parent constraints, and does not define any background, extra padding or a gravity, it is totally useless. We only made the UI more complex for no good reason. But how could we get rid of this FrameLayout? After all, XML documents require a root tag and tags in XML layouts always represent view instances.

    That's where the <merge /> tag comes in handy. When the LayoutInflater encounters this tag, it skips it and adds the <merge /> children to the <merge /> parent. Confused? Let's rewrite our previous XML layout by replacing the FrameLayout with <merge />:

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

    <ImageView
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"

    android:scaleType="center"
    android:src="@drawable/golden_gate" />

    <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginBottom="20dip"
    android:layout_gravity="center_horizontal|bottom"

    android:padding="12dip"

    android:background="#AA000000"
    android:textColor="#ffffffff"

    android:text="Golden Gate" />

    </merge>

    With this new version, both the TextView and the ImageView will be added directly to the top-level FrameLayout. The result will be visually the same but the view hierarchy is simpler:

    Optimized view hierarchy using the merge tag

    Obviously, using <merge /> works in this case because the parent of an activity's content view is always a FrameLayout. You could not apply this trick if your layout was using a LinearLayout as its root tag for instance. The <merge /> can be useful in other situations though. For instance, it works perfectly when combined with the <include /> tag. You can also use <merge /> when you create a custom composite view. Let's see how we can use this tag to create a new view called OkCancelBar which simply shows two buttons with customizable labels. You can also download the complete source code of this example. Here is the XML used to display this custom view on top of an image:

    <merge
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:okCancelBar="http://schemas.android.com/apk/res/com.example.android.merge">

    <ImageView
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"

    android:scaleType="center"
    android:src="@drawable/golden_gate" />

    <com.example.android.merge.OkCancelBar
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"

    android:paddingTop="8dip"
    android:gravity="center_horizontal"

    android:background="#AA000000"

    okCancelBar:okLabel="Save"
    okCancelBar:cancelLabel="Don't save" />

    </merge>

    This new layout produces the following result on a device:

    Creating a custom view with the merge tag

    The source code of OkCancelBar is very simple because the two buttons are defined in an external XML file, loaded using a LayoutInflate. As you can see in the following snippet, the XML layout R.layout.okcancelbar is inflated with the OkCancelBar as the parent:

    public class OkCancelBar extends LinearLayout {
    public OkCancelBar(Context context, AttributeSet attrs) {
    super(context, attrs);
    setOrientation(HORIZONTAL);
    setGravity(Gravity.CENTER);
    setWeightSum(1.0f);

    LayoutInflater.from(context).inflate(R.layout.okcancelbar, this, true);

    TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.OkCancelBar, 0, 0);

    String text = array.getString(R.styleable.OkCancelBar_okLabel);
    if (text == null) text = "Ok";
    ((Button) findViewById(R.id.okcancelbar_ok)).setText(text);

    text = array.getString(R.styleable.OkCancelBar_cancelLabel);
    if (text == null) text = "Cancel";
    ((Button) findViewById(R.id.okcancelbar_cancel)).setText(text);

    array.recycle();
    }
    }

    The two buttons are defined in the following XML layout. As you can see, we use the <merge /> tag to add the two buttons directly to the OkCancelBar. Each button is included from the same external XML layout file to make them easier to maintain; we simply override their id:

    <merge xmlns:android="http://schemas.android.com/apk/res/android">
    <include
    layout="@layout/okcancelbar_button"
    android:id="@+id/okcancelbar_ok" />

    <include
    layout="@layout/okcancelbar_button"
    android:id="@+id/okcancelbar_cancel" />
    </merge>

    We have created a flexible and easy to maintain custom view that generates an efficient view hierarchy:

    The resulting hierarchy is simple and efficient

    The <merge /> tag is extremely useful and can do wonders in your code. However, it suffers from a couple of limitation:

    • <merge /> can only be used as the root tag of an XML layout

    • When inflating a layout starting with a <merge />, you must specify a parent ViewGroup and you must set attachToRoot to true (see the documentation of the inflate() method)

    In the next installment of Android Layout Tricks you will learn about ViewStub, a powerful variation of <include /> that can help you further optimize your layouts without sacrificing features.

    Download the complete source code of this example.

    read more

    Subscribe