click tracking

http://is.gd/DeepDiveComp

Built-in Components

  • TextView
  • EditText
  • Button
  • Spinner
  • DatePicker

Why custom components?

  • Modularize repeated code
  • Access protected methods
  • Optimize rendering speed
  • Complete control with draw, measure and layout

Simple view, container and compound control

Simple View Container Compound Control
  ↳ TextView
    ↳ EditText
    ↳ Button
  ↳ ImageView
    ↳ ImageButton
  ↳ AdapterView
    ↳ ListView
    ↳ Gallery
    ↳ GridView
  ↳ LinearLayout
  ↳ RelativeLayout
  ↳ FrameLayout
  ↳ TableLayout
  ↳ DatePicker
  ↳ TwoLineListItem

Custom components

  • Shortcut View and ViewGroup
  • Custom View
  • Custom ViewGroup

Shortcut View and ViewGroup

Modularize repeated code

  • Subclass existing widget
  • Group existing widgets into compound control

TextView showing date

http://github.com/chiuki/android-date-view

<TextView
  android:id="@+id/date"
  android:layout_width="match_parent"
  android:layout_height="wrap_content" />
TextView dateView = (TextView) findViewById(R.id.date);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String today = dateFormat.format(Calendar.getInstance().getTime());
dateView.setText(today);

TextView showing date: screenshot

DateView: constructor

package com.sqisland.android.dateview;

public class DateView extends TextView {
  public DateView(Context context) {
    super(context);
  }

  public DateView(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  public DateView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
  }
}

DateView: setDate

public class DateView extends TextView {
  public DateView(Context context) {
    super(context);
    setDate();
  }

  public DateView(Context context, AttributeSet attrs) {
    super(context, attrs);
    setDate();
  }

  public DateView(
      Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    setDate();
  }
}

DateView: setDate

private void setDate() {
  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
  String today = dateFormat.format(Calendar.getInstance().getTime());
  setText(today);  // self = DateView = subclass of TextView
}

DateView: create with code

public class DateActivity extends Activity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    DateView dateView = new DateView(this);
    setContentView(dateView);
  }
}

DateView: create with xml

public class DateActivity extends Activity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
  }
}
<com.sqisland.android.dateview.DateView
  android:layout_width="match_parent"
  android:layout_height="wrap_content" />

LengthPicker

http://github.com/chiuki/android-length-picker

Fully customized components

  • onLayout
  • onMeasure
  • onDraw / dispatchDraw

Android layout procedure

  • requestLayout up to ViewRoot
  • measure all children
  • layout all children

Custom View

Customize size and appearance of the View

  • onMeasure - how big?
  • onDraw - what to show?

onMeasure

void onMeasure(int widthMeasureSpec, int heightMeasureSpec);
  • Measure its own size according to measure spec
  • Call setMeasuredDimension to store results

MeasureSpec

  • UNSPECIFIED - anything goes!
  • AT_MOST - as large as the specified size
  • EXACTLY - as given by parent

SquareView

http://github.com/chiuki/android-square-view

public void onMeasure(int widthSpec, int heightSpec) {
  super.onMeasure(widthSpec, heightSpec);
  int size = Math.min(getMeasuredWidth(), getMeasuredHeight());
  setMeasuredDimension(size, size);
}

onDraw

void onDraw(Canvas canvas);
  • drawLine
  • drawRect
  • drawCircle
  • drawPath
  • drawText
  • drawBitmap

Canvas

  • clipRect
  • translate
  • rotate
  • scale
  • skew
  • save
  • restore

Pizza

http://github.com/chiuki/android-pizza

CharacterArea

Custom ViewGroup

Container for positioning child views

  • onMeasure - how big are the children?
  • onLayout - where are the children?
  • dispatchDraw - adjustments before or after drawing the children

onLayout: PhotoSpiral

http://github.com/chiuki/android-photo-spiral

dispatchDraw: Monkey Write Bookshelf

Bookshelf building blocks


           

BookshelfGridView

public class BookshelfGridView extends GridView {
  // Constructors etc

  protected void dispatchDraw(Canvas canvas) {
    for (int y = top; y < height; y += mWoodPanelHeight) {
      for (int x = mLeftWidth; x < width; x += mWoodPanelWidth) {
        canvas.drawBitmap(mWoodPanelImage, x, y, null);
      }
    }

    for (int y = top; y < height; y += mShelfHeight) {
      // Draw left edge
      // Draw shelf
      // Draw right edge
    }

    super.dispatchDraw(canvas);
  }
}

Monkey Write CharacterArea

CharacterArea

  • SurfaceView
  • Always square
public void onDraw(Canvas canvas) {
  canvas.drawColor(mBackgroundColor);
  drawGuidelines(canvas);
  drawCharacter(canvas);
  drawAnimation(canvas);
  drawPenStrokes(canvas);
  drawStrokeNumbers(canvas);
}

onDraw

Stroke numbers



// CharacaterActivity.java
mCharacterArea = (CharacterArea) findViewById(R.id.sketch_pad);

mStrokeNumbersCheckBox = (CheckBox) findViewById(R.id.stroke_numbers_checkbox);
mStrokeNumbersCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
  public void onCheckedChanged(CompoundButton view, boolean isChecked) {
    mCharacterArea.toggleStrokeNumbers();
  }
});

toggleStrokeNumbers

// CharacterArea.java
public void toggleStrokeNumbers() {
  mShowStrokeNumbers = !mShowStrokeNumbers;
}

public void drawStrokeNumbers(Canvas canvas) {
  if (!mShowStrokeNumbers) {
    return;
  }
  // Proceed to draw stroke numbers
}

Stroke grade

Listener: CharacterArea.java

public void registerListener(Listener listener) {
  mListeners.add(listener);
}
public void unregisterListener(Listener listener) {
  mListeners.remove(listener);
}
private void notifyPenStrokeComplete(PenStroke penStroke) {
  for (Listener listener : mListeners) {
    listener.onPenStrokeComplete(penStroke);
  }
}

public interface Listener {
  public void onPenStrokeComplete(PenStroke penStroke);
}

Listener: CharacterActivity.java

public void onPenStrokeComplete(PenStroke penStroke) {
  boolean pass = (penStroke.grade == PenStroke.Grade.PASS);
  if (mMonkeyView != null) {
    mMonkeyView.setImageResource(pass ?
        R.drawable.happy_monkey : R.drawable.sad_monkey);
  }

  // Just a bit more, wrong stroke etc
  String feedback = getFeedback(penStroke.grade);  
  mFeedbackView.setText(feedback);
}

Pen stroke

onTouchEvent

  • ACTION_DOWN
    • stopAnimation()
    • new PenStroke()
  • ACTION_DOWN / ACTION_MOVE / ACTION_UP
    Add point to PenStroke
  • ACTION_UP
    • Grade stroke
      • Update feedback
      • PASS: Show next stroke
      • FAIL: startAnimation()

Animation: state machine with timer

Summary

  • Shortcut View and ViewGroup: Modularize repeated code
    • Subclass existing widget
    • Group existing widgets into compound control

  • Custom View
    • onMeasure: Customize size
    • onDraw: Customize appearance

  • Custom ViewGroup
    • onLayout: Position the children
    • dispatchDraw: Adjustment before/after drawing the children

Monkey Write

  • BookshelfGridView
    • onDispatchDraw
  • CharacterArea
    • onMeasure
    • onDraw
    • onTouchEvent
    • Custom listener

Thank you!