1 /* 2 * Copyright (C) 2008 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.android.camera; 18 19 import android.content.Context; 20 import android.util.AttributeSet; 21 import android.view.GestureDetector; 22 import android.view.GestureDetector.SimpleOnGestureListener; 23 import android.view.MotionEvent; 24 import android.view.View; 25 import android.widget.ImageView; 26 27 import com.android.camera.debug.Log; 28 import com.android.camera.ui.TouchCoordinate; 29 30 import java.util.ArrayList; 31 import java.util.List; 32 33 /** 34 * A button designed to be used for the on-screen shutter button. 35 * It's currently an {@code ImageView} that can call a delegate when the 36 * pressed state changes. 37 */ 38 public class ShutterButton extends ImageView { 39 private static final Log.Tag TAG = new Log.Tag("ShutterButton"); 40 public static final float ALPHA_WHEN_ENABLED = 1f; 41 public static final float ALPHA_WHEN_DISABLED = 0.2f; 42 private boolean mTouchEnabled = true; 43 private TouchCoordinate mTouchCoordinate; 44 private final GestureDetector mGestureDetector; 45 46 /** 47 * A callback to be invoked when a ShutterButton's pressed state changes. 48 */ 49 public interface OnShutterButtonListener { 50 /** 51 * Called when a ShutterButton has been pressed. 52 * 53 * @param pressed The ShutterButton that was pressed. 54 */ onShutterButtonFocus(boolean pressed)55 void onShutterButtonFocus(boolean pressed); onShutterCoordinate(TouchCoordinate coord)56 void onShutterCoordinate(TouchCoordinate coord); onShutterButtonClick()57 void onShutterButtonClick(); 58 59 /** 60 * Called when shutter button is held down for a long press. 61 */ onShutterButtonLongPressed()62 void onShutterButtonLongPressed(); 63 } 64 65 /** 66 * A gesture listener to detect long presses. 67 */ 68 private class LongPressGestureListener extends SimpleOnGestureListener { 69 @Override onLongPress(MotionEvent event)70 public void onLongPress(MotionEvent event) { 71 for (OnShutterButtonListener listener : mListeners) { 72 listener.onShutterButtonLongPressed(); 73 } 74 } 75 } 76 77 private List<OnShutterButtonListener> mListeners 78 = new ArrayList<OnShutterButtonListener>(); 79 private boolean mOldPressed; 80 ShutterButton(Context context, AttributeSet attrs)81 public ShutterButton(Context context, AttributeSet attrs) { 82 super(context, attrs); 83 mGestureDetector = new GestureDetector(context, new LongPressGestureListener()); 84 mGestureDetector.setIsLongpressEnabled(true); 85 } 86 87 /** 88 * Add an {@link OnShutterButtonListener} to a set of listeners. 89 */ addOnShutterButtonListener(OnShutterButtonListener listener)90 public void addOnShutterButtonListener(OnShutterButtonListener listener) { 91 if (!mListeners.contains(listener)) { 92 mListeners.add(listener); 93 } 94 } 95 96 /** 97 * Remove an {@link OnShutterButtonListener} from a set of listeners. 98 */ removeOnShutterButtonListener(OnShutterButtonListener listener)99 public void removeOnShutterButtonListener(OnShutterButtonListener listener) { 100 if (mListeners.contains(listener)) { 101 mListeners.remove(listener); 102 } 103 } 104 105 @Override dispatchTouchEvent(MotionEvent m)106 public boolean dispatchTouchEvent(MotionEvent m) { 107 if (mTouchEnabled) { 108 // Don't send ACTION_MOVE messages to gesture detector unless event motion is out of 109 // shutter button view. A small motion resets the long tap status. A long tap should 110 // be interpreted as the duration the finger is held down on the shutter button, 111 // regardless of any small motions. If motion moves out of shutter button view, the 112 // gesture detector needs to be notified to reset the long tap status. 113 if (m.getActionMasked() != MotionEvent.ACTION_MOVE 114 || m.getX() < 0 || m.getY() < 0 115 || m.getX() >= getWidth() || m.getY() >= getHeight()) { 116 mGestureDetector.onTouchEvent(m); 117 } 118 if (m.getActionMasked() == MotionEvent.ACTION_UP) { 119 mTouchCoordinate = new TouchCoordinate(m.getX(), m.getY(), this.getMeasuredWidth(), 120 this.getMeasuredHeight()); 121 } 122 return super.dispatchTouchEvent(m); 123 } else { 124 return false; 125 } 126 } 127 enableTouch(boolean enable)128 public void enableTouch(boolean enable) { 129 mTouchEnabled = enable; 130 } 131 132 /** 133 * Hook into the drawable state changing to get changes to isPressed -- the 134 * onPressed listener doesn't always get called when the pressed state 135 * changes. 136 */ 137 @Override drawableStateChanged()138 protected void drawableStateChanged() { 139 super.drawableStateChanged(); 140 final boolean pressed = isPressed(); 141 if (pressed != mOldPressed) { 142 if (!pressed) { 143 // When pressing the physical camera button the sequence of 144 // events is: 145 // focus pressed, optional camera pressed, focus released. 146 // We want to emulate this sequence of events with the shutter 147 // button. When clicking using a trackball button, the view 148 // system changes the drawable state before posting click 149 // notification, so the sequence of events is: 150 // pressed(true), optional click, pressed(false) 151 // When clicking using touch events, the view system changes the 152 // drawable state after posting click notification, so the 153 // sequence of events is: 154 // pressed(true), pressed(false), optional click 155 // Since we're emulating the physical camera button, we want to 156 // have the same order of events. So we want the optional click 157 // callback to be delivered before the pressed(false) callback. 158 // 159 // To do this, we delay the posting of the pressed(false) event 160 // slightly by pushing it on the event queue. This moves it 161 // after the optional click notification, so our client always 162 // sees events in this sequence: 163 // pressed(true), optional click, pressed(false) 164 post(new Runnable() { 165 @Override 166 public void run() { 167 callShutterButtonFocus(pressed); 168 } 169 }); 170 } else { 171 callShutterButtonFocus(pressed); 172 } 173 mOldPressed = pressed; 174 } 175 } 176 callShutterButtonFocus(boolean pressed)177 private void callShutterButtonFocus(boolean pressed) { 178 for (OnShutterButtonListener listener : mListeners) { 179 listener.onShutterButtonFocus(pressed); 180 } 181 } 182 183 @Override performClick()184 public boolean performClick() { 185 boolean result = super.performClick(); 186 if (getVisibility() == View.VISIBLE) { 187 for (OnShutterButtonListener listener : mListeners) { 188 listener.onShutterCoordinate(mTouchCoordinate); 189 mTouchCoordinate = null; 190 listener.onShutterButtonClick(); 191 } 192 } 193 return result; 194 } 195 } 196