summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDanny Baumann <dannybaumann@web.de>2013-04-07 13:33:43 +0200
committerDanny Baumann <dannybaumann@web.de>2013-04-18 11:36:44 +0200
commit0d81e44b5ca1d363ddfde32ad0d833e429e33af1 (patch)
treebf756289050ccc17e9809152ffac0279569f6028 /src
parentbdb1b7f1a1d2b1bc0c1ddcd22a3c2cc63b85a031 (diff)
downloadpackages_apps_Settings-0d81e44b5ca1d363ddfde32ad0d833e429e33af1.zip
packages_apps_Settings-0d81e44b5ca1d363ddfde32ad0d833e429e33af1.tar.gz
packages_apps_Settings-0d81e44b5ca1d363ddfde32ad0d833e429e33af1.tar.bz2
Overhaul auto-brightness level UI.
This changes a few things: - Don't imply a 'bucket' type of algorithm is used by showing an ambient brightness range for each line - Allow manually entering the screen brightness - Add a preview window for the cubic spline interpolation - Add a help text JIRA:CYAN-612 Change-Id: I64274280872b9fe4f6fdc368d654aca81e0a4e0c
Diffstat (limited to 'src')
-rw-r--r--src/com/android/settings/cyanogenmod/AutoBrightnessCustomizeDialog.java347
-rw-r--r--src/com/android/settings/cyanogenmod/CubicSplinePreviewView.java253
2 files changed, 512 insertions, 88 deletions
diff --git a/src/com/android/settings/cyanogenmod/AutoBrightnessCustomizeDialog.java b/src/com/android/settings/cyanogenmod/AutoBrightnessCustomizeDialog.java
index f9b6e19..4901f29 100644
--- a/src/com/android/settings/cyanogenmod/AutoBrightnessCustomizeDialog.java
+++ b/src/com/android/settings/cyanogenmod/AutoBrightnessCustomizeDialog.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.settings.cyanogenmod;
import android.app.AlertDialog;
@@ -14,6 +30,7 @@ import android.provider.Settings;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
+import android.util.Spline;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
@@ -25,11 +42,16 @@ import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
+import android.widget.ImageButton;
import android.widget.ListView;
+import android.widget.PopupMenu;
import android.widget.SeekBar;
import android.widget.TextView;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
+import java.util.Locale;
import com.android.settings.R;
@@ -38,19 +60,16 @@ public class AutoBrightnessCustomizeDialog extends AlertDialog
private static final String TAG = "AutoBrightnessCustomizeDialog";
private TextView mSensorLevel;
- private TextView mBrightnessLevel;
private ListView mConfigList;
private SensorManager mSensorManager;
private Sensor mLightSensor;
private static class SettingRow {
- int luxFrom;
- int luxTo;
+ int lux;
int backlight;
- public SettingRow(int luxFrom, int luxTo, int backlight) {
- this.luxFrom = luxFrom;
- this.luxTo = luxTo;
+ public SettingRow(int lux, int backlight) {
+ this.lux = lux;
this.backlight = backlight;
}
};
@@ -97,7 +116,30 @@ public class AutoBrightnessCustomizeDialog extends AlertDialog
mMinLevel = pm.getMinimumAbsoluteScreenBrightness();
mSensorLevel = (TextView) view.findViewById(R.id.light_sensor_value);
- mBrightnessLevel = (TextView) view.findViewById(R.id.current_brightness);
+
+ ImageButton more = (ImageButton) view.findViewById(R.id.more);
+ more.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ PopupMenu pm = new PopupMenu(v.getContext(), v);
+ pm.inflate(R.menu.auto_brightness_setup_more);
+ pm.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.auto_brightness_menu_preview:
+ showPreview();
+ return true;
+ case R.id.auto_brightness_menu_help:
+ showHelp();
+ return true;
+ }
+ return false;
+ }
+ });
+ pm.show();
+ }
+ });
mConfigList = (ListView) view.findViewById(android.R.id.list);
mAdapter = new SettingRowAdapter(context, new ArrayList<SettingRow>());
@@ -151,7 +193,7 @@ public class AutoBrightnessCustomizeDialog extends AlertDialog
switch (item.getItemId() - Menu.FIRST) {
case 0:
- showLuxSetup(position);
+ showSetup(position);
return true;
case 1:
showSplitDialog(position);
@@ -198,22 +240,65 @@ public class AutoBrightnessCustomizeDialog extends AlertDialog
mAdapter.initFromSettings(lux, values);
}
- private void showLuxSetup(final int position) {
+ private void showHelp() {
+ final View v = getLayoutInflater().inflate(R.layout.auto_brightness_help, null);
+
+ final AlertDialog d = new AlertDialog.Builder(getContext())
+ .setTitle(R.string.auto_brightness_help_dialog_title)
+ .setCancelable(true)
+ .setView(v)
+ .setNegativeButton(R.string.auto_brightness_close_button, null)
+ .create();
+
+ d.show();
+ }
+
+ private void showPreview() {
+ final int n = mAdapter.getCount();
+ float[] x = new float[n];
+ float[] y = new float[n];
+
+ for (int i = 0; i < n; i++) {
+ SettingRow row = mAdapter.getItem(i);
+ x[i] = row.lux;
+ //XXX: should mMinLevel be treated as 0 in the preview?
+ y[i] = (float) row.backlight / PowerManager.BRIGHTNESS_ON;
+ }
+
+ final View v = getLayoutInflater().inflate(R.layout.auto_brightness_preview, null);
+ final CubicSplinePreviewView preview =
+ (CubicSplinePreviewView) v.findViewById(R.id.brightness_preview);
+ preview.setSpline(x, y);
+
+ final AlertDialog d = new AlertDialog.Builder(getContext())
+ .setTitle(R.string.auto_brightness_preview_dialog_title)
+ .setCancelable(true)
+ .setView(v)
+ .setNegativeButton(R.string.auto_brightness_close_button, null)
+ .create();
+
+ d.show();
+ }
+
+ private void showSetup(final int position) {
final SettingRow row = mAdapter.getItem(position);
- final View v = getLayoutInflater().inflate(R.layout.auto_brightness_lux_config, null);
- final EditText startLux = (EditText) v.findViewById(R.id.start_lux);
- final EditText endLux = (EditText) v.findViewById(R.id.end_lux);
+ final View v = getLayoutInflater().inflate(R.layout.auto_brightness_level_setup, null);
+ final EditText lux = (EditText) v.findViewById(R.id.lux);
+ final SeekBar backlightBar = (SeekBar) v.findViewById(R.id.backlight);
+ final EditText backlightInput = (EditText) v.findViewById(R.id.backlight_input);
+ final DecimalFormat backlightInputFormat = new DecimalFormat("0.0",
+ DecimalFormatSymbols.getInstance(Locale.US));
final AlertDialog d = new AlertDialog.Builder(getContext())
- .setTitle(R.string.auto_brightness_lux_dialog_title)
+ .setTitle(R.string.auto_brightness_level_dialog_title)
.setCancelable(true)
.setView(v)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface d, int which) {
try {
- int newLux = Integer.valueOf(endLux.getText().toString());
- mAdapter.setLuxToForRow(position, newLux);
+ int newLux = Integer.valueOf(lux.getText().toString());
+ mAdapter.setLuxForRow(position, newLux);
} catch (NumberFormatException e) {
//ignored
}
@@ -222,8 +307,61 @@ public class AutoBrightnessCustomizeDialog extends AlertDialog
.setNegativeButton(R.string.cancel, null)
.create();
- startLux.setText(String.valueOf(row.luxFrom));
- endLux.setText(String.valueOf(row.luxTo));
+ backlightBar.setMax(brightnessToProgress(PowerManager.BRIGHTNESS_ON));
+
+ lux.setText(String.valueOf(row.lux));
+ backlightBar.setProgress(brightnessToProgress(row.backlight));
+ backlightInput.setText(backlightInputFormat.format(brightnessToPercent(row.backlight)));
+
+ backlightBar.setOnSeekBarChangeListener(new BrightnessSeekBarChangeListener() {
+ @Override
+ protected int getPosition(SeekBar seekBar) {
+ return position;
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ super.onProgressChanged(seekBar, progress, fromUser);
+
+ float brightness = progressToBrightness(seekBar.getProgress());
+ backlightInput.setText(backlightInputFormat.format(brightnessToPercent(brightness)));
+ }
+ });
+
+ backlightInput.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+ @Override
+ public void afterTextChanged(Editable s) {
+ boolean ok = false;
+ try {
+ float minValue = position == 0
+ ? mMinLevel
+ : mAdapter.getItem(position - 1).backlight;
+ float maxValue = mAdapter.isLastItem(position)
+ ? PowerManager.BRIGHTNESS_ON
+ : mAdapter.getItem(position + 1).backlight;
+ float newBrightness = percentToBrightness(Float.valueOf(s.toString()));
+
+ if (newBrightness >= minValue && newBrightness <= maxValue) {
+ ok = true;
+ backlightBar.setProgress(brightnessToProgress(newBrightness));
+ }
+ } catch (NumberFormatException e) {
+ //ignored, ok is false anyway
+ }
+
+ Button okButton = d.getButton(DialogInterface.BUTTON_POSITIVE);
+ if (okButton != null) {
+ okButton.setEnabled(ok);
+ }
+ }
+ });
+
d.show();
}
@@ -234,7 +372,7 @@ public class AutoBrightnessCustomizeDialog extends AlertDialog
final EditText value = (EditText) v.findViewById(R.id.split_position);
final AlertDialog d = new AlertDialog.Builder(getContext())
- .setTitle(R.string.auto_brightness_lux_dialog_title)
+ .setTitle(R.string.auto_brightness_split_dialog_title)
.setCancelable(true)
.setView(v)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@@ -247,9 +385,12 @@ public class AutoBrightnessCustomizeDialog extends AlertDialog
.setNegativeButton(R.string.cancel, null)
.create();
+ final int minLux = row.lux + 1;
+ final int maxLux = mAdapter.isLastItem(position) ? 0 : mAdapter.getItem(position + 1).lux - 1;
+
label.setText(getContext().getString(R.string.auto_brightness_split_lux_format,
- row.luxFrom + 1, row.luxTo - 1));
- value.setText(String.valueOf(row.luxFrom + 1));
+ minLux, maxLux));
+ value.setText(String.valueOf(minLux));
value.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@@ -262,7 +403,7 @@ public class AutoBrightnessCustomizeDialog extends AlertDialog
boolean ok = false;
try {
int newLux = Integer.valueOf(s.toString());
- ok = newLux > row.luxFrom && newLux < row.luxTo;
+ ok = newLux >= minLux && newLux <= maxLux;
} catch (NumberFormatException e) {
//ignored, ok is false anyway
}
@@ -360,15 +501,16 @@ public class AutoBrightnessCustomizeDialog extends AlertDialog
}
SettingRow row = getItem(position);
- return row.luxTo > (row.luxFrom + 1);
+ SettingRow next = getItem(position + 1);
+ return next.lux > (row.lux + 1);
}
public void initFromSettings(int[] lux, int[] values) {
ArrayList<SettingRow> settings = new ArrayList<SettingRow>(values.length);
for (int i = 0; i < lux.length; i++) {
- settings.add(new SettingRow(i == 0 ? 0 : lux[i - 1], lux[i], values[i]));
+ settings.add(new SettingRow(i == 0 ? 0 : lux[i - 1], values[i]));
}
- settings.add(new SettingRow(lux[lux.length - 1], Integer.MAX_VALUE, values[values.length - 1]));
+ settings.add(new SettingRow(lux[lux.length - 1], values[values.length - 1]));
clear();
addAll(settings);
@@ -379,8 +521,8 @@ public class AutoBrightnessCustomizeDialog extends AlertDialog
int count = getCount();
int[] lux = new int[count - 1];
- for (int i = 0; i < count - 1; i++) {
- lux[i] = getItem(i).luxTo;
+ for (int i = 1; i < count; i++) {
+ lux[i - 1] = getItem(i).lux;
}
return lux;
@@ -407,8 +549,7 @@ public class AutoBrightnessCustomizeDialog extends AlertDialog
}
SettingRow lastRow = getItem(position);
- SettingRow nextRow = getItem(position + 1);
- rows.add(new SettingRow(splitLux, nextRow.luxFrom, lastRow.backlight));
+ rows.add(new SettingRow(splitLux, lastRow.backlight));
for (int i = position + 1; i < getCount(); i++) {
rows.add(getItem(i));
@@ -428,45 +569,33 @@ public class AutoBrightnessCustomizeDialog extends AlertDialog
sanitizeValuesAndNotify();
}
- public void setLuxToForRow(final int position, int newLuxTo) {
+ public void setLuxForRow(final int position, int newLux) {
final SettingRow row = getItem(position);
- if (isLastItem(position) || row.luxTo == newLuxTo) {
+ if (isLastItem(position) || row.lux == newLux) {
return;
}
- row.luxTo = newLuxTo;
+ row.lux = newLux;
sanitizeValuesAndNotify();
}
public void sanitizeValuesAndNotify() {
final int count = getCount();
- getItem(0).luxFrom = 0;
+ getItem(0).lux = 0;
for (int i = 1; i < count; i++) {
SettingRow lastRow = getItem(i - 1);
SettingRow thisRow = getItem(i);
- thisRow.luxFrom = Math.max(lastRow.luxFrom + 1, thisRow.luxFrom);
+ thisRow.lux = Math.max(lastRow.lux + 1, thisRow.lux);
thisRow.backlight = Math.max(lastRow.backlight, thisRow.backlight);
- lastRow.luxTo = thisRow.luxFrom;
}
- getItem(count - 1).luxTo = Integer.MAX_VALUE;
mIsDefault = false;
mAdapter.notifyDataSetChanged();
}
- private int brightnessToProgress(int brightness) {
- brightness -= mMinLevel;
- return brightness * 100;
- }
-
- private float progressToBrightness(int progress) {
- float brightness = (float) progress / 100F;
- return brightness + mMinLevel;
- }
-
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final Holder holder;
@@ -481,57 +610,25 @@ public class AutoBrightnessCustomizeDialog extends AlertDialog
convertView.setTag(holder);
holder.backlight.setMax(brightnessToProgress(PowerManager.BRIGHTNESS_ON));
- holder.backlight.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
- private boolean mIsDragging = false;
-
- private void updateBrightness(float brightness) {
- final Window window = getWindow();
- final WindowManager.LayoutParams params = window.getAttributes();
- params.screenBrightness = brightness;
- window.setAttributes(params);
+ holder.backlight.setOnSeekBarChangeListener(new BrightnessSeekBarChangeListener() {
+ @Override
+ protected int getPosition(SeekBar seekBar) {
+ return (Integer) seekBar.getTag();
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- int pos = (Integer) seekBar.getTag();
- if (fromUser) {
- int minValue = pos == 0
- ? 0
- : brightnessToProgress(getItem(pos - 1).backlight);
- int maxValue = isLastItem(pos)
- ? seekBar.getMax()
- : brightnessToProgress(getItem(pos + 1).backlight);
-
- if (progress < minValue) {
- seekBar.setProgress(minValue);
- progress = minValue;
- } else if (progress > maxValue) {
- seekBar.setProgress(maxValue);
- progress = maxValue;
- }
+ super.onProgressChanged(seekBar, progress, fromUser);
+ if (fromUser) {
+ int pos = getPosition(seekBar);
+ progress = seekBar.getProgress();
getItem(pos).backlight = Math.round(progressToBrightness(progress));
mIsDefault = false;
}
- if (mIsDragging) {
- float brightness = progressToBrightness(progress);
- updateBrightness(brightness / PowerManager.BRIGHTNESS_ON);
- }
-
holder.updatePercent();
}
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- float brightness = progressToBrightness(seekBar.getProgress());
- updateBrightness(brightness / PowerManager.BRIGHTNESS_ON);
- mIsDragging = true;
- }
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- updateBrightness(WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE);
- mIsDragging = false;
- }
});
} else {
holder = (Holder) convertView.getTag();
@@ -539,9 +636,11 @@ public class AutoBrightnessCustomizeDialog extends AlertDialog
SettingRow row = (SettingRow) getItem(position);
- final String to = row.luxTo == Integer.MAX_VALUE ? "\u221e" : String.valueOf(row.luxTo);
- holder.lux.setText(getContext().getString(R.string.auto_brightness_level_format,
- String.valueOf(row.luxFrom), to));
+ final int resId = isLastItem(position)
+ ? R.string.auto_brightness_last_level_format
+ : R.string.auto_brightness_level_format;
+
+ holder.lux.setText(getContext().getString(resId, row.lux));
holder.backlight.setTag(position);
holder.backlight.setProgress(brightnessToProgress(row.backlight));
@@ -562,4 +661,76 @@ public class AutoBrightnessCustomizeDialog extends AlertDialog
}
};
};
+
+ private float brightnessToPercent(float brightness) {
+ brightness -= mMinLevel;
+ return brightness * 100F / (PowerManager.BRIGHTNESS_ON - mMinLevel);
+ }
+
+ private float percentToBrightness(float percent) {
+ float brightness = percent * (PowerManager.BRIGHTNESS_ON - mMinLevel) / 100F;
+ return brightness + mMinLevel;
+ }
+
+ private int brightnessToProgress(float brightness) {
+ brightness -= mMinLevel;
+ return (int) (brightness * 100F);
+ }
+
+ private float progressToBrightness(int progress) {
+ float brightness = (float) progress / 100F;
+ return brightness + mMinLevel;
+ }
+
+ private abstract class BrightnessSeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
+ private boolean mIsDragging = false;
+
+ private void updateBrightness(float brightness) {
+ final Window window = getWindow();
+ final WindowManager.LayoutParams params = window.getAttributes();
+ params.screenBrightness = brightness;
+ window.setAttributes(params);
+ }
+
+ protected abstract int getPosition(SeekBar seekBar);
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ int pos = getPosition(seekBar);
+ if (fromUser) {
+ int minValue = pos == 0
+ ? 0
+ : brightnessToProgress(mAdapter.getItem(pos - 1).backlight);
+ int maxValue = mAdapter.isLastItem(pos)
+ ? seekBar.getMax()
+ : brightnessToProgress(mAdapter.getItem(pos + 1).backlight);
+
+ if (progress < minValue) {
+ seekBar.setProgress(minValue);
+ progress = minValue;
+ } else if (progress > maxValue) {
+ seekBar.setProgress(maxValue);
+ progress = maxValue;
+ }
+ }
+
+ if (mIsDragging) {
+ float brightness = progressToBrightness(progress);
+ updateBrightness(brightness / PowerManager.BRIGHTNESS_ON);
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ float brightness = progressToBrightness(seekBar.getProgress());
+ updateBrightness(brightness / PowerManager.BRIGHTNESS_ON);
+ mIsDragging = true;
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ updateBrightness(WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE);
+ mIsDragging = false;
+ }
+ };
}
diff --git a/src/com/android/settings/cyanogenmod/CubicSplinePreviewView.java b/src/com/android/settings/cyanogenmod/CubicSplinePreviewView.java
new file mode 100644
index 0000000..6baf9bc
--- /dev/null
+++ b/src/com/android/settings/cyanogenmod/CubicSplinePreviewView.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.cyanogenmod;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Paint.Style;
+import android.graphics.Path;
+import android.graphics.Shader;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Spline;
+import android.view.SurfaceView;
+import android.view.View;
+
+import com.android.settings.R;
+
+public class CubicSplinePreviewView extends SurfaceView {
+ private static final String TAG = "CubicSplinePreviewView";
+ private static final boolean DEBUG = false;
+
+ private float[] mXPoints;
+ private float[] mYPoints;
+ private Spline mSpline;
+
+ private static final int POINTS = 100;
+
+ private final Paint mFgPaint, mGridLinePaint;
+ private final Paint mXTextPaint, mYTextPaint;
+ private final Paint mPointPaint;
+ private final int mBgColor;
+ private final float mMarkerRadius;
+
+ public CubicSplinePreviewView(Context context) {
+ this(context, null);
+ }
+
+ public CubicSplinePreviewView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CubicSplinePreviewView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.CubicSplinePreviewView, defStyle, 0);
+
+ int fgColor = a.getColor(R.styleable.CubicSplinePreviewView_foregroundColor, Color.WHITE);
+ int markerColor = a.getColor(R.styleable.CubicSplinePreviewView_markerColor, 0x22ffffff);
+ int gridColor = a.getColor(R.styleable.CubicSplinePreviewView_gridColor, Color.WHITE);
+ mBgColor = a.getColor(R.styleable.CubicSplinePreviewView_backgroundColor, Color.BLACK);
+
+ float textSize = a.getDimensionPixelSize(R.styleable.CubicSplinePreviewView_textSize, 0);
+ float strokeWidth = a.getDimensionPixelSize(R.styleable.CubicSplinePreviewView_strokeWidth, 0);
+ mMarkerRadius = a.getDimensionPixelSize(R.styleable.CubicSplinePreviewView_markerSize, 1);
+
+ a.recycle();
+
+ mFgPaint = new Paint();
+ mFgPaint.setColor(fgColor);
+ mFgPaint.setStyle(Style.STROKE);
+ mFgPaint.setStrokeWidth(strokeWidth);
+ mFgPaint.setTextSize(textSize);
+ mFgPaint.setAntiAlias(true);
+
+ mGridLinePaint = new Paint();
+ mGridLinePaint.setColor(gridColor);
+ mGridLinePaint.setStyle(Style.STROKE);
+
+ mXTextPaint = new Paint(mFgPaint);
+ mXTextPaint.setTextAlign(Align.CENTER);
+ mXTextPaint.setStrokeWidth(0);
+ mXTextPaint.setShadowLayer(2, 0, 0, 0xff000000);
+
+ mYTextPaint = new Paint(mXTextPaint);
+ mYTextPaint.setTextAlign(Align.LEFT);
+
+ mPointPaint = new Paint();
+ mPointPaint.setStyle(Style.FILL);
+ mPointPaint.setColor(markerColor);
+ mPointPaint.setAntiAlias(true);
+
+ setWillNotDraw(false);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ buildLayer();
+ }
+
+ /**
+ * Sets the spline control points.
+ *
+ * @param xPoints Array of X coordinates of control points
+ * @param yPoints Array of Y coordinates of control points
+ *
+ * There are some assumptions made about those arrays:
+ * - xPoints and yPoints must be of equal length
+ * - The value range of yPoints is 0..1
+ */
+ public void setSpline(float[] xPoints, float[] yPoints) {
+ mXPoints = xPoints;
+ mYPoints = yPoints;
+ for (int i = 0; i < xPoints.length; i++) {
+ Log.d(TAG, "Spline data[" + i + "]: x = " + xPoints[i] + " y = " + yPoints[i]);
+ }
+ mSpline = Spline.createMonotoneCubicSpline(xPoints, yPoints);
+ postInvalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ /* clear canvas */
+ canvas.drawRGB(Color.red(mBgColor), Color.green(mBgColor), Color.blue(mBgColor));
+
+ if (mSpline == null) {
+ return;
+ }
+
+ Path curve = new Path();
+
+ int width = getWidth();
+ int height = getHeight();
+ double dist = (double) width / (POINTS - 1);
+
+ for (int i = 0; i < POINTS; i++) {
+ double xPixel = dist * i;
+ float x = (float) reverseProjectX(xPixel / width);
+ float y = mSpline.interpolate(x);
+ float yPixel = (float) (projectY(y) * height);
+
+ if (DEBUG) {
+ Log.d(TAG, "point[" + i + "]: X = (" + x + "," + xPixel + "), Y = (" + y + "," + yPixel + ")");
+ }
+
+ if (i == 0) {
+ curve.moveTo((float) xPixel, (float) height - yPixel);
+ } else {
+ curve.lineTo((float) xPixel, (float) height - yPixel);
+ }
+ }
+
+ canvas.drawPath(curve, mFgPaint);
+
+ /* draw vertical lines */
+ float minX = getMinX();
+ float maxX = getMaxX();
+ float minY = getMinY();
+ float maxY = getMaxY();
+
+ for (float xPos = minX; xPos <= maxX; ) {
+ float x = (float) (projectX(xPos) * width);
+ canvas.drawLine(x, 0, x, height - 1, mGridLinePaint);
+ if (xPos < 10) {
+ xPos += 1;
+ } else if (xPos < 100) {
+ xPos += 10;
+ } else if (xPos < 1000) {
+ xPos += 100;
+ } else if (xPos < 10000) {
+ xPos += 1000;
+ } else {
+ xPos += 10000;
+ }
+ }
+
+ /* draw horizontal lines */
+ float yDist = (maxY - minY) / 10;
+ for (int i = 1; i <= 10; i++) {
+ float y = (float) ((1.0 - projectY(yDist * i + minY)) * height);
+ canvas.drawLine(0, y, width - 1, y, mGridLinePaint);
+ canvas.drawText(String.format("%.0f%%", yDist * i * 100), 1,
+ y + mYTextPaint.getTextSize(), mYTextPaint);
+ }
+
+ for (int i = 0; i < mXPoints.length; i ++) {
+ float x = (i == 0) ? getMinX() : mXPoints[i];
+ float xPixel = (float) (projectX(x) * width);
+ float yPixel = (float) ((1.0 - projectY(mYPoints[i])) * height);
+ if (DEBUG) {
+ Log.d(TAG, "Print control point " + mXPoints[i] + " at (" + xPixel + "," + (height - 2) + ")");
+ }
+ if (i == 0) {
+ mXTextPaint.setTextAlign(Align.LEFT);
+ } else if (i == (mXPoints.length - 1)) {
+ mXTextPaint.setTextAlign(Align.RIGHT);
+ } else {
+ mXTextPaint.setTextAlign(Align.CENTER);
+ }
+ canvas.drawCircle(xPixel, yPixel, mMarkerRadius, mPointPaint);
+ canvas.drawText(String.format("%.0f", mXPoints[i]), xPixel, height - 2, mXTextPaint);
+ }
+ }
+
+ private double projectX(double value) {
+ double pos = Math.log(value);
+ double minPos = Math.log(getMinX());
+ double maxPos = Math.log(getMaxX());
+ return (pos - minPos) / (maxPos - minPos);
+ }
+
+ private double reverseProjectX(double pos) {
+ double minPos = Math.log(getMinX());
+ double maxPos = Math.log(getMaxX());
+ return Math.exp(pos * (maxPos - minPos) + minPos);
+ }
+
+ private double projectY(double value) {
+ double min = getMinY();
+ double max = getMaxY();
+ return (value - min) / (max - min);
+ }
+
+ private float getMinX() {
+ return Math.max(mXPoints[0], 1);
+ }
+
+ private float getMaxX() {
+ return mXPoints[mXPoints.length - 1];
+ }
+
+ private float getMinY() {
+ return Math.min(mYPoints[0], 0);
+ }
+
+ private float getMaxY() {
+ return Math.max(mYPoints[mYPoints.length - 1], 1);
+ }
+}