My own View

crookpatedhatMobile - Wireless

Dec 10, 2013 (3 years and 11 months ago)

122 views

My own View

Android development

Maarten
Pennings

2011
oct

14

Views


User Interface elements are known as Views


Button


TextView






I have written my own view


NumGridView


That can be used from XML layout in activity

NumGridView


My app contains a 5x5 grid with numbers


This is my
NumGridView

drawing itself


When you press a grid cell it is incremented


This is activity specific (coded in a listener)

Activity code is simple

public

class

NumGridActivity

extends

Activity {




NumGridView
mNumGridView;




OnCellTouchListener mNumGridView_OnCellTouchListener=
new

OnCellTouchListener() {



@Override public

void
onCellTouch
(
NumGridView

v,
int

x,
int

y )
{



v.setCell(x, y, v.getCell(x,y)+1 );



}


};




@Override public

void
onCreate
(Bundle
savedInstanceState
) {



super.onCreate(savedInstanceState);



setContentView(R.layout.
main);





mNumGridView= (NumGridView)findViewById(R.id.
numgridview);



mNumGridView.setOnCellTouchListener(mNumGridView_OnCellTouchListener);


}


}

Activity layout has minor twists

<?xml version="1.0" encoding="utf
-
8"?>

<
LinearLayout


xmlns:android="http://schemas.android.com/apk/res/android"


xmlns:numgrid="http://schemas.android.com/apk/res/nl.fampennings.numgrid"


android:orientation="vertical"


android:layout_width="fill_parent"


android:layout_height="fill_parent"


>


<
TextView



android:layout_width="fill_parent"


android:layout_height="wrap_content"


android:text="@string/hint"


android:textSize="10mm"


/>


<
nl.fampennings.numgrid.NumGridView



android:id="@+id/numgridview"


android:layout_width="fill_parent"


android:layout_height="fill_parent"


numgrid:cellCountX="5"


numgrid:cellCountY="5"


/>

</
LinearLayout
>


Required: file attrs.xml

<?xml version="1.0" encoding="utf
-
8"?>

<
resources
>


<
declare
-
styleable

name="NumGridView">


<
attr

name="stretch" format="
boolean
" />


<
attr

name="cellCountX" format="integer" min="1" />


<
attr

name="
cellCountY
" format="integer" min="1" />


<
/declare
-
styleable
>

<
/resources
>


Required: file NumGridView.java

package

nl.fampennings.numgrid;


import

android.view.View;

...


public

class

NumGridView
extends

View {



//
Member variables


...




public

NumGridView (Context context, AttributeSet attrs) {...}


public

void setCell (int x, int y, int v) {...}


public

int getCell (int x, int y ) {...}




@Override protected
void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {...}


@Override protected
void onDraw (Canvas canvas) {...}




//
OnCellTouchListener


...

}


Resides in
same package

Extends basic
View class

Lazy: only implemented
constructor for XML

Setter and Getter
for cell value

“Mandatory”

overrides

Cell touch

call
-
back

Constructor


public

NumGridView(Context context, AttributeSet attrs) {


super(context, attrs);



...



//
Get the xml attributes


TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NumGridView);


mStretch = a.getBoolean(R.styleable.NumGridView_stretch, false);


mCellCountX = a.getInt(R.styleable.NumGridView_cellCountX, 8);


mCellCountY = a.getInt(R.styleable.NumGridView_cellCountY, 8);


a.recycle();




//
Setup the grid cells


mCells=
new

int[mCellCountX][mCellCountY];


for
(int y=0; y<mCellCountY; y++)
for
(int x=0; x<mCellCountX; x++) mCells[x][y]= 0;

}

Call parent
constructor

Retrieve styled
attribute information

Lookup the attributes

(pass default in case
attribute is not set)

“free”!

Create the actual grid data
array, and fill with zero’s

Setter and Getter

public

void setCell(int x, int y, int v) {


if
( ! (0<=x && x<mCellCountX) )



throw

new

IllegalArgumentException("setCell: x coordinate out of range");


if
( ! (0<=y && y<mCellCountY) )



throw

new

IllegalArgumentException("setCell: y coordinate out of range");


mCells[x][y]= v;


invalidate();

}


public

int getCell(int x, int y ) {


if
( ! (0<=x && x<mCellCountX) )



throw

new

IllegalArgumentException("setCell: x coordinate out of range");


if
( ! (0<=y && y<mCellCountY) )



throw

new

IllegalArgumentException("setCell: y coordinate out of range");


return

mCells[x][y];

}


Sort of assert

(don’t know if it’s wise)

Set the value

Get the value

Force a re
-
draw

o
nMeasure

@Override protected
void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {


//
Extract the Ms (MesaureSpec) parameters


int widthMsMode = MeasureSpec.getMode(widthMeasureSpec);


int widthMsSize = MeasureSpec.getSize(widthMeasureSpec);


int heightMsMode = MeasureSpec.getMode(heightMeasureSpec);


int heightMsSize = MeasureSpec.getSize(heightMeasureSpec);




//
Determine preferred size


mCellWidth = (int)Math.floor(widthMsSize / mCellCountX );


mCellHeight= (int)Math.floor(heightMsSize / mCellCountY );


mOffsetX
= ( widthMsSize

-

mCellWidth*mCellCountX )
/ 2;



mOffsetY= ( heightMsSize
-

mCellHeight*mCellCountY ) / 2
;



//
Must declare my size


setMeasuredDimension( mOffsetX + mCellCountX*mCellWidth + mOffsetX,



mOffsetY + mCellCountY*mCellHeight + mOffsetY );

}


The renderer calls us with
onMeasure

to
ask how big we want to be (sometimes
passing max sizes). We must respond by
calling
setMeasuredDimension
.

Just a fragment;
real code is harder

onDraw

@Override protected
void onDraw(Canvas canvas) {


super.onDraw(canvas);


...




// Draw all cells


for
(int y=0; y<mCellCountY; y++) {


for
(int x=0; x<mCellCountX; x++) {



//
Get cell data


int v = mCells[x][y];


int dx= x*mCellWidth+mOffsetX;


int dy= y*mCellHeight+mOffsetY;


//
Draw a rectangle in the cell


canvas.drawRect( new Rect(dx+1,dy+1,dx+mCellWidth
-
2,dy+mCellHeight
-
2), mPaintBg);


//
Draw value


canvas.drawText( ""+v, dx+cx, dy+cy, mPaintFg);


}


}

}


The renderer passes us the
canvas to draw ourselves on

We loop over all cells, …


… draw a box on the canvas, …

… and draw the cell value
centered in the box

The ‘paint’ (created in
onCreate
) is our
pencil (color, font style, typeface, …)

onCellTouchListener

public

interface

OnCellTouchListener {


void onCellTouch(NumGridView v, int x, int y);

}


protected

OnCellTouchListener mOnCellTouchListener;


public

void setOnCellTouchListener(OnCellTouchListener listener) {


mOnCellTouchListener = listener;

}


@Override public

boolean dispatchTouchEvent(MotionEvent event) {


//
First dispatch calls to our cell touch listener...


if
( mOnCellTouchListener!=null ) {


int x=(int)(event.getX())
-

mOffsetX;


int y=(int)(event.getY())
-

mOffsetY;


if
( 0<=x && x<mCellWidth*mCellCountX && 0<=y && y<mCellHeight*mCellCountY ) {


//
Touch was on cell (not on padding area)


mOnCellTouchListener.onCellTouch(this,x/mCellWidth,y/mCellHeight);


}


}


//
... next dispatch calls from the super class


return

super.dispatchTouchEvent(event);

}




A class local interface
definition for our listener

One function: touch on
x,y

The member variable to
store the listener

The Setter for the listener

The
NumGridView

does not

register itself as listener
, it
overrides the dispatcher
. This
allows our customers to
register an event listener for
raw coordinates.

map raw (
x,y
)

to cell (
x,y
)

Call listener (if set, if touch was on cell)

Let super class do rest
of dispatching

THE END