diff options
author | Danny Baumann <dannybaumann@web.de> | 2013-04-07 13:33:43 +0200 |
---|---|---|
committer | Danny Baumann <dannybaumann@web.de> | 2013-04-18 11:36:44 +0200 |
commit | 0d81e44b5ca1d363ddfde32ad0d833e429e33af1 (patch) | |
tree | bf756289050ccc17e9809152ffac0279569f6028 /src/com/android | |
parent | bdb1b7f1a1d2b1bc0c1ddcd22a3c2cc63b85a031 (diff) | |
download | packages_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/com/android')
-rw-r--r-- | src/com/android/settings/cyanogenmod/AutoBrightnessCustomizeDialog.java | 347 | ||||
-rw-r--r-- | src/com/android/settings/cyanogenmod/CubicSplinePreviewView.java | 253 |
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); + } +} |