Friday, March 15, 2013

TextView action on disappearing soft keyboard event

The task is to reformat a TextView when the user hides the soft keyboard (ime). It should correct it as a proper money amount: two decimal digits, decimal separator. Of course there is no way on Android to query if the keyboard is going to hide. But there are a few ways, the keyboard can be hidden by the user: backpress when it is visible, focus outside of the TextView, change container TabFragment to other Fragment. This is my solution for this:
 * Simple subclass of an EditText that allows to capture and dispatch BackPress, when the EditText has the focus.
 * This event is later used to finalise formatted text in the amount field (append trailing ".00" if necessary).
 * I could not find other way to detect keyboard is about to hide.
 * Created by: mikinw
public class HwKeySensibleEditText extends EditText {

    public HwKeySensibleEditText(Context context) {

    public HwKeySensibleEditText(Context context, AttributeSet attrs) {
        super(context, attrs);

    public HwKeySensibleEditText(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
        return super.dispatchKeyEvent(event);

    public Parcelable onSaveInstanceState() {
        // This forces a focus changed event to allow {@link AmountReformatter} to do
        // validation when user hides keyboard
        onFocusChanged(false, FOCUS_BACKWARD, null);
        return super.onSaveInstanceState();

This class is responsible to reformat the text inside the TextView. It handles the forced focus changed event also. I didn't delete the references to the outer Constants class, but you can find out, what those values should be.
 * Controller class for formatting amount field.
 * Handles all automatic correction that is needed for the amount field.
 * In the constructor it gets a {@link HwKeySensibleEditText} - which is a slightly modified EditText.
 * Auto correction has two steps:
 * - as to user types (add pound sign, don't allow multiple zeros at the beginning, etc. please check
 *   correctOnTheFlyText() function for this. And related unit tests)
 * - on focus lost (add ".00" as necessary. please check correctFinalAmount() function and related unit tests)
 * Also 2 listener can be attached also: OnValueChangedListener and OnEnterLeaveListener.
 * Created by: mikinw
public class AmountReformatter implements TextWatcher, View.OnKeyListener, View.OnFocusChangeListener {

    private HwKeySensibleEditText mAmountTextView;
    private OnValueChangedListener mValueChangedListener;
    private OnEnterLeaveListener mEnterLeaveListener;
    private static final int MAX_INTEGER_DIGITS = 15;

    public interface OnValueChangedListener {
        void onValueChanged (String value);

    public interface OnEnterLeaveListener {
        void onEnter();

    private AmountReformatter(HwKeySensibleEditText amountTextView,
                              OnValueChangedListener valueChangedListener,
                              OnEnterLeaveListener enterLeaveListener) {
        if (amountTextView == null) {
            throw new IllegalArgumentException("amountTextView can't be null");
        mAmountTextView = amountTextView;
        mValueChangedListener = valueChangedListener;
        mEnterLeaveListener = enterLeaveListener;

    private boolean mDeleting = false;
    public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
        mDeleting = after < count && mAmountTextView.hasFocus();

    public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {

    public void afterTextChanged(Editable editable) {
            //update onthefly only if tv has focus, cause correctFinalAmount will be overwrited
            final String curValue = editable.toString();
            String corrected = null;
            if(mDeleting && curValue.length() == 0){
                //don't allow user to delete pound char
                corrected = Constants.POUND_STRING;
                corrected = correctOnTheFlyText(curValue);
            // prevent infinite recursive call
            if ( !corrected.equals(editable.toString())) {
                editable.replace(0, editable.length(), corrected);

        if (mValueChangedListener != null) {

    public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
        if (keyCode == KeyEvent.KEYCODE_BACK && keyEvent.getAction() == KeyEvent.ACTION_UP) {
        } else if (keyCode == KeyEvent.KEYCODE_ENTER && keyEvent.getAction() == KeyEvent.ACTION_UP) {
            final InputMethodManager imm =
            imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
            return false;
        return false;

    public void onFocusChange(View view, boolean focusGained) {
        final String txtAmount = mAmountTextView.getText().toString();

        if (focusGained) {
            if (TextUtils.isEmpty(txtAmount)) {
                //needs to remove thousands separator
            //move cursor to the end

            mAmountTextView.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
            if (mEnterLeaveListener != null) {
        } else {
            //restore texthint if there is only pound char
                                            ? ""
                                            : correctFinalAmount(txtAmount));

     * Appends ".00" as needed or truncates to have exactly 2 decimals. Does not check other validity of the original
     * string (eg. will accept alphaliterals). It it's value is zero, then returns empty string.
     * @param original amount string that needs to be modified according to the above rules
     * @return modified amount text containing decimal separator and two digits after that
    static String correctFinalAmount(String original) {
        // if no . add . to end
        // fill the end to have 2 decimals
        if (TextUtils.isEmpty(original)) {
            return original;

        final String withoutPoundSign = original.charAt(0) == Constants.POUND_CHAR ? original.substring(1) : original;
        BigDecimal decimal;
        try {
            decimal = new BigDecimal(withoutPoundSign);
            if (decimal.compareTo(new BigDecimal(0)) == 0) {
                return "";
        } catch (NumberFormatException nfe) {
            return "";

        decimal = decimal.setScale(2, RoundingMode.DOWN);
        final DecimalFormat df = new DecimalFormat();
        return df.format(decimal);


     * Filters out not allowed characters (eg. letters, symbols). Also truncates the leading zeros, but does allow to
     * have "0.". Also adds pound symbol to the beginning. Truncates ending numbers after the first "." character to
     * have at most 2 of them.
     * @param original String that has to be formatted, corrected
     * @return formatted string according to the rules above
    static String correctOnTheFlyText(String original) {
        if (TextUtils.isEmpty(original)) {
            return original;
        final StringBuilder preDot = new StringBuilder(original.length() + 1);
        final StringBuilder postDot = new StringBuilder(original.length() + 1);
        boolean leadingZero = false;
        boolean dot = false;

        int i = 0;
        final int to = original.length();
        for (; i < to; i++) {
            final char ch = original.charAt(i);
            if (ch == '0') {
                leadingZero = true;
            } else if (ch > '0' && ch <= '9') {
            } else if (ch == Constants.FORCED_FORMAT_SYMBOLS.getDecimalSeparator()) {
                dot = true;
                leadingZero = true;

        for (; i < to; i++) {
            final char ch = original.charAt(i);
            if (ch >= '0' && ch <= '9') {
                if (preDot.length() < MAX_INTEGER_DIGITS) {
            }else if(ch == Constants.FORCED_FORMAT_SYMBOLS.getGroupingSeparator()){
                //thousands separator ignor, will be added later
            } else if (ch == Constants.FORCED_FORMAT_SYMBOLS.getDecimalSeparator()) {
                dot = true;

        for (int decimalCount = 0; i < to && decimalCount < 2; i++) {
            final char c = original.charAt(i);
            if (c >= '0' && c <= '9') {

        if (leadingZero && preDot.length() == 0) {
            preDot.insert(0, '0');
        if (dot) {

        //add thousands separator

        preDot.insert(0, Constants.POUND_CHAR);

        return preDot.toString();


    /** Static initiliser */
    public static void setListenersFor(HwKeySensibleEditText amountTextView,
                                       OnValueChangedListener valueChangedListener,
                                       OnEnterLeaveListener enterListener) {
        final AmountReformatter reformatter = new AmountReformatter(amountTextView, valueChangedListener, enterListener);

Activity lifecycle events

It took me quite a bit of trouble to understand Activity lifecycle. I decided to make a practical reference of how the lifecycle callbacks are called. I cover some typical use cases (I think it is quite self explanatory, but if you have question or suggestion how can I make it more readable, please contact me):
  • open activity - press home
  • open activity - press back
  • open activity - rotate activity
  • open activity - rotate activity (with custom handler)
  • open activity - open other activity - press back
  • open activity - open other activity - rotate - press back
  • open activity - open other activity for result - press back
Here is the link to the spreadsheet. And some results of other experiments:
  • onConfigurationChange() is not called when ime (soft keyboard) pops up or hides
  • onConfigurationChange() is called during rotate, if manifest is set so. does min api have to be below 13? if this is called, no lifecycle event will be called (?), however onCreateView is called for each view. - no. it is called even with min api level set to 13
  • when to use onRetainNonConfigurationInstance()? - when you want to preserve activity when configuration changes (keep a music decoder running)
  • will onPause be called, when an alert dialog appears? notificaion bar is dragged down? - no. no. it will be called though when a dialog (full screen activity, but in that case onStop will be called also) of an other application is started eg. by taping on it in notifiation bar. And onSaveInstanceState() will be called also.
  • onRestoreInstanceState() is only called, when onSaveInstanceState() returns non empty bundle? - no, it gets called all times
  • Will onDestroy() be called if the app is removed from the recent task list? - yes. (mostly :-) we have to get used to this framework feature is purposedly unreliable)
  • onSaveInstanceState saves a bundle. when this bundle is returned? does it override intent bundle? what is the difference if android:stateNotNeeded is set in manifest? - rather not do anything with android:stateNotNeeded flag, because it is yet another special solution for yet another marginal problem. But! saveInstanceState saves a bundle indeed. This bundle is given to onCreate() as an argument. This is null, if the activity is newly started (or backpress and start again). It is not null otherwise (mostly when it is being rebuilt after a configuration change (eg. rotation). Remember that, onCreate is not called when the activity only goes to background normally. But, if the framework calls onDestroy() eg. because of a memory shortage, the onCreate() will be called and this argument will be not null). You can try to get non existing values from it, you will get the default value (0 in case of int, false in case of boolean, etc). Intent extras are different. You can set extras when starting an activity. You can call intent.putExtra() overloads. There is a putExtras(Bundle) call also, but this bundle has nothing to do with saveInstanceState argument. These extras can be retrieved from the new activity with getIntent().getXXXExtra(String, defaultValue) (where XXX can be Int, Boolean, etc.). This cal will always return something, even if non existing key is requested, because a default value has to be provided.

Wednesday, March 13, 2013

List animation with drawingCache

A bit of animation for the rest of the items when a listitem is removed from a ListView. I've seen an interesting solution for this. The basic idea is to get a bitmap copy of the existing layout and animate the bitmap only. And when the animation finishes, hide the bitmap. With this we don't have to inflate (and measure and layout again and again).
One addition might be the ViewGroup.setClipChildren(false) call. This may prevent a visual glitch: the item at the bottom of the screen may be clipped so the animation may animate a half item. I didn't try it out though.
I made a small change in the code linked above, but I copy here the code to have syntax highlight.
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    public void onItemClick(AdapterView<?> parent, View rowView, int positon,long id) {
        listView.setDrawingCacheEnabled(true); // turn this on to be able to get the bitmapcopy
        Bitmap bitmap = listView.getDrawingCache(); // get the bitmapcopy of the view
        int bottom = rowView.getBottom();
        Bitmap myBitmap1 = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), Math.min(bottom, bitmap.getHeight())); // get the upper half
        Bitmap myBitmap2 = Bitmap.createBitmap(bitmap,
                                               bitmap.getHeight() - myBitmap1.getHeight()); // get the lower half (that will slide up)
        listView.setDrawingCacheEnabled(false); // turn off, not to mess up anything
        imgView1.setBackgroundDrawable(new BitmapDrawable(getResources(), myBitmap1)); // these to imageView are added to the layout (eg. in the layout file)
        imgView2.setBackgroundDrawable(new BitmapDrawable(getResources(), myBitmap2));
        FrameLayout.LayoutParams lp2 = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
        FrameLayout.LayoutParams lp1 = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
        lp2.setMargins(0, rowView.getBottom(), 0, 0);
        lp1.height = bottom;
        TranslateAnimation transanim=new TranslateAnimation(0, 0, 0, -rowView.getHeight());
        transanim.setAnimationListener(new Animation.AnimationListener() {
            public void onAnimationStart(Animation animation) {}
            public void onAnimationRepeat(Animation animation) {}
            public void onAnimationEnd(Animation animation) {

Tuesday, March 12, 2013

Animated removal of list items

Task to do: we have a ListView in an Android layout. Whenever an item is removed, the list item flies out from its original position to the top of the ListView.

Android has predefined animations for adding and removing items from a ViewGroup. Even more, you can define animation for the other elements (that are not being removed), when some sibling element is being removed. See: . However a ListView is a ViewGroup, this animation never happens, because the adapter of the ListView is immediately updates the view after a removal. You can set animation for the listItems and start them eg. during addition, but not during removal.

There are a couple of ways to make it work:
  • Seaching google, many suggest to utilise onListItemClick listener, and start animation than (eg. And at the same time schedule a runnable with postDelayed(). I don't believe, relying on times schedules is a good idea generally, even though we might know the exact value of the delay (with Animation.getDuration() in this case). It still can be unprecise.
  • A bit improvement is to use setAnimationListener to remove the item exactly at the end of the animation. Problem in my case is, the event of the removal of the item comes from the model (and view should not modify its data, that should be a copy of the model, or else synch issues may raise). So the onListItemClick is not involved in this process at all.
  • Implement a custom ListView. (but if for each problem I have to implement a custom class, what the Android framework is good for?)
  • My approach is to initiate an animation from the Adapter (that receives update messages from the model). Unfortunately it will have a small visual flaw: the other list items will jump to their new space without animation.
    1. The adapter inflates a new view (getView() is visible from there). Actually it is weird the adapter doesn't have reference to the ui element, it controls, so we can't get the view itself by default (however we could set this reference manually).
    2. places it to the same position where the removable item was,
    3. starts a custom animation
    4. and at the same time it can remove the item.
A fairly good post about interpolators is here, to show how each interpolator works.

The adapter starts the animation when an item is removed.
class MyAdapter extends ArrayAdapter<WhateverListItem> {
// ... removed some code for clarity

    /** Animation used when an item is removed from the adapter. Could be set by the constructor */
    private RemoveAnimation mRemoveAnimation;

     * Sets the remove animation. This could be done in the constructor, but in my project, this method was more appropriate
    public void setRemoveAnimation(RemoveAnimation removeAnimation) {
        mRemoveAnimation = removeAnimation;

    public void remove(SpendingRewardItem object) {
        final View view = mVisibleItems.get(object.mOfferId);
        if (view != null) { // we only want to play the animation for visible items. This mVisibleItems set is maintained by us
            final View removedView = getView(getPosition(object), null, null); // create a copy of the removed item
            if (mRemoveAnimation != null) mRemoveAnimation.animate(view, removedView); // play the animation

     * Tracks which item is visible currently. Used to test if a removed item should be animated.
    private WeakHashMap<String, View> mVisibleItems = new WeakHashMap<String, View>();

     * ListAdapter override.
     * @param position item position in the parents' inner list
     * @param convertView reused view if any
     * @param parent parent of the view
     * @return inflated view
    public View getView(int position, View convertView, ViewGroup parent) {
        final View view = super.getView(position, convertView, parent);

        // ... customize view, holder pattern, etc.

        mVisibleItems.put(getItem(position).mOfferId, view);
        return view;


The animation itself. I use the end of animation to initiate an other one (described in a previous post), I leave related lines here commented out. There are some secret additions. The animated item can go out of the original container. It plays multiple animations on the same item. It is important, that this translates only vertically. Horizontally the item will stay where it was (in my case it will stay in the middle).
public class RemoveAnimation {

//    private static final int GLOW_ANIM_DURATION = 500;
    /** This will be the root ViewGroup  for the animated item. With this, the view can "fly out" of the original ListView */
    private RelativeLayout mAnimationRoot;
    /** Used to acquire interpolators */
    private Context mContext;
//    private final View mTargetTab;
//    private TabGlow mGlow;

    /** In this implementation, animationRoot has to be RelativeLayout, because animate() uses its typed LayoutParams */
    public SpendingRewardsRemoveAnimation(RelativeLayout animationRoot, View targetTab) {
        mAnimationRoot = animationRoot;
        mContext = animationRoot.getContext();
//        mTargetTab = targetTab;
//        mGlow = new TabGlow();

    public void animate(View originalView, View copyView) {
        if (originalView != null && copyView != null) {

            final View animatingView;
            animatingView = copyView;
            // place the copyView to where the originalView was.
            final RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(originalView.getWidth(), originalView.getHeight());
            layoutParams.leftMargin = originalView.getLeft();
            layoutParams.topMargin = originalView.getTop() + getDistanceFromRoot(originalView);

// you need to set the background colour of the list items, or else they will have transparent background. Which is weird usually.
//            animatingView.setBackgroundColor(Color.argb(255, 14, 52, 128));
            mAnimationRoot.invalidate(); // ask the framework to redraw this view

            // create scale animations. You can play with these parameters. Check reference
            final ScaleAnimation scaleUp = new ScaleAnimation(1.0f, 1.1f, 1.0f, 1.1f, Animation.RELATIVE_TO_SELF,0.5f, Animation.RELATIVE_TO_SELF, 0f);
            final ScaleAnimation scaleDown = new ScaleAnimation(1.0f, 0.0f, 1.0f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0f);
            scaleUp.setInterpolator(mContext, android.R.anim.decelerate_interpolator);
            scaleDown.setInterpolator(mContext, android.R.anim.accelerate_interpolator);
            scaleDown.setStartOffset(50); // second animation starts, when firsts finishes

            // create translation animation
            final TranslateAnimation trans = new TranslateAnimation(
                    -1.0f * layoutParams.topMargin // move the item from the current position upwards, to 0 (finally topMargin + (-1 * topMargin) will be equal to 0
            trans.setInterpolator(mContext, android.R.anim.accelerate_interpolator);

            // add new animations to the set
            final AnimationSet animationSet = new AnimationSet(false);
            animationSet.addAnimation(scaleUp); // view first pops up
            animationSet.addAnimation(scaleDown); // a bit later shrinks to nothing
            animationSet.addAnimation(trans); // and flies to the top of 

            animationSet.setAnimationListener(new Animation.AnimationListener() {
                public void onAnimationStart(Animation animation) {

                public void onAnimationEnd(Animation animation) {
           Runnable() {
                        public void run() {
                            mAnimationRoot.removeView(animatingView); // we don't want to keep the animated View forever


//                    mGlow.glow(mTargetTab, GLOW_ANIM_DURATION);

                public void onAnimationRepeat(Animation animation) {


    /** This calculates the absolute position of the view. Which can differ from getTop() when the any parent container also has a vertical displacement. */
    private int getDistanceFromRoot(View originalView) {
        View current = originalView;
        ViewGroup parent = null;
        int ret = 0;

        do {
            if (current.getParent() instanceof ViewGroup) {
                parent = (ViewGroup)current.getParent();
                ret += parent.getTop();
                current = parent;
        } while (parent != null && parent != mAnimationRoot);
        return ret;

Wiring up is easy:
class MyFragment extends Fragment {

// removed some code for brevity

    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        final View inflated = inflater.inflate(R.layout.spending_rewards_hub, null);

        final ListView listView = (ListView) inflated.findViewById(;
        mAdapter = new MyAdapter(/* whatever you initialise with */);
        mAdapter.setRemoveAnimation(new SpendingRewardsRemoveAnimation((RelativeLayout)inflated, activeTab));

        // ... more UI initialisation as needed

Friday, March 8, 2013

Glow effect

The task to accomplish is to make a bit of colour animation on a button (or practically any view), when a defined event happens (lats say, when a "flying" list item flies to it). The animation should be a growing-then-declining glow effect.
So the background should be animated from the current state (which is #ff333333)
to change its color to #ff3355dd within 200ms
than change its color to #ff333333 again within 200ms.

Using Android Honeycomb this can easily done with property animation:
private static final int GLOW_ANIM_DURATION = 400;
    private void tabGlowAnimation(View targetTab) {
        final ObjectAnimator objAnim =
                                        "backgroundColor", // we want to modify the backgroundColor
                                        new ArgbEvaluator(), // this can be used to interpolate between two color values
                                        targetTab.getContext().getResources().getColor(R.color.tab_background), // start color defined in resources as #ff333333
                                        targetTab.getContext().getResources().getColor(R.color.tab_glow) // end color defined in resources as #ff3355dd
        objAnim.setDuration(GLOW_ANIM_DURATION / 2);
        objAnim.setRepeatMode(ValueAnimator.REVERSE); // start reverse animation after the "growing" phase

This is very convenient. However it still has 2 problems:
  • It doesn't work on preHC devices
  • the start color is predefined during coding time (which is not a problem in this case). It is not easy to determine the color of the background, because the background is a drawable and not a color at this point. And/or you needed to implement a custom Evaluator

Unfortunately on earlier devices it is - lets say - challenging to implement a same effect. My solution is an intermittent view layer between the foreground and background of the button. This view has a custom drawable, which alpha value is animated as required. Actually with this we can have any fancy glow (circular, tiger shaped, etc.). I used an idea found in

I've seen this post:, but since I tried to use it on a tabview, it messed up the layout.

public class GlowEffect implements Runnable {
    private static final int MAX_ALPHA = 255;
     * time interval to reschedule a redraw.
    private static final int MIN_TICK_MILLIS = 30;
     * The alpha of the drawable of this view will be adjusted for the glow effect
    private View mGlowLayer;

     * the time, when the animation started. (Or should have started if we restart the glow animation)
    private long mStartTimeMillis;
     * duration of the whole animation = glow increase + glow decrease
    private int mDuration;
     * inner representation of the required alpha value. Used when the animation restarts.
     * We have to store it, because Drawable.getAlpha() doesn't exist.
    private double mAlpha;
     * The time between each animation steps.
    private int mTickMillis;

    public GlowEffect() {

    public void glow(View glowLayer, int durationMillis) {
        mGlowLayer = glowLayer;
        // correct duration time if the animation has been restarted and the previous is still in progress
        // we continue the animation from that point
        final long suspectedTimeFromStart = Math.round(mAlpha * mDuration / 2);
        mDuration = durationMillis;

        mStartTimeMillis = SystemClock.uptimeMillis() - suspectedTimeFromStart;
        mTickMillis = Math.max(MIN_TICK_MILLIS, durationMillis / MAX_ALPHA);
        mGlowLayer.postDelayed(this, mTickMillis);

    public void run() {
        boolean done = true;
        final Drawable who = mGlowLayer.getBackground();

        float normalized = (float) (SystemClock.uptimeMillis() - mStartTimeMillis) / mDuration;
        normalized = Math.min(normalized, 1.0f);
        done = normalized >= 1.0f;

        // linear increase for half of the duration, than linear decrease
        //noinspection MagicNumber
        mAlpha = (0.5 - Math.abs(normalized - 0.5f)) * 2.0f;
        int alpha = (int)Math.round(mAlpha * MAX_ALPHA);

        alpha = Math.max(0, alpha);
        alpha = Math.min(MAX_ALPHA, alpha);


        if (!done) {
            mGlowLayer.postDelayed(this, mTickMillis);
        } else {


Whenever a glow is needed, the glow() should be called.