Optimize CoverView scrolling by only redrawing the dirty region
This commit is contained in:
parent
aadbf2c9e2
commit
c880907a03
@ -41,28 +41,50 @@ import android.widget.Scroller;
|
||||
* generated by CoverBitmap.
|
||||
*/
|
||||
public final class CoverView extends View implements Handler.Callback {
|
||||
/**
|
||||
* The system-provided snap velocity, used as a threshold for detecting
|
||||
* flings.
|
||||
*/
|
||||
private static int sSnapVelocity = -1;
|
||||
|
||||
/**
|
||||
* The Handler with which to do background work. Will be null until
|
||||
* setupHandler is called.
|
||||
*/
|
||||
private Handler mHandler;
|
||||
/**
|
||||
* A handler running on the UI thread, for UI operations.
|
||||
*/
|
||||
private final Handler mUiHandler = new Handler(this);
|
||||
/**
|
||||
* How to render cover art and metadata. One of
|
||||
* CoverBitmap.STYLE_*
|
||||
*/
|
||||
private int mCoverStyle;
|
||||
|
||||
/**
|
||||
* Interface to respond to CoverView motion actions.
|
||||
*/
|
||||
public interface Callback {
|
||||
/**
|
||||
* Called after the view has scrolled to the next (right) cover.
|
||||
*/
|
||||
public void nextSong();
|
||||
/**
|
||||
* Called after the view has scrolled to the previous (left) cover.
|
||||
*/
|
||||
public void previousSong();
|
||||
/**
|
||||
* Called when the user has swiped up on the view.
|
||||
*/
|
||||
public void upSwipe();
|
||||
/**
|
||||
* Called when the user had swiped down on the view.
|
||||
*/
|
||||
public void downSwipe();
|
||||
}
|
||||
|
||||
/**
|
||||
* The instance of the callback.
|
||||
*/
|
||||
private Callback mCallback;
|
||||
|
||||
/**
|
||||
* The current set of songs: 0 = previous, 1 = current, and 2 = next.
|
||||
*/
|
||||
@ -79,24 +101,49 @@ public final class CoverView extends View implements Handler.Callback {
|
||||
* Cover art to use when a song has no cover art in no info display styles.
|
||||
*/
|
||||
private Bitmap mDefaultCover;
|
||||
|
||||
/**
|
||||
* Computes scroll animations.
|
||||
*/
|
||||
private final Scroller mScroller;
|
||||
/**
|
||||
* Computes scroll velocity to detect flings.
|
||||
*/
|
||||
private VelocityTracker mVelocityTracker;
|
||||
/**
|
||||
* The x coordinate of the last touch down or move event.
|
||||
*/
|
||||
private float mLastMotionX;
|
||||
/**
|
||||
* The y coordinate of the last touch down or move event.
|
||||
*/
|
||||
private float mLastMotionY;
|
||||
/**
|
||||
* The x coordinate of the last touch down event.
|
||||
*/
|
||||
private float mStartX;
|
||||
/**
|
||||
* The y coordinate of the last touch down event.
|
||||
*/
|
||||
private float mStartY;
|
||||
/**
|
||||
* The index of the cover that is being scrolled to during a fling
|
||||
* animation, or -1 if the cover is the active (middle) cover.
|
||||
*/
|
||||
private int mTentativeCover = -1;
|
||||
/**
|
||||
* Ignore the next pointer up event, for long presses.
|
||||
*/
|
||||
private boolean mIgnoreNextUp;
|
||||
|
||||
/**
|
||||
* If true, querySongs was called before the view initialized and should
|
||||
* be called when initialization finishes.
|
||||
*/
|
||||
private boolean mPendingQuery;
|
||||
/**
|
||||
* If true, calls to invalidate() will do nothing. We use this so we can
|
||||
* only invalidate the dirty rect during scrolling.
|
||||
*/
|
||||
private boolean mSuppressInvalidate;
|
||||
|
||||
/**
|
||||
* Constructor intended to be called by inflating from XML.
|
||||
@ -151,21 +198,20 @@ public final class CoverView extends View implements Handler.Callback {
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas)
|
||||
{
|
||||
super.onDraw(canvas);
|
||||
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
int x = 0;
|
||||
int scrollX = getScrollX();
|
||||
|
||||
canvas.drawColor(Color.BLACK);
|
||||
|
||||
for (int x = 0, i = 0; i != 3; ++i, x += width) {
|
||||
Bitmap bitmap = mBitmaps[i];
|
||||
for (Bitmap bitmap : mBitmaps) {
|
||||
if (bitmap != null && scrollX + width > x && scrollX < x + width) {
|
||||
int xOffset = (width - bitmap.getWidth()) / 2;
|
||||
int yOffset = (height - bitmap.getHeight()) / 2;
|
||||
canvas.drawBitmap(bitmap, x + xOffset, yOffset, null);
|
||||
}
|
||||
x += width;
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,7 +244,7 @@ public final class CoverView extends View implements Handler.Callback {
|
||||
mLastMotionX = x;
|
||||
mLastMotionY = y;
|
||||
|
||||
mHandler.sendEmptyMessageDelayed(MSG_LONG_CLICK, ViewConfiguration.getLongPressTimeout());
|
||||
mUiHandler.sendEmptyMessageDelayed(MSG_LONG_CLICK, ViewConfiguration.getLongPressTimeout());
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
float deltaX = mLastMotionX - x;
|
||||
@ -221,7 +267,7 @@ public final class CoverView extends View implements Handler.Callback {
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_UP: {
|
||||
mHandler.removeMessages(MSG_LONG_CLICK);
|
||||
mUiHandler.removeMessages(MSG_LONG_CLICK);
|
||||
|
||||
VelocityTracker velocityTracker = mVelocityTracker;
|
||||
velocityTracker.computeCurrentVelocity(250);
|
||||
@ -266,7 +312,7 @@ public final class CoverView extends View implements Handler.Callback {
|
||||
if (whichCover != 1)
|
||||
mTentativeCover = whichCover;
|
||||
|
||||
postInvalidate();
|
||||
mUiHandler.sendEmptyMessage(MSG_SCROLL);
|
||||
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
@ -279,28 +325,6 @@ public final class CoverView extends View implements Handler.Callback {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update position for fling scroll animation and, when it is finished,
|
||||
* notify PlaybackService that the user has requested a track change and
|
||||
* update the cover art views.
|
||||
*/
|
||||
@Override
|
||||
public void computeScroll()
|
||||
{
|
||||
if (mScroller.computeScrollOffset()) {
|
||||
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
|
||||
postInvalidate();
|
||||
} else if (mTentativeCover != -1) {
|
||||
int delta = mTentativeCover - 1;
|
||||
mTentativeCover = -1;
|
||||
if (delta == 1)
|
||||
mCallback.nextSong();
|
||||
else
|
||||
mCallback.previousSong();
|
||||
resetScroll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a bitmap for the given song if the cache does not contain one
|
||||
* for it, or moves the bitmap to the top of the cache if it does.
|
||||
@ -390,6 +414,13 @@ public final class CoverView extends View implements Handler.Callback {
|
||||
* @see View#performLongClick()
|
||||
*/
|
||||
private static final int MSG_LONG_CLICK = 2;
|
||||
/**
|
||||
* Update position for fling scroll animation and, when it is finished,
|
||||
* notify PlaybackService that the user has requested a track change and
|
||||
* update the cover art views. Will resend message until scrolling is
|
||||
* finished.
|
||||
*/
|
||||
private static final int MSG_SCROLL = 3;
|
||||
|
||||
public boolean handleMessage(Message message)
|
||||
{
|
||||
@ -403,6 +434,27 @@ public final class CoverView extends View implements Handler.Callback {
|
||||
performLongClick();
|
||||
}
|
||||
break;
|
||||
case MSG_SCROLL:
|
||||
if (mScroller.computeScrollOffset()) {
|
||||
// scrollTo calls invalidate(), however, we want to invalidate
|
||||
// only the region where the covers are drawn, so we need to
|
||||
// suppress this call.
|
||||
mSuppressInvalidate = true;
|
||||
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
|
||||
mSuppressInvalidate = false;
|
||||
|
||||
invalidateCovers();
|
||||
mUiHandler.sendEmptyMessage(MSG_SCROLL);
|
||||
} else if (mTentativeCover != -1) {
|
||||
int delta = mTentativeCover - 1;
|
||||
mTentativeCover = -1;
|
||||
if (delta == 1)
|
||||
mCallback.nextSong();
|
||||
else
|
||||
mCallback.previousSong();
|
||||
resetScroll();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -430,4 +482,40 @@ public final class CoverView extends View implements Handler.Callback {
|
||||
setMeasuredDimension(size, size);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridden to allow redraws to be suppressed.
|
||||
*/
|
||||
@Override
|
||||
public void invalidate()
|
||||
{
|
||||
if (!mSuppressInvalidate)
|
||||
super.invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call {@link View#invalidate(int,int,int,int)} with the area
|
||||
* containing the visible cover(s).
|
||||
*/
|
||||
public void invalidateCovers()
|
||||
{
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
int scrollX = getScrollX();
|
||||
int x = 0;
|
||||
int maxHeight = 0;
|
||||
|
||||
for (Bitmap bitmap : mBitmaps) {
|
||||
if (bitmap != null && scrollX + width > x && scrollX < x + width) {
|
||||
int bitmapHeight = bitmap.getHeight();
|
||||
if (bitmapHeight > maxHeight) {
|
||||
maxHeight = bitmapHeight;
|
||||
}
|
||||
}
|
||||
x += width;
|
||||
}
|
||||
|
||||
int offset = (height - maxHeight) / 2;
|
||||
invalidate(scrollX, offset, scrollX + width, height - offset);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user