130 likes | 278 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
E N D
My own ViewAndroid 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 publicclassNumGridActivityextendsActivity { NumGridView mNumGridView; OnCellTouchListener mNumGridView_OnCellTouchListener= new OnCellTouchListener() { @Override public void onCellTouch( NumGridView v, int x, inty ) { 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 Resides in same package packagenl.fampennings.numgrid; import android.view.View; ... publicclass 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 ... } Extends basic View class Lazy: only implemented constructor for XML Setter and Getter for cell value “Mandatory”overrides Cell touch call-back
Constructor Call parent constructor publicNumGridView(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; } 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 Sort of assert(don’t know if it’s wise) publicvoid setCell(int x, int y, int v) { if( ! (0<=x && x<mCellCountX) ) thrownew IllegalArgumentException("setCell: x coordinate out of range"); if( ! (0<=y && y<mCellCountY) ) thrownew IllegalArgumentException("setCell: y coordinate out of range"); mCells[x][y]= v; invalidate(); } public int getCell(int x, int y ) { if( ! (0<=x && x<mCellCountX) ) thrownew IllegalArgumentException("setCell: x coordinate out of range"); if( ! (0<=y && y<mCellCountY) ) thrownew IllegalArgumentException("setCell: y coordinate out of range"); return mCells[x][y]; } Set the value Force a re-draw Get the value
onMeasure The renderer calls us with onMeasure to ask how big we want to be (sometimes passing max sizes). We must respond by calling setMeasuredDimension. @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 ); } Just a fragment; real code is harder
onDraw The renderer passes us the canvas to draw ourselves on @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); } } } 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 A class local interface definition for our listener publicinterface 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); } 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