Thursday, March 15, 2012

Putting simple transition effect to fragments

Nowadays it's recommended to use Fragments, even when developing non-tablet apps.

One of the first thing I notice when moving between fragments is that the user immediately see the new fragments without any transition animation. We want to add some kind of animation, unfortunately the FragmentTransaction documentation is very scarce.

After experiments, this works even when using v4 Support Library:

It happens that we need to consider several things:
  1. setTransition and setTransitionStyle is confusing and useless. Forget them.
  2. setCustomAnimations is the way to go. But you have to call it before you call add/remove/hide or similar operations.
  3. The resource ids passed to setCustomAnimations will be decoded using AnimationUtils.loadAnimation(). So you can pass anim resources (the old-style animations). I haven't tested whether the new animator resources work.
In order to "push" a fragment as if you were starting a new Activity using startActivity, call the following:


public static void moveForward(FragmentManager fm, Fragment current, Fragment newer, String backstackName, boolean allowStateLoss) {
 FragmentTransaction ft = fm.beginTransaction();
 ft.setCustomAnimations(android.R.anim.fade_in, 0, android.R.anim.fade_in, 0);
 ft.hide(current);
 ft.add(android.R.id.content, newer);
 ft.addToBackStack(backstackName);
 if (allowStateLoss) {
  ft.commitAllowingStateLoss();
 } else {
  ft.commit();
 }
}

For example, from the current Fragment, you want to transition to the AnotherFragment, call the method above as follows:

moveForward(getFragmentManager(), this, new AnotherFragment(), "another", false)

Bonus: If you call the above method after returning from other applications (for example inside onActivityResult), make sure to pass true to allow state loss, or your app will crash. It's not discussed here why =).

Tuesday, January 10, 2012

Convert RGB to HSL and vice-versa in Java

I wanted to add colored labels to my Android application and I wanted to make it such that the users need only to select one color, the background color, and the foreground color would be the same color, only lighter or darker.

I thought I needed a RGB to HSL (Hue, Saturation, Lightness) converter, but Android SDK only provides RGB to HSV value, which has some similarity but different.

Based on the above Wikipedia article, I wrote the Java code to convert between RGB and HSL. Hopefully this can be useful to your project.
  • rgb is an int (with alpha ignored) 0xrrggbb
  • hsl is an array of 3 floats:
    • [0] is hue, 0..360
    • [1] is saturation, 0..1
    • [2] is lightness, 0..1
public static void rgbToHsl(int rgb, float[] hsl) {
 float r = ((0x00ff0000 & rgb) >> 16) / 255.f;
 float g = ((0x0000ff00 & rgb) >> 8) / 255.f;
 float b = ((0x000000ff & rgb)) / 255.f;
 float max = Math.max(Math.max(r, g), b);
 float min = Math.min(Math.min(r, g), b);
 float c = max - min;
 
 float h_ = 0.f;
 if (c == 0) {
  h_ = 0;
 } else if (max == r) {
  h_ = (float)(g-b) / c;
  if (h_ < 0) h_ += 6.f;
 } else if (max == g) {
  h_ = (float)(b-r) / c + 2.f;
 } else if (max == b) {
  h_ = (float)(r-g) / c + 4.f;
 }
 float h = 60.f * h_;
 
 float l = (max + min) * 0.5f;
 
 float s;
 if (c == 0) {
  s = 0.f;
 } else {
  s = c / (1 - Math.abs(2.f * l - 1.f));
 }
 
 hsl[0] = h;
 hsl[1] = s;
 hsl[2] = l;
}

public static int hslToRgb(float[] hsl) {
 float h = hsl[0];
 float s = hsl[1];
 float l = hsl[2];
 
 float c = (1 - Math.abs(2.f * l - 1.f)) * s;
 float h_ = h / 60.f;
 float h_mod2 = h_;
 if (h_mod2 >= 4.f) h_mod2 -= 4.f;
 else if (h_mod2 >= 2.f) h_mod2 -= 2.f;
 
 float x = c * (1 - Math.abs(h_mod2 - 1));
 float r_, g_, b_;
 if (h_ < 1)      { r_ = c; g_ = x; b_ = 0; }
 else if (h_ < 2) { r_ = x; g_ = c; b_ = 0; }
 else if (h_ < 3) { r_ = 0; g_ = c; b_ = x; }
 else if (h_ < 4) { r_ = 0; g_ = x; b_ = c; }
 else if (h_ < 5) { r_ = x; g_ = 0; b_ = c; }
 else             { r_ = c; g_ = 0; b_ = x; }
 
 float m = l - (0.5f * c); 
 int r = (int)((r_ + m) * (255.f) + 0.5f);
 int g = (int)((g_ + m) * (255.f) + 0.5f);
 int b = (int)((b_ + m) * (255.f) + 0.5f);
 return r << 16 | g << 8 | b;
}

Friday, January 6, 2012

Add "screenSize" flag to android:configChanges when targetting API level 13 (Android 3.2) or newer

If you're setting "keyboardHidden|orientation" to android:configChanges in order to prevent activity from restarting itself when the device is rotated, you might be surprised when you find your activity restarts itself when running on Android 3.2 or newer.

The reason is, starting from Android 3.2 (API Level 13), rotated device will also trigger "screenSize" change. From attrs_manifest.xml:



        <!-- The current available screen size has changed.  If applications don't
             target at least {@link android.os.Build.VERSION_CODES#HONEYCOMB_MR2}
             then the activity will always handle this itself (the change
             will not result in a restart).  This represents a change in the
             currently available size, so will change when the user switches
             between landscape and portrait. -->
        <flag name="screenSize" value="0x0400" />


It means, if your android:targetSdkVersion is 13 or more, your application will receive "screenSize" config change. Therefore, to prevent your activity from restarting, add "screenSize". E.g.

    android:configChanges="keyboardHidden|orientation|screenSize"

Hope this helps you.

Tuesday, November 29, 2011

Convert UNIX timestamp to human date using command-line

I'm working on a project where date/time are sent in UNIX timestamp. Sometimes when I'm debugging, I want to check whether the usually 10-digit number is correct.

The easiest way I found is using the command-line terminal:


~$ date -r 1322532029 
Tue Nov 29 10:00:29 SGT 2011

Friday, November 4, 2011

List of Inlined/intrinsic methods in Android's Dalvik VM

Android's Designing for Performance document, states:
In addition to all the usual reasons to prefer library code over rolling your own, bear in mind that the system is at liberty to replace calls to library methods with hand-coded assembler, which may be better than the best code the JIT can produce for the equivalent Java. The typical example here is String.indexOf and friends, which Dalvik replaces with an inlined intrinsic. Similarly, the System.arraycopy method is about 9x faster than a hand-coded loop on a Nexus One with the JIT.
Unfortunately, as are other Android SDK documentation, this doesn't tell you much which methods gets inlined with machine code.

Examining the Dalvik VM source code, I found dalvik/vm/InlineNative.c that starts with an introduction:


/*
 * Inlined native functions.  These definitions replace interpreted or
 * native implementations at runtime; "intrinsic" might be a better word.
 */


So the inlined methods are:
  • class String:
    • charAt(int)
    • compareTo(String)
    • equals(Object)
    • fastIndexOf(int, int)
    • isEmpty()
    • length()
  • class Math:
    • abs(int)
    • abs(long)
    • abs(float)
    • abs(double)
    • min(int, int)
    • max(int, int)
    • sqrt(double)
    • cos(double)
    • sin(double)
  • class Float:
    • floatToIntBits(float)
    • floatToRawIntBits(float)
    • intBitsToFloat(int)
  • class Double:
    • doubleToLongBits(double)
    • doubleToRawLongBits(double)
    • longBitsToDouble(long)
That's it! Now don't be so scared to use those methods in your code.

Bonus: fastIndexOf ? What is that? Java's String has indexOf, but never an fastIndexOf.

Answer: The implementation of String's indexOf(int) or indexOf(int, int) that Android uses, calls fastIndexOf if the char to be searched is <= 0xffff (which most probably is). So, if your code has a call of indexOf('x'), the native implementation will still be used.

Saturday, October 22, 2011

AnimationDrawable doesn't start after calling start()

Frame animations (sometimes declared in xml as <animation>) produces an AnimationDrawable. However, in some situations, such as using it as one of the ListView items, the animation doesn't start. Even if you call start().

The solution is simple. Just call stop() and then start().

In the source code of AnimationDrawable, the method start() can sometimes do nothing.

public void start() {
        if (!isRunning()) {
            run();
        }
    }

I have encountered this twice. I hope this helps you.

Thursday, August 11, 2011

Don't use Adapter#isEnabled(int) to disable individual list items (Android)

When we have items in a ListView, we may want to disable some of the items.

Disabling some of the items can be done by overriding isEnabled(int position) on the adapter, but unfortunately the list item dividers are not drawn anymore! Overriding isEnabled(int position) was meant to be used to create some kind of "separator" or "list header".

The correct way to disable an item in a ListView is to call setEnabled(false) on the view of the item itself. In other words, call setEnabled(false) on the view that is returned from getView(int position, View convertView, ViewGroup parent).

Reference: Romain Guy's post on android-developers.