mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 09:08:52 -06:00
Implement fling and more natural scrolling on ScrollableDataView
This commit is contained in:
@@ -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,60 +35,26 @@ 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)
|
|
||||||
{
|
|
||||||
int newDataOffset = dataOffset + (int) (dx / columnWidth);
|
|
||||||
newDataOffset = Math.max(0, newDataOffset);
|
|
||||||
|
|
||||||
if (newDataOffset != dataOffset)
|
|
||||||
{
|
|
||||||
dataOffset = newDataOffset;
|
|
||||||
fetchData();
|
|
||||||
invalidate();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onTouchEvent(MotionEvent event)
|
public boolean onTouchEvent(MotionEvent event)
|
||||||
{
|
{
|
||||||
int action = event.getAction();
|
return detector.onTouchEvent(event);
|
||||||
|
|
||||||
int pointerIndex = MotionEventCompat.getActionIndex(event);
|
|
||||||
final float x = MotionEventCompat.getX(event, pointerIndex);
|
|
||||||
final float y = MotionEventCompat.getY(event, pointerIndex);
|
|
||||||
|
|
||||||
if (action == MotionEvent.ACTION_DOWN)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
getParent().requestDisallowInterceptTouchEvent(true);
|
|
||||||
if (move(dx))
|
|
||||||
{
|
|
||||||
prevX = x;
|
|
||||||
prevY = y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -102,4 +71,69 @@ public abstract class ScrollableDataView extends View
|
|||||||
nColumns = w / columnWidth;
|
nColumns = w / columnWidth;
|
||||||
fetchData();
|
fetchData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onDown(MotionEvent e)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onShowPress(MotionEvent e)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
|
||||||
|
scroller.startScroll(scroller.getCurrX(), scroller.getCurrY(), (int) -dx, (int) dy, 0);
|
||||||
|
scroller.computeScrollOffset();
|
||||||
|
dataOffset = Math.max(0, scroller.getCurrX() / columnWidth);
|
||||||
|
postInvalidate();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLongPress(MotionEvent e)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
|
||||||
|
{
|
||||||
|
scroller.fling(scroller.getCurrX(), scroller.getCurrY(), (int) velocityX / 2, 0, 0, 100000,
|
||||||
|
0, 0);
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user