Tuesday, September 27, 2011

Grid, Adaptor, left to right into top to bottom

Say we have list of object as follows,
list =
.size() = 14
and need to show it in grid control that scroll vertically as follows,
// assume we want three columns.
int columns = 3;
// first calculate number of rows.
int rows = (columns-1+list.size())/columns;

Or into grid control that scroll horizontally as follows,
//assume we want three rows.
int rows = 3;
// first calculate number of rows.
int columns = (rows-1+list.size())/rows;

Mostly we use adaptor to fetch the item to show in each cell.
In this case adaptor can choose the index equal to position, 

int index = position;
return list.get(index); 

But say some cases we may have to change to order in display. for example. top to bottom as shown below:
But if the control fetch base on position from left to right as show below:
then, we need to add proper calculation inside the adaptor:

int index =position/columns+position%columns*rows;
return list.get(index); 

Thursday, September 15, 2011

Android: Aligning Custom Views at Base Line

Have you ever try to align a non-symmetric custom view at a custom base line as follows?


I tried it using base line attribute of Relative Layout.
First adjust the size of view:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//Height should be always width * 7 / 12
setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth()*7/12);
}


Then Override the getBaseLine method:
@Override
public int getBaseline() {
//base line is from bottom to width/12 (radius of the blue circle)
return getMeasuredHeight()-getMeasuredWidth()/12;
}


Following onDraw method would explain the reason for the above calculation:

@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setAntiAlias(true);
//Draw the half circle
paint.setColor(Color.GRAY);
float width = getWidth();
float height = getHeight();
canvas.save();
canvas.translate(width/2, height-width/12);
canvas.drawArc(new RectF(-width/3, -width/3, +width/3, width/3), 0, -180, true, paint);
//Draw small circles
paint.setColor(Color.DKGRAY);
RectF rectF = new RectF(-width/2+width/24, -width/12+width/24, -width/3-width/24,                +width/12-width/24);
canvas.drawOval(rectF, paint);
for(int i = 0 ; i < 15 ;i++){
canvas.rotate(12f);
canvas.drawOval(rectF, paint);
}
//Draw blue circle
paint.setColor(Color.BLUE);
canvas.rotate(-180+30);
canvas.drawOval(new RectF(-width/2, -width/12, -width/3, +width/12), paint);
//Draw red arrow
paint.setColor(Color.RED);
Path path = new Path();
path.moveTo(-width*5/12, 0);
path.lineTo(10, -width/12);
path.lineTo(0, 0);
path.lineTo(10, +width/12);
path.close();
canvas.drawPath(path, paint);
canvas.restore();
}


Finally, create a layout xml as following:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<demo.ui.testing.DemoUITesting
android:layout_height="fill_parent" android:layout_width="fill_parent"
android:layout_alignBaseline="@+id/radioButton1"
android:layout_toRightOf="@+id/radioButton1">
<RadioButton android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/radioButton1"
android:layout_centerVertical="true">
</RelativeLayout>

Enjoy!.

Wednesday, September 14, 2011

Android: convert Immutable Bitmap into Mutable

Android provide Bitmap class to manipulate images. We can load, draw, edit or save. But incase of editing bitmap should be mutable.

For example:
Canvas canvas = new Canvas(mBitmap);
Will throw IllegalArgumentException: image is immuable ...

An image file can be loaded in to bitmap using BitmapFactory as follows:

mBitmap = BitmapFactory.decodeFile(path);

But this will be an immutable bitmap. This will not be able to edited.

Anyway can load image using BitmapFactory.decodeFile(path,options);
Here BitmapFactory.Options options = new BitmapFactory.Options();

But from API Level 11 only options.inMutable available to load the file into a mutable bitmap.

So, if we are building application with API level less than 11, then we have to find some other alternatives.

One alternative is creating another bitmap by copying the source bitmap.
mBitmap = mBitmap.copy(ARGB_8888 ,true);

But the will throw OutOfMemoryException if the source file is big. Actually incase if we want to edit an original file, then we will face this issue. We should be able to load at-least image into memory, but most we can not allocate another copy into memory.

So, we have to save the decoded bytes into some where and clear existing bitmap, then create a new mutable bitmap and load back the  saved bytes into bitmap again. Even to copy bytes we cannot create another ByteBuffer inside the memory. In that case need to use MappedByteBuffer that will allocate bytes inside a disk file.

Following code would explain clearly:

//this is the file going to use temporally to save the bytes. 

File file = new File("/mnt/sdcard/sample/temp.txt");
file.getParentFile().mkdirs();

//Open an RandomAccessFile
/*Make sure you have added uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
into AndroidManifest.xml file*/
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); 

// get the width and height of the source bitmap.
int width = bitmap.getWidth();
int height = bitmap.getHeight();

//Copy the byte to the file
//Assume source bitmap loaded using options.inPreferredConfig = Config.ARGB_8888;
FileChannel channel = randomAccessFile.getChannel();
MappedByteBuffer map = channel.map(MapMode.READ_WRITE, 0, width*height*4);
bitmap.copyPixelsToBuffer(map);
//recycle the source bitmap, this will be no longer used.
bitmap.recycle();
//Create a new bitmap to load the bitmap again.
bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
map.position(0);
//load it back from temporary 
bitmap.copyPixelsFromBuffer(map);
//close the temporary file and channel , then delete that also
channel.close();
randomAccessFile.close();

Hope this wil help you.

I have uploaded the sample source code here:
http://dl.dropbox.com/u/7717254/DemoPaint.zip
In which application, user can load a image and draw and save back. In case of saving back to original image size.