1/*
2 * Copyright (C) 2011 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.gallery3d.app;
18
19import com.android.gallery3d.common.Utils;
20import com.android.gallery3d.R;
21
22import android.content.Context;
23import android.graphics.Bitmap;
24import android.graphics.BitmapFactory;
25import android.graphics.Canvas;
26import android.graphics.Paint;
27import android.graphics.Rect;
28import android.util.DisplayMetrics;
29import android.view.MotionEvent;
30import android.view.View;
31
32/**
33 * The time bar view, which includes the current and total time, the progress bar,
34 * and the scrubber.
35 */
36public class TimeBar extends View {
37
38  public interface Listener {
39    void onScrubbingStart();
40    void onScrubbingMove(int time);
41    void onScrubbingEnd(int time);
42  }
43
44  // Padding around the scrubber to increase its touch target
45  private static final int SCRUBBER_PADDING_IN_DP = 10;
46
47  // The total padding, top plus bottom
48  private static final int V_PADDING_IN_DP = 30;
49
50  private static final int TEXT_SIZE_IN_DP = 14;
51
52  private final Listener listener;
53
54  // the bars we use for displaying the progress
55  private final Rect progressBar;
56  private final Rect playedBar;
57
58  private final Paint progressPaint;
59  private final Paint playedPaint;
60  private final Paint timeTextPaint;
61
62  private final Bitmap scrubber;
63  private final int scrubberPadding; // adds some touch tolerance around the scrubber
64
65  private int scrubberLeft;
66  private int scrubberTop;
67  private int scrubberCorrection;
68  private boolean scrubbing;
69  private boolean showTimes;
70  private boolean showScrubber;
71
72  private int totalTime;
73  private int currentTime;
74
75  private final Rect timeBounds;
76
77  private int vPaddingInPx;
78
79  public TimeBar(Context context, Listener listener) {
80    super(context);
81    this.listener = Utils.checkNotNull(listener);
82
83    showTimes = true;
84    showScrubber = true;
85
86    progressBar = new Rect();
87    playedBar = new Rect();
88
89    progressPaint = new Paint();
90    progressPaint.setColor(0xFF808080);
91    playedPaint = new Paint();
92    playedPaint.setColor(0xFFFFFFFF);
93
94    DisplayMetrics metrics = context.getResources().getDisplayMetrics();
95    float textSizeInPx = metrics.density * TEXT_SIZE_IN_DP;
96    timeTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
97    timeTextPaint.setColor(0xFFCECECE);
98    timeTextPaint.setTextSize(textSizeInPx);
99    timeTextPaint.setTextAlign(Paint.Align.CENTER);
100
101    timeBounds = new Rect();
102    timeTextPaint.getTextBounds("0:00:00", 0, 7, timeBounds);
103
104    scrubber = BitmapFactory.decodeResource(getResources(), R.drawable.scrubber_knob);
105    scrubberPadding = (int) (metrics.density * SCRUBBER_PADDING_IN_DP);
106
107    vPaddingInPx = (int) (metrics.density * V_PADDING_IN_DP);
108  }
109
110  private void update() {
111    playedBar.set(progressBar);
112
113    if (totalTime > 0) {
114      playedBar.right =
115          playedBar.left + (int) ((progressBar.width() * (long) currentTime) / totalTime);
116    } else {
117      playedBar.right = progressBar.left;
118    }
119
120    if (!scrubbing) {
121      scrubberLeft = playedBar.right - scrubber.getWidth() / 2;
122    }
123    invalidate();
124  }
125
126  /**
127   * @return the preferred height of this view, including invisible padding
128   */
129  public int getPreferredHeight() {
130    return timeBounds.height() + vPaddingInPx + scrubberPadding;
131  }
132
133  /**
134   * @return the height of the time bar, excluding invisible padding
135   */
136  public int getBarHeight() {
137    return timeBounds.height() + vPaddingInPx;
138  }
139
140  public void setTime(int currentTime, int totalTime) {
141    if (this.currentTime == currentTime && this.totalTime == totalTime) {
142        return;
143    }
144    this.currentTime = currentTime;
145    this.totalTime = totalTime;
146    update();
147  }
148
149  public void setShowTimes(boolean showTimes) {
150    this.showTimes = showTimes;
151    requestLayout();
152  }
153
154  public void resetTime() {
155    setTime(0, 0);
156  }
157
158  public void setShowScrubber(boolean showScrubber) {
159    this.showScrubber = showScrubber;
160    if (!showScrubber && scrubbing) {
161      listener.onScrubbingEnd(getScrubberTime());
162      scrubbing = false;
163    }
164    requestLayout();
165  }
166
167  private boolean inScrubber(float x, float y) {
168    int scrubberRight = scrubberLeft + scrubber.getWidth();
169    int scrubberBottom = scrubberTop + scrubber.getHeight();
170    return scrubberLeft - scrubberPadding < x && x < scrubberRight + scrubberPadding
171        && scrubberTop - scrubberPadding < y && y < scrubberBottom + scrubberPadding;
172  }
173
174  private void clampScrubber() {
175    int half = scrubber.getWidth() / 2;
176    int max = progressBar.right - half;
177    int min = progressBar.left - half;
178    scrubberLeft = Math.min(max, Math.max(min, scrubberLeft));
179  }
180
181  private int getScrubberTime() {
182    return (int) ((long) (scrubberLeft + scrubber.getWidth() / 2 - progressBar.left)
183        * totalTime / progressBar.width());
184  }
185
186  @Override
187  protected void onLayout(boolean changed, int l, int t, int r, int b) {
188    int w = r - l;
189    int h = b - t;
190    if (!showTimes && !showScrubber) {
191      progressBar.set(0, 0, w, h);
192    } else {
193      int margin = scrubber.getWidth() / 3;
194      if (showTimes) {
195        margin += timeBounds.width();
196      }
197      int progressY = (h + scrubberPadding) / 2;
198      scrubberTop = progressY - scrubber.getHeight() / 2 + 1;
199      progressBar.set(
200          getPaddingLeft() + margin, progressY,
201          w - getPaddingRight() - margin, progressY + 4);
202    }
203    update();
204  }
205
206  @Override
207  public void draw(Canvas canvas) {
208    super.draw(canvas);
209
210    // draw progress bars
211    canvas.drawRect(progressBar, progressPaint);
212    canvas.drawRect(playedBar, playedPaint);
213
214    // draw scrubber and timers
215    if (showScrubber) {
216      canvas.drawBitmap(scrubber, scrubberLeft, scrubberTop, null);
217    }
218    if (showTimes) {
219      canvas.drawText(
220          stringForTime(currentTime),
221          timeBounds.width() / 2 + getPaddingLeft(),
222          timeBounds.height() + vPaddingInPx / 2 + scrubberPadding + 1,
223          timeTextPaint);
224      canvas.drawText(
225          stringForTime(totalTime),
226          getWidth() - getPaddingRight() - timeBounds.width() / 2,
227          timeBounds.height() + vPaddingInPx / 2 + scrubberPadding + 1,
228          timeTextPaint);
229    }
230  }
231
232  @Override
233  public boolean onTouchEvent(MotionEvent event) {
234
235    if (showScrubber) {
236      int x = (int) event.getX();
237      int y = (int) event.getY();
238
239      switch (event.getAction()) {
240        case MotionEvent.ACTION_DOWN:
241          if (inScrubber(x, y)) {
242            scrubbing = true;
243            scrubberCorrection = x - scrubberLeft;
244            listener.onScrubbingStart();
245            return true;
246          }
247          break;
248        case MotionEvent.ACTION_MOVE:
249          if (scrubbing) {
250            scrubberLeft = x - scrubberCorrection;
251            clampScrubber();
252            currentTime = getScrubberTime();
253            listener.onScrubbingMove(currentTime);
254            invalidate();
255            return true;
256          }
257          break;
258        case MotionEvent.ACTION_UP:
259          if (scrubbing) {
260            listener.onScrubbingEnd(getScrubberTime());
261            scrubbing = false;
262            return true;
263          }
264          break;
265      }
266    }
267    return false;
268  }
269
270  private String stringForTime(long millis) {
271    int totalSeconds = (int) millis / 1000;
272    int seconds = totalSeconds % 60;
273    int minutes = (totalSeconds / 60) % 60;
274    int hours = totalSeconds / 3600;
275    if (hours > 0) {
276      return String.format("%d:%02d:%02d", hours, minutes, seconds).toString();
277    } else {
278      return String.format("%02d:%02d", minutes, seconds).toString();
279    }
280  }
281
282}
283