Home | History | Annotate | Download | only in graphics
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.example.android.apis.graphics;
     18 
     19 import android.app.Activity;
     20 import android.content.Context;
     21 import android.graphics.Bitmap;
     22 import android.graphics.Canvas;
     23 import android.graphics.Paint;
     24 import android.graphics.Rect;
     25 import android.os.Bundle;
     26 import android.os.Handler;
     27 import android.os.Message;
     28 import android.view.Menu;
     29 import android.view.MenuItem;
     30 import android.view.MotionEvent;
     31 import android.view.View;
     32 
     33 //Need the following import to get access to the app resources, since this
     34 //class is in a sub-package.
     35 
     36 
     37 /**
     38  * Demonstrates the handling of touch screen and trackball events to
     39  * implement a simple painting app.
     40  */
     41 public class TouchPaint extends GraphicsActivity {
     42     /** Used as a pulse to gradually fade the contents of the window. */
     43     private static final int FADE_MSG = 1;
     44 
     45     /** Menu ID for the command to clear the window. */
     46     private static final int CLEAR_ID = Menu.FIRST;
     47     /** Menu ID for the command to toggle fading. */
     48     private static final int FADE_ID = Menu.FIRST+1;
     49 
     50     /** How often to fade the contents of the window (in ms). */
     51     private static final int FADE_DELAY = 100;
     52 
     53     /** The view responsible for drawing the window. */
     54     MyView mView;
     55     /** Is fading mode enabled? */
     56     boolean mFading;
     57 
     58     @Override protected void onCreate(Bundle savedInstanceState) {
     59         super.onCreate(savedInstanceState);
     60 
     61         // Create and attach the view that is responsible for painting.
     62         mView = new MyView(this);
     63         setContentView(mView);
     64         mView.requestFocus();
     65 
     66         // Restore the fading option if we are being thawed from a
     67         // previously saved state.  Note that we are not currently remembering
     68         // the contents of the bitmap.
     69         mFading = savedInstanceState != null ? savedInstanceState.getBoolean("fading", true) : true;
     70     }
     71 
     72     @Override public boolean onCreateOptionsMenu(Menu menu) {
     73         menu.add(0, CLEAR_ID, 0, "Clear");
     74         menu.add(0, FADE_ID, 0, "Fade").setCheckable(true);
     75         return super.onCreateOptionsMenu(menu);
     76     }
     77 
     78     @Override public boolean onPrepareOptionsMenu(Menu menu) {
     79         menu.findItem(FADE_ID).setChecked(mFading);
     80         return super.onPrepareOptionsMenu(menu);
     81     }
     82 
     83     @Override public boolean onOptionsItemSelected(MenuItem item) {
     84         switch (item.getItemId()) {
     85             case CLEAR_ID:
     86                 mView.clear();
     87                 return true;
     88             case FADE_ID:
     89                 mFading = !mFading;
     90                 if (mFading) {
     91                     startFading();
     92                 } else {
     93                     stopFading();
     94                 }
     95                 return true;
     96             default:
     97                 return super.onOptionsItemSelected(item);
     98         }
     99     }
    100 
    101     @Override protected void onResume() {
    102         super.onResume();
    103         // If fading mode is enabled, then as long as we are resumed we want
    104         // to run pulse to fade the contents.
    105         if (mFading) {
    106             startFading();
    107         }
    108     }
    109 
    110     @Override protected void onSaveInstanceState(Bundle outState) {
    111         super.onSaveInstanceState(outState);
    112         // Save away the fading state to restore if needed later.  Note that
    113         // we do not currently save the contents of the display.
    114         outState.putBoolean("fading", mFading);
    115     }
    116 
    117     @Override protected void onPause() {
    118         super.onPause();
    119         // Make sure to never run the fading pulse while we are paused or
    120         // stopped.
    121         stopFading();
    122     }
    123 
    124     /**
    125      * Start up the pulse to fade the screen, clearing any existing pulse to
    126      * ensure that we don't have multiple pulses running at a time.
    127      */
    128     void startFading() {
    129         mHandler.removeMessages(FADE_MSG);
    130         mHandler.sendMessageDelayed(
    131                 mHandler.obtainMessage(FADE_MSG), FADE_DELAY);
    132     }
    133 
    134     /**
    135      * Stop the pulse to fade the screen.
    136      */
    137     void stopFading() {
    138         mHandler.removeMessages(FADE_MSG);
    139     }
    140 
    141     private Handler mHandler = new Handler() {
    142         @Override public void handleMessage(Message msg) {
    143             switch (msg.what) {
    144                 // Upon receiving the fade pulse, we have the view perform a
    145                 // fade and then enqueue a new message to pulse at the desired
    146                 // next time.
    147                 case FADE_MSG: {
    148                     mView.fade();
    149                     mHandler.sendMessageDelayed(
    150                             mHandler.obtainMessage(FADE_MSG), FADE_DELAY);
    151                     break;
    152                 }
    153                 default:
    154                     super.handleMessage(msg);
    155             }
    156         }
    157     };
    158 
    159     public class MyView extends View {
    160         private static final int FADE_ALPHA = 0x06;
    161         private static final int MAX_FADE_STEPS = 256/FADE_ALPHA + 4;
    162         private static final int TRACKBALL_SCALE = 10;
    163 
    164         private Bitmap mBitmap;
    165         private Canvas mCanvas;
    166         private final Rect mRect = new Rect();
    167         private final Paint mPaint;
    168         private final Paint mFadePaint;
    169         private float mCurX;
    170         private float mCurY;
    171         private int mFadeSteps = MAX_FADE_STEPS;
    172 
    173         public MyView(Context c) {
    174             super(c);
    175             setFocusable(true);
    176             mPaint = new Paint();
    177             mPaint.setAntiAlias(true);
    178             mPaint.setARGB(255, 255, 255, 255);
    179             mFadePaint = new Paint();
    180             mFadePaint.setDither(true);
    181             mFadePaint.setARGB(FADE_ALPHA, 0, 0, 0);
    182         }
    183 
    184         public void clear() {
    185             if (mCanvas != null) {
    186                 mPaint.setARGB(0xff, 0, 0, 0);
    187                 mCanvas.drawPaint(mPaint);
    188                 invalidate();
    189                 mFadeSteps = MAX_FADE_STEPS;
    190             }
    191         }
    192 
    193         public void fade() {
    194             if (mCanvas != null && mFadeSteps < MAX_FADE_STEPS) {
    195                 mCanvas.drawPaint(mFadePaint);
    196                 invalidate();
    197                 mFadeSteps++;
    198             }
    199         }
    200 
    201         @Override protected void onSizeChanged(int w, int h, int oldw,
    202                 int oldh) {
    203             int curW = mBitmap != null ? mBitmap.getWidth() : 0;
    204             int curH = mBitmap != null ? mBitmap.getHeight() : 0;
    205             if (curW >= w && curH >= h) {
    206                 return;
    207             }
    208 
    209             if (curW < w) curW = w;
    210             if (curH < h) curH = h;
    211 
    212             Bitmap newBitmap = Bitmap.createBitmap(curW, curH,
    213                                                    Bitmap.Config.RGB_565);
    214             Canvas newCanvas = new Canvas();
    215             newCanvas.setBitmap(newBitmap);
    216             if (mBitmap != null) {
    217                 newCanvas.drawBitmap(mBitmap, 0, 0, null);
    218             }
    219             mBitmap = newBitmap;
    220             mCanvas = newCanvas;
    221             mFadeSteps = MAX_FADE_STEPS;
    222         }
    223 
    224         @Override protected void onDraw(Canvas canvas) {
    225             if (mBitmap != null) {
    226                 canvas.drawBitmap(mBitmap, 0, 0, null);
    227             }
    228         }
    229 
    230         @Override public boolean onTrackballEvent(MotionEvent event) {
    231             int N = event.getHistorySize();
    232             final float scaleX = event.getXPrecision() * TRACKBALL_SCALE;
    233             final float scaleY = event.getYPrecision() * TRACKBALL_SCALE;
    234             for (int i=0; i<N; i++) {
    235                 //Log.i("TouchPaint", "Intermediate trackball #" + i
    236                 //        + ": x=" + event.getHistoricalX(i)
    237                 //        + ", y=" + event.getHistoricalY(i));
    238                 mCurX += event.getHistoricalX(i) * scaleX;
    239                 mCurY += event.getHistoricalY(i) * scaleY;
    240                 drawPoint(mCurX, mCurY, 1.0f, 16.0f);
    241             }
    242             //Log.i("TouchPaint", "Trackball: x=" + event.getX()
    243             //        + ", y=" + event.getY());
    244             mCurX += event.getX() * scaleX;
    245             mCurY += event.getY() * scaleY;
    246             drawPoint(mCurX, mCurY, 1.0f, 16.0f);
    247             return true;
    248         }
    249 
    250         @Override public boolean onTouchEvent(MotionEvent event) {
    251             int action = event.getActionMasked();
    252             if (action != MotionEvent.ACTION_UP && action != MotionEvent.ACTION_CANCEL) {
    253                 int N = event.getHistorySize();
    254                 int P = event.getPointerCount();
    255                 for (int i = 0; i < N; i++) {
    256                     for (int j = 0; j < P; j++) {
    257                         mCurX = event.getHistoricalX(j, i);
    258                         mCurY = event.getHistoricalY(j, i);
    259                         drawPoint(mCurX, mCurY,
    260                                 event.getHistoricalPressure(j, i),
    261                                 event.getHistoricalTouchMajor(j, i));
    262                     }
    263                 }
    264                 for (int j = 0; j < P; j++) {
    265                     mCurX = event.getX(j);
    266                     mCurY = event.getY(j);
    267                     drawPoint(mCurX, mCurY, event.getPressure(j), event.getTouchMajor(j));
    268                 }
    269             }
    270             return true;
    271         }
    272 
    273         private void drawPoint(float x, float y, float pressure, float width) {
    274             //Log.i("TouchPaint", "Drawing: " + x + "x" + y + " p="
    275             //        + pressure + " width=" + width);
    276             if (width < 1) width = 1;
    277             if (mBitmap != null) {
    278                 float radius = width / 2;
    279                 int pressureLevel = (int)(pressure * 255);
    280                 mPaint.setARGB(pressureLevel, 255, 255, 255);
    281                 mCanvas.drawCircle(x, y, radius, mPaint);
    282                 mRect.set((int) (x - radius - 2), (int) (y - radius - 2),
    283                         (int) (x + radius + 2), (int) (y + radius + 2));
    284                 invalidate(mRect);
    285             }
    286             mFadeSteps = 0;
    287         }
    288     }
    289 }
    290