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