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