| /* |
| * 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; |
| } |
| } |
| } |