Due to the broad interest of many users of Java ME applications for displaying the data of Oceanographic buoy Vida (OceanBuoyPiran) and tides (TideKP) and also in text format (WAP), which were released in 2007, I have recently prepared an application for mobile devices running Android.
Application MBP can be obtained on the Android Market by searching the keywords "mbp" or "mbp vida" or directly on this link:
https://play.google.com/store/apps/details?id=org.mbp&feature=search_result#?t=W251bGwsMSwyLDEsIm9yZy5tYnAiXQ
Special thanks also go to Mr. Mavricij Bizjak and the colleagues from Marine Biology Station Piran of the National Institute of Biology for the comments that have been welcomed in the development of the application.
četrtek, 29. november 2012
četrtek, 8. november 2012
Android Custom UI: Horizontal Number Spinner
Hello everyone!
I would like to show how to design a custom UI element for Android the "Horizontal Number Spinner".
The final result looks like this:
Background images I have used:
1.) Create a custom component which extends View and implement GestureDetector.OnGestureListener interface
2.Android Layout file
3. HorizontalSpinnerActivity file
In the "main" activity we initialize our HorizontalSpinner with some values:
I would like to show how to design a custom UI element for Android the "Horizontal Number Spinner".
The final result looks like this:
Gray background | |
Glass background (9-patch) | |
Arrow |
1.) Create a custom component which extends View and implement GestureDetector.OnGestureListener interface
package si.in2.ui; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.widget.Scroller; public class HorizontalNumberSpinner extends View implements GestureDetector.OnGestureListener { public static final int ARROW_BORDER = 10; public static final int BOTTOM_BORDER = 10; public static final int MARKER_DIFFERENCE = 8; public static final int TEXT_BORDER = 10; public static final int TEXT_HEIGHT = 10; public static final int TOP_BORDER = 10; private Bitmap arrowBitmap; private Paint arrowPaint; private Paint fontPaint; private Paint markerPaint; private Paint backgroundPaint; private Paint smallMarkerPaint; private GestureDetector gestureDetector; private OnChangeListener onChangeListener; private Scroller scroller; private Drawable backgroundBitmap; private int arrowOffset; private int bottomBorder; private int currentX; private int firstMarkerIndex; private String labelFormat; private int lastMarkerIndex; private int markerDifference; private int markerSpacing; private int maxScrollOffset; private double maxValue; private int minScrollOffset; private double minValue; private int scaleDivisions; private double scaleStep; private int scrollOffset; private double step; private int stepInPixels; private int textBorder; private int textHeight; private int topBorder; //interface for event handling public interface OnChangeListener { public void onValueChanged(String pString, double pDouble); } public HorizontalNumberSpinner(Context ctx) { super(ctx); init(ctx); } public HorizontalNumberSpinner(Context ctx, AttributeSet pAttributeSet) { super(ctx, pAttributeSet); init(ctx); } public HorizontalNumberSpinner(Context ctx, AttributeSet pAttrSet, int p) { super(ctx, pAttrSet, p); init(ctx); } private void getArrowOffset() { this.arrowOffset = (getMeasuredWidth() - this.arrowBitmap.getWidth() - (int) TypedValue .applyDimension(1, 10.0F, getResources().getDisplayMetrics())); } private int getMaxScrollOffset() { return (int) Math.ceil((this.maxValue - this.minValue) * this.scaleDivisions * this.markerSpacing / this.scaleStep); } private int getMinScrollOffset() { return (int) Math.ceil(this.minValue * this.scaleDivisions * this.markerSpacing / this.scaleStep); } /** * init * @param ctx */ private void init(Context ctx) { // scroller = new Scroller(ctx); //This class encapsulates scrolling gestureDetector = new GestureDetector(this); //Detects various gestures and events using the supplied MotionEvents topBorder = (int)TypedValue.applyDimension(1, 10, getResources().getDisplayMetrics()); bottomBorder = (int) TypedValue.applyDimension(1, 10, getResources().getDisplayMetrics()); markerDifference = (int) TypedValue.applyDimension(1, 8,getResources().getDisplayMetrics()); textHeight = (int) TypedValue.applyDimension(1, 10, getResources().getDisplayMetrics()); textBorder = (int) TypedValue.applyDimension(1, 10, getResources().getDisplayMetrics()); //default values minValue = -200000; maxValue = 1000000; scaleStep = 1000; step = 100; scaleDivisions = 5; arrowOffset = 200; markerSpacing = 16; labelFormat = "%10.3f"; stepInPixels = (int) ((step * scaleDivisions * markerSpacing) / scaleStep); maxScrollOffset = getMaxScrollOffset(); minScrollOffset = getMinScrollOffset(); //Log.i("SPINER", "maxScrollOffset: " + maxScrollOffset); //Log.i("SPINER", "minScrollOffset: " + minScrollOffset); //Here we set the background image this.backgroundBitmap = getResources().getDrawable(R.drawable.sp_bg_3); //copy the arrow bitmap into variable arrowBitmap this.arrowBitmap = BitmapFactory.decodeResource( getContext().getResources(), R.drawable.spinner_pointer).copy( Bitmap.Config.ARGB_8888, true); //get the arrow offset. getArrowOffset(); //Setup paint objects for initPaintObjects(); } private void initPaintObjects() { markerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); markerPaint.setColor(Color.WHITE); markerPaint.setStrokeWidth(3); markerPaint.setStyle(Paint.Style.FILL); smallMarkerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); smallMarkerPaint.setColor(Color.WHITE); smallMarkerPaint.setStrokeWidth(1.0F); smallMarkerPaint.setStyle(Paint.Style.FILL); smallMarkerPaint.setTextAlign(Paint.Align.CENTER); smallMarkerPaint.setTextSize(10); fontPaint = new Paint(Paint.ANTI_ALIAS_FLAG); fontPaint.setStyle(Paint.Style.STROKE); fontPaint.setColor(Color.WHITE); fontPaint.setStrokeWidth(1); fontPaint.setAntiAlias(true); fontPaint.setTextSize(12); fontPaint.setTextAlign(Paint.Align.CENTER); backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); arrowPaint = new Paint(); } private int measureHeigth(int height) { View.MeasureSpec.getMode(height); return View.MeasureSpec.getSize(height); } private int measureWidth(int width) { View.MeasureSpec.getMode(width); return View.MeasureSpec.getSize(width); } private void notifyListener() { if (this.onChangeListener != null) { double d = getValue(); //fire event onChangeListener.onValueChanged(String.format(this.getFormat(), d), d); } } private void scrollToSelectableValue() { int val = Math.round(this.scrollOffset / this.stepInPixels) * this.stepInPixels; scroller.startScroll(scrollOffset, 0, val - this.scrollOffset, 0); Log.i("SPINER", "scrollToSelectableValue val="+ val +"," + scrollOffset); post(new Runnable() { public void run() { if (!scroller.isFinished()) { postDelayed(this, 20); invalidate(); //correct the value on the spinner return; } } }); } @Override public boolean onDown(MotionEvent paramMotionEvent) { Log.i("SPINER", "onDown: "); scroller.abortAnimation(); scrollToSelectableValue(); return true; } @Override public void onDraw(Canvas pCanvas) { //check if the scroller is not finished if (scroller.isFinished() == false) { scroller.computeScrollOffset(); scrollOffset = scroller.getCurrX(); //get the scroller currnet x value notifyListener(); } this.backgroundBitmap.setBounds(0, 0, getMeasuredWidth(),getMeasuredHeight()); this.backgroundBitmap.draw(pCanvas); //calcukate curentx, first and last marker index... this.firstMarkerIndex = ((-this.scrollOffset - this.arrowOffset) / this.markerSpacing); this.firstMarkerIndex -= (this.scaleDivisions - this.firstMarkerIndex) % this.scaleDivisions; this.currentX = (this.arrowOffset + this.markerSpacing * this.firstMarkerIndex + this.scrollOffset); this.lastMarkerIndex = ((-this.scrollOffset - this.arrowOffset + getMeasuredWidth()) / this.markerSpacing); this.lastMarkerIndex += this.scaleDivisions - this.lastMarkerIndex % this.scaleDivisions; int chk = firstMarkerIndex; while(true) { if (chk > lastMarkerIndex) { pCanvas.drawBitmap(arrowBitmap, arrowOffset - (arrowBitmap.getWidth() / 2), 0.0F, backgroundPaint); return; } if (chk % scaleDivisions == 0) { double d = (chk / scaleDivisions) * scaleStep; //draw the leading line pCanvas.drawLine(currentX, topBorder, currentX, getMeasuredHeight() - bottomBorder - textHeight - textBorder, markerPaint); //draw text below the leading line pCanvas.drawText(String.format(labelFormat, d), currentX, getMeasuredHeight() - bottomBorder, fontPaint); currentX += markerSpacing; } else { //lines between leading lines pCanvas.drawLine(currentX, topBorder, currentX, getMeasuredHeight() - bottomBorder - textHeight - textBorder - markerDifference, smallMarkerPaint); currentX += markerSpacing; } chk++; } } @Override public boolean onFling(MotionEvent pMotionEvent1, MotionEvent pMotionEvent2, float x, float y) { Log.i("SPINER", "onFling: " + x + ", " + y ); scroller.fling(this.scrollOffset, 0, (int) x, (int) y, -this.maxScrollOffset, -this.minScrollOffset, 0, 0); //start a thread and after 20msec verify if scroller is finished then repaint object.. post(new Runnable() { public void run() { if (!scroller.isFinished()) { invalidate(); postDelayed(this, 20); } else { scrollToSelectableValue(); } } }); return false; } protected void onMeasure(int w, int h) { Log.i("SPINER", "onMeasure: " + w + ", " + h ); int i = measureHeigth(w); int j = measureWidth(h); getArrowOffset(); setMeasuredDimension(j, i); } public boolean onScroll(MotionEvent paramMotionEvent1, MotionEvent paramMotionEvent2, float x, float y) { Log.i("SPINER", "onScroll: " + x + ", " + y ); this.scrollOffset = (int) (this.scrollOffset - x); if (this.scrollOffset < -this.maxScrollOffset) { this.scrollOffset = -this.maxScrollOffset; } invalidate(); notifyListener(); return true; } @Override public void onShowPress(MotionEvent pMotionEvent) { return; } @Override public boolean onSingleTapUp(MotionEvent pMotionEvent) { return false; } @Override public boolean onTouchEvent(MotionEvent pMotionEvent) { if ((pMotionEvent.getAction() == MotionEvent.ACTION_UP) && (this.scroller.isFinished())) scrollToSelectableValue(); this.gestureDetector.onTouchEvent(pMotionEvent); return true; } public void setBackgroundBitmap(Drawable pDrawable) { this.backgroundBitmap = pDrawable; } public void setFormat(String format) { this.labelFormat = format; } public void setMaxValue(double mValue) { this.maxValue = mValue; this.maxScrollOffset = getMaxScrollOffset(); } public void setMinValue(double minValue) { this.minValue = minValue; this.minScrollOffset = getMinScrollOffset(); this.maxScrollOffset = getMaxScrollOffset(); } public void setOnChangeListener(OnChangeListener pOnChangeListener) { this.onChangeListener = pOnChangeListener; } public void setScaleDivisions(int i) { scaleDivisions = i; minScrollOffset = getMinScrollOffset(); maxScrollOffset = getMaxScrollOffset(); } public void setScaleStep(double d) { scaleStep = d; step = d / (2 * scaleDivisions); stepInPixels = (int) ((step * scaleDivisions * markerSpacing) / d); minScrollOffset = getMinScrollOffset(); maxScrollOffset = getMaxScrollOffset(); } public void setValue(double d) { scrollOffset = (int) ((-d * (scaleDivisions * markerSpacing)) / scaleStep); invalidate(); scrollToSelectableValue(); } public String getFormat() { return this.labelFormat; } public double getMaxValue() { return this.maxValue; } public double getMinValue() { return this.minValue; } public int getScaleDivisions() { return this.scaleDivisions; } public double getScaleStep() { return this.scaleStep; } public double getStep() { return this.step; } public double getValue() { return ( (-scrollOffset) * scaleStep) / (scaleDivisions * markerSpacing); } @Override public void onLongPress(MotionEvent arg0) { // TODO Auto-generated method stub return; } }
2.Android Layout file
3. HorizontalSpinnerActivity file
In the "main" activity we initialize our HorizontalSpinner with some values:
package si.in2.ui; import android.app.Activity; import android.os.Bundle; import android.widget.LinearLayout; import android.widget.TextView; public class HorizontalSpinnerActivity extends Activity { /** Called when the activity is first created. */ HorizontalNumberSpinner spinner; LinearLayout layout; TextView text; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); text = (TextView) findViewById(R.id.textView1); spinner = (HorizontalNumberSpinner)findViewById(R.id.spiner); spinner.setMaxValue(10000.0); // set the maximum value spinner.setMinValue(-10000.0);// set the minimum value spinner.setScaleStep(50.0); // step for 50 values spinner.setScaleDivisions(5); // set scale division between numbers spinner.setFormat("%.000f"); // set the format displayed below leading line spinner.setValue(5000.0); // start at value //event handling spinner.setOnChangeListener(new HorizontalNumberSpinner.OnChangeListener() { public void onValueChanged(String paramString, double paramDouble) { text.setText("Value: " + paramDouble); } }); } }
Naročite se na:
Objave (Atom)