CoverView improvements

- Switch to next song immediately when fling is started. Makes response
  faster, but can also cause a minor delay on slower devices. Setting
  a lower priority on worker threads helps with this a bit.
- Simplify scrolling code. No longer uses View's scrolling code
- Remove invalidateCovers(). There is no advantage to using this since
  the view has no children.
- Various minor tweaks
This commit is contained in:
Christopher Eby 2012-02-25 17:10:03 -06:00
parent 7b37db261c
commit eea5deceb6
3 changed files with 43 additions and 84 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2010, 2011 Christopher Eby <kreed@kreed.org>
* Copyright (C) 2012 Christopher Eby <kreed@kreed.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -48,9 +48,9 @@ public final class CoverView extends View implements Handler.Callback {
*/
private static int sSnapVelocity = -1;
/**
* Display metrics, used for scaling.
* The screen density, from {@link DisplayMetrics#density}.
*/
private static DisplayMetrics sDisplayMetrics;
private static double sDensity = -1;
/**
* The Handler with which to do background work. Will be null until
* setupHandler is called.
@ -70,7 +70,7 @@ public final class CoverView extends View implements Handler.Callback {
*/
public interface Callback {
/**
* Called after the view has scrolled to the next or previous cover.
* Called after the view has scrolled to the previous or next cover.
*
* @param delta -1 for the previous cover, 1 for the next.
*/
@ -80,7 +80,7 @@ public final class CoverView extends View implements Handler.Callback {
*/
public void upSwipe();
/**
* Called when the user had swiped down on the view.
* Called when the user has swiped down on the view.
*/
public void downSwipe();
}
@ -124,11 +124,6 @@ public final class CoverView extends View implements Handler.Callback {
* 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.
*/
@ -139,10 +134,12 @@ public final class CoverView extends View implements Handler.Callback {
*/
private boolean mPendingQuery;
/**
* If true, calls to invalidate() will do nothing. We use this so we can
* only invalidate the dirty rect during scrolling.
* The current x scroll position of the view.
*
* Scrolling code from {@link View} is not used for this class since many of
* its features are not required.
*/
private boolean mSuppressInvalidate;
private int mScrollX;
/**
* Constructor intended to be called by inflating from XML.
@ -155,7 +152,7 @@ public final class CoverView extends View implements Handler.Callback {
if (sSnapVelocity == -1) {
sSnapVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
sDisplayMetrics = context.getResources().getDisplayMetrics();
sDensity = context.getResources().getDisplayMetrics().density;
}
}
@ -181,7 +178,8 @@ public final class CoverView extends View implements Handler.Callback {
{
if (!mScroller.isFinished())
mScroller.abortAnimation();
scrollTo(getWidth(), 0);
mScrollX = getWidth();
invalidate();
}
@Override
@ -202,7 +200,7 @@ public final class CoverView extends View implements Handler.Callback {
int width = getWidth();
int height = getHeight();
int x = 0;
int scrollX = getScrollX();
int scrollX = mScrollX;
canvas.drawColor(Color.BLACK);
@ -210,7 +208,7 @@ public final class CoverView extends View implements Handler.Callback {
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);
canvas.drawBitmap(bitmap, x + xOffset - scrollX, yOffset, null);
}
x += width;
}
@ -232,7 +230,7 @@ public final class CoverView extends View implements Handler.Callback {
float x = ev.getX();
float y = ev.getY();
int scrollX = getScrollX();
int scrollX = mScrollX;
int width = getWidth();
switch (ev.getAction()) {
@ -254,12 +252,16 @@ public final class CoverView extends View implements Handler.Callback {
if (Math.abs(deltaX) > Math.abs(deltaY)) {
if (deltaX < 0) {
int availableToScroll = scrollX - (mSongs[0] == null ? width : 0);
if (availableToScroll > 0)
scrollBy(Math.max(-availableToScroll, (int)deltaX), 0);
if (availableToScroll > 0) {
mScrollX += Math.max(-availableToScroll, (int)deltaX);
invalidate();
}
} else if (deltaX > 0) {
int availableToScroll = width * 2 - scrollX;
if (availableToScroll > 0)
scrollBy(Math.min(availableToScroll, (int)deltaX), 0);
if (availableToScroll > 0) {
mScrollX += Math.min(availableToScroll, (int)deltaX);
invalidate();
}
}
}
@ -277,10 +279,11 @@ public final class CoverView extends View implements Handler.Callback {
int mvx = Math.abs(velocityX);
int mvy = Math.abs(velocityY);
int min = mSongs[0] == null ? 1 : 0;
int max = 2;
int whichCover = 1;
// If -1 or 1, play the previous or next song, respectively and scroll
// to that song's cover. If 0, just scroll back to current song's cover.
int whichCover = 0;
int min = mSongs[0] == null ? 0 : -1;
int max = 1;
if (Math.abs(mStartX - x) + Math.abs(mStartY - y) < 10) {
// A long press was performed and thus the normal action should
@ -289,7 +292,6 @@ public final class CoverView extends View implements Handler.Callback {
mIgnoreNextUp = false;
else
performClick();
whichCover = 1;
} else if (mvx > sSnapVelocity || mvy > sSnapVelocity) {
if (mvy > mvx) {
if (velocityY > 0)
@ -303,16 +305,18 @@ public final class CoverView extends View implements Handler.Callback {
whichCover = max;
}
} else {
int nearestCover = (scrollX + width / 2) / width;
int nearestCover = (scrollX + width / 2) / width - 1;
whichCover = Math.max(min, Math.min(nearestCover, max));
}
int newX = whichCover * width;
int delta = newX - scrollX;
mScroller.startScroll(scrollX, 0, delta, 0, (int)(Math.abs(delta) * 2 / sDisplayMetrics.density));
if (whichCover != 1)
mTentativeCover = whichCover;
if (whichCover != 0) {
scrollX = scrollX - width * whichCover;
mCallback.shiftCurrentSong(whichCover);
mScrollX = scrollX;
}
int delta = width - scrollX;
mScroller.startScroll(scrollX, 0, delta, 0, (int)(Math.abs(delta) * 2 / sDensity));
mUiHandler.sendEmptyMessage(MSG_SCROLL);
if (mVelocityTracker != null) {
@ -406,7 +410,6 @@ public final class CoverView extends View implements Handler.Callback {
}
resetScroll();
invalidate();
}
/**
@ -444,19 +447,9 @@ public final class CoverView extends View implements Handler.Callback {
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();
mScrollX = mScroller.getCurrX();
invalidate();
mUiHandler.sendEmptyMessage(MSG_SCROLL);
} else if (mTentativeCover != -1) {
mCallback.shiftCurrentSong(mTentativeCover - 1);
mTentativeCover = -1;
resetScroll();
}
break;
default:
@ -486,40 +479,4 @@ 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);
}
}

View File

@ -31,6 +31,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.SystemClock;
import android.view.ContextMenu;
import android.view.KeyEvent;
@ -87,7 +88,7 @@ public class PlaybackActivity extends Activity
setVolumeControlStream(AudioManager.STREAM_MUSIC);
HandlerThread thread = new HandlerThread(getClass().getName());
HandlerThread thread = new HandlerThread(getClass().getName(), Process.THREAD_PRIORITY_LOWEST);
thread.start();
mLooper = thread.getLooper();

View File

@ -50,6 +50,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.Process;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
@ -371,7 +372,7 @@ public final class PlaybackService extends Service
@Override
public void onCreate()
{
HandlerThread thread = new HandlerThread("PlaybackService");
HandlerThread thread = new HandlerThread("PlaybackService", Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
mTimeline = new SongTimeline(this);