blob: ba48da0c6e3008dbe766ffec48f4c86a4740d160 [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.apis.graphics;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
//Need the following import to get access to the app resources, since this
//class is in a sub-package.
/**
* Demonstrates the handling of touch screen and trackball events to
* implement a simple painting app.
*/
public class TouchPaint extends GraphicsActivity {
/** Used as a pulse to gradually fade the contents of the window. */
private static final int FADE_MSG = 1;
/** Menu ID for the command to clear the window. */
private static final int CLEAR_ID = Menu.FIRST;
/** Menu ID for the command to toggle fading. */
private static final int FADE_ID = Menu.FIRST+1;
/** How often to fade the contents of the window (in ms). */
private static final int FADE_DELAY = 100;
/** The view responsible for drawing the window. */
MyView mView;
/** Is fading mode enabled? */
boolean mFading;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create and attach the view that is responsible for painting.
mView = new MyView(this);
setContentView(mView);
mView.requestFocus();
// Restore the fading option if we are being thawed from a
// previously saved state. Note that we are not currently remembering
// the contents of the bitmap.
mFading = savedInstanceState != null ? savedInstanceState.getBoolean("fading", true) : true;
}
@Override public boolean onCreateOptionsMenu(Menu menu) {
menu.add(0, CLEAR_ID, 0, "Clear");
menu.add(0, FADE_ID, 0, "Fade").setCheckable(true);
return super.onCreateOptionsMenu(menu);
}
@Override public boolean onPrepareOptionsMenu(Menu menu) {
menu.findItem(FADE_ID).setChecked(mFading);
return super.onPrepareOptionsMenu(menu);
}
@Override public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case CLEAR_ID:
mView.clear();
return true;
case FADE_ID:
mFading = !mFading;
if (mFading) {
startFading();
} else {
stopFading();
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override protected void onResume() {
super.onResume();
// If fading mode is enabled, then as long as we are resumed we want
// to run pulse to fade the contents.
if (mFading) {
startFading();
}
}
@Override protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save away the fading state to restore if needed later. Note that
// we do not currently save the contents of the display.
outState.putBoolean("fading", mFading);
}
@Override protected void onPause() {
super.onPause();
// Make sure to never run the fading pulse while we are paused or
// stopped.
stopFading();
}
/**
* Start up the pulse to fade the screen, clearing any existing pulse to
* ensure that we don't have multiple pulses running at a time.
*/
void startFading() {
mHandler.removeMessages(FADE_MSG);
mHandler.sendMessageDelayed(
mHandler.obtainMessage(FADE_MSG), FADE_DELAY);
}
/**
* Stop the pulse to fade the screen.
*/
void stopFading() {
mHandler.removeMessages(FADE_MSG);
}
private Handler mHandler = new Handler() {
@Override public void handleMessage(Message msg) {
switch (msg.what) {
// Upon receiving the fade pulse, we have the view perform a
// fade and then enqueue a new message to pulse at the desired
// next time.
case FADE_MSG: {
mView.fade();
mHandler.sendMessageDelayed(
mHandler.obtainMessage(FADE_MSG), FADE_DELAY);
break;
}
default:
super.handleMessage(msg);
}
}
};
public class MyView extends View {
private static final int FADE_ALPHA = 0x06;
private static final int MAX_FADE_STEPS = 256/FADE_ALPHA + 4;
private Bitmap mBitmap;
private Canvas mCanvas;
private final Rect mRect = new Rect();
private final Paint mPaint;
private final Paint mFadePaint;
private boolean mCurDown;
private int mCurX;
private int mCurY;
private float mCurPressure;
private float mCurSize;
private int mCurWidth;
private int mFadeSteps = MAX_FADE_STEPS;
public MyView(Context c) {
super(c);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setARGB(255, 255, 255, 255);
mFadePaint = new Paint();
mFadePaint.setDither(true);
mFadePaint.setARGB(FADE_ALPHA, 0, 0, 0);
}
public void clear() {
if (mCanvas != null) {
mPaint.setARGB(0xff, 0, 0, 0);
mCanvas.drawPaint(mPaint);
invalidate();
mFadeSteps = MAX_FADE_STEPS;
}
}
public void fade() {
if (mCanvas != null && mFadeSteps < MAX_FADE_STEPS) {
mCanvas.drawPaint(mFadePaint);
invalidate();
mFadeSteps++;
}
}
@Override protected void onSizeChanged(int w, int h, int oldw,
int oldh) {
int curW = mBitmap != null ? mBitmap.getWidth() : 0;
int curH = mBitmap != null ? mBitmap.getHeight() : 0;
if (curW >= w && curH >= h) {
return;
}
if (curW < w) curW = w;
if (curH < h) curH = h;
Bitmap newBitmap = Bitmap.createBitmap(curW, curH,
Bitmap.Config.RGB_565);
Canvas newCanvas = new Canvas();
newCanvas.setBitmap(newBitmap);
if (mBitmap != null) {
newCanvas.drawBitmap(mBitmap, 0, 0, null);
}
mBitmap = newBitmap;
mCanvas = newCanvas;
mFadeSteps = MAX_FADE_STEPS;
}
@Override protected void onDraw(Canvas canvas) {
if (mBitmap != null) {
canvas.drawBitmap(mBitmap, 0, 0, null);
}
}
@Override public boolean onTrackballEvent(MotionEvent event) {
boolean oldDown = mCurDown;
mCurDown = true;
int N = event.getHistorySize();
int baseX = mCurX;
int baseY = mCurY;
final float scaleX = event.getXPrecision();
final float scaleY = event.getYPrecision();
for (int i=0; i<N; i++) {
//Log.i("TouchPaint", "Intermediate trackball #" + i
// + ": x=" + event.getHistoricalX(i)
// + ", y=" + event.getHistoricalY(i));
drawPoint(baseX+event.getHistoricalX(i)*scaleX,
baseY+event.getHistoricalY(i)*scaleY,
event.getHistoricalPressure(i),
event.getHistoricalSize(i));
}
//Log.i("TouchPaint", "Trackball: x=" + event.getX()
// + ", y=" + event.getY());
drawPoint(baseX+event.getX()*scaleX, baseY+event.getY()*scaleY,
event.getPressure(), event.getSize());
mCurDown = oldDown;
return true;
}
@Override public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
mCurDown = action == MotionEvent.ACTION_DOWN
|| action == MotionEvent.ACTION_MOVE;
int N = event.getHistorySize();
for (int i=0; i<N; i++) {
//Log.i("TouchPaint", "Intermediate pointer #" + i);
drawPoint(event.getHistoricalX(i), event.getHistoricalY(i),
event.getHistoricalPressure(i),
event.getHistoricalSize(i));
}
drawPoint(event.getX(), event.getY(), event.getPressure(),
event.getSize());
return true;
}
private void drawPoint(float x, float y, float pressure, float size) {
//Log.i("TouchPaint", "Drawing: " + x + "x" + y + " p="
// + pressure + " s=" + size);
mCurX = (int)x;
mCurY = (int)y;
mCurPressure = pressure;
mCurSize = size;
mCurWidth = (int)(mCurSize*(getWidth()/3));
if (mCurWidth < 1) mCurWidth = 1;
if (mCurDown && mBitmap != null) {
int pressureLevel = (int)(mCurPressure*255);
mPaint.setARGB(pressureLevel, 255, 255, 255);
mCanvas.drawCircle(mCurX, mCurY, mCurWidth, mPaint);
mRect.set(mCurX-mCurWidth-2, mCurY-mCurWidth-2,
mCurX+mCurWidth+2, mCurY+mCurWidth+2);
invalidate(mRect);
}
mFadeSteps = 0;
}
}
}