Category Archives: UI

Android Layout Finder online tool

Hi guys, hope you had a great new years!

If you’ve ever developed an Android app, you’ve probably used the findViewById() method for finding views and findFragmentById() for finding fragments from your inflated layout. This has to be done with every View and Fragment that you want to access from your code.

After getting tired of writing the same code over and over again, I created a tool to generate that code for me from an arbitrary XML layout:

Behold: The Android Layout Finder!

You can try it out on a sample XML layout by pressing the ‘Paste example XML’ button in the right side of the screen.

Android Layout Finder is open source and licensed under the GNU General Public License. The code can be found on GitHub: http://www.github.com/jesperborgstrup/android-layout-finder. Enjoy!

Easy measuring of custom Views with specific aspect ratio

Introduction

A lot of the custom Views I have made all have to respect some aspect ratio. In order to make the measuring of these Views easier (and more modular, to avoid that horrible copy-pasting of code), I have created a helper class that will help you measure your custom Views that respect an aspect ratio.

The solution

Behold: The ViewAspectRatioMeasurer! (Available for download in the GitHub repository: ViewAspectRatioMeasurer.java. You can also find the code in the bottom of this post)

How to use

Simply instantiate the ViewAspectRatioMeasurer when your custom View is constructed, and run the measure() method in your onMeasure() method:

public class MyView extends View {

	public MyView(Context context) {
		super(context);
	}
	
	// The aspect ratio to be respected by the measurer
	private static final double VIEW_ASPECT_RATIO = 2.5;
	
	private ViewAspectRatioMeasurer varm = new ViewAspectRatioMeasurer( VIEW_ASPECT_RATIO );

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		varm.measure(widthMeasureSpec, heightMeasureSpec);
		setMeasuredDimension( varm.getMeasuredWidth(), varm.getMeasuredHeight() );
	}
	
}

How it works

When measuring, there are four possible options in regards to whether the height and width are fixed or dynamic. Dynamic being using "wrap_content" as size, and fixed is "match_parent" or an explicit dimension (e.g. "100dp"). These four options are:

Both width and height fixed: Fixed (Aspect ratio isn’t respected)
Width dynamic, height fixed: Set width depending on height
Width fixed, height dynamic: Set height depending on width
Both width and height dynamic: Largest size possible

All of these options (except the first) respect the provided aspect ratio.

The code

For the lazy, I have included the source code below:

/**
 * This class is a helper to measure views that require a specific aspect ratio.<br />
 * <br />
 * The measurement calculation is differing depending on whether the height and width
 * are fixed (match_parent or a dimension) or not (wrap_content)
 * 
 * <pre>
 *                | Width fixed | Width dynamic |
 * ---------------+-------------+---------------|
 * Height fixed   |      1      |       2       |
 * ---------------+-------------+---------------|
 * Height dynamic |      3      |       4       |
 * </pre>
 * Everything is measured according to a specific aspect ratio.<br />
 * <br />
 * <ul>
 * <li>1: Both width and height fixed:   Fixed (Aspect ratio isn't respected)</li>
 * <li>2: Width dynamic, height fixed:   Set width depending on height</li>
 * <li>3: Width fixed, height dynamic:   Set height depending on width</li>
 * <li>4: Both width and height dynamic: Largest size possible</li>
 * </ul>
 * 
 * @author Jesper Borgstrup
 */
public class ViewAspectRatioMeasurer {
	
	private double aspectRatio;
	
	/**
	 * Create a ViewAspectRatioMeasurer instance.<br/>
	 * <br/>
	 * Note: Don't construct a new instance everytime your <tt>View.onMeasure()</tt> method
	 * is called.<br />
	 * Instead, create one instance when your <tt>View</tt> is constructed, and
	 * use this instance's <tt>measure()</tt> methods in the <tt>onMeasure()</tt> method.
	 * @param aspectRatio
	 */
	public ViewAspectRatioMeasurer(double aspectRatio) {
		this.aspectRatio = aspectRatio; 
	}
	
	/**
	 * Measure with the aspect ratio given at construction.<br />
	 * <br />
	 * After measuring, get the width and height with the {@link #getMeasuredWidth()}
	 * and {@link #getMeasuredHeight()} methods, respectively.
	 * @param widthMeasureSpec The width <tt>MeasureSpec</tt> passed in your <tt>View.onMeasure()</tt> method
	 * @param heightMeasureSpec The height <tt>MeasureSpec</tt> passed in your <tt>View.onMeasure()</tt> method
	 */
	public void measure(int widthMeasureSpec, int heightMeasureSpec) {
		measure(widthMeasureSpec, heightMeasureSpec, this.aspectRatio);
	}

	/**
	 * Measure with a specific aspect ratio<br />
	 * <br />
	 * After measuring, get the width and height with the {@link #getMeasuredWidth()}
	 * and {@link #getMeasuredHeight()} methods, respectively.
	 * @param widthMeasureSpec The width <tt>MeasureSpec</tt> passed in your <tt>View.onMeasure()</tt> method
	 * @param heightMeasureSpec The height <tt>MeasureSpec</tt> passed in your <tt>View.onMeasure()</tt> method
	 * @param aspectRatio The aspect ratio to calculate measurements in respect to 
	 */
	public void measure(int widthMeasureSpec, int heightMeasureSpec, double aspectRatio) {
		int widthMode = MeasureSpec.getMode( widthMeasureSpec );
		int widthSize = widthMode == MeasureSpec.UNSPECIFIED ? Integer.MAX_VALUE : MeasureSpec.getSize( widthMeasureSpec );
		int heightMode = MeasureSpec.getMode( heightMeasureSpec );
		int heightSize = heightMode == MeasureSpec.UNSPECIFIED ? Integer.MAX_VALUE : MeasureSpec.getSize( heightMeasureSpec );
		
		if ( heightMode == MeasureSpec.EXACTLY && widthMode == MeasureSpec.EXACTLY ) {
			/* 
			 * Possibility 1: Both width and height fixed
			 */
			measuredWidth = widthSize;
			measuredHeight = heightSize;
			
		} else if ( heightMode == MeasureSpec.EXACTLY ) {
			/*
			 * Possibility 2: Width dynamic, height fixed
			 */
			measuredWidth = (int) Math.min( widthSize, heightSize * aspectRatio );
			measuredHeight = (int) (measuredWidth / aspectRatio);
			
		} else if ( widthMode == MeasureSpec.EXACTLY ) {
			/*
			 * Possibility 3: Width fixed, height dynamic
			 */
			measuredHeight = (int) Math.min( heightSize, widthSize / aspectRatio );
			measuredWidth = (int) (measuredHeight * aspectRatio);
			
		} else {
			/* 
			 * Possibility 4: Both width and height dynamic
			 */
			if ( widthSize > heightSize * aspectRatio ) {
				measuredHeight = heightSize;
				measuredWidth = (int)( measuredHeight * aspectRatio );
			} else {
				measuredWidth = widthSize;
				measuredHeight = (int) (measuredWidth / aspectRatio);
			}
			
		}
	}
	
	private Integer measuredWidth = null;
	/**
	 * Get the width measured in the latest call to <tt>measure()</tt>.
	 */
	public int getMeasuredWidth() {
		if ( measuredWidth == null ) {
			throw new IllegalStateException( "You need to run measure() before trying to get measured dimensions" );
		}
		return measuredWidth;
	}

	private Integer measuredHeight = null;
	/**
	 * Get the height measured in the latest call to <tt>measure()</tt>.
	 */
	public int getMeasuredHeight() {
		if ( measuredHeight == null ) {
			throw new IllegalStateException( "You need to run measure() before trying to get measured dimensions" );
		}
		return measuredHeight;
	}
	
}

HSV Color Picker Dialog

Introduction

After being unable to find any really good color picker dialogs for Android, I decided to make my own, and this is how it turned out:

The HSVColorPickerDialog

I made the dialog a two-part color picker based on the HSV color model: First you select the hue and saturation parts in a circle, and then finally select the value in a gradient below as seen in the screenshots below. The dialog is available for download in the GitHub repository: HSVColorPickerDialog.java

How to use it

The HSVColorPickerDialog is based on the AlertDialog, and it is really easy to use:

public HSVColorPickerDialog(Context context, int initialColor, final OnColorSelectedListener listener);

public interface OnColorSelectedListener {
    /**
     * @param color The color code selected, or null if no color. No color is only
     * possible if {@link HSVColorPickerDialog#setNoColorButton(int) setNoColorButton()}
     * has been called on the dialog before showing it
     */
    public void colorSelected( Integer color );
}

You call the constructor with a Context, a color that is to be selected as default, and a OnColorSelectedListener that is invoked when the
user has selected a color and pressed OK:

Example

HSVColorPickerDialog cpd = new HSVColorPickerDialog( MainActivity.this, 0xFF4488CC, new OnColorSelectedListener() {
    @Override
    public void colorSelected(Integer color) {
        // Do something with the selected color
    }
});
cpd.setTitle( "Pick a color" );
cpd.show();

Notice how the callback’s color parameter is an Integer and not an int? That is because the dialog has the option of letting the user select no color, in which case the Integer is null. To allow the user to do this, simply call the following method on the dialog before showing it:

cpd.setNoColorButton( R.string.no_color );

This makes the dialog add a third button in the bottom like this:

Tab swipe between fragments using ActionBarSherlock

Introduction

First post in my new Android blog, so I thought I should start out with something lightweight, yet pretty cool: Swiping between action bar tabs using ActionBarSherlock.

The Android Developer tools include a wizard to make this for the native action bar, but this requires your project to break backwards compatibility for devices that aren’t updated to API 14 (Ice Cream Sandwich).

This easily extendable snippet of code allows you to implement swiping between tabs on all devices in any project that uses ActionBarSherlock.

The custom activity to inherit from

Instead of letting your activity inherit from SherlockActivity or any of the likes, now let your activity inherit from the following TabSwipeActivity:

TabSwipeActivity.java (on GitHub):

import java.util.ArrayList;
import java.util.List;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager;

import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.ActionBar.Tab;
import com.actionbarsherlock.app.ActionBar.TabListener;
import com.actionbarsherlock.app.SherlockFragmentActivity;

public abstract class TabSwipeActivity extends SherlockFragmentActivity {

    private ViewPager mViewPager;
    private TabsAdapter adapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
    	/*
    	 * Create the ViewPager and our custom adapter
    	 */
        mViewPager = new ViewPager(this);
        adapter = new TabsAdapter( this, mViewPager );
        mViewPager.setAdapter( adapter );
        mViewPager.setOnPageChangeListener( adapter );

        /*
         * We need to provide an ID for the ViewPager, otherwise we will get an exception like:
         *
         * java.lang.IllegalArgumentException: No view found for id 0xffffffff for fragment TestFragment{40de5b90 #0 id=0xffffffff android:switcher:-1:0}
		 * at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:864)
		 *
		 * The ID 0x7F04FFF0 is large enough to probably never be used for anything else
         */
        mViewPager.setId( 0x7F04FFF0 );

        super.onCreate(savedInstanceState);

        /*
         * Set the ViewPager as the content view
         */
        setContentView(mViewPager);
    }

    /**
     * Add a tab with a backing Fragment to the action bar
     * @param titleRes A string resource pointing to the title for the tab
     * @param fragmentClass The class of the Fragment to instantiate for this tab
     * @param args An optional Bundle to pass along to the Fragment (may be null)
     */
    protected void addTab(int titleRes, Class fragmentClass, Bundle args ) {
        adapter.addTab( getString( titleRes ), fragmentClass, args );
    }
    /**
     * Add a tab with a backing Fragment to the action bar
     * @param titleRes A string to be used as the title for the tab
     * @param fragmentClass The class of the Fragment to instantiate for this tab
     * @param args An optional Bundle to pass along to the Fragment (may be null)
     */
    protected void addTab(CharSequence title, Class fragmentClass, Bundle args ) {
        adapter.addTab( title, fragmentClass, args );
    }

    private static class TabsAdapter extends FragmentPagerAdapter implements TabListener, ViewPager.OnPageChangeListener {

        private final SherlockFragmentActivity mActivity;
        private final ActionBar mActionBar;
        private final ViewPager mPager;

        /**
         * @param fm
         * @param fragments
         */
        public TabsAdapter(SherlockFragmentActivity activity, ViewPager pager) {
            super(activity.getSupportFragmentManager());
            this.mActivity = activity;
            this.mActionBar = activity.getSupportActionBar();
            this.mPager = pager;

            mActionBar.setNavigationMode( ActionBar.NAVIGATION_MODE_TABS );
        }

        private static class TabInfo {
            public final Class fragmentClass;
            public final Bundle args;
            public TabInfo(Class fragmentClass,
                    Bundle args) {
                this.fragmentClass = fragmentClass;
                this.args = args;
            }
        }

        private List mTabs = new ArrayList();

        public void addTab( CharSequence title, Class fragmentClass, Bundle args ) {
            final TabInfo tabInfo = new TabInfo( fragmentClass, args );

            Tab tab = mActionBar.newTab();
            tab.setText( title );
            tab.setTabListener( this );
            tab.setTag( tabInfo );

            mTabs.add( tabInfo );

            mActionBar.addTab( tab );
            notifyDataSetChanged();
        }

        @Override
        public Fragment getItem(int position) {
            final TabInfo tabInfo = mTabs.get( position );
            return (Fragment) Fragment.instantiate( mActivity, tabInfo.fragmentClass.getName(), tabInfo.args );
        }

        @Override
        public int getCount() {
            return mTabs.size();
        }

        public void onPageScrollStateChanged(int arg0) {
        }

        public void onPageScrolled(int arg0, float arg1, int arg2) {
        }

        public void onPageSelected(int position) {
            mActionBar.setSelectedNavigationItem( position );
        }

        public void onTabSelected(Tab tab, FragmentTransaction ft) {
            TabInfo tabInfo = (TabInfo) tab.getTag();
            for ( int i = 0; i < mTabs.size(); i++ ) {
                if ( mTabs.get( i ) == tabInfo ) {
                    mPager.setCurrentItem( i );
                }
            }
        }

        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        }

        public void onTabReselected(Tab tab, FragmentTransaction ft) {
        }
    }
}

Using TabSwipeActivity

The only extra method(s) that TabSwipeActivity supports are the addTab() methods:

void addTab(int titleRes, Class fragmentClass, Bundle args );
void addTab(CharSequence title, Class fragmentClass, Bundle args );

Each of these will add a tab and assign a fragment to that tab. Only difference between the two is that the first accepts a string resource ID for the tab title, and the second accepts any CharSequence.

Example usage

TabSwipeTestActivity.java:

import android.os.Bundle;

public class TabSwipeTestActivity extends TabSwipeActivity {

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

		addTab( "Tab 1", TestFragment.class, TestFragment.createBundle( "Fragment 1") );
		addTab( "Tab 2", TestFragment.class, TestFragment.createBundle( "Fragment 2") );
		addTab( "Tab 3", TestFragment.class, TestFragment.createBundle( "Fragment 3") );
	}

}

Note that your activity shouldn’t call setContentView(). TabSwipeActivity does that for you!

TestFragment.java:

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class TestFragment extends Fragment {

	public static final String EXTRA_TITLE = "title";

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		TextView txt = new TextView( inflater.getContext() );
		txt.setGravity( Gravity.CENTER );
		txt.setText( "Fragment" );

		if ( getArguments() != null && getArguments().containsKey( EXTRA_TITLE ) ) {
			txt.setText( getArguments().getString( EXTRA_TITLE ) );
		}
		return txt;
	}

	public static Bundle createBundle( String title ) {
		Bundle bundle = new Bundle();
		bundle.putString( EXTRA_TITLE, title );
		return bundle;
	}

}