blob: c07500e4fee1685f240fb4fc036a393833ae66c9 [file] [log] [blame]
/*
* Copyright (C) 2011 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.gallery3d.app;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import com.android.gallery3d.R;
import com.android.gallery3d.common.Utils;
import java.util.Locale;
/**
* The time bar view, which includes the current and total time, the progress
* bar, and the scrubber.
*/
public class TimeBar extends View {
public interface Listener {
void onScrubbingStart();
void onScrubbingMove(int time);
void onScrubbingEnd(int time, int start, int end);
}
// Padding around the scrubber to increase its touch target
private static final int SCRUBBER_PADDING_IN_DP = 10;
// The total padding, top plus bottom
private static final int V_PADDING_IN_DP = 30;
private static final int TEXT_SIZE_IN_DP = 14;
private static final String TAG = "Gallery3D/TimeBar";
private static final boolean LOG = true;
public static final int UNKNOWN = -1;
protected final Listener mListener;
// the bars we use for displaying the progress
protected final Rect mProgressBar;
protected final Rect mPlayedBar;
protected final Paint mProgressPaint;
protected final Paint mPlayedPaint;
protected final Paint mTimeTextPaint;
protected final Bitmap mScrubber;
protected int mScrubberPadding; // adds some touch tolerance around the
// scrubber
protected int mScrubberLeft;
protected int mScrubberTop;
protected int mScrubberCorrection;
protected boolean mScrubbing;
protected boolean mShowTimes;
protected boolean mShowScrubber;
private boolean mEnableScrubbing;
protected int mTotalTime;
protected int mCurrentTime;
protected final Rect mTimeBounds;
protected int mVPaddingInPx;
private int mLastShowTime = UNKNOWN;
private ITimeBarSecondaryProgressExt mSecondaryProgressExt = new TimeBarSecondaryProgressExtImpl();
private ITimeBarInfoExt mInfoExt = new TimeBarInfoExtImpl();
private ITimeBarLayoutExt mLayoutExt = new TimeBarLayoutExtImpl();
public TimeBar(Context context, Listener listener) {
super(context);
mListener = Utils.checkNotNull(listener);
mShowTimes = true;
mShowScrubber = true;
mProgressBar = new Rect();
mPlayedBar = new Rect();
mProgressPaint = new Paint();
mProgressPaint.setColor(0xFF808080);
mPlayedPaint = new Paint();
mPlayedPaint.setColor(0xFFFFFFFF);
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
float textSizeInPx = metrics.density * TEXT_SIZE_IN_DP;
mTimeTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTimeTextPaint.setColor(0xFFCECECE);
mTimeTextPaint.setTextSize(textSizeInPx);
mTimeTextPaint.setTextAlign(Paint.Align.CENTER);
mTimeBounds = new Rect();
mScrubber = BitmapFactory.decodeResource(getResources(), R.drawable.scrubber_knob);
mScrubberPadding = (int) (metrics.density * SCRUBBER_PADDING_IN_DP);
mVPaddingInPx = (int) (metrics.density * V_PADDING_IN_DP);
mLayoutExt.init(mScrubberPadding, mVPaddingInPx);
mInfoExt.init(textSizeInPx);
mSecondaryProgressExt.init();
}
private void update() {
mPlayedBar.set(mProgressBar);
if (mTotalTime > 0) {
if (View.LAYOUT_DIRECTION_RTL == TextUtils
.getLayoutDirectionFromLocale(Locale.getDefault())) {
// The progress bar should be reversed in RTL.
mPlayedBar.left = mPlayedBar.right
- (int) ((mProgressBar.width() * (long) mCurrentTime) / mTotalTime);
} else {
mPlayedBar.right = mPlayedBar.left
+ (int) ((mProgressBar.width() * (long) mCurrentTime) / mTotalTime);
}
/*
* M: if duration is not accurate, here just adjust playedBar we
* also show the accurate position text to final user.
*/
if (mPlayedBar.right > mProgressBar.right) {
mPlayedBar.right = mProgressBar.right;
}
} else {
if (View.LAYOUT_DIRECTION_RTL == TextUtils
.getLayoutDirectionFromLocale(Locale.getDefault())) {
// The progress bar should be reversed in RTL.
mPlayedBar.left = mProgressBar.right;
} else {
mPlayedBar.right = mProgressBar.left;
}
}
if (!mScrubbing) {
if (View.LAYOUT_DIRECTION_RTL == TextUtils.getLayoutDirectionFromLocale(
Locale.getDefault())) {
// The progress bar should be reversed in RTL.
mScrubberLeft = mPlayedBar.left - mScrubber.getWidth() / 2;
} else {
mScrubberLeft = mPlayedBar.right - mScrubber.getWidth() / 2;
}
}
// update text bounds when layout changed or time changed
updateBounds();
mInfoExt.updateVisibleText(this, mProgressBar, mTimeBounds);
invalidate();
}
/**
* @return the preferred height of this view, including invisible padding
*/
public int getPreferredHeight() {
int preferredHeight = mTimeBounds.height() + mVPaddingInPx + mScrubberPadding;
return mLayoutExt.getPreferredHeight(preferredHeight, mTimeBounds);
}
/**
* @return the height of the time bar, excluding invisible padding
*/
public int getBarHeight() {
int barHeight = mTimeBounds.height() + mVPaddingInPx;
return mLayoutExt.getBarHeight(barHeight, mTimeBounds);
}
public void setTime(int currentTime, int totalTime,
int trimStartTime, int trimEndTime) {
if (mCurrentTime == currentTime && mTotalTime == totalTime) {
return;
}
mCurrentTime = currentTime;
mTotalTime = Math.abs(totalTime);
if (totalTime <= 0) { /// M: disable scrubbing before mediaplayer ready.
setScrubbing(false);
}
update();
}
private boolean inScrubber(float x, float y) {
int scrubberRight = mScrubberLeft + mScrubber.getWidth();
int scrubberBottom = mScrubberTop + mScrubber.getHeight();
return mScrubberLeft - mScrubberPadding < x && x < scrubberRight + mScrubberPadding
&& mScrubberTop - mScrubberPadding < y && y < scrubberBottom + mScrubberPadding;
}
private void clampScrubber() {
int half = mScrubber.getWidth() / 2;
int max = mProgressBar.right - half;
int min = mProgressBar.left - half;
mScrubberLeft = Math.min(max, Math.max(min, mScrubberLeft));
}
private int getScrubberTime() {
if (View.LAYOUT_DIRECTION_RTL == TextUtils
.getLayoutDirectionFromLocale(Locale.getDefault())) {
// The progress bar's scrubber time should be reversed in RTL.
return (int) ((long) (mProgressBar.width() - (mScrubberLeft
+ mScrubber.getWidth() / 2 - mProgressBar.left))
* mTotalTime / mProgressBar.width());
} else {
return (int) ((long) (mScrubberLeft + mScrubber.getWidth() / 2 - mProgressBar.left)
* mTotalTime / mProgressBar.width());
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int w = r - l;
int h = b - t;
if (!mShowTimes && !mShowScrubber) {
mProgressBar.set(0, 0, w, h);
} else {
int margin = mScrubber.getWidth() / 3;
if (mShowTimes) {
margin += mTimeBounds.width();
}
margin = mLayoutExt.getProgressMargin(margin);
int progressY = (h + mScrubberPadding) / 2 + mLayoutExt.getProgressOffset(mTimeBounds);
mScrubberTop = progressY - mScrubber.getHeight() / 2 + 1;
mProgressBar.set(
getPaddingLeft() + margin, progressY,
w - getPaddingRight() - margin, progressY + 4);
}
update();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// draw progress bars
canvas.drawRect(mProgressBar, mProgressPaint);
mSecondaryProgressExt.draw(canvas, mProgressBar);
canvas.drawRect(mPlayedBar, mPlayedPaint);
// draw scrubber and timers
if (mShowScrubber) {
canvas.drawBitmap(mScrubber, mScrubberLeft, mScrubberTop, null);
}
if (mShowTimes) {
if (View.LAYOUT_DIRECTION_RTL == TextUtils
.getLayoutDirectionFromLocale(Locale.getDefault())) {
// The progress bar's time should be reversed in RTL.
canvas.drawText(
stringForTime(mCurrentTime),
getWidth() - getPaddingRight() - mTimeBounds.width() / 2,
mTimeBounds.height() + mVPaddingInPx / 2 + mScrubberPadding + 1,
mTimeTextPaint);
canvas.drawText(
stringForTime(mTotalTime),
mTimeBounds.width() / 2 + getPaddingLeft(),
mTimeBounds.height() + mVPaddingInPx / 2 + mScrubberPadding + 1,
mTimeTextPaint);
} else {
canvas.drawText(
stringForTime(mCurrentTime),
mTimeBounds.width() / 2 + getPaddingLeft(),
mTimeBounds.height() + mVPaddingInPx / 2 + mScrubberPadding + 1,
mTimeTextPaint);
canvas.drawText(
stringForTime(mTotalTime),
getWidth() - getPaddingRight() - mTimeBounds.width() / 2,
mTimeBounds.height() + mVPaddingInPx / 2 + mScrubberPadding + 1,
mTimeTextPaint);
}
}
mInfoExt.draw(canvas, mLayoutExt.getInfoBounds(this, mTimeBounds));
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (LOG) {
Log.v(TAG, "onTouchEvent() showScrubber=" + mShowScrubber
+ ", enableScrubbing=" + mEnableScrubbing + ", totalTime="
+ mTotalTime + ", scrubbing=" + mScrubbing + ", event="
+ event);
}
if (mShowScrubber && mEnableScrubbing) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
mScrubberCorrection = inScrubber(x, y)
? x - mScrubberLeft
: mScrubber.getWidth() / 2;
mScrubbing = true;
mListener.onScrubbingStart();
}
// fall-through
case MotionEvent.ACTION_MOVE: {
mScrubberLeft = x - mScrubberCorrection;
clampScrubber();
mCurrentTime = getScrubberTime();
mListener.onScrubbingMove(mCurrentTime);
update();
invalidate();
return true;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (mScrubbing) {
mListener.onScrubbingEnd(getScrubberTime(), 0, 0);
mScrubbing = false;
update();
return true;
}
break;
}
}
return false;
}
protected String stringForTime(long millis) {
int totalSeconds = (int) millis / 1000;
int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
int hours = totalSeconds / 3600;
if (hours > 0) {
return String.format("%d:%02d:%02d", hours, minutes, seconds).toString();
} else {
return String.format("%02d:%02d", minutes, seconds).toString();
}
}
public void setSeekable(boolean canSeek) {
mShowScrubber = canSeek;
}
private void updateBounds() {
int showTime = mTotalTime > mCurrentTime ? mTotalTime : mCurrentTime;
if (mLastShowTime == showTime) {
// do not need to recompute the bounds.
return;
}
String durationText = stringForTime(showTime);
int length = durationText.length();
mTimeTextPaint.getTextBounds(durationText, 0, length, mTimeBounds);
mLastShowTime = showTime;
if (LOG) {
Log.v(TAG, "updateBounds() durationText=" + durationText + ", timeBounds="
+ mTimeBounds);
}
}
public void setScrubbing(boolean enable) {
if (LOG) {
Log.v(TAG, "setScrubbing(" + enable + ") scrubbing=" + mScrubbing);
}
mEnableScrubbing = enable;
if (mScrubbing) { // if it is scrubbing, change it to false
mListener.onScrubbingEnd(getScrubberTime(), 0, 0);
mScrubbing = false;
}
}
public boolean getScrubbing() {
if (LOG) {
Log.v(TAG, "mEnableScrubbing=" + mEnableScrubbing);
}
return mEnableScrubbing;
}
public void setInfo(String info) {
if (LOG) {
Log.v(TAG, "setInfo(" + info + ")");
}
mInfoExt.setInfo(info);
mInfoExt.updateVisibleText(this, mProgressBar, mTimeBounds);
invalidate();
}
public void setSecondaryProgress(int percent) {
if (LOG) {
Log.v(TAG, "setSecondaryProgress(" + percent + ")");
}
mSecondaryProgressExt.setSecondaryProgress(mProgressBar, percent);
invalidate();
}
}
interface ITimeBarInfoExt {
void init(float textSizeInPx);
void setInfo(String info);
void draw(Canvas canvas, Rect infoBounds);
void updateVisibleText(View parent, Rect progressBar, Rect timeBounds);
}
interface ITimeBarSecondaryProgressExt {
void init();
void setSecondaryProgress(Rect progressBar, int percent);
void draw(Canvas canvas, Rect progressBounds);
}
interface ITimeBarLayoutExt {
void init(int scrubberPadding, int vPaddingInPx);
int getPreferredHeight(int originalPreferredHeight, Rect timeBounds);
int getBarHeight(int originalBarHeight, Rect timeBounds);
int getProgressMargin(int originalMargin);
int getProgressOffset(Rect timeBounds);
int getTimeOffset();
Rect getInfoBounds(View parent, Rect timeBounds);
}
class TimeBarInfoExtImpl implements ITimeBarInfoExt {
private static final String TAG = "TimeBarInfoExtensionImpl";
private static final boolean LOG = true;
private static final String ELLIPSE = "...";
private Paint mInfoPaint;
private Rect mInfoBounds;
private String mInfoText;
private String mVisibleText;
private int mEllipseLength;
@Override
public void init(float textSizeInPx) {
mInfoPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mInfoPaint.setColor(0xFFCECECE);
mInfoPaint.setTextSize(textSizeInPx);
mInfoPaint.setTextAlign(Paint.Align.CENTER);
mEllipseLength = (int) Math.ceil(mInfoPaint.measureText(ELLIPSE));
}
@Override
public void draw(Canvas canvas, Rect infoBounds) {
if (mInfoText != null && mVisibleText != null) {
canvas.drawText(mVisibleText, infoBounds.centerX(), infoBounds.centerY(), mInfoPaint);
}
}
@Override
public void setInfo(String info) {
mInfoText = info;
}
public void updateVisibleText(View parent, Rect progressBar, Rect timeBounds) {
if (mInfoText == null) {
mVisibleText = null;
return;
}
float tw = mInfoPaint.measureText(mInfoText);
float space = progressBar.width() - timeBounds.width() * 2 - parent.getPaddingLeft()
- parent.getPaddingRight();
if (tw > 0 && space > 0 && tw > space) {
// we need to cut the info text for visible
float originalNum = mInfoText.length();
int realNum = (int) ((space - mEllipseLength) * originalNum / tw);
if (LOG) {
Log.v(TAG, "updateVisibleText() infoText=" + mInfoText + " text width=" + tw
+ ", space=" + space + ", originalNum=" + originalNum + ", realNum="
+ realNum
+ ", getPaddingLeft()=" + parent.getPaddingLeft() + ", getPaddingRight()="
+ parent.getPaddingRight()
+ ", progressBar=" + progressBar + ", timeBounds=" + timeBounds);
}
mVisibleText = mInfoText.substring(0, realNum) + ELLIPSE;
} else {
mVisibleText = mInfoText;
}
if (LOG) {
Log.v(TAG, "updateVisibleText() infoText=" + mInfoText + ", visibleText="
+ mVisibleText
+ ", text width=" + tw + ", space=" + space);
}
}
}
class TimeBarSecondaryProgressExtImpl implements ITimeBarSecondaryProgressExt {
private static final String TAG = "TimeBarSecondaryProgressExtensionImpl";
private static final boolean LOG = true;
private int mBufferPercent;
private Rect mSecondaryBar;
private Paint mSecondaryPaint;
@Override
public void init() {
mSecondaryBar = new Rect();
mSecondaryPaint = new Paint();
mSecondaryPaint.setColor(0xFF5CA0C5);
}
@Override
public void draw(Canvas canvas, Rect progressBounds) {
if (mBufferPercent >= 0) {
mSecondaryBar.set(progressBounds);
mSecondaryBar.right = mSecondaryBar.left
+ (int) (mBufferPercent * progressBounds.width() / 100);
canvas.drawRect(mSecondaryBar, mSecondaryPaint);
}
if (LOG) {
Log.v(TAG, "draw() bufferPercent=" + mBufferPercent + ", secondaryBar="
+ mSecondaryBar);
}
}
@Override
public void setSecondaryProgress(Rect progressBar, int percent) {
mBufferPercent = percent;
}
}
class TimeBarLayoutExtImpl implements ITimeBarLayoutExt {
private static final String TAG = "TimeBarLayoutExtensionImpl";
private static final boolean LOG = true;
private int mTextPadding;
private int mVPaddingInPx;
@Override
public void init(int scrubberPadding, int vPaddingInPx) {
mTextPadding = scrubberPadding / 2;
mVPaddingInPx = vPaddingInPx;
}
@Override
public int getPreferredHeight(int originalPreferredHeight, Rect timeBounds) {
return originalPreferredHeight + timeBounds.height() + mTextPadding;
}
@Override
public int getBarHeight(int originalBarHeight, Rect timeBounds) {
return originalBarHeight + timeBounds.height() + mTextPadding;
}
@Override
public int getProgressMargin(int originalMargin) {
return 0;
}
@Override
public int getProgressOffset(Rect timeBounds) {
return (timeBounds.height() + mTextPadding) / 2;
}
@Override
public int getTimeOffset() {
return mTextPadding - mVPaddingInPx / 2;
}
@Override
public Rect getInfoBounds(View parent, Rect timeBounds) {
Rect bounds = new Rect(parent.getPaddingLeft(), 0,
parent.getWidth() - parent.getPaddingRight(),
(timeBounds.height() + mTextPadding * 3 + 1) * 2);
return bounds;
}
}