settings and about activity

This commit is contained in:
serso 2011-09-18 22:25:28 +04:00
parent 898ac936df
commit 540ed3ce26
35 changed files with 1395 additions and 67 deletions

View File

@ -19,6 +19,7 @@
<activity a:name=".CalculatorPreferencesActivity" a:label="@string/c_app_settings"/>
<activity a:name=".DragButtonCalibrationActivity" a:label="@string/c_prefs_drag_button_calibration"/>
<activity a:name=".AboutActivity" a:label="@string/c_about"/>
</application>
</manifest>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2009-2011. Created by serso aka se.solovyev.
~ For more information, please, contact se.solovyev@gmail.com
~ or visit http://se.solovyev.org
-->
<!-- Copyright (C) 2008 The Android Open Source 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.
-->
<selector xmlns:a="http://schemas.android.com/apk/res/android">
<item a:state_pressed="false"
a:state_enabled="true"
a:state_focused="false"
a:drawable="@drawable/timepicker_down_normal" />
<item a:state_pressed="true"
a:state_enabled="true"
a:drawable="@drawable/timepicker_down_pressed" />
<item a:state_pressed="false"
a:state_enabled="true"
a:state_focused="true"
a:drawable="@drawable/timepicker_down_selected" />
<item a:state_pressed="false"
a:state_enabled="false"
a:state_focused="false"
a:drawable="@drawable/timepicker_down_disabled" />
<item a:state_pressed="false"
a:state_enabled="false"
a:state_focused="true"
a:drawable="@drawable/timepicker_down_disabled_focused" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2009-2011. Created by serso aka se.solovyev.
~ For more information, please, contact se.solovyev@gmail.com
~ or visit http://se.solovyev.org
-->
<!-- Copyright (C) 2008 The Android Open Source 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false" android:state_enabled="true"
android:state_focused="false" android:drawable="@drawable/timepicker_input_normal" />
<item android:state_pressed="true" android:state_enabled="true"
android:drawable="@drawable/timepicker_input_pressed" />
<item android:state_pressed="false" android:state_enabled="true"
android:state_focused="true" android:drawable="@drawable/timepicker_input_selected" />
<item android:state_pressed="false" android:state_enabled="false"
android:state_focused="false" android:drawable="@drawable/timepicker_input_disabled" />
<item android:state_pressed="false" android:state_enabled="false"
android:state_focused="true" android:drawable="@drawable/timepicker_input_normal" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2009-2011. Created by serso aka se.solovyev.
~ For more information, please, contact se.solovyev@gmail.com
~ or visit http://se.solovyev.org
-->
<!-- Copyright (C) 2008 The Android Open Source 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false" android:state_enabled="true"
android:state_focused="false" android:drawable="@drawable/timepicker_up_normal"/>
<item android:state_pressed="true" android:state_enabled="true"
android:drawable="@drawable/timepicker_up_pressed"/>
<item android:state_pressed="false" android:state_enabled="true"
android:state_focused="true" android:drawable="@drawable/timepicker_up_selected"/>
<item android:state_pressed="false" android:state_enabled="false"
android:state_focused="false" android:drawable="@drawable/timepicker_up_disabled"/>
<item android:state_pressed="false" android:state_enabled="false"
android:state_focused="true" android:drawable="@drawable/timepicker_up_disabled_focused"/>
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 989 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

22
res/layout-land/about.xml Normal file
View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2009-2011. Created by serso aka se.solovyev.
~ For more information, please, contact se.solovyev@gmail.com
~ or visit http://se.solovyev.org
-->
<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
a:orientation="vertical"
a:layout_width="match_parent"
a:layout_height="match_parent"
a:background="#ff000000">
<TextView
a:id="@+id/aboutTextView"
a:textSize="20dp"
a:text="@string/c_copyright"
style="@style/display_style"
a:gravity="center|top"/>
</LinearLayout>

22
res/layout-port/about.xml Normal file
View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2009-2011. Created by serso aka se.solovyev.
~ For more information, please, contact se.solovyev@gmail.com
~ or visit http://se.solovyev.org
-->
<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
a:orientation="vertical"
a:layout_width="match_parent"
a:layout_height="match_parent"
a:background="#ff000000">
<TextView
a:id="@+id/aboutTextView"
a:text="@string/c_copyright"
a:textSize="20dp"
style="@style/display_style"
a:gravity="center|top"/>
</LinearLayout>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2009-2011. Created by serso aka se.solovyev.
~ For more information, please, contact se.solovyev@gmail.com
~ or visit http://se.solovyev.org
-->
<!--
**
** Copyright 2008, The Android Open Source 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.
*/
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<org.solovyev.android.view.NumberPickerButton android:id="@+id/increment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/timepicker_up_btn" />
<EditText android:id="@+id/timepicker_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:singleLine="true"
style="?android:attr/textAppearanceLargeInverse"
android:textColor="@android:color/primary_text_light"
android:textSize="30sp"
android:background="@drawable/timepicker_input" />
<org.solovyev.android.view.NumberPickerButton android:id="@+id/decrement"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/timepicker_down_btn" />
</merge>

View File

@ -1,9 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:a="http://schemas.android.com/apk/res/android">
<item a:id="@+id/menu_item_settings"
a:title="@string/c_settings"/>
<item a:id="@+id/menu_item_help"
a:title="@string/c_help"/>
<item a:id="@+id/menu_item_about"
a:title="@string/c_about"/>
</menu>

View File

@ -6,10 +6,20 @@
<string name="c_result_copied">Result copied to clipboard!</string>
<string name="c_settings">Settings</string>
<string name="c_help">Help</string>
<string name="c_about">About</string>
<!--PREFERENCE ACTIVITY-->
<string name="c_prefs_main_category">Main settings</string>
<string name="c_prefs_drag_button_category">Drag buttons settings</string>
<string name="c_prefs_drag_button_calibration">Drag button calibration</string>
<string name="c_prefs_drag_button_calibration_summary">Allows to calibrate drag button behaviour</string>
<string name="c_down">Down</string>
<string name="c_up">Up</string>
<string name="c_restart">Restart</string>
<!--ABOUT ACTIVITY-->
<string name="c_copyright">Copyright (c) 2009-2011.\nCreated by serso aka se.solovyev.\n
For more information please\ncontact me via email\n<a href="mailto:se.solovyev@gmail.com">se.solovyev@gmail.com</a>
\nor visit\n<a href="http://se.solovyev.org">http://se.solovyev.org</a>
</string>
</resources>

View File

@ -1,20 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:a="http://schemas.android.com/apk/res/android">
<PreferenceCategory a:title="@string/c_prefs_main_category">
<Preference
a:title="@string/c_prefs_drag_button_calibration"
a:summary="@string/c_prefs_drag_button_calibration_summary"
a:key="dragButtonCalibration"/>
<CheckBoxPreference
a:title="Checkbox Preference"
a:defaultValue="false"
a:summary="This preference can be true or false"
a:key="checkboxPref"/>
<PreferenceCategory a:title="@string/c_prefs_drag_button_category">
<!-- <org.solovyev.android.view.SeekBarPreference a:key="duration"
a:title="Duration of something"
a:summary="How long something will last"
a:dialogMessage="Something duration"
a:defaultValue="5"
a:text=" minutes"
a:max="60"/>-->
</PreferenceCategory>
</PreferenceScreen>

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.calculator;
import android.app.Activity;
import android.os.Bundle;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.widget.TextView;
import org.jetbrains.annotations.Nullable;
/**
* User: serso
* Date: 9/16/11
* Time: 11:52 PM
*/
public class AboutActivity extends Activity {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.about);
final TextView about = (TextView) findViewById(R.id.aboutTextView);
about.setMovementMethod(LinkMovementMethod.getInstance());
}
}

View File

@ -25,15 +25,13 @@ import org.solovyev.common.utils.history.HistoryAction;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
public class CalculatorActivity extends Activity implements FontSizeAdjuster {
private static final int HVGA_WIDTH_PIXELS = 320;
@NotNull
private List<SimpleOnDragListener> onDragListeners = new ArrayList<SimpleOnDragListener>();
private final DragPreferencesChangeListenerRegister dpclRegister = new DragPreferencesChangeListenerRegister();
@NotNull
private CalculatorView calculatorView;
@ -64,7 +62,7 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster {
final DragButtonCalibrationActivity.Preferences dragPreferences = DragButtonCalibrationActivity.getPreferences(this);
final SimpleOnDragListener onDragListener = new SimpleOnDragListener(new DigitButtonDragProcessor(calculatorView), dragPreferences);
onDragListeners.add(onDragListener);
dpclRegister.addListener(onDragListener);
// todo serso: check if there is more convenient method for doing this
final R.id ids = new R.id();
@ -86,12 +84,12 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster {
final SimpleOnDragListener historyOnDragListener = new SimpleOnDragListener(new HistoryDragProcessor<CalculatorHistoryState>(this.calculatorView), dragPreferences);
((DragButton) findViewById(R.id.historyButton)).setOnDragListener(historyOnDragListener);
onDragListeners.add(historyOnDragListener);
dpclRegister.addListener(historyOnDragListener);
final SimpleOnDragListener toPositionOnDragListener = new SimpleOnDragListener(new CursorDragProcessor(calculatorView), dragPreferences);
((DragButton) findViewById(R.id.rightButton)).setOnDragListener(toPositionOnDragListener);
((DragButton) findViewById(R.id.leftButton)).setOnDragListener(toPositionOnDragListener);
onDragListeners.add(toPositionOnDragListener);
dpclRegister.addListener(toPositionOnDragListener);
preferencesChangesReceiver = new BroadcastReceiver() {
@ -100,9 +98,7 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster {
if (DragButtonCalibrationActivity.INTENT_ACTION.equals(intent.getAction())) {
final DragButtonCalibrationActivity.Preferences preferences = DragButtonCalibrationActivity.getPreferences(CalculatorActivity.this);
for (SimpleOnDragListener dragListener : onDragListeners) {
dragListener.setPreferences(preferences);
}
dpclRegister.announce().onDragPreferencesChange(preferences);
}
}
};
@ -189,9 +185,8 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// todo serso: inflate menu as soon as it will implemented in proper way
/* final MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.main_menu, menu);*/
final MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.main_menu, menu);
return true;
}
@ -204,8 +199,8 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster {
showSettings();
result = true;
break;
case R.id.menu_item_help:
showHelp();
case R.id.menu_item_about:
showAbout();
result = true;
break;
default:
@ -219,8 +214,8 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster {
startActivity(new Intent(this, CalculatorPreferencesActivity.class));
}
private void showHelp() {
Log.d(CalculatorActivity.class + "showHelp()", "Show help!");
private void showAbout() {
startActivity(new Intent(this, AboutActivity.class));
}
/**

View File

@ -22,19 +22,5 @@ public class CalculatorPreferencesActivity extends PreferenceActivity {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
final Preference dragButtonCalibration = findPreference("dragButtonCalibration");
dragButtonCalibration.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
startActivity(new Intent(CalculatorPreferencesActivity.this, DragButtonCalibrationActivity.class));
return true;
}
});
}
@Override
protected void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
}
}

View File

@ -1,17 +0,0 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.calculator;
import android.app.Activity;
/**
* User: serso
* Date: 9/16/11
* Time: 11:52 PM
*/
public class HelpActivity extends Activity {
// todo serso: implement
}

View File

@ -0,0 +1,290 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view;
import android.content.Context;
import android.graphics.Canvas;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;
/**
* Text view that auto adjusts text size to fit within the view.
* If the text size equals the minimum text size and still does not
* fit, append with an ellipsis.
*
* @author Chase Colburn
* @since Apr 4, 2011
*/
public class AutoResizeTextView extends TextView {
// Minimum text size for this text view
public static final float MIN_TEXT_SIZE = 20;
// Interface for resize notifications
public interface OnTextResizeListener {
public void onTextResize(TextView textView, float oldSize, float newSize);
}
// Off screen canvas for text size rendering
private static final Canvas sTextResizeCanvas = new Canvas();
// Our ellipse string
private static final String mEllipsis = "...";
// Registered resize listener
private OnTextResizeListener mTextResizeListener;
// Flag for text and/or size changes to force a resize
private boolean mNeedsResize = false;
// Text size that is set from code. This acts as a starting point for resizing
private float mTextSize;
// Temporary upper bounds on the starting text size
private float mMaxTextSize = 0;
// Lower bounds for text size
private float mMinTextSize = MIN_TEXT_SIZE;
// Text view line spacing multiplier
private float mSpacingMult = 1.0f;
// Text view additional line spacing
private float mSpacingAdd = 0.0f;
// Add ellipsis to text that overflows at the smallest text size
private boolean mAddEllipsis = true;
// Default constructor override
public AutoResizeTextView(Context context) {
this(context, null);
}
// Default constructor when inflating from XML file
public AutoResizeTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
// Default constructor override
public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mTextSize = getTextSize();
}
/**
* When text changes, set the force resize flag to true and reset the text size.
*/
@Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
mNeedsResize = true;
// Since this view may be reused, it is good to reset the text size
resetTextSize();
}
/**
* If the text view size changed, set the force resize flag to true
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw || h != oldh) {
mNeedsResize = true;
}
}
/**
* Register listener to receive resize notifications
* @param listener
*/
public void setOnResizeListener(OnTextResizeListener listener) {
mTextResizeListener = listener;
}
/**
* Override the set text size to update our internal reference values
*/
@Override
public void setTextSize(float size) {
super.setTextSize(size);
mTextSize = getTextSize();
}
/**
* Override the set text size to update our internal reference values
*/
@Override
public void setTextSize(int unit, float size) {
super.setTextSize(unit, size);
mTextSize = getTextSize();
}
/**
* Override the set line spacing to update our internal reference values
*/
@Override
public void setLineSpacing(float add, float mult) {
super.setLineSpacing(add, mult);
mSpacingMult = mult;
mSpacingAdd = add;
}
/**
* Set the upper text size limit and invalidate the view
* @param maxTextSize
*/
public void setMaxTextSize(float maxTextSize) {
mMaxTextSize = maxTextSize;
requestLayout();
invalidate();
}
/**
* Return upper text size limit
* @return
*/
public float getMaxTextSize() {
return mMaxTextSize;
}
/**
* Set the lower text size limit and invalidate the view
* @param minTextSize
*/
public void setMinTextSize(float minTextSize) {
mMinTextSize = minTextSize;
requestLayout();
invalidate();
}
/**
* Return lower text size limit
* @return
*/
public float getMinTextSize() {
return mMinTextSize;
}
/**
* Set flag to add ellipsis to text that overflows at the smallest text size
* @param addEllipsis
*/
public void setAddEllipsis(boolean addEllipsis) {
mAddEllipsis = addEllipsis;
}
/**
* Return flag to add ellipsis to text that overflows at the smallest text size
* @return
*/
public boolean getAddEllipsis() {
return mAddEllipsis;
}
/**
* Reset the text to the original size
*/
public void resetTextSize() {
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
mMaxTextSize = mTextSize;
}
/**
* Resize text after measuring
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if(changed || mNeedsResize) {
resizeText(right - left, bottom - top);
}
super.onLayout(changed, left, top, right, bottom);
}
/**
* Resize the text size with default width and height
*/
public void resizeText() {
int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
resizeText(widthLimit, heightLimit);
}
/**
* Resize the text size with specified width and height
* @param width
* @param height
*/
public void resizeText(int width, int height) {
CharSequence text = getText();
// Do not resize if the view does not have dimensions or there is no text
if(text == null || text.length() == 0 || height <= 0 || width <= 0) {
return;
}
// Get the text view's paint object
TextPaint textPaint = getPaint();
// Store the current text size
float oldTextSize = textPaint.getTextSize();
// If there is a max text size set, use the lesser of that and the default text size
float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;
// Get the required text height
int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
// Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
while(textHeight > height && targetTextSize > mMinTextSize) {
targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
textHeight = getTextHeight(text, textPaint, width, targetTextSize);
}
// If we had reached our minimum text size and still don't fit, append an ellipsis
if(mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
// Draw using a static layout
StaticLayout layout = new StaticLayout(text, textPaint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
layout.draw(sTextResizeCanvas);
int lastLine = layout.getLineForVertical(height) - 1;
int start = layout.getLineStart(lastLine);
int end = layout.getLineEnd(lastLine);
float lineWidth = layout.getLineWidth(lastLine);
float ellipseWidth = textPaint.measureText(mEllipsis);
// Trim characters off until we have enough room to draw the ellipsis
while(width < lineWidth + ellipseWidth) {
lineWidth = textPaint.measureText(text.subSequence(start, --end + 1).toString());
}
setText(text.subSequence(0, end) + mEllipsis);
}
// Some devices try to auto adjust line spacing, so force default line spacing
// and invalidate the layout as a side effect
textPaint.setTextSize(targetTextSize);
setLineSpacing(mSpacingAdd, mSpacingMult);
// Notify the listener if registered
if(mTextResizeListener != null) {
mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
}
// Reset force resize flag
mNeedsResize = false;
}
// Set the text size of the text paint object and use a static layout to render text off screen before measuring
private int getTextHeight(CharSequence source, TextPaint paint, int width, float textSize) {
// Update the text paint object
paint.setTextSize(textSize);
// Draw using a static layout
StaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
layout.draw(sTextResizeCanvas);
return layout.getHeight();
}
}

View File

@ -0,0 +1,21 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.view;
import org.jetbrains.annotations.NotNull;
import org.solovyev.android.calculator.DragButtonCalibrationActivity;
import java.util.EventListener;
/**
* User: serso
* Date: 9/18/11
* Time: 8:48 PM
*/
public interface DragPreferencesChangeListener extends EventListener{
void onDragPreferencesChange(@NotNull DragButtonCalibrationActivity.Preferences preferences );
}

View File

@ -0,0 +1,20 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
*/
package org.solovyev.android.view;
import org.solovyev.common.utils.Announcer;
/**
* User: serso
* Date: 9/18/11
* Time: 8:53 PM
*/
public class DragPreferencesChangeListenerRegister extends Announcer<DragPreferencesChangeListener> {
public DragPreferencesChangeListenerRegister() {
super(DragPreferencesChangeListener.class);
}
}

View File

@ -0,0 +1,538 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view;
/**
* User: serso
* Date: 9/18/11
* Time: 10:03 PM
*/
/*
* Copyright (C) 2008 The Android Open Source 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.
*/
import android.content.Context;
import android.os.Handler;
import android.text.InputFilter;
import android.text.InputType;
import android.text.Spanned;
import android.text.method.NumberKeyListener;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.solovyev.android.calculator.R;
/**
* A view for selecting a number
*
* For a dialog using this view, see {@link android.app.TimePickerDialog}.
* @hide
*/
public class NumberPicker extends LinearLayout {
/**
* The callback interface used to indicate the number value has been adjusted.
*/
public interface OnChangedListener {
/**
* @param picker The NumberPicker associated with this listener.
* @param oldVal The previous value.
* @param newVal The new value.
*/
void onChanged(NumberPicker picker, int oldVal, int newVal);
}
/**
* Interface used to format the number into a string for presentation
*/
public interface Formatter {
String toString(int value);
}
/*
* Use a custom NumberPicker formatting callback to use two-digit
* minutes strings like "01". Keeping a static formatter etc. is the
* most efficient way to do this; it avoids creating temporary objects
* on every call to format().
*/
public static final NumberPicker.Formatter TWO_DIGIT_FORMATTER =
new NumberPicker.Formatter() {
final StringBuilder mBuilder = new StringBuilder();
final java.util.Formatter mFmt = new java.util.Formatter(
mBuilder, java.util.Locale.US);
final Object[] mArgs = new Object[1];
public String toString(int value) {
mArgs[0] = value;
mBuilder.delete(0, mBuilder.length());
mFmt.format("%02d", mArgs);
return mFmt.toString();
}
};
private final Handler mHandler;
private final Runnable mRunnable = new Runnable() {
public void run() {
if (mIncrement) {
changeCurrent(mCurrent + 1);
mHandler.postDelayed(this, mSpeed);
} else if (mDecrement) {
changeCurrent(mCurrent - 1);
mHandler.postDelayed(this, mSpeed);
}
}
};
private final EditText mText;
private final InputFilter mNumberInputFilter;
private String[] mDisplayedValues;
/**
* Lower value of the range of numbers allowed for the NumberPicker
*/
private int mStart;
/**
* Upper value of the range of numbers allowed for the NumberPicker
*/
private int mEnd;
/**
* Current value of this NumberPicker
*/
private int mCurrent;
/**
* Previous value of this NumberPicker.
*/
private int mPrevious;
private OnChangedListener mListener;
private Formatter mFormatter;
private long mSpeed = 300;
private boolean mIncrement;
private boolean mDecrement;
/**
* Create a new number picker
* @param context the application environment
*/
public NumberPicker(Context context) {
this(context, null);
}
/**
* Create a new number picker
* @param context the application environment
* @param attrs a collection of attributes
*/
public NumberPicker(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(VERTICAL);
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.number_picker, this, true);
mHandler = new Handler();
OnClickListener clickListener = new OnClickListener() {
public void onClick(View v) {
validateInput(mText);
if (!mText.hasFocus()) mText.requestFocus();
// now perform the increment/decrement
if (R.id.increment == v.getId()) {
changeCurrent(mCurrent + 1);
} else if (R.id.decrement == v.getId()) {
changeCurrent(mCurrent - 1);
}
}
};
OnFocusChangeListener focusListener = new OnFocusChangeListener() {
public void onFocusChange(View v, boolean hasFocus) {
/* When focus is lost check that the text field
* has valid values.
*/
if (!hasFocus) {
validateInput(v);
}
}
};
OnLongClickListener longClickListener = new OnLongClickListener() {
/**
* We start the long click here but rely on the {@link NumberPickerButton}
* to inform us when the long click has ended.
*/
public boolean onLongClick(View v) {
/* The text view may still have focus so clear it's focus which will
* trigger the on focus changed and any typed values to be pulled.
*/
mText.clearFocus();
if (R.id.increment == v.getId()) {
mIncrement = true;
mHandler.post(mRunnable);
} else if (R.id.decrement == v.getId()) {
mDecrement = true;
mHandler.post(mRunnable);
}
return true;
}
};
InputFilter inputFilter = new NumberPickerInputFilter();
mNumberInputFilter = new NumberRangeKeyListener();
mIncrementButton = (NumberPickerButton) findViewById(R.id.increment);
mIncrementButton.setOnClickListener(clickListener);
mIncrementButton.setOnLongClickListener(longClickListener);
mIncrementButton.setNumberPicker(this);
mDecrementButton = (NumberPickerButton) findViewById(R.id.decrement);
mDecrementButton.setOnClickListener(clickListener);
mDecrementButton.setOnLongClickListener(longClickListener);
mDecrementButton.setNumberPicker(this);
mText = (EditText) findViewById(R.id.timepicker_input);
mText.setOnFocusChangeListener(focusListener);
mText.setFilters(new InputFilter[] {inputFilter});
mText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
if (!isEnabled()) {
setEnabled(false);
}
}
/**
* Set the enabled state of this view. The interpretation of the enabled
* state varies by subclass.
*
* @param enabled True if this view is enabled, false otherwise.
*/
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
mIncrementButton.setEnabled(enabled);
mDecrementButton.setEnabled(enabled);
mText.setEnabled(enabled);
}
/**
* Set the callback that indicates the number has been adjusted by the user.
* @param listener the callback, should not be null.
*/
public void setOnChangeListener(OnChangedListener listener) {
mListener = listener;
}
/**
* Set the formatter that will be used to format the number for presentation
* @param formatter the formatter object. If formatter is null, String.valueOf()
* will be used
*/
public void setFormatter(Formatter formatter) {
mFormatter = formatter;
}
/**
* Set the range of numbers allowed for the number picker. The current
* value will be automatically set to the start.
*
* @param start the start of the range (inclusive)
* @param end the end of the range (inclusive)
*/
public void setRange(int start, int end) {
setRange(start, end, null/*displayedValues*/);
}
/**
* Set the range of numbers allowed for the number picker. The current
* value will be automatically set to the start. Also provide a mapping
* for values used to display to the user.
*
* @param start the start of the range (inclusive)
* @param end the end of the range (inclusive)
* @param displayedValues the values displayed to the user.
*/
public void setRange(int start, int end, String[] displayedValues) {
mDisplayedValues = displayedValues;
mStart = start;
mEnd = end;
mCurrent = start;
updateView();
if (displayedValues != null) {
// Allow text entry rather than strictly numeric entry.
mText.setRawInputType(InputType.TYPE_CLASS_TEXT |
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
}
}
/**
* Set the current value for the number picker.
*
* @param current the current value the start of the range (inclusive)
* @throws IllegalArgumentException when current is not within the range
* of of the number picker
*/
public void setCurrent(int current) {
if (current < mStart || current > mEnd) {
throw new IllegalArgumentException(
"current should be >= start and <= end");
}
mCurrent = current;
updateView();
}
/**
* Sets the speed at which the numbers will scroll when the +/-
* buttons are longpressed
*
* @param speed The speed (in milliseconds) at which the numbers will scroll
* default 300ms
*/
public void setSpeed(long speed) {
mSpeed = speed;
}
private String formatNumber(int value) {
return (mFormatter != null)
? mFormatter.toString(value)
: String.valueOf(value);
}
/**
* Sets the current value of this NumberPicker, and sets mPrevious to the previous
* value. If current is greater than mEnd less than mStart, the value of mCurrent
* is wrapped around.
*
* Subclasses can override this to change the wrapping behavior
*
* @param current the new value of the NumberPicker
*/
protected void changeCurrent(int current) {
// Wrap around the values if we go past the start or end
if (current > mEnd) {
current = mStart;
} else if (current < mStart) {
current = mEnd;
}
mPrevious = mCurrent;
mCurrent = current;
notifyChange();
updateView();
}
/**
* Notifies the listener, if registered, of a change of the value of this
* NumberPicker.
*/
private void notifyChange() {
if (mListener != null) {
mListener.onChanged(this, mPrevious, mCurrent);
}
}
/**
* Updates the view of this NumberPicker. If displayValues were specified
* in {@link #setRange}, the string corresponding to the index specified by
* the current value will be returned. Otherwise, the formatter specified
* in will be used to format the number.
*/
private void updateView() {
/* If we don't have displayed values then use the
* current number else find the correct value in the
* displayed values for the current number.
*/
if (mDisplayedValues == null) {
mText.setText(formatNumber(mCurrent));
} else {
mText.setText(mDisplayedValues[mCurrent - mStart]);
}
mText.setSelection(mText.getText().length());
}
private void validateCurrentView(CharSequence str) {
int val = getSelectedPos(str.toString());
if ((val >= mStart) && (val <= mEnd)) {
if (mCurrent != val) {
mPrevious = mCurrent;
mCurrent = val;
notifyChange();
}
}
updateView();
}
private void validateInput(View v) {
String str = String.valueOf(((TextView) v).getText());
if ("".equals(str)) {
// Restore to the old value as we don't allow empty values
updateView();
} else {
// Check the new value and ensure it's in range
validateCurrentView(str);
}
}
/**
* @hide
*/
public void cancelIncrement() {
mIncrement = false;
}
/**
* @hide
*/
public void cancelDecrement() {
mDecrement = false;
}
private static final char[] DIGIT_CHARACTERS = new char[] {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
};
private NumberPickerButton mIncrementButton;
private NumberPickerButton mDecrementButton;
private class NumberPickerInputFilter implements InputFilter {
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
if (mDisplayedValues == null) {
return mNumberInputFilter.filter(source, start, end, dest, dstart, dend);
}
CharSequence filtered = String.valueOf(source.subSequence(start, end));
String result = String.valueOf(dest.subSequence(0, dstart))
+ filtered
+ dest.subSequence(dend, dest.length());
String str = String.valueOf(result).toLowerCase();
for (String val : mDisplayedValues) {
val = val.toLowerCase();
if (val.startsWith(str)) {
return filtered;
}
}
return "";
}
}
private class NumberRangeKeyListener extends NumberKeyListener {
// XXX This doesn't allow for range limits when controlled by a
// soft input method!
public int getInputType() {
return InputType.TYPE_CLASS_NUMBER;
}
@Override
protected char[] getAcceptedChars() {
return DIGIT_CHARACTERS;
}
@Override
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
CharSequence filtered = super.filter(source, start, end, dest, dstart, dend);
if (filtered == null) {
filtered = source.subSequence(start, end);
}
String result = String.valueOf(dest.subSequence(0, dstart))
+ filtered
+ dest.subSequence(dend, dest.length());
if ("".equals(result)) {
return result;
}
int val = getSelectedPos(result);
/* Ensure the user can't type in a value greater
* than the max allowed. We have to allow less than min
* as the user might want to delete some numbers
* and then type a new number.
*/
if (val > mEnd) {
return "";
} else {
return filtered;
}
}
}
private int getSelectedPos(String str) {
if (mDisplayedValues == null) {
try {
return Integer.parseInt(str);
} catch (NumberFormatException e) {
/* Ignore as if it's not a number we don't care */
}
} else {
for (int i = 0; i < mDisplayedValues.length; i++) {
/* Don't force the user to type in jan when ja will do */
str = str.toLowerCase();
if (mDisplayedValues[i].toLowerCase().startsWith(str)) {
return mStart + i;
}
}
/* The user might have typed in a number into the month field i.e.
* 10 instead of OCT so support that too.
*/
try {
return Integer.parseInt(str);
} catch (NumberFormatException e) {
/* Ignore as if it's not a number we don't care */
}
}
return mStart;
}
/**
* Returns the current value of the NumberPicker
* @return the current value.
*/
public int getCurrent() {
return mCurrent;
}
/**
* Returns the upper value of the range of the NumberPicker
* @return the uppper number of the range.
*/
protected int getEndRange() {
return mEnd;
}
/**
* Returns the lower value of the range of the NumberPicker
* @return the lower number of the range.
*/
protected int getBeginRange() {
return mStart;
}
}

View File

@ -0,0 +1,106 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view;
/**
* User: serso
* Date: 9/18/11
* Time: 10:04 PM
*/
/*
* Copyright (C) 2008 The Android Open Source 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.
*/
import android.content.Context;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.widget.ImageButton;
import org.solovyev.android.calculator.R;
/**
* This class exists purely to cancel long click events, that got
* started in NumberPicker
*/
class NumberPickerButton extends ImageButton {
private NumberPicker mNumberPicker;
public NumberPickerButton(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public NumberPickerButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NumberPickerButton(Context context) {
super(context);
}
public void setNumberPicker(NumberPicker picker) {
mNumberPicker = picker;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
cancelLongpressIfRequired(event);
return super.onTouchEvent(event);
}
@Override
public boolean onTrackballEvent(MotionEvent event) {
cancelLongpressIfRequired(event);
return super.onTrackballEvent(event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER)
|| (keyCode == KeyEvent.KEYCODE_ENTER)) {
cancelLongpress();
}
return super.onKeyUp(keyCode, event);
}
private void cancelLongpressIfRequired(MotionEvent event) {
if ((event.getAction() == MotionEvent.ACTION_CANCEL)
|| (event.getAction() == MotionEvent.ACTION_UP)) {
cancelLongpress();
}
}
private void cancelLongpress() {
if (R.id.increment == getId()) {
mNumberPicker.cancelIncrement();
} else if (R.id.decrement == getId()) {
mNumberPicker.cancelDecrement();
}
}
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (!hasWindowFocus) {
cancelLongpress();
}
}
}

View File

@ -0,0 +1,134 @@
/*
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
* For more information, please, contact se.solovyev@gmail.com
* or visit http://se.solovyev.org
*/
package org.solovyev.android.view;
import android.preference.DialogPreference;
import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.LinearLayout;
import org.jetbrains.annotations.NotNull;
/* The following code was written by Matthew Wiggins
* and is released under the APACHE 2.0 license
*
* http://www.apache.org/licenses/LICENSE-2.0
*/
public class SeekBarPreference extends DialogPreference implements SeekBar.OnSeekBarChangeListener {
private static final String androidns = "http://schemas.android.com/apk/res/android";
@NotNull
private SeekBar seekBar;
@NotNull
private TextView splashText, valueText;
@NotNull
private final Context context;
private String dialogMessage, suffix;
private int defaultValue, max, value = 0;
public SeekBarPreference(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
dialogMessage = attrs.getAttributeValue(androidns, "dialogMessage");
suffix = attrs.getAttributeValue(androidns, "text");
defaultValue = attrs.getAttributeIntValue(androidns, "defaultValue", 0);
max = attrs.getAttributeIntValue(androidns, "max", 100);
}
@Override
protected View onCreateDialogView() {
LinearLayout.LayoutParams params;
LinearLayout layout = new LinearLayout(context);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(6, 6, 6, 6);
splashText = new TextView(context);
if (dialogMessage != null)
splashText.setText(dialogMessage);
layout.addView(splashText);
valueText = new TextView(context);
valueText.setGravity(Gravity.CENTER_HORIZONTAL);
valueText.setTextSize(32);
params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
layout.addView(valueText, params);
seekBar = new SeekBar(context);
seekBar.setOnSeekBarChangeListener(this);
layout.addView(seekBar, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
if (shouldPersist())
value = getPersistedInt(defaultValue);
seekBar.setMax(max);
seekBar.setProgress(value);
return layout;
}
@Override
protected void onBindDialogView(View v) {
super.onBindDialogView(v);
seekBar.setMax(max);
seekBar.setProgress(value);
}
@Override
protected void onSetInitialValue(boolean restore, Object defaultValue) {
super.onSetInitialValue(restore, defaultValue);
if (restore)
value = shouldPersist() ? getPersistedInt(this.defaultValue) : 0;
else
value = (Integer) defaultValue;
}
public void onProgressChanged(SeekBar seek, int value, boolean fromTouch) {
String t = String.valueOf(value);
valueText.setText(suffix == null ? t : t.concat(suffix));
if (shouldPersist())
persistInt(value);
callChangeListener(new Integer(value));
}
public void onStartTrackingTouch(SeekBar seek) {
}
public void onStopTrackingTouch(SeekBar seek) {
}
public void setMax(int max) {
this.max = max;
}
public int getMax() {
return max;
}
public void setProgress(int progress) {
value = progress;
if (seekBar != null)
seekBar.setProgress(progress);
}
public int getProgress() {
return value;
}
}

View File

@ -15,7 +15,7 @@ import org.solovyev.common.utils.Point2d;
import java.util.Map;
public class SimpleOnDragListener implements OnDragListener {
public class SimpleOnDragListener implements OnDragListener, DragPreferencesChangeListener {
@NotNull
public static final Point2d axis = new Point2d(0, 1);
@ -35,10 +35,6 @@ public class SimpleOnDragListener implements OnDragListener {
this.preferences = preferences;
}
public void setPreferences(@NotNull DragButtonCalibrationActivity.Preferences preferences) {
this.preferences = preferences;
}
@Override
public boolean onDrag(@NotNull DragButton dragButton, @NotNull DragEvent event) {
boolean result = false;
@ -119,6 +115,11 @@ public class SimpleOnDragListener implements OnDragListener {
this.dragProcessor = dragProcessor;
}
@Override
public void onDragPreferencesChange(@NotNull DragButtonCalibrationActivity.Preferences preferences) {
this.preferences = preferences;
}
public interface DragProcessor {
boolean processDragEvent(@NotNull DragDirection dragDirection, @NotNull DragButton dragButton, @NotNull Point2d startPoint2d, @NotNull MotionEvent motionEvent);