@ -1,83 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of Loop Habit Tracker.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by the
|
|
||||||
* Free Software Foundation, either version 3 of the License, or (at your
|
|
||||||
* option) any later version.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
||||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.isoron.uhabits.activities.common.views;
|
|
||||||
|
|
||||||
import androidx.test.filters.*;
|
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
|
||||||
import org.isoron.uhabits.core.models.*;
|
|
||||||
import org.isoron.uhabits.core.utils.*;
|
|
||||||
import org.isoron.uhabits.utils.*;
|
|
||||||
import org.junit.*;
|
|
||||||
import org.junit.runner.*;
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
@MediumTest
|
|
||||||
public class BarChartTest extends BaseViewTest
|
|
||||||
{
|
|
||||||
private static final String BASE_PATH = "common/BarChart/";
|
|
||||||
|
|
||||||
private BarChart view;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Before
|
|
||||||
public void setUp()
|
|
||||||
{
|
|
||||||
super.setUp();
|
|
||||||
Habit habit = fixtures.createLongNumericalHabit();
|
|
||||||
view = new BarChart(targetContext);
|
|
||||||
Timestamp today = DateUtils.getToday();
|
|
||||||
EntryList entries = habit.getComputedEntries();
|
|
||||||
view.setEntries(entries.getByInterval(today.minus(20), today));
|
|
||||||
view.setColor(PaletteUtilsKt.toThemedAndroidColor(habit.getColor(), targetContext));
|
|
||||||
measureView(view, dpToPixels(300), dpToPixels(200));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRender() throws Throwable
|
|
||||||
{
|
|
||||||
assertRenders(view, BASE_PATH + "render.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRender_withDataOffset() throws Throwable
|
|
||||||
{
|
|
||||||
view.onScroll(null, null, -dpToPixels(150), 0);
|
|
||||||
view.invalidate();
|
|
||||||
|
|
||||||
assertRenders(view, BASE_PATH + "renderDataOffset.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRender_withDifferentSize() throws Throwable
|
|
||||||
{
|
|
||||||
measureView(view, dpToPixels(200), dpToPixels(200));
|
|
||||||
assertRenders(view, BASE_PATH + "renderDifferentSize.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRender_withTransparentBackground() throws Throwable
|
|
||||||
{
|
|
||||||
view.setIsTransparencyEnabled(true);
|
|
||||||
assertRenders(view, BASE_PATH + "renderTransparent.png");
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2020 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.platform.gui
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.GestureDetector
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.widget.Scroller
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An AndroidView that implements scrolling.
|
||||||
|
*/
|
||||||
|
class AndroidDataView(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet,
|
||||||
|
) : AndroidView<DataView>(context, attrs),
|
||||||
|
GestureDetector.OnGestureListener,
|
||||||
|
ValueAnimator.AnimatorUpdateListener {
|
||||||
|
|
||||||
|
private val detector = GestureDetector(context, this)
|
||||||
|
private val scroller = Scroller(context, null, true)
|
||||||
|
private val scrollAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
|
||||||
|
addUpdateListener(this@AndroidDataView)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTouchEvent(event: MotionEvent?) = detector.onTouchEvent(event)
|
||||||
|
override fun onDown(e: MotionEvent?) = true
|
||||||
|
override fun onShowPress(e: MotionEvent?) = Unit
|
||||||
|
override fun onSingleTapUp(e: MotionEvent?) = false
|
||||||
|
override fun onLongPress(e: MotionEvent?) = Unit
|
||||||
|
|
||||||
|
override fun onScroll(
|
||||||
|
e1: MotionEvent?,
|
||||||
|
e2: MotionEvent?,
|
||||||
|
dx: Float,
|
||||||
|
dy: Float,
|
||||||
|
): Boolean {
|
||||||
|
if (Math.abs(dx) > Math.abs(dy)) {
|
||||||
|
val parent = parent
|
||||||
|
parent?.requestDisallowInterceptTouchEvent(true)
|
||||||
|
}
|
||||||
|
scroller.startScroll(
|
||||||
|
scroller.currX,
|
||||||
|
scroller.currY,
|
||||||
|
-dx.toInt(),
|
||||||
|
dy.toInt(),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
scroller.computeScrollOffset()
|
||||||
|
updateDataOffset()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFling(
|
||||||
|
e1: MotionEvent?,
|
||||||
|
e2: MotionEvent?,
|
||||||
|
velocityX: Float,
|
||||||
|
velocityY: Float,
|
||||||
|
): Boolean {
|
||||||
|
scroller.fling(scroller.currX, scroller.currY, velocityX.toInt() / 2, 0, 0, 10000, 0, 0)
|
||||||
|
invalidate()
|
||||||
|
scrollAnimator.duration = scroller.duration.toLong()
|
||||||
|
scrollAnimator.start()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationUpdate(animation: ValueAnimator?) {
|
||||||
|
if (!scroller.isFinished) {
|
||||||
|
scroller.computeScrollOffset()
|
||||||
|
updateDataOffset()
|
||||||
|
} else {
|
||||||
|
scrollAnimator.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetDataOffset() {
|
||||||
|
scroller.finalX = 0
|
||||||
|
scroller.computeScrollOffset()
|
||||||
|
updateDataOffset()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateDataOffset() {
|
||||||
|
var newDataOffset: Int = scroller.currX / (view.dataColumnWidth * canvas.density).toInt()
|
||||||
|
newDataOffset = Math.max(0, newDataOffset)
|
||||||
|
if (newDataOffset != view.dataOffset) {
|
||||||
|
view.dataOffset = newDataOffset
|
||||||
|
postInvalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2020 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.platform.gui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
|
||||||
|
class AndroidTestView(context: Context, attrs: AttributeSet) : android.view.View(context, attrs) {
|
||||||
|
val canvas = AndroidCanvas()
|
||||||
|
|
||||||
|
override fun onDraw(canvas: android.graphics.Canvas) {
|
||||||
|
this.canvas.context = context
|
||||||
|
this.canvas.innerCanvas = canvas
|
||||||
|
this.canvas.density = resources.displayMetrics.density.toDouble()
|
||||||
|
this.canvas.drawTestImage()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2020 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.platform.gui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
|
||||||
|
open class AndroidView<T : View>(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet,
|
||||||
|
) : android.view.View(context, attrs) {
|
||||||
|
|
||||||
|
lateinit var view: T
|
||||||
|
val canvas = AndroidCanvas()
|
||||||
|
|
||||||
|
override fun onDraw(canvas: android.graphics.Canvas) {
|
||||||
|
this.canvas.context = context
|
||||||
|
this.canvas.innerCanvas = canvas
|
||||||
|
this.canvas.density = resources.displayMetrics.density.toDouble()
|
||||||
|
view.draw(this.canvas)
|
||||||
|
}
|
||||||
|
}
|
@ -1,474 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of Loop Habit Tracker.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by the
|
|
||||||
* Free Software Foundation, either version 3 of the License, or (at your
|
|
||||||
* option) any later version.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
||||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.isoron.uhabits.activities.common.views;
|
|
||||||
|
|
||||||
import android.content.*;
|
|
||||||
import android.graphics.*;
|
|
||||||
import android.util.*;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
|
||||||
import org.isoron.uhabits.activities.habits.list.views.*;
|
|
||||||
import org.isoron.uhabits.core.models.*;
|
|
||||||
import org.isoron.uhabits.core.utils.*;
|
|
||||||
import org.isoron.uhabits.utils.*;
|
|
||||||
|
|
||||||
import java.text.*;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import static org.isoron.uhabits.utils.InterfaceUtils.*;
|
|
||||||
|
|
||||||
public class BarChart extends ScrollableChart
|
|
||||||
{
|
|
||||||
private static final PorterDuffXfermode XFERMODE_CLEAR =
|
|
||||||
new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
|
|
||||||
|
|
||||||
private static final PorterDuffXfermode XFERMODE_SRC =
|
|
||||||
new PorterDuffXfermode(PorterDuff.Mode.SRC);
|
|
||||||
|
|
||||||
private Paint pGrid;
|
|
||||||
|
|
||||||
private float em;
|
|
||||||
|
|
||||||
private SimpleDateFormat dfMonth;
|
|
||||||
|
|
||||||
private SimpleDateFormat dfDay;
|
|
||||||
|
|
||||||
private SimpleDateFormat dfYear;
|
|
||||||
|
|
||||||
private Paint pText, pGraph;
|
|
||||||
|
|
||||||
private RectF rect, prevRect;
|
|
||||||
|
|
||||||
private int baseSize;
|
|
||||||
|
|
||||||
private int paddingTop;
|
|
||||||
|
|
||||||
private float columnWidth;
|
|
||||||
|
|
||||||
private int columnHeight;
|
|
||||||
|
|
||||||
private int nColumns;
|
|
||||||
|
|
||||||
private int textColor;
|
|
||||||
|
|
||||||
private int gridColor;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private List<Entry> entries;
|
|
||||||
|
|
||||||
private int bucketSize = 7;
|
|
||||||
|
|
||||||
private int backgroundColor;
|
|
||||||
|
|
||||||
private Bitmap drawingCache;
|
|
||||||
|
|
||||||
private Canvas cacheCanvas;
|
|
||||||
|
|
||||||
private boolean isTransparencyEnabled;
|
|
||||||
|
|
||||||
private int skipYear = 0;
|
|
||||||
|
|
||||||
private String previousYearText;
|
|
||||||
|
|
||||||
private String previousMonthText;
|
|
||||||
|
|
||||||
private double maxValue;
|
|
||||||
|
|
||||||
private int primaryColor;
|
|
||||||
|
|
||||||
public BarChart(Context context)
|
|
||||||
{
|
|
||||||
super(context);
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
public BarChart(Context context, AttributeSet attrs)
|
|
||||||
{
|
|
||||||
super(context, attrs);
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void populateWithRandomData()
|
|
||||||
{
|
|
||||||
Random random = new Random();
|
|
||||||
List<Entry> entries = new LinkedList<>();
|
|
||||||
|
|
||||||
Timestamp today = DateUtils.getToday();
|
|
||||||
|
|
||||||
for (int i = 1; i < 100; i++)
|
|
||||||
{
|
|
||||||
int value = random.nextInt(1000);
|
|
||||||
entries.add(new Entry(today.minus(i), value));
|
|
||||||
}
|
|
||||||
|
|
||||||
setEntries(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBucketSize(int bucketSize)
|
|
||||||
{
|
|
||||||
this.bucketSize = bucketSize;
|
|
||||||
postInvalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEntries(@NonNull List<Entry> entries)
|
|
||||||
{
|
|
||||||
this.entries = entries;
|
|
||||||
|
|
||||||
maxValue = 1.0;
|
|
||||||
for (Entry e : entries)
|
|
||||||
maxValue = Math.max(maxValue, e.getValue());
|
|
||||||
maxValue = Math.ceil(maxValue / 1000 * 1.05) * 1000;
|
|
||||||
|
|
||||||
postInvalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setColor(int primaryColor)
|
|
||||||
{
|
|
||||||
StyledResources res = new StyledResources(getContext());
|
|
||||||
this.primaryColor = primaryColor;
|
|
||||||
postInvalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsTransparencyEnabled(boolean enabled)
|
|
||||||
{
|
|
||||||
this.isTransparencyEnabled = enabled;
|
|
||||||
postInvalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDraw(Canvas canvas)
|
|
||||||
{
|
|
||||||
super.onDraw(canvas);
|
|
||||||
Canvas activeCanvas;
|
|
||||||
|
|
||||||
if (isTransparencyEnabled)
|
|
||||||
{
|
|
||||||
if (drawingCache == null) initCache(getWidth(), getHeight());
|
|
||||||
|
|
||||||
activeCanvas = cacheCanvas;
|
|
||||||
drawingCache.eraseColor(Color.TRANSPARENT);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
activeCanvas = canvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entries == null) return;
|
|
||||||
|
|
||||||
rect.set(0, 0, nColumns * columnWidth, columnHeight);
|
|
||||||
rect.offset(0, paddingTop);
|
|
||||||
|
|
||||||
drawGrid(activeCanvas, rect);
|
|
||||||
|
|
||||||
pText.setColor(textColor);
|
|
||||||
pGraph.setColor(primaryColor);
|
|
||||||
prevRect.setEmpty();
|
|
||||||
|
|
||||||
previousMonthText = "";
|
|
||||||
previousYearText = "";
|
|
||||||
skipYear = 0;
|
|
||||||
|
|
||||||
for (int k = 0; k < nColumns; k++)
|
|
||||||
{
|
|
||||||
int offset = nColumns - k - 1 + getDataOffset();
|
|
||||||
if (offset >= entries.size()) continue;
|
|
||||||
|
|
||||||
double value = entries.get(offset).getValue();
|
|
||||||
Timestamp timestamp = entries.get(offset).getTimestamp();
|
|
||||||
int height = (int) (columnHeight * value / maxValue);
|
|
||||||
|
|
||||||
rect.set(0, 0, baseSize, height);
|
|
||||||
rect.offset(k * columnWidth + (columnWidth - baseSize) / 2,
|
|
||||||
paddingTop + columnHeight - height);
|
|
||||||
|
|
||||||
drawValue(activeCanvas, rect, value);
|
|
||||||
drawBar(activeCanvas, rect, value);
|
|
||||||
|
|
||||||
prevRect.set(rect);
|
|
||||||
rect.set(0, 0, columnWidth, columnHeight);
|
|
||||||
rect.offset(k * columnWidth, paddingTop);
|
|
||||||
|
|
||||||
drawFooter(activeCanvas, rect, timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeCanvas != canvas) canvas.drawBitmap(drawingCache, 0, 0, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
|
||||||
{
|
|
||||||
int width = MeasureSpec.getSize(widthMeasureSpec);
|
|
||||||
int height = MeasureSpec.getSize(heightMeasureSpec);
|
|
||||||
setMeasuredDimension(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSizeChanged(int width,
|
|
||||||
int height,
|
|
||||||
int oldWidth,
|
|
||||||
int oldHeight)
|
|
||||||
{
|
|
||||||
if (height < 9) height = 200;
|
|
||||||
|
|
||||||
float maxTextSize = getDimension(getContext(), R.dimen.tinyTextSize);
|
|
||||||
float textSize = height * 0.06f;
|
|
||||||
pText.setTextSize(Math.min(textSize, maxTextSize));
|
|
||||||
em = pText.getFontSpacing();
|
|
||||||
|
|
||||||
int footerHeight = (int) (3 * em);
|
|
||||||
paddingTop = (int) (em);
|
|
||||||
|
|
||||||
baseSize = (height - footerHeight - paddingTop) / 12;
|
|
||||||
columnWidth = baseSize;
|
|
||||||
columnWidth = Math.max(columnWidth, getMaxDayWidth() * 1.5f);
|
|
||||||
columnWidth = Math.max(columnWidth, getMaxMonthWidth() * 1.2f);
|
|
||||||
|
|
||||||
nColumns = (int) (width / columnWidth);
|
|
||||||
columnWidth = (float) width / nColumns;
|
|
||||||
setScrollerBucketSize((int) columnWidth);
|
|
||||||
|
|
||||||
columnHeight = 12 * baseSize;
|
|
||||||
|
|
||||||
float minStrokeWidth = dpToPixels(getContext(), 1);
|
|
||||||
pGraph.setTextSize(baseSize * 0.5f);
|
|
||||||
pGraph.setStrokeWidth(baseSize * 0.1f);
|
|
||||||
pGrid.setStrokeWidth(Math.min(minStrokeWidth, baseSize * 0.05f));
|
|
||||||
|
|
||||||
if (isTransparencyEnabled) initCache(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawBar(Canvas canvas, RectF rect, double value)
|
|
||||||
{
|
|
||||||
float margin = baseSize * 0.225f;
|
|
||||||
float round = dpToPixels(getContext(), 2);
|
|
||||||
|
|
||||||
int color = primaryColor;
|
|
||||||
|
|
||||||
rect.inset(-margin, 0);
|
|
||||||
setModeOrColor(pGraph, XFERMODE_CLEAR, backgroundColor);
|
|
||||||
canvas.drawRoundRect(rect, round, round, pGraph);
|
|
||||||
|
|
||||||
rect.inset(margin, 0);
|
|
||||||
setModeOrColor(pGraph, XFERMODE_SRC, color);
|
|
||||||
canvas.drawRoundRect(rect, round, round, pGraph);
|
|
||||||
rect.set(rect.left,
|
|
||||||
rect.top + rect.height() / 2.0f,
|
|
||||||
rect.right,
|
|
||||||
rect.bottom);
|
|
||||||
canvas.drawRect(rect, pGraph);
|
|
||||||
|
|
||||||
if (isTransparencyEnabled) pGraph.setXfermode(XFERMODE_SRC);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawFooter(Canvas canvas, RectF rect, Timestamp currentDate)
|
|
||||||
{
|
|
||||||
String yearText = dfYear.format(currentDate.toJavaDate());
|
|
||||||
String monthText = dfMonth.format(currentDate.toJavaDate());
|
|
||||||
String dayText = dfDay.format(currentDate.toJavaDate());
|
|
||||||
|
|
||||||
GregorianCalendar calendar = currentDate.toCalendar();
|
|
||||||
pText.setColor(textColor);
|
|
||||||
|
|
||||||
String text;
|
|
||||||
int year = calendar.get(Calendar.YEAR);
|
|
||||||
|
|
||||||
boolean shouldPrintYear = true;
|
|
||||||
if (yearText.equals(previousYearText)) shouldPrintYear = false;
|
|
||||||
|
|
||||||
if (skipYear > 0)
|
|
||||||
{
|
|
||||||
skipYear--;
|
|
||||||
shouldPrintYear = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bucketSize >= 365) shouldPrintYear = true;
|
|
||||||
|
|
||||||
if (shouldPrintYear)
|
|
||||||
{
|
|
||||||
previousYearText = yearText;
|
|
||||||
previousMonthText = "";
|
|
||||||
|
|
||||||
pText.setTextAlign(Paint.Align.CENTER);
|
|
||||||
canvas.drawText(yearText, rect.centerX(), rect.bottom + em * 2.2f, pText);
|
|
||||||
skipYear = 1;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bucketSize < 365)
|
|
||||||
{
|
|
||||||
if (!monthText.equals(previousMonthText))
|
|
||||||
{
|
|
||||||
previousMonthText = monthText;
|
|
||||||
text = monthText;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
text = dayText;
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.drawText(text, rect.centerX(), rect.bottom + em * 1.2f,
|
|
||||||
pText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawGrid(Canvas canvas, RectF rGrid)
|
|
||||||
{
|
|
||||||
int nRows = 5;
|
|
||||||
float rowHeight = rGrid.height() / nRows;
|
|
||||||
|
|
||||||
pText.setColor(textColor);
|
|
||||||
pGrid.setColor(gridColor);
|
|
||||||
|
|
||||||
for (int i = 0; i < nRows; i++)
|
|
||||||
{
|
|
||||||
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top,
|
|
||||||
pGrid);
|
|
||||||
rGrid.offset(0, rowHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawValue(Canvas canvas, RectF rect, double value)
|
|
||||||
{
|
|
||||||
if (value == 0) return;
|
|
||||||
int activeColor = primaryColor;
|
|
||||||
|
|
||||||
String label = NumberButtonViewKt.toShortString(value / 1000);
|
|
||||||
Rect rText = new Rect();
|
|
||||||
pText.getTextBounds(label, 0, label.length(), rText);
|
|
||||||
|
|
||||||
float offset = 0.5f * em;
|
|
||||||
float x = rect.centerX();
|
|
||||||
float y = rect.top - offset;
|
|
||||||
int cap = (int) (-0.1f * em);
|
|
||||||
|
|
||||||
rText.offset((int) x, (int) y);
|
|
||||||
rText.offset(-rText.width() / 2, 0);
|
|
||||||
rText.inset(3 * cap, cap);
|
|
||||||
|
|
||||||
setModeOrColor(pText, XFERMODE_CLEAR, backgroundColor);
|
|
||||||
canvas.drawRect(rText, pText);
|
|
||||||
|
|
||||||
setModeOrColor(pText, XFERMODE_SRC, activeColor);
|
|
||||||
canvas.drawText(label, x, y, pText);
|
|
||||||
}
|
|
||||||
|
|
||||||
private float getMaxDayWidth()
|
|
||||||
{
|
|
||||||
float maxDayWidth = 0;
|
|
||||||
GregorianCalendar day = DateUtils.getStartOfTodayCalendarWithOffset();
|
|
||||||
|
|
||||||
for (int i = 0; i < 28; i++)
|
|
||||||
{
|
|
||||||
day.set(Calendar.DAY_OF_MONTH, i);
|
|
||||||
float monthWidth = pText.measureText(dfMonth.format(day.getTime()));
|
|
||||||
maxDayWidth = Math.max(maxDayWidth, monthWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
return maxDayWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
private float getMaxMonthWidth()
|
|
||||||
{
|
|
||||||
float maxMonthWidth = 0;
|
|
||||||
GregorianCalendar day = DateUtils.getStartOfTodayCalendarWithOffset();
|
|
||||||
|
|
||||||
for (int i = 0; i < 12; i++)
|
|
||||||
{
|
|
||||||
day.set(Calendar.MONTH, i);
|
|
||||||
float monthWidth = pText.measureText(dfMonth.format(day.getTime()));
|
|
||||||
maxMonthWidth = Math.max(maxMonthWidth, monthWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
return maxMonthWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init()
|
|
||||||
{
|
|
||||||
initPaints();
|
|
||||||
initColors();
|
|
||||||
initDateFormats();
|
|
||||||
initRects();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initCache(int width, int height)
|
|
||||||
{
|
|
||||||
if (drawingCache != null) drawingCache.recycle();
|
|
||||||
drawingCache =
|
|
||||||
Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
|
||||||
cacheCanvas = new Canvas(drawingCache);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initColors()
|
|
||||||
{
|
|
||||||
StyledResources res = new StyledResources(getContext());
|
|
||||||
|
|
||||||
primaryColor = Color.BLACK;
|
|
||||||
textColor = res.getColor(R.attr.mediumContrastTextColor);
|
|
||||||
gridColor = res.getColor(R.attr.lowContrastTextColor);
|
|
||||||
backgroundColor = res.getColor(R.attr.cardBgColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initDateFormats()
|
|
||||||
{
|
|
||||||
if (isInEditMode())
|
|
||||||
{
|
|
||||||
dfYear = new SimpleDateFormat("yyyy", Locale.US);
|
|
||||||
dfMonth = new SimpleDateFormat("MMM", Locale.US);
|
|
||||||
dfDay = new SimpleDateFormat("d", Locale.US);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dfYear = DateExtensionsKt.toSimpleDataFormat("yyyy");
|
|
||||||
dfMonth = DateExtensionsKt.toSimpleDataFormat("MMM");
|
|
||||||
dfDay = DateExtensionsKt.toSimpleDataFormat("d");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initPaints()
|
|
||||||
{
|
|
||||||
pText = new Paint();
|
|
||||||
pText.setAntiAlias(true);
|
|
||||||
pText.setTextAlign(Paint.Align.CENTER);
|
|
||||||
|
|
||||||
pGraph = new Paint();
|
|
||||||
pGraph.setTextAlign(Paint.Align.CENTER);
|
|
||||||
pGraph.setAntiAlias(true);
|
|
||||||
|
|
||||||
pGrid = new Paint();
|
|
||||||
pGrid.setAntiAlias(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initRects()
|
|
||||||
{
|
|
||||||
rect = new RectF();
|
|
||||||
prevRect = new RectF();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setModeOrColor(Paint p, PorterDuffXfermode mode, int color)
|
|
||||||
{
|
|
||||||
if (isTransparencyEnabled) p.setXfermode(mode);
|
|
||||||
else p.setColor(color);
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 350 B |
Before Width: | Height: | Size: 345 B |
Before Width: | Height: | Size: 424 B |
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 3.5 KiB |