Category: Uncategorized


When an ImageView scales its source image to fit inside its bounds, by default it scales by width and keeps aspect ratio. Great, but it doesn’t reduce the height of the ImageView to compensate, so you end up with large blank areas above/below the image to compensate. Two things need to be done to fix this:

  1. add android:anyDensity=”true” to your manifest (you should be pre-scaling these images based on dip anyway)
  2. Log your bitmap.getHeight()/Width() and ImageView.getMeasuredHeight()/Width() and adjust your scaling so that it’s not getting scaled at all – that way, the height shouldn’t push out the imageview.
Advertisements

ListViews are, generally, pretty boring. If we’re displaying some kind of image-based data, we can bind ImageViews into them – and to make them slightly prettier, round off the top/bottom edges of the list. This works especially well with subheaded-lists.

To do this, I grabbed some code from http://www.ruibm.com/?p=184 that rounds all edges of a bitmap and modified it.

public Bitmap getRoundedCornerBitmap(Bitmap bitmap) 
{
	final int color = 0xff424242;
	final Paint paint = new Paint();
	final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
	Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888);
	Canvas canvas = new Canvas(output);

	// the float array passed to this function defines the x/y values of the corners
	// it starts top-left and works clockwise
	// so top-left-x, top-left-y, top-right-x etc
	// a curve of 10.0f will typically work pretty well for a larger image
	RoundRectShape rrs = new RoundRectShape(
			new float[] {0, 0, 0, 0, 0, 0, 0, 0}, 
						null, null);
	
	canvas.drawARGB(0, 0, 0, 0);
	paint.setAntiAlias(true);
	paint.setColor(color);
	rrs.resize(bitmap.getWidth(), bitmap.getHeight());
	rrs.draw(canvas, paint);
	
	paint.setXfermode(new PorterDuffXfermode(android.graphics.PorterDuff.Mode.SRC_IN));
	canvas.drawBitmap(bitmap, rect, rect, paint);
	return output;
}

With this, we can do something like the below.

private List<something> mData;

public SomeAdapter(...)
{
	super(...);
	mData = data;
}

@Override
public View getView(int position, View convertView, ViewGroup parent)
{
	ViewHolder holder;
	
	if (convertView == null)
	{
		// ...
	}
	else
	{
		// ...
	}
	
	// get Bitmap b from a file or somewhere (it's our image for this list item)
	
	// i modified the function again to be 0 = top edges rounded
	if (position == 0)
		b = getRoundedCornerBitmap(b, 0);

	// no else, our item could be the only one in its list and hence may need rounding on both edges

	if (position == mData.size() - 1)
		b = getRoundedCornerBitmap(b, 1); // and 1 is bottom

The official android documentation suggests using getContext().getResources().getDisplayMetrics().density; to get the density of the device in order to scale images, etc, to a size appropriate to the device being used. This doesn’t seem to work, returning a value of 1.0f on every emulator (and my desire) across all versions. Alas, there’s another place to actually find it.

DisplayMetrics dm = new DisplayMetrics();
mActivity.getWindowManager().getDefaultDisplay().getMetrics(dm);

int pixelHeight = (int) (dipHeight * dm.density);

So what do we do when we have an adapter being bound to a ListView but we want to change something with a field prior to display? We use a TransformationMethod. In this case, our adapter is ActiveAndroid’s EntityAdapter.

P.S. I’ve just created a new view for simplicity’s sake, but you should always be re-using views. See Romain Guy’s Google IO presentation for details.

public class CustomViewAdapter extends EntityAdapter
{
	public CustomViewAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)
	{
		super(context, data, resource, from, to);
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent)
	{
		View v = super.getView(position, convertView, parent);
		TextView date = (TextView) v.findViewById(R.id.date);
		date.setTransformationMethod(new DateTransformationMethod());

		return v;
	}

	private class DateTransformationMethod implements TransformationMethod
	{
		@Override
		public CharSequence getTransformation(CharSequence text, View view)
		{
			String result = "";

			if (!text.toString().equals(new SimpleDateFormat("yyyy/MM/dd").format(new Date(0))))
			{
				try
				{
					result = DateUtils.getRelativeTimeSpanString(
						new SimpleDateFormat("yyyy/MM/dd").parse(text.toString()).getTime(),
						new Date().getTime(),
						DateUtils.DAY_IN_MILLIS,
						DateUtils.FORMAT_ABBREV_RELATIVE)
					.toString();
				}
				catch (Exception e)
				{
					e.printStackTrace();
				}
			}

			// the output isn't quite what we're after, so let's modify it slightly
			if (result.equals(""))
			{
				result = "N/A";
			}
			else if (result.equals("0 days ago"))
			{
				result = "Today";
			}
			else if (result.equals("yesterday"))
			{
				result = "Yesterday";
			}

			return result;
		}

		@Override
		public void onFocusChanged(View arg0, CharSequence arg1, boolean arg2, int arg3, Rect arg4)
		{
			// unimplemented
			// worth noting that TransformationMethods are usually used for password fields etc,
			// so on loss of focus you'd blur the last remaining letter, for example
			// this is the function you'd use to trigger it
		}

	}
}

ActiveAndroid, an ORM package from Michael Pardo, is a great addition to your Android toolset. One of the things I missed dearly from web development was a decent ORM package, and this fills the gap pretty nicely – blending easy DB interaction with helpful adapters to bind querysets to ListViews. There are some problems, however (deriving from a particular AOSP ticket).

Essentially, using a Date field in an AA model will result in storage and recall as a Date, which is fine. Using an EntityAdapter to bind it to a ListView, however, will result in Date.toString() being called – as you’d expect. We don’t always want date output as “Mon Jun 21 08:00:00 Australia/Perth 2010” though. Especially in a ListView, where that’s defined as too damned long. We can’t easily change EntityAdapter (being a closed proprietary package) to do something other than call toString() though, so what are our options?

  1. Store the date as a Date and parse the field as necessary
  2. Attempt to mangle an extended TextView class with an onDraw() call
  3. Store the date as something other than Date (like a String) and parse it in/out as necessary
  4. Bind the TextView showing our date with a TransformationMethod

Some more unappealing than others, I tried all 4.

1. Store the date as a Date and parse the field as necessary

There’s a key problem with this solution – Android 2.x’s implementation of Date.toString() doesn’t match any accepted standard – the TimeZone IDs that are outputted with the rest of the date are unparseable, meaning that we either have to strip out the ID with replace() or simply ditch the idea altogether. Of course, the upside of storing it as a date meant that ActiveAndroid could easily sort it for return to our list.

2. Attempt to mangle an extended TextView class with an onDraw() call

By supplying a custom view class, we can extend the usefulness of particular View widgets and make them do different things. So if we know we’re being passed a String of Date format EEE MMM dd hh:mm:ss zzz yyyy, we can attempt 1) and strip the zone out, then parse the result and reformat it into something appropriate.

The problem with this approach was that onDraw() tended to simply write every DateView in the list with the same date, which is unacceptable.

3. Store the date as something other than Date (like a String) and parse it in/out as necessary

In this particular case, we didn’t really care about timezones (an approximate date (no time) was good enough). As such, we could simply store the date as a yyyy/MM/dd string. Unlike the MM/dd/yyyy string we were taking as input, the former can be sorted using an alphabetic sort algorithm – so it doesn’t matter that it’s not stored in the DB as a Date. While I was initially using Joda to parse dates, further research into android-DateUtils and SimpleDateFormat did the job. For example, to get a time period between dates we can just use:

String period = DateUtils.getRelativeTimeSpanString(new SimpleDateFormat("yyyy/MM/dd").parse(input.toString()).getTime(), new Date().getTime(), DateUtils.DAY_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString();

In this, we take a yyyy/MM/dd datestring and get back something like “1 day ago” or “in 4 days”, or for exceptionally far-away periods, something like “September 1”. Bonus: cutting Joda (while it’s a terrific library) saved 300kb of install size + memory, and considering the non-Joda version is only ~60k, that’s a significant decrease.

4. Bind the TextView showing our date with a TransformationMethod

I haven’t seen anything about TransformationMethods anywhere else, so I’ll do that in more detail on another post.

TV Start v0.4

The first (public) release of TV Start is on the market now.

Designed to easily allow you to search for and keep updated on various TV show release dates, TV Start is the first application released by Murodese since shifting to the Android platform. Please post any feature requests or bug reports, though there are already several feature updates in mind.

TV Start Barcode

TV Start Barcode

While I write something~