1 /* 2 * Copyright (C) 2010 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.videoeditor.widgets; 18 19 import java.util.ArrayList; 20 import java.util.List; 21 22 import com.android.videoeditor.R; 23 24 import android.app.Activity; 25 import android.content.Context; 26 import android.content.res.Resources; 27 import android.graphics.Canvas; 28 import android.graphics.drawable.Drawable; 29 import android.os.Handler; 30 import android.os.SystemClock; 31 import android.util.AttributeSet; 32 import android.view.Display; 33 import android.view.MotionEvent; 34 import android.view.ScaleGestureDetector; 35 36 /** 37 * The timeline scroll view 38 */ 39 public class TimelineHorizontalScrollView extends HorizontalScrollView { 40 public final static int PLAYHEAD_NORMAL = 1; 41 public final static int PLAYHEAD_MOVE_OK = 2; 42 public final static int PLAYHEAD_MOVE_NOT_OK = 3; 43 44 // Instance variables 45 private final List<ScrollViewListener> mScrollListenerList; 46 private final Handler mHandler; 47 private final int mPlayheadMarginTop; 48 private final int mPlayheadMarginTopOk; 49 private final int mPlayheadMarginTopNotOk; 50 private final int mPlayheadMarginBottom; 51 private final Drawable mNormalPlayheadDrawable; 52 private final Drawable mMoveOkPlayheadDrawable; 53 private final Drawable mMoveNotOkPlayheadDrawable; 54 private final int mHalfParentWidth; 55 private ScaleGestureDetector mScaleDetector; 56 private int mLastScrollX; 57 private boolean mIsScrolling; 58 private boolean mAppScroll; 59 private boolean mEnableUserScrolling; 60 61 // The runnable which executes when the scrolling ends 62 private Runnable mScrollEndedRunnable = new Runnable() { 63 @Override 64 public void run() { 65 mIsScrolling = false; 66 67 for (ScrollViewListener listener : mScrollListenerList) { 68 listener.onScrollEnd(TimelineHorizontalScrollView.this, getScrollX(), 69 getScrollY(), mAppScroll); 70 } 71 72 mAppScroll = false; 73 } 74 }; 75 76 public TimelineHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) { 77 super(context, attrs, defStyle); 78 79 mEnableUserScrolling = true; 80 mScrollListenerList = new ArrayList<ScrollViewListener>(); 81 mHandler = new Handler(); 82 83 // Compute half the width of the screen (and therefore the parent view) 84 final Display display = ((Activity)context).getWindowManager().getDefaultDisplay(); 85 mHalfParentWidth = display.getWidth() / 2; 86 87 // This value is shared by all children. It represents the width of 88 // the left empty view. 89 setTag(R.id.left_view_width, mHalfParentWidth); 90 setTag(R.id.playhead_offset, -1); 91 setTag(R.id.playhead_type, PLAYHEAD_NORMAL); 92 93 final Resources resources = context.getResources(); 94 95 // Get the playhead margins 96 mPlayheadMarginTop = (int)resources.getDimension(R.dimen.playhead_margin_top); 97 mPlayheadMarginBottom = (int)resources.getDimension(R.dimen.playhead_margin_bottom); 98 mPlayheadMarginTopOk = (int)resources.getDimension(R.dimen.playhead_margin_top_ok); 99 mPlayheadMarginTopNotOk = (int)resources.getDimension(R.dimen.playhead_margin_top_not_ok); 100 101 // Prepare the playhead drawable 102 mNormalPlayheadDrawable = resources.getDrawable(R.drawable.ic_playhead); 103 mMoveOkPlayheadDrawable = resources.getDrawable(R.drawable.playhead_move_ok); 104 mMoveNotOkPlayheadDrawable = resources.getDrawable(R.drawable.playhead_move_not_ok); 105 } 106 107 public TimelineHorizontalScrollView(Context context, AttributeSet attrs) { 108 this(context, attrs, 0); 109 } 110 111 public TimelineHorizontalScrollView(Context context) { 112 this(context, null, 0); 113 } 114 115 /** 116 * Invoked to enable/disable user scrolling (as opposed to programmatic scrolling) 117 * @param enable true to enable user scrolling 118 */ 119 public void enableUserScrolling(boolean enable) { 120 mEnableUserScrolling = enable; 121 } 122 123 @Override 124 public boolean onInterceptTouchEvent(MotionEvent ev) { 125 mScaleDetector.onTouchEvent(ev); 126 return super.onInterceptTouchEvent(ev); 127 } 128 129 @Override 130 public boolean onTouchEvent(MotionEvent ev) { 131 if (mEnableUserScrolling) { 132 mScaleDetector.onTouchEvent(ev); 133 return super.onTouchEvent(ev); 134 } else { 135 if (mScaleDetector.isInProgress()) { 136 final MotionEvent cancelEvent = MotionEvent.obtain(SystemClock.uptimeMillis(), 137 SystemClock.uptimeMillis(), MotionEvent.ACTION_CANCEL, 0, 0, 0); 138 mScaleDetector.onTouchEvent(cancelEvent); 139 cancelEvent.recycle(); 140 } 141 return true; 142 } 143 } 144 145 /** 146 * @param listener The scale listener 147 */ 148 public void setScaleListener(ScaleGestureDetector.SimpleOnScaleGestureListener listener) { 149 mScaleDetector = new ScaleGestureDetector(getContext(), listener); 150 } 151 152 /** 153 * @param listener The listener 154 */ 155 public void addScrollListener(ScrollViewListener listener) { 156 mScrollListenerList.add(listener); 157 } 158 159 /** 160 * @param listener The listener 161 */ 162 public void removeScrollListener(ScrollViewListener listener) { 163 mScrollListenerList.remove(listener); 164 } 165 166 /** 167 * @return true if scrolling is in progress 168 */ 169 public boolean isScrolling() { 170 return mIsScrolling; 171 } 172 173 /** 174 * The app wants to scroll (as opposed to the user) 175 * 176 * @param scrollX Horizontal scroll position 177 * @param smooth true to scroll smoothly 178 */ 179 public void appScrollTo(int scrollX, boolean smooth) { 180 if (getScrollX() == scrollX) { 181 return; 182 } 183 184 mAppScroll = true; 185 186 if (smooth) { 187 smoothScrollTo(scrollX, 0); 188 } else { 189 scrollTo(scrollX, 0); 190 } 191 } 192 193 /** 194 * The app wants to scroll (as opposed to the user) 195 * 196 * @param scrollX Horizontal scroll offset 197 * @param smooth true to scroll smoothly 198 */ 199 public void appScrollBy(int scrollX, boolean smooth) { 200 mAppScroll = true; 201 202 if (smooth) { 203 smoothScrollBy(scrollX, 0); 204 } else { 205 scrollBy(scrollX, 0); 206 } 207 } 208 209 @Override 210 public void computeScroll() { 211 super.computeScroll(); 212 213 final int scrollX = getScrollX(); 214 if (mLastScrollX != scrollX) { 215 mLastScrollX = scrollX; 216 217 // Cancel the previous event 218 mHandler.removeCallbacks(mScrollEndedRunnable); 219 220 // Post a new event 221 mHandler.postDelayed(mScrollEndedRunnable, 300); 222 223 final int scrollY = getScrollY(); 224 if (mIsScrolling) { 225 for (ScrollViewListener listener : mScrollListenerList) { 226 listener.onScrollProgress(this, scrollX, scrollY, mAppScroll); 227 } 228 } else { 229 mIsScrolling = true; 230 231 for (ScrollViewListener listener : mScrollListenerList) { 232 listener.onScrollBegin(this, scrollX, scrollY, mAppScroll); 233 } 234 } 235 } 236 } 237 238 @Override 239 protected void dispatchDraw(Canvas canvas) { 240 super.dispatchDraw(canvas); 241 242 final int playheadOffset = (Integer)getTag(R.id.playhead_offset); 243 final int startX; 244 if (playheadOffset < 0) { 245 // Draw the playhead in the middle of the screen 246 startX = mHalfParentWidth + getScrollX(); 247 } else { 248 // Draw the playhead at the specified position (during trimming) 249 startX = playheadOffset; 250 } 251 252 final int playheadType = (Integer)getTag(R.id.playhead_type); 253 final int halfPlayheadWidth = mNormalPlayheadDrawable.getIntrinsicWidth() / 2; 254 switch (playheadType) { 255 case PLAYHEAD_NORMAL: { 256 // Draw the normal playhead 257 mNormalPlayheadDrawable.setBounds( 258 startX - halfPlayheadWidth, 259 mPlayheadMarginTop, 260 startX + halfPlayheadWidth, 261 getHeight() - mPlayheadMarginBottom); 262 mNormalPlayheadDrawable.draw(canvas); 263 break; 264 } 265 266 case PLAYHEAD_MOVE_OK: { 267 // Draw the move playhead 268 mMoveOkPlayheadDrawable.setBounds( 269 startX - halfPlayheadWidth, 270 mPlayheadMarginTopOk, 271 startX + halfPlayheadWidth, 272 mPlayheadMarginTopOk + mMoveOkPlayheadDrawable.getIntrinsicHeight()); 273 mMoveOkPlayheadDrawable.draw(canvas); 274 break; 275 } 276 277 case PLAYHEAD_MOVE_NOT_OK: { 278 // Draw the move playhead 279 mMoveNotOkPlayheadDrawable.setBounds( 280 startX - halfPlayheadWidth, 281 mPlayheadMarginTopNotOk, 282 startX + halfPlayheadWidth, 283 mPlayheadMarginTopNotOk + mMoveNotOkPlayheadDrawable.getIntrinsicHeight()); 284 mMoveNotOkPlayheadDrawable.draw(canvas); 285 break; 286 } 287 288 default: { 289 break; 290 } 291 } 292 } 293 } 294