Lecture 6 - How GUI Works, Custom Views
< CS193A Android Programming
How GUI Works
- On screen see pixels representing a nesting of View objects
- View objects in ram, each associated with pixel regions on screen
- Each object draws itself (onDraw() method), and handles clicks and other events on it
- Events like clicks etc. map to java methods called on the relevant view object
- System coordinates the big picture
- "data model" is data for a view, e.g. an int or some text
- Data change pattern: change the model data, then need to re-draw the pixels
- Android method invalidate() on a view requests a re-draw of that view
- e.g. type the text Hello in your word processor. Hit the delete key .. what happens so you eventually see different pixels on screen without the 'o'?
- System keeps a queue of things to do, and one "ui thread" that does things off that queue
- invalidate() just adds a re-draw request to the queue
- When the queue gets to the re-draw request, it sends onDraw() to the needed views, compositing the pixels together for the screen (back to front)
- Therefore, invalidate() is asynchronous
- The queue is smart about duplicate removal -- i.e. redundant invalidate() requests
BasicView Example Code
- Constructor vs. onCreate()
- Set up Paint objects
- Here have mCount -- our data model is a single int
- onDraw() method -- key. Look at our data model, render out pixels on the given canvas object.
- onDraw() -- first look at what size you are (layout system as set position/size). Upper left is 0,0
- Never have some width constant assuming a size. Size is dynamic.
- e.g. here position text at 25% of whatever our height is
- Prefer float type for math (faster on ARM) -- write constants as 2.0f, otherwise you get the double type
invalidate() logic, UI Thread Queue
- increment method shows the correct pattern: change data model, then invalidate()
- Activity has button wired up to increment() method -- this way it works correctly
- Experiments:
- Comment out the invalidate() -- what happens to the pixels? to the java object?
- Button calls invalidate many times, what happens?
- Button calls increment twice, what happens?
- Button does something real slow inbetween increments, what happens? (an incorrect practice, to be fixed when we do threads)
- Note this code does not survive a kill cycle correctly -- mCount will come back as 0. How to fix?
package edu.stanford.nick;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View;
// Simple example of a view which draws custom pixels.
public class BasicView extends View {
// paint = color font etc. state for drawing
private Paint mLinePaint;
private Paint mTextPaint;
// our "data model" one number -- we draw this
private int mCount;
/** Constructor takes in context (activity).
Note that we do one-time setup here (there is no onCreate() for views).
*/
public BasicView(Context context) {
super(context);
// Set up a couple paint objects -- see onDraw().
mLinePaint = new Paint();
mLinePaint.setStyle(Paint.Style.STROKE);
mLinePaint.setARGB(200, 0, 255, 0);
mLinePaint.setAntiAlias(true);
mLinePaint.setStrokeWidth(10);
mTextPaint = new Paint();
mTextPaint.setTextSize(50);
mTextPaint.setColor(Color.RED);
}
/**
The key view method.
Sent to the view when it should draw itself to the given canvas.
Essentially maps data-model -> pixels
*/
public void onDraw(Canvas canvas) {
// See how big we are. 0,0 is our upper-left.
int width = getWidth();
int height = getHeight();
// Draw a line from upper left to lower right
canvas.drawLine(0, 0, width-1, height-1, mLinePaint);
// Draw a rect around our border
canvas.drawRect(0, 0, width-1, height-1, mLinePaint); // left top right bottom
// Draw the current count as large red text, 25% below our top edge.
canvas.drawText("Count:" + mCount, 30, height * 0.25f, mTextPaint);
}
/** Increments the count (and prompts a redraw). */
public void increment() {
mCount++;
invalidate();
}
/** Gets current count. */
public int getCount() {
return mCount;
}
/** Sets count and prompts a re-draw. */
public void setCount(int count) {
mCount = count;
invalidate();
}
}
---
Activity:
package edu.stanford.nick;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
// Activity for BasicView example
public class BasicViewActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
LinearLayout container = (LinearLayout) findViewById(R.id.container);
// Create BasicView (final so can refer to from anon inner class below)
final BasicView view = new BasicView(this);
view.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT));
// Add it at end of linear layout "container" in main.xml layout
container.addView(view);
Button button = (Button) findViewById(R.id.button1);
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
view.increment();
// This represents doing something slow for 500ms the UI thread --
// a bad practice, but shows how invalidate() works.
// try {
// Thread.sleep(3000);
// }
// catch (InterruptedException e) {}
//
// // Then increment again.
// view.increment();
}
});
}
}
Got this far. Used to have MonkeyView here, but it was improved and extended and presented in the next lecture.