Merge branch 'feature/scroll-header' into dev

Fixes #155
pull/157/merge
Alinson S. Xavier 9 years ago
commit 876d4f0ac7

@ -40,7 +40,7 @@ public class BundleSavedState extends View.BaseSavedState
}
};
final Bundle bundle;
public final Bundle bundle;
public BundleSavedState(Parcelable superState, Bundle bundle)
{

@ -33,7 +33,9 @@ public abstract class ScrollableChart extends View
private int dataOffset;
private int scrollerBucketSize;
private int scrollerBucketSize = 1;
private int direction = 1;
private GestureDetector detector;
@ -41,6 +43,10 @@ public abstract class ScrollableChart extends View
private ValueAnimator scrollAnimator;
private ScrollController scrollController;
private int maxDataOffset = 10000;
public ScrollableChart(Context context)
{
super(context);
@ -64,8 +70,7 @@ public abstract class ScrollableChart extends View
if (!scroller.isFinished())
{
scroller.computeScrollOffset();
dataOffset = Math.max(0, scroller.getCurrX() / scrollerBucketSize);
postInvalidate();
updateDataOffset();
}
else
{
@ -86,19 +91,44 @@ public abstract class ScrollableChart extends View
float velocityY)
{
scroller.fling(scroller.getCurrX(), scroller.getCurrY(),
(int) velocityX / 2, 0, 0, 100000, 0, 0);
direction * ((int) velocityX) / 2, 0, 0, getMaxX(), 0, 0);
invalidate();
scrollAnimator.setDuration(scroller.getDuration());
scrollAnimator.start();
return false;
}
private int getMaxX()
{
return maxDataOffset * scrollerBucketSize;
}
@Override
public void onLongPress(MotionEvent e)
public void onRestoreInstanceState(Parcelable state)
{
BundleSavedState bss = (BundleSavedState) state;
int x = bss.bundle.getInt("x");
int y = bss.bundle.getInt("y");
direction = bss.bundle.getInt("direction");
dataOffset = bss.bundle.getInt("dataOffset");
maxDataOffset = bss.bundle.getInt("maxDataOffset");
scroller.startScroll(0, 0, x, y, 0);
scroller.computeScrollOffset();
super.onRestoreInstanceState(bss.getSuperState());
}
@Override
public Parcelable onSaveInstanceState()
{
Parcelable superState = super.onSaveInstanceState();
Bundle bundle = new Bundle();
bundle.putInt("x", scroller.getCurrX());
bundle.putInt("y", scroller.getCurrY());
bundle.putInt("dataOffset", dataOffset);
bundle.putInt("direction", direction);
bundle.putInt("maxDataOffset", maxDataOffset);
return new BundleSavedState(superState, bundle);
}
@Override
@ -112,12 +142,14 @@ public abstract class ScrollableChart extends View
if (parent != null) parent.requestDisallowInterceptTouchEvent(true);
}
scroller.startScroll(scroller.getCurrX(), scroller.getCurrY(),
(int) -dx, (int) dy, 0);
scroller.computeScrollOffset();
dataOffset = Math.max(0, scroller.getCurrX() / scrollerBucketSize);
postInvalidate();
dx = - direction * dx;
dx = Math.min(dx, getMaxX() - scroller.getCurrX());
scroller.startScroll(scroller.getCurrX(), scroller.getCurrY(), (int) dx,
(int) dy, 0);
scroller.computeScrollOffset();
updateDataOffset();
return true;
}
@ -139,32 +171,35 @@ public abstract class ScrollableChart extends View
return detector.onTouchEvent(event);
}
public void setScrollerBucketSize(int scrollerBucketSize)
public void setDirection(int direction)
{
this.scrollerBucketSize = scrollerBucketSize;
if (direction != 1 && direction != -1)
throw new IllegalArgumentException();
this.direction = direction;
}
@Override
public void onRestoreInstanceState(Parcelable state)
public void onLongPress(MotionEvent e)
{
BundleSavedState bss = (BundleSavedState) state;
int x = bss.bundle.getInt("x");
int y = bss.bundle.getInt("y");
dataOffset = bss.bundle.getInt("dataOffset");
scroller.startScroll(0, 0, x, y, 0);
scroller.computeScrollOffset();
super.onRestoreInstanceState(bss.getSuperState());
}
@Override
public Parcelable onSaveInstanceState()
public void setMaxDataOffset(int maxDataOffset)
{
Parcelable superState = super.onSaveInstanceState();
Bundle bundle = new Bundle();
bundle.putInt("x", scroller.getCurrX());
bundle.putInt("y", scroller.getCurrY());
bundle.putInt("dataOffset", dataOffset);
return new BundleSavedState(superState, bundle);
this.maxDataOffset = maxDataOffset;
this.dataOffset = Math.min(dataOffset, maxDataOffset);
scrollController.onDataOffsetChanged(this.dataOffset);
postInvalidate();
}
public void setScrollController(ScrollController scrollController)
{
this.scrollController = scrollController;
}
public void setScrollerBucketSize(int scrollerBucketSize)
{
this.scrollerBucketSize = scrollerBucketSize;
}
private void init(Context context)
@ -173,5 +208,25 @@ public abstract class ScrollableChart extends View
scroller = new Scroller(context, null, true);
scrollAnimator = ValueAnimator.ofFloat(0, 1);
scrollAnimator.addUpdateListener(this);
scrollController = new ScrollController() {};
}
private void updateDataOffset()
{
int newDataOffset = scroller.getCurrX() / scrollerBucketSize;
newDataOffset = Math.max(0, newDataOffset);
newDataOffset = Math.min(maxDataOffset, newDataOffset);
if (newDataOffset != dataOffset)
{
dataOffset = newDataOffset;
scrollController.onDataOffsetChanged(dataOffset);
postInvalidate();
}
}
public interface ScrollController
{
default void onDataOffsetChanged(int newDataOffset) {}
}
}

@ -28,6 +28,7 @@ import android.widget.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.activities.*;
import org.isoron.uhabits.activities.common.views.*;
import org.isoron.uhabits.activities.habits.list.controllers.*;
import org.isoron.uhabits.activities.habits.list.model.*;
import org.isoron.uhabits.activities.habits.list.views.*;
@ -43,7 +44,7 @@ import butterknife.*;
public class ListHabitsRootView extends BaseRootView
implements ModelObservable.Listener, TaskRunner.Listener
{
public static final int MAX_CHECKMARK_COUNT = 21;
public static final int MAX_CHECKMARK_COUNT = 60;
@BindView(R.id.listView)
HabitCardListView listView;
@ -132,6 +133,13 @@ public class ListHabitsRootView extends BaseRootView
listController.setSelectionListener(menu);
listView.setController(listController);
menu.setListController(listController);
header.setScrollController(new ScrollableChart.ScrollController() {
@Override
public void onDataOffsetChanged(int newDataOffset)
{
listView.setDataOffset(newDataOffset);
}
});
}
@Override
@ -156,6 +164,7 @@ public class ListHabitsRootView extends BaseRootView
{
int count = getCheckmarkCount();
header.setButtonCount(count);
header.setMaxDataOffset(Math.max(MAX_CHECKMARK_COUNT - count, 0));
listView.setCheckmarkCount(count);
super.onSizeChanged(w, h, oldw, oldh);
}

@ -178,6 +178,20 @@ public class HabitCardListAdapter
listView.bindCardView(holder, habit, score, checkmarks, selected);
}
@Override
public void onViewAttachedToWindow(@Nullable HabitCardViewHolder holder)
{
if (listView == null) return;
listView.attachCardView(holder);
}
@Override
public void onViewDetachedFromWindow(@Nullable HabitCardViewHolder holder)
{
if (listView == null) return;
listView.detachCardView(holder);
}
@Override
public HabitCardViewHolder onCreateViewHolder(ViewGroup parent,
int viewType)

@ -53,6 +53,8 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List
@NonNull
private Habit habit;
private int dataOffset;
public CheckmarkPanelView(Context context)
{
super(context);
@ -75,19 +77,23 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List
return (CheckmarkButtonView) getChildAt(position);
}
public void setCheckmarkValues(int[] checkmarkValues)
public void setButtonCount(int newButtonCount)
{
this.checkmarkValues = checkmarkValues;
if (this.nButtons != checkmarkValues.length)
if(nButtons != newButtonCount)
{
this.nButtons = checkmarkValues.length;
nButtons = newButtonCount;
addCheckmarkButtons();
}
setupCheckmarkButtons();
}
public void setCheckmarkValues(int[] checkmarkValues)
{
this.checkmarkValues = checkmarkValues;
setupCheckmarkButtons();
}
public void setColor(int color)
{
this.color = color;
@ -100,6 +106,12 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List
setupCheckmarkButtons();
}
public void setDataOffset(int dataOffset)
{
this.dataOffset = dataOffset;
setupCheckmarkButtons();
}
public void setHabit(@NonNull Habit habit)
{
this.habit = habit;
@ -170,11 +182,13 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List
{
long timestamp = DateUtils.getStartOfToday();
long day = DateUtils.millisecondsInOneDay;
timestamp -= day * dataOffset;
for (int i = 0; i < nButtons; i++)
{
CheckmarkButtonView buttonView = indexToButton(i);
buttonView.setValue(checkmarkValues[i]);
if(i + dataOffset >= checkmarkValues.length) break;
buttonView.setValue(checkmarkValues[i + dataOffset]);
buttonView.setColor(color);
setupButtonControllers(timestamp, buttonView);
timestamp -= day;

@ -20,15 +20,17 @@
package org.isoron.uhabits.activities.habits.list.views;
import android.content.*;
import android.os.*;
import android.support.annotation.*;
import android.support.v7.widget.*;
import android.support.v7.widget.helper.*;
import android.util.*;
import android.view.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.activities.common.views.*;
import org.isoron.uhabits.activities.habits.list.controllers.*;
import org.isoron.uhabits.activities.habits.list.model.*;
import org.isoron.uhabits.models.*;
import java.util.*;
@ -44,6 +46,10 @@ public class HabitCardListView extends RecyclerView
private int checkmarkCount;
private int dataOffset;
private LinkedList<HabitCardViewHolder> attachedHolders;
public HabitCardListView(Context context, AttributeSet attrs)
{
super(context, attrs);
@ -54,6 +60,13 @@ public class HabitCardListView extends RecyclerView
TouchHelperCallback callback = new TouchHelperCallback();
touchHelper = new ItemTouchHelper(callback);
touchHelper.attachToRecyclerView(this);
attachedHolders = new LinkedList<>();
}
public void attachCardView(HabitCardViewHolder holder)
{
attachedHolders.add(holder);
}
/**
@ -75,13 +88,12 @@ public class HabitCardListView extends RecyclerView
int[] checkmarks,
boolean selected)
{
int visibleCheckmarks[] =
Arrays.copyOfRange(checkmarks, 0, checkmarkCount);
HabitCardView cardView = (HabitCardView) holder.itemView;
cardView.setHabit(habit);
cardView.setSelected(selected);
cardView.setCheckmarkValues(visibleCheckmarks);
cardView.setCheckmarkValues(checkmarks);
cardView.setCheckmarkCount(checkmarkCount);
cardView.setDataOffset(dataOffset);
cardView.setScore(score);
if (controller != null) setupCardViewController(holder);
return cardView;
@ -92,6 +104,11 @@ public class HabitCardListView extends RecyclerView
return new HabitCardView(getContext());
}
public void detachCardView(HabitCardViewHolder holder)
{
attachedHolders.remove(holder);
}
@Override
public void setAdapter(RecyclerView.Adapter adapter)
{
@ -109,6 +126,16 @@ public class HabitCardListView extends RecyclerView
this.controller = controller;
}
public void setDataOffset(int dataOffset)
{
this.dataOffset = dataOffset;
for (HabitCardViewHolder holder : attachedHolders)
{
HabitCardView cardView = (HabitCardView) holder.itemView;
cardView.setDataOffset(dataOffset);
}
}
@Override
protected void onAttachedToWindow()
{
@ -123,6 +150,23 @@ public class HabitCardListView extends RecyclerView
super.onDetachedFromWindow();
}
@Override
protected void onRestoreInstanceState(Parcelable state)
{
BundleSavedState bss = (BundleSavedState) state;
dataOffset = bss.bundle.getInt("dataOffset");
super.onRestoreInstanceState(bss.getSuperState());
}
@Override
protected Parcelable onSaveInstanceState()
{
Parcelable superState = super.onSaveInstanceState();
Bundle bundle = new Bundle();
bundle.putInt("dataOffset", dataOffset);
return new BundleSavedState(superState, bundle);
}
protected void setupCardViewController(@NonNull HabitCardViewHolder holder)
{
HabitCardView cardView = (HabitCardView) holder.itemView;
@ -168,7 +212,7 @@ public class HabitCardListView extends RecyclerView
{
int position = holder.getAdapterPosition();
if (controller != null) controller.onItemLongClick(position);
if(adapter.isSortable()) touchHelper.startDrag(holder);
if (adapter.isSortable()) touchHelper.startDrag(holder);
}
@Override

@ -72,6 +72,8 @@ public class HabitCardView extends FrameLayout
@Nullable
private Habit habit;
private int dataOffset;
public HabitCardView(Context context)
{
super(context);
@ -90,6 +92,11 @@ public class HabitCardView extends FrameLayout
new Handler(Looper.getMainLooper()).post(() -> refresh());
}
public void setCheckmarkCount(int checkmarkCount)
{
checkmarkPanel.setButtonCount(checkmarkCount);
}
public void setCheckmarkValues(int checkmarks[])
{
checkmarkPanel.setCheckmarkValues(checkmarks);
@ -103,6 +110,12 @@ public class HabitCardView extends FrameLayout
checkmarkPanel.setController(controller);
}
public void setDataOffset(int dataOffset)
{
this.dataOffset = dataOffset;
checkmarkPanel.setDataOffset(dataOffset);
}
public void setHabit(@NonNull Habit habit)
{
if (this.habit != null) detachFromHabit();
@ -134,7 +147,7 @@ public class HabitCardView extends FrameLayout
{
long today = DateUtils.getStartOfToday();
long day = DateUtils.millisecondsInOneDay;
int offset = (int) ((today - timestamp) / day);
int offset = (int) ((today - timestamp) / day) - dataOffset;
CheckmarkButtonView button = checkmarkPanel.indexToButton(offset);
float y = button.getHeight() / 2.0f;

@ -20,22 +20,23 @@
package org.isoron.uhabits.activities.habits.list.views;
import android.content.*;
import android.content.res.*;
import android.graphics.*;
import android.support.annotation.*;
import android.text.*;
import android.util.*;
import android.view.*;
import android.widget.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.activities.common.views.*;
import org.isoron.uhabits.activities.habits.list.*;
import org.isoron.uhabits.preferences.*;
import org.isoron.uhabits.utils.*;
import java.util.*;
public class HeaderView extends LinearLayout
public class HeaderView extends ScrollableChart
implements Preferences.Listener, MidnightTimer.MidnightListener
{
private final Context context;
private int buttonCount;
@ -45,10 +46,15 @@ public class HeaderView extends LinearLayout
@Nullable
private MidnightTimer midnightTimer;
private final TextPaint paint;
private RectF rect;
private int maxDataOffset;
public HeaderView(Context context, AttributeSet attrs)
{
super(context, attrs);
this.context = context;
if (isInEditMode())
{
@ -67,24 +73,40 @@ public class HeaderView extends LinearLayout
ListHabitsActivity activity = (ListHabitsActivity) context;
midnightTimer = activity.getListHabitsComponent().getMidnightTimer();
}
Resources res = context.getResources();
setScrollerBucketSize((int) res.getDimension(R.dimen.checkmarkWidth));
setDirection(shouldReverseCheckmarks() ? 1 : -1);
StyledResources sr = new StyledResources(context);
paint = new TextPaint();
paint.setColor(Color.BLACK);
paint.setAntiAlias(true);
paint.setTextSize(getResources().getDimension(R.dimen.tinyTextSize));
paint.setTextAlign(Paint.Align.CENTER);
paint.setTypeface(Typeface.DEFAULT_BOLD);
paint.setColor(sr.getColor(R.attr.mediumContrastTextColor));
rect = new RectF();
}
@Override
public void atMidnight()
{
post(() -> createButtons());
post(() -> invalidate());
}
@Override
public void onCheckmarkOrderChanged()
{
createButtons();
setDirection(shouldReverseCheckmarks() ? 1 : -1);
postInvalidate();
}
public void setButtonCount(int buttonCount)
{
this.buttonCount = buttonCount;
createButtons();
postInvalidate();
}
@Override
@ -103,23 +125,45 @@ public class HeaderView extends LinearLayout
super.onDetachedFromWindow();
}
private void createButtons()
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = (int) getContext()
.getResources()
.getDimension(R.dimen.checkmarkHeight);
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas)
{
removeAllViews();
super.onDraw(canvas);
GregorianCalendar day = DateUtils.getStartOfTodayCalendar();
Resources res = getContext().getResources();
float width = res.getDimension(R.dimen.checkmarkWidth);
float height = res.getDimension(R.dimen.checkmarkHeight);
boolean reverse = shouldReverseCheckmarks();
for (int i = 0; i < buttonCount; i++)
addView(
inflate(context, R.layout.list_habits_header_checkmark, null));
day.add(GregorianCalendar.DAY_OF_MONTH, -getDataOffset());
float em = paint.measureText("m");
for (int i = 0; i < getChildCount(); i++)
for (int i = 0; i < buttonCount; i++)
{
int position = i;
if (shouldReverseCheckmarks()) position = getChildCount() - i - 1;
rect.set(0, 0, width, height);
rect.offset(canvas.getWidth(), 0);
if(reverse) rect.offset(- (i + 1) * width, 0);
else rect.offset((i - buttonCount) * width, 0);
String text = DateUtils.formatHeaderDate(day).toUpperCase();
String[] lines = text.split("\n");
int y1 = (int)(rect.centerY() - 0.25 * em);
int y2 = (int)(rect.centerY() + 1.25 * em);
View button = getChildAt(position);
TextView label = (TextView) button.findViewById(R.id.tvCheck);
label.setText(DateUtils.formatHeaderDate(day));
canvas.drawText(lines[0], rect.centerX(), y1, paint);
canvas.drawText(lines[1], rect.centerX(), y2, paint);
day.add(GregorianCalendar.DAY_OF_MONTH, -1);
}
}

Loading…
Cancel
Save