Implement fling and more natural scrolling on ScrollableDataView

pull/30/head
Alinson S. Xavier 10 years ago
commit 6b05004647

@ -294,6 +294,16 @@ public class Habit extends Model
return checks; return checks;
} }
public int[] getAllCheckmarks()
{
Repetition oldestRep = getOldestRep();
if(oldestRep == null) return new int[0];
Long toTimestamp = DateHelper.getStartOfToday();
Long fromTimestamp = oldestRep.timestamp;
return getCheckmarks(fromTimestamp, toTimestamp);
}
public void updateCheckmarks() public void updateCheckmarks()
{ {
long beginning; long beginning;
@ -512,13 +522,40 @@ public class Habit extends Model
return lastScore; return lastScore;
} }
public List<Score> getScores(long fromTimestamp, long toTimestamp, int divisor, long offset) public int[] getScores(Long fromTimestamp, Long toTimestamp, Integer divisor, Long offset)
{ {
return new Select().from(Score.class) String query = "select score from Score where habit = ? and timestamp > ? and " +
.where("habit = ? and timestamp > ? and " + "timestamp <= ? and (timestamp - ?) % ? = 0 order by timestamp desc";
"timestamp <= ? and (timestamp - ?) % ? = 0", getId(), fromTimestamp,
toTimestamp, offset, divisor) String params[] = {getId().toString(), fromTimestamp.toString(), toTimestamp.toString(),
.execute(); offset.toString(), divisor.toString()};
SQLiteDatabase db = Cache.openDatabase();
Cursor c = db.rawQuery(query, params);
if(!c.moveToFirst()) return new int[0];
int k = 0;
int[] scores = new int[c.getCount()];
do
{
scores[k++] = c.getInt(0);
}
while (c.moveToNext());
return scores;
}
public int[] getAllScores(int divisor)
{
Repetition oldestRep = getOldestRep();
if(oldestRep == null) return new int[0];
long fromTimestamp = oldestRep.timestamp;
long toTimestamp = DateHelper.getStartOfToday();
return getScores(fromTimestamp, toTimestamp, divisor, toTimestamp);
} }
public List<Streak> getStreaks() public List<Streak> getStreaks()

@ -126,22 +126,7 @@ public class HabitHistoryView extends ScrollableDataView
protected void fetchData() protected void fetchData()
{ {
Calendar currentDate = new GregorianCalendar(); checkmarks = habit.getAllCheckmarks();
currentDate.add(Calendar.DAY_OF_YEAR, -dataOffset * 7);
int dayOfWeek = currentDate.get(Calendar.DAY_OF_WEEK) % 7;
long dateTo = DateHelper.getStartOfToday();
for (int i = 0; i < 7 - dayOfWeek; i++)
dateTo += DateHelper.millisecondsInOneDay;
for (int i = 0; i < dataOffset * 7; i++)
dateTo -= DateHelper.millisecondsInOneDay;
long dateFrom = dateTo;
for (int i = 0; i < (nColumns - 1) * 7; i++)
dateFrom -= DateHelper.millisecondsInOneDay;
checkmarks = habit.getCheckmarks(dateFrom, dateTo);
updateDate(); updateDate();
} }
@ -160,6 +145,7 @@ public class HabitHistoryView extends ScrollableDataView
previousYear = ""; previousYear = "";
justPrintedYear = false; justPrintedYear = false;
updateDate();
GregorianCalendar currentDate = (GregorianCalendar) baseDate.clone(); GregorianCalendar currentDate = (GregorianCalendar) baseDate.clone();
for (int column = 0; column < nColumns - 1; column++) for (int column = 0; column < nColumns - 1; column++)
@ -180,7 +166,7 @@ public class HabitHistoryView extends ScrollableDataView
{ {
if (!(column == nColumns - 2 && dataOffset == 0 && j > todayWeekday)) if (!(column == nColumns - 2 && dataOffset == 0 && j > todayWeekday))
{ {
int checkmarkOffset = nDays - 7 * column - j; int checkmarkOffset = dataOffset * 7 + nDays - 7 * (column + 1) + todayWeekday - j;
drawSquare(canvas, location, date, checkmarkOffset); drawSquare(canvas, location, date, checkmarkOffset);
} }

@ -25,10 +25,8 @@ import android.graphics.RectF;
import org.isoron.helpers.ColorHelper; import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.DateHelper; import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.models.Score;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.List;
public class HabitScoreView extends ScrollableDataView public class HabitScoreView extends ScrollableDataView
{ {
@ -41,7 +39,7 @@ public class HabitScoreView extends ScrollableDataView
private Paint pText, pGraph; private Paint pText, pGraph;
private int[] colors; private int[] colors;
private List<Score> scores; private int[] scores;
public HabitScoreView(Context context, Habit habit, int columnWidth) public HabitScoreView(Context context, Habit habit, int columnWidth)
{ {
@ -82,17 +80,7 @@ public class HabitScoreView extends ScrollableDataView
protected void fetchData() protected void fetchData()
{ {
scores = habit.getAllScores(BUCKET_SIZE * DateHelper.millisecondsInOneDay);
long toTimestamp = DateHelper.getStartOfToday();
for (int i = 0; i < dataOffset * BUCKET_SIZE; i++)
toTimestamp -= DateHelper.millisecondsInOneDay;
long fromTimestamp = toTimestamp;
for (int i = 0; i < nColumns * BUCKET_SIZE; i++)
fromTimestamp -= DateHelper.millisecondsInOneDay;
scores = habit.getScores(fromTimestamp, toTimestamp,
BUCKET_SIZE * DateHelper.millisecondsInOneDay, toTimestamp);
} }
@Override @Override
@ -114,19 +102,25 @@ public class HabitScoreView extends ScrollableDataView
pGraph.setColor(habit.color); pGraph.setColor(habit.color);
RectF prevR = null; RectF prevR = null;
for (int offset = nColumns - scores.size(); offset < nColumns; offset++) long currentDate = DateHelper.getStartOfToday();
for(int k = 0; k < nColumns + dataOffset - 1; k++)
currentDate -= 7 * DateHelper.millisecondsInOneDay;
for (int k = 0; k < nColumns; k++)
{ {
Score score = scores.get(offset - nColumns + scores.size()); String month = dfMonth.format(currentDate);
String month = dfMonth.format(score.timestamp); String day = dfDay.format(currentDate);
String day = dfDay.format(score.timestamp);
long s = score.score; int score = 0;
double sRelative = ((double) s) / Habit.MAX_SCORE; int offset = nColumns - k - 1 + dataOffset;
if(offset < scores.length) score = scores[offset];
double sRelative = ((double) score) / Habit.MAX_SCORE;
int height = (int) (columnHeight * sRelative); int height = (int) (columnHeight * sRelative);
RectF r = new RectF(0, 0, columnWidth, columnWidth); RectF r = new RectF(0, 0, columnWidth, columnWidth);
r.offset(offset * columnWidth, r.offset(k * columnWidth,
headerHeight + columnHeight - height - columnWidth / 2); headerHeight + columnHeight - height - columnWidth / 2);
if (prevR != null) if (prevR != null)
@ -135,19 +129,19 @@ public class HabitScoreView extends ScrollableDataView
drawMarker(canvas, prevR); drawMarker(canvas, prevR);
} }
if (offset == nColumns - 1) drawMarker(canvas, r); if (k == nColumns - 1) drawMarker(canvas, r);
prevR = r; prevR = r;
r = new RectF(0, 0, columnWidth, columnHeight); r = new RectF(0, 0, columnWidth, columnHeight);
r.offset(offset * columnWidth, headerHeight); r.offset(k * columnWidth, headerHeight);
if (!month.equals(previousMonth)) if (!month.equals(previousMonth))
canvas.drawText(month, r.centerX(), r.bottom + lineHeight * 1.2f, pText); canvas.drawText(month, r.centerX(), r.bottom + lineHeight * 1.2f, pText);
else else
canvas.drawText(day, r.centerX(), r.bottom + lineHeight * 1.2f, pText); canvas.drawText(day, r.centerX(), r.bottom + lineHeight * 1.2f, pText);
previousMonth = month; previousMonth = month;
currentDate += 7 * DateHelper.millisecondsInOneDay;
} }
} }

@ -96,13 +96,14 @@ public class HabitStreakView extends ScrollableDataView
float barHeaderOffset = lineHeight * 0.4f; float barHeaderOffset = lineHeight * 0.4f;
int nStreaks = streaks.size(); int nStreaks = streaks.size();
int start = Math.max(0, nStreaks - nColumns - dataOffset); int start = nStreaks - nColumns - dataOffset;
SimpleDateFormat dfMonth = new SimpleDateFormat("MMM"); SimpleDateFormat dfMonth = new SimpleDateFormat("MMM");
String previousMonth = ""; String previousMonth = "";
for (int offset = 0; offset < nColumns && start + offset < nStreaks; offset++) for (int offset = 0; offset < nColumns && start + offset < nStreaks; offset++)
{ {
if(start + offset < 0) continue;
String month = dfMonth.format(streaks.get(start + offset).start); String month = dfMonth.format(streaks.get(start + offset).start);
long l = streaks.get(offset + start).length; long l = streaks.get(offset + start).length;

@ -19,12 +19,15 @@
package org.isoron.uhabits.views; package org.isoron.uhabits.views;
import android.animation.ValueAnimator;
import android.content.Context; import android.content.Context;
import android.support.v4.view.MotionEventCompat; import android.view.GestureDetector;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.widget.Scroller;
public abstract class ScrollableDataView extends View public abstract class ScrollableDataView extends View implements GestureDetector.OnGestureListener,
ValueAnimator.AnimatorUpdateListener
{ {
protected int dataOffset; protected int dataOffset;
@ -32,74 +35,105 @@ public abstract class ScrollableDataView extends View
protected int columnWidth, columnHeight; protected int columnWidth, columnHeight;
protected int headerHeight, footerHeight; protected int headerHeight, footerHeight;
private float prevX, prevY; private GestureDetector detector;
private Scroller scroller;
private ValueAnimator scrollAnimator;
public ScrollableDataView(Context context) public ScrollableDataView(Context context)
{ {
super(context); super(context);
detector = new GestureDetector(context, this);
scroller = new Scroller(context, null, true);
scrollAnimator = ValueAnimator.ofFloat(0, 1);
scrollAnimator.addUpdateListener(this);
} }
protected abstract void fetchData(); protected abstract void fetchData();
protected boolean move(float dx) @Override
public boolean onTouchEvent(MotionEvent event)
{ {
int newDataOffset = dataOffset + (int) (dx / columnWidth); return detector.onTouchEvent(event);
newDataOffset = Math.max(0, newDataOffset); }
if (newDataOffset != dataOffset) @Override
{ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
dataOffset = newDataOffset; {
fetchData(); super.onMeasure(widthMeasureSpec, heightMeasureSpec);
invalidate(); setMeasuredDimension(getMeasuredWidth(), columnHeight + headerHeight + footerHeight);
return true;
}
else return false;
} }
@Override @Override
public boolean onTouchEvent(MotionEvent event) protected void onSizeChanged(int w, int h, int oldw, int oldh)
{ {
int action = event.getAction(); super.onSizeChanged(w, h, oldw, oldh);
nColumns = w / columnWidth;
fetchData();
}
int pointerIndex = MotionEventCompat.getActionIndex(event); @Override
final float x = MotionEventCompat.getX(event, pointerIndex); public boolean onDown(MotionEvent e)
final float y = MotionEventCompat.getY(event, pointerIndex); {
return true;
}
if (action == MotionEvent.ACTION_DOWN) @Override
{ public void onShowPress(MotionEvent e)
prevX = x; {
prevY = y;
}
if (action == MotionEvent.ACTION_MOVE) }
{
float dx = x - prevX;
float dy = y - prevY;
if (Math.abs(dy) > Math.abs(dx)) return false; @Override
public boolean onSingleTapUp(MotionEvent e)
{
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy)
{
if(Math.abs(dx) > Math.abs(dy))
getParent().requestDisallowInterceptTouchEvent(true); getParent().requestDisallowInterceptTouchEvent(true);
if (move(dx))
{
prevX = x;
prevY = y;
}
}
scroller.startScroll(scroller.getCurrX(), scroller.getCurrY(), (int) -dx, (int) dy, 0);
scroller.computeScrollOffset();
dataOffset = Math.max(0, scroller.getCurrX() / columnWidth);
postInvalidate();
return true; return true;
} }
@Override @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) public void onLongPress(MotionEvent e)
{ {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getMeasuredWidth(), columnHeight + headerHeight + footerHeight);
} }
@Override @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{ {
super.onSizeChanged(w, h, oldw, oldh); scroller.fling(scroller.getCurrX(), scroller.getCurrY(), (int) velocityX / 2, 0, 0, 100000,
nColumns = w / columnWidth; 0, 0);
fetchData(); invalidate();
scrollAnimator.setDuration(scroller.getDuration());
scrollAnimator.start();
return false;
}
@Override
public void onAnimationUpdate(ValueAnimator animation)
{
if (!scroller.isFinished())
{
scroller.computeScrollOffset();
dataOffset = Math.max(0, scroller.getCurrX() / columnWidth);
postInvalidate();
}
else
{
scrollAnimator.cancel();
}
} }
} }

Loading…
Cancel
Save