| /* |
| * Copyright (C) 2010 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.android.videoeditor.widgets; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import com.android.videoeditor.R; |
| |
| import android.app.Activity; |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.graphics.Canvas; |
| import android.graphics.drawable.Drawable; |
| import android.os.Handler; |
| import android.os.SystemClock; |
| import android.util.AttributeSet; |
| import android.view.Display; |
| import android.view.MotionEvent; |
| import android.view.ScaleGestureDetector; |
| |
| /** |
| * The timeline scroll view |
| */ |
| public class TimelineHorizontalScrollView extends HorizontalScrollView { |
| public final static int PLAYHEAD_NORMAL = 1; |
| public final static int PLAYHEAD_MOVE_OK = 2; |
| public final static int PLAYHEAD_MOVE_NOT_OK = 3; |
| |
| // Instance variables |
| private final List<ScrollViewListener> mScrollListenerList; |
| private final Handler mHandler; |
| private final int mPlayheadMarginTop; |
| private final int mPlayheadMarginTopOk; |
| private final int mPlayheadMarginTopNotOk; |
| private final int mPlayheadMarginBottom; |
| private final Drawable mNormalPlayheadDrawable; |
| private final Drawable mMoveOkPlayheadDrawable; |
| private final Drawable mMoveNotOkPlayheadDrawable; |
| private final int mHalfParentWidth; |
| private ScaleGestureDetector mScaleDetector; |
| private int mLastScrollX; |
| private boolean mIsScrolling; |
| private boolean mAppScroll; |
| private boolean mEnableUserScrolling; |
| |
| // The runnable which executes when the scrolling ends |
| private Runnable mScrollEndedRunnable = new Runnable() { |
| @Override |
| public void run() { |
| mIsScrolling = false; |
| |
| for (ScrollViewListener listener : mScrollListenerList) { |
| listener.onScrollEnd(TimelineHorizontalScrollView.this, getScrollX(), |
| getScrollY(), mAppScroll); |
| } |
| |
| mAppScroll = false; |
| } |
| }; |
| |
| public TimelineHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) { |
| super(context, attrs, defStyle); |
| |
| mEnableUserScrolling = true; |
| mScrollListenerList = new ArrayList<ScrollViewListener>(); |
| mHandler = new Handler(); |
| |
| // Compute half the width of the screen (and therefore the parent view) |
| final Display display = ((Activity)context).getWindowManager().getDefaultDisplay(); |
| mHalfParentWidth = display.getWidth() / 2; |
| |
| // This value is shared by all children. It represents the width of |
| // the left empty view. |
| setTag(R.id.left_view_width, mHalfParentWidth); |
| setTag(R.id.playhead_offset, -1); |
| setTag(R.id.playhead_type, PLAYHEAD_NORMAL); |
| |
| final Resources resources = context.getResources(); |
| |
| // Get the playhead margins |
| mPlayheadMarginTop = (int)resources.getDimension(R.dimen.playhead_margin_top); |
| mPlayheadMarginBottom = (int)resources.getDimension(R.dimen.playhead_margin_bottom); |
| mPlayheadMarginTopOk = (int)resources.getDimension(R.dimen.playhead_margin_top_ok); |
| mPlayheadMarginTopNotOk = (int)resources.getDimension(R.dimen.playhead_margin_top_not_ok); |
| |
| // Prepare the playhead drawable |
| mNormalPlayheadDrawable = resources.getDrawable(R.drawable.ic_playhead); |
| mMoveOkPlayheadDrawable = resources.getDrawable(R.drawable.playhead_move_ok); |
| mMoveNotOkPlayheadDrawable = resources.getDrawable(R.drawable.playhead_move_not_ok); |
| } |
| |
| public TimelineHorizontalScrollView(Context context, AttributeSet attrs) { |
| this(context, attrs, 0); |
| } |
| |
| public TimelineHorizontalScrollView(Context context) { |
| this(context, null, 0); |
| } |
| |
| /** |
| * Invoked to enable/disable user scrolling (as opposed to programmatic scrolling) |
| * @param enable true to enable user scrolling |
| */ |
| public void enableUserScrolling(boolean enable) { |
| mEnableUserScrolling = enable; |
| } |
| |
| @Override |
| public boolean onInterceptTouchEvent(MotionEvent ev) { |
| mScaleDetector.onTouchEvent(ev); |
| return mScaleDetector.isInProgress() || super.onInterceptTouchEvent(ev); |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent ev) { |
| if (mEnableUserScrolling) { |
| mScaleDetector.onTouchEvent(ev); |
| |
| if (!mScaleDetector.isInProgress()) { |
| return super.onTouchEvent(ev); |
| } else { |
| return true; |
| } |
| } else { |
| if (mScaleDetector.isInProgress()) { |
| final MotionEvent cancelEvent = MotionEvent.obtain(SystemClock.uptimeMillis(), |
| SystemClock.uptimeMillis(), MotionEvent.ACTION_CANCEL, 0, 0, 0); |
| mScaleDetector.onTouchEvent(cancelEvent); |
| cancelEvent.recycle(); |
| } |
| return true; |
| } |
| } |
| |
| /** |
| * @param listener The scale listener |
| */ |
| public void setScaleListener(ScaleGestureDetector.SimpleOnScaleGestureListener listener) { |
| mScaleDetector = new ScaleGestureDetector(getContext(), listener); |
| } |
| |
| /** |
| * @param listener The listener |
| */ |
| public void addScrollListener(ScrollViewListener listener) { |
| mScrollListenerList.add(listener); |
| } |
| |
| /** |
| * @param listener The listener |
| */ |
| public void removeScrollListener(ScrollViewListener listener) { |
| mScrollListenerList.remove(listener); |
| } |
| |
| /** |
| * @return true if scrolling is in progress |
| */ |
| public boolean isScrolling() { |
| return mIsScrolling; |
| } |
| |
| /** |
| * The app wants to scroll (as opposed to the user) |
| * |
| * @param scrollX Horizontal scroll position |
| * @param smooth true to scroll smoothly |
| */ |
| public void appScrollTo(int scrollX, boolean smooth) { |
| if (getScrollX() == scrollX) { |
| return; |
| } |
| |
| mAppScroll = true; |
| |
| if (smooth) { |
| smoothScrollTo(scrollX, 0); |
| } else { |
| scrollTo(scrollX, 0); |
| } |
| } |
| |
| /** |
| * The app wants to scroll (as opposed to the user) |
| * |
| * @param scrollX Horizontal scroll offset |
| * @param smooth true to scroll smoothly |
| */ |
| public void appScrollBy(int scrollX, boolean smooth) { |
| mAppScroll = true; |
| |
| if (smooth) { |
| smoothScrollBy(scrollX, 0); |
| } else { |
| scrollBy(scrollX, 0); |
| } |
| } |
| |
| @Override |
| public void computeScroll() { |
| super.computeScroll(); |
| |
| final int scrollX = getScrollX(); |
| if (mLastScrollX != scrollX) { |
| mLastScrollX = scrollX; |
| |
| // Cancel the previous event |
| mHandler.removeCallbacks(mScrollEndedRunnable); |
| |
| // Post a new event |
| mHandler.postDelayed(mScrollEndedRunnable, 300); |
| |
| final int scrollY = getScrollY(); |
| if (mIsScrolling) { |
| for (ScrollViewListener listener : mScrollListenerList) { |
| listener.onScrollProgress(this, scrollX, scrollY, mAppScroll); |
| } |
| } else { |
| mIsScrolling = true; |
| |
| for (ScrollViewListener listener : mScrollListenerList) { |
| listener.onScrollBegin(this, scrollX, scrollY, mAppScroll); |
| } |
| } |
| } |
| } |
| |
| @Override |
| protected void dispatchDraw(Canvas canvas) { |
| super.dispatchDraw(canvas); |
| |
| final int playheadOffset = (Integer)getTag(R.id.playhead_offset); |
| final int startX; |
| if (playheadOffset < 0) { |
| // Draw the playhead in the middle of the screen |
| startX = mHalfParentWidth + getScrollX(); |
| } else { |
| // Draw the playhead at the specified position (during trimming) |
| startX = playheadOffset; |
| } |
| |
| final int playheadType = (Integer)getTag(R.id.playhead_type); |
| final int halfPlayheadWidth = mNormalPlayheadDrawable.getIntrinsicWidth() / 2; |
| switch (playheadType) { |
| case PLAYHEAD_NORMAL: { |
| // Draw the normal playhead |
| mNormalPlayheadDrawable.setBounds( |
| startX - halfPlayheadWidth, |
| mPlayheadMarginTop, |
| startX + halfPlayheadWidth, |
| getHeight() - mPlayheadMarginBottom); |
| mNormalPlayheadDrawable.draw(canvas); |
| break; |
| } |
| |
| case PLAYHEAD_MOVE_OK: { |
| // Draw the move playhead |
| mMoveOkPlayheadDrawable.setBounds( |
| startX - halfPlayheadWidth, |
| mPlayheadMarginTopOk, |
| startX + halfPlayheadWidth, |
| mPlayheadMarginTopOk + mMoveOkPlayheadDrawable.getIntrinsicHeight()); |
| mMoveOkPlayheadDrawable.draw(canvas); |
| break; |
| } |
| |
| case PLAYHEAD_MOVE_NOT_OK: { |
| // Draw the move playhead |
| mMoveNotOkPlayheadDrawable.setBounds( |
| startX - halfPlayheadWidth, |
| mPlayheadMarginTopNotOk, |
| startX + halfPlayheadWidth, |
| mPlayheadMarginTopNotOk + mMoveNotOkPlayheadDrawable.getIntrinsicHeight()); |
| mMoveNotOkPlayheadDrawable.draw(canvas); |
| break; |
| } |
| |
| default: { |
| break; |
| } |
| } |
| } |
| } |