I created my own TextView by extending the TextView class in order to improve its display. I created various Paint and stuff to add a kind of margin. Then text has to be displayed right after the margin. If I set
android:layout_width="fill_parent"
the display is ok and my line is fully filled with a white background (as defined in my layout).
BUT if I set
android:layout_width="wrap_content"
the display goes wrong and the end of the text of my TextView is cropped. I guess this is due to the fact that I made a Translate in the onDraw method of my TextView but I don't know how to fix it.
Please note that I need the set wrap_content because I want to add another TextBox right after, and a LinearLayout around both, but for the moment the other TextBox erase a part of the content of the first one.
The code of my new TextBox is the following one :
package com.flo.ui;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.widget.TextView;
import com.flo.musicalnotes.R;
public class NoteItemTextView extends TextView {
// Properties
private Paint marginPaint;
private Paint linePaint;
private Paint circlePaint;
private int paperColor;
private float margin;
private float marginEnd;
private float textStart;
// Initialization
public NoteItemTextView(Context context) {
super(context);
this.Init(context);
}
public NoteItemTextView(Context context, AttributeSet attrs)
{
super(context, attrs);
this.Init(context);
}
private void Init(Context context)
{
// Resources retrieval
Resources myResources = getResources();
// Brush definition
this.marginPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
this.marginPaint.setColor(myResources.getColor(R.color.marginColor));
this.marginPaint.setStrokeWidth((float) 1.8);
this.linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
this.linePaint.setColor(myResources.getColor(R.color.underlineColor));
this.circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
this.circlePaint.setColor(myResources.getColor(R.color.marginColor));
this.circlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
// various resources
this.paperColor = myResources.getColor(R.color.bgColor);
this.margin = myResources.getDimension(R.dimen.marginSize);
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
int ot = getResources().getConfiguration().orientation;
switch(ot)
{
case Configuration.ORIENTATION_LANDSCAPE:
this.marginEnd = this.margin + metrics.widthPixels / 100;
this.textStart = this.marginEnd + metrics.widthPixels / 100;
case Configuration.ORIENTATION_PORTRAIT:
this.marginEnd = this.margin + metrics.heightPixels / 100;
this.textStart = this.marginEnd + metrics.heightPixels / 100;
default:
this.marginEnd = this.margin + 5;
this.textStart = this.marginEnd + 10;
}
}
//#Override
protected void onDraw(Canvas canvas) {
// paper color
canvas.drawColor(this.paperColor);
// lines drawing
canvas.drawLine(0, getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight(), this.linePaint);
// marge drawing
canvas.drawLine(this.margin, 0, this.margin, getMeasuredHeight(), this.marginPaint);
canvas.drawLine(this.marginEnd, 0, this.marginEnd, getMeasuredHeight(), this.marginPaint);
double x = (this.textStart + this.marginEnd) / 1.8;
float y1 = getMeasuredHeight() / 3;
float y2 = getMeasuredHeight() * 2 / 3;
float radius = (float) 2.5;
canvas.drawCircle((float) x, y1, radius, this.circlePaint);
canvas.drawCircle((float) x, y2, radius, this.circlePaint);
canvas.save();
canvas.translate(this.textStart, 0);
super.onDraw(canvas);
canvas.restore();
}
}
Thanks for your help !
Try to add this code to your custom textview class
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = getMeasuredHeight();
int width = getMeasuredWidth();
Log.e(getClass().getSimpleName() , String.format("height x %s ::: width x %s ",height , width));
float density = getResources().getDisplayMetrics().density;
//Extra space after last letter.
float px = 2 * density;
int adjustedWidth = (int) (width + textStart + px);
setMeasuredDimension(adjustedWidth, height);
}
add this to your textview
android:paddingRight="25dp"
Related
I want to create custom sliders or seekbars in android (just as in the gif, slider on the bottom and right), could you provide me with any relevant process how to achieve this.
After searching for several days I have finally got enough resources to address the problem statement.
For staters go through the following resources:
1) https://guides.codepath.com/android/Basic-Painting-with-Views
2) https://guides.codepath.com/android/Progress-Bar-Custom-View
3) https://developer.android.com/guide/topics/ui/custom-components
Basics Steps -
Extend an existing View class or subclass with your own class.
Override some of the methods from the superclass. The superclass methods to override start with 'on', for example, onDraw(), onMeasure(), and onKeyDown(). This is similar to the on... events in Activity or ListActivity that you override for lifecycle and other functionality hooks.
Use your new extension class. Once completed, your new extension class can be used in place of the view upon which it was based.
Below is the code that demonstrate a working Clock in canvas -
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import java.util.Calendar;
/**
* Created by moonis
* on 23/06/18.
*/
public class CustomClock extends View {
private int height, width = 0;
private int padding = 0;
private int fontSize = 0;
int numeralSpacing = 0;
private int handTruncation, hourHandTruncation = 0;
private int radius = 0;
private Paint paint;
private boolean isInit;
private int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
private Rect rect = new Rect();
public CustomClock(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
setFocusable(true);
setFocusableInTouchMode(true);
}
private void initClock() {
height = getHeight();
width = getWidth();
padding = numeralSpacing + 50;
fontSize = (int) DeviceDimensionHelper.convertDpToPixel(13, getContext());
int min = Math.min(height, width);
radius = min / 2 - padding;
handTruncation = min / 20;
hourHandTruncation = min / 7;
paint = new Paint();
isInit = false;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!isInit) {
initClock();
}
canvas.drawColor(Color.BLACK);
drawCircle(canvas);
drawCentre(canvas);
drawNumeral(canvas);
drawHands(canvas);
postInvalidateDelayed(500);
}
private void drawCircle(Canvas canvas) {
paint.reset();
paint.setColor(Color.WHITE);
paint.setAntiAlias(true);
paint.setStrokeWidth(5);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(width / 2, height / 2, radius + padding - 10, paint);
}
private void drawCentre(Canvas canvas) {
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(width / 2, height / 2, 12, paint);
}
private void drawNumeral(Canvas canvas) {
paint.setTextSize(fontSize);
for (int number : numbers) {
String tmp = String.valueOf(number);
paint.getTextBounds(tmp, 0, tmp.length(), rect);
double angle = Math.PI / 6 * (number - 3);
int x = (int) (width / 2 + Math.cos(angle) * radius - rect.width() / 2);
int y = (int) (height / 2 + Math.sin(angle) * radius - rect.height() / 2);
canvas.drawText(tmp, x, y, paint);
}
}
private void drawHands(Canvas canvas) {
Calendar c = Calendar.getInstance();
float hour = c.get(Calendar.HOUR_OF_DAY);
hour = hour > 12 ? hour - 12 : hour;
drawHand(canvas, (hour + c.get(Calendar.MINUTE) / 60) * 5f, true);
drawHand(canvas, c.get(Calendar.MINUTE), false);
drawHand(canvas, c.get(Calendar.SECOND), false);
}
private void drawHand(Canvas canvas, double loc, boolean isHour) {
double angle = Math.PI * loc / 30 - Math.PI / 2;
int handRadius = isHour ? radius - handTruncation - hourHandTruncation : radius - handTruncation;
canvas.drawLine(width / 2, height / 2, (float) (width / 2 + Math.cos(angle) * handRadius), (float) (height / 2 + Math.sin(angle) * handRadius), paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
//code to move clock hands on screen gestures
break;
case MotionEvent.ACTION_MOVE:
//code to move clock hands on screen gestures
break;
default:
return false;
}
//redraw view
postInvalidate();
return true;
}
}
Finally this library can be used to achieve the desired output -
https://github.com/moldedbits/android-dial-picker
have a look at this Wheelview Library to achieve the bottom wheel
and this for your vertical ruler
to scale your image horizontally and vertically, probably you might have to go with some sort of custom solution, Vector images would be a suitable fit.
Also refer this
Hope this helps you.
In my Android app, I have to customize a seekbar and I wonder how I can set a seekbar thumb above its progress line instead of center by default?
You can check it out Discrete Seekbar
seekBar.setMin(0);
seekBar.setMax(yourArray.length);
seekBar.setOnProgressChangeListener(new DiscreteSeekBar.OnProgressChangeListener() {
int onProgressChanged =0;
#Override
public void onProgressChanged(DiscreteSeekBar seekBar, int value, boolean fromUser) {
onProgressChanged = value;
}
#Override
public void onStartTrackingTouch(DiscreteSeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(DiscreteSeekBar seekBar) {
}
});
I looked for this info in many posts and get some ideas from there and here, and created SeekBayHint, which creates text of progress above arrow:
SeekBar
MainClass:
package your_package;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import your_package.R;
public class SeekBarHint extends android.support.v7.widget.AppCompatSeekBar {
private Paint mTextPaint;
private Rect mTextBounds = new Rect();
private Bitmap mBitmapIconArrowDown;
/** for this class yPosition must be as minHeight in layoutFile for this seekBar*/
private static int sTextYPositionIndent = 20;
private float mTextSizeDecrease = 1.75f;
public static void setTextYPositionIndent(int textYPositionIndent) {
sTextYPositionIndent = textYPositionIndent;
}
public SeekBarHint(Context context) {
super(context);
mBitmapIconArrowDown = BitmapFactory.decodeResource(context.getResources(),
R.drawable.progress_seek_bar_arrow_down);
mTextPaint = new Paint();
mTextPaint.setColor(getResources().getColor(R.color.colorPrimary));
}
public SeekBarHint(Context context, AttributeSet attrs) {
super(context, attrs);
mBitmapIconArrowDown = BitmapFactory.decodeResource(context.getResources(),
R.drawable.progress_seek_bar_arrow_down);
mTextPaint = new Paint();
mTextPaint.setColor(getResources().getColor(R.color.colorPrimary));
}
public SeekBarHint(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mBitmapIconArrowDown = BitmapFactory.decodeResource(context.getResources(),
R.drawable.progress_seek_bar_arrow_down);
mTextPaint = new Paint();
mTextPaint.setColor(getResources().getColor(R.color.colorPrimary));
}
#Override
protected synchronized void onDraw(Canvas canvas) {
// first draw the regular progress bar, then custom draw our textString
super.onDraw(canvas);
// now progress position and convert to textString.
String textString = Integer.toString(getProgress()) + "%";
// now get size of seek bar.
float width = getWidth();
float height = getHeight();
// set textString size.
mTextPaint.setTextSize(height / mTextSizeDecrease);
// get size of textString.
mTextPaint.getTextBounds(textString, 0, textString.length(), mTextBounds);
// calculate where to start printing textString.
float position = (width / getMax()) * getProgress();
// get start and end points of where textString will be printed.
float textXStart = position - mTextBounds.centerX();
float textXEnd = position + mTextBounds.centerX();
// check does not start drawing text outside seek bar.
if (textXStart < 0)
textXStart = 0;
if (textXEnd > width)
textXStart -= (textXEnd - width);
// calculate y textString print position.
float yPosition = (height / 2) - mTextBounds.centerY();
canvas.drawText(textString, textXStart, yPosition - sTextYPositionIndent, mTextPaint);
// arrow draw logic
// check does not start drawing arrow outside seek bar
int seekBarAbsoluteWidth = getWidth() - getPaddingLeft() - getPaddingRight();
int thumbPos = (getPaddingLeft() / 2) + (seekBarAbsoluteWidth * getProgress() / getMax());
// set height and width for new bitmap
int arrowHeight = Math.round(mTextBounds.height()/2f);
int arrowWidth = mTextBounds.width()/3;
Bitmap scaledBitmapIconArrowDown = Bitmap
.createScaledBitmap(mBitmapIconArrowDown, arrowWidth, arrowHeight, true);
canvas.drawBitmap(scaledBitmapIconArrowDown, thumbPos, yPosition, null);
}
}
To support different dimension use this code in OnCreateView (if seekBar used in fragment) or in OnCreate (if seekBar used in activity):
DisplayMetrics metrics = getResources().getDisplayMetrics();
int dpi = metrics.densityDpi;
if(dpi < 230){
SeekBarHint.setTextYPositionIndent(5);
} else if (dpi < 310){
SeekBarHint.setTextYPositionIndent(15);
} else if (dpi < 470){
SeekBarHint.setTextYPositionIndent(10);
}
I am trying to get this paddle_user to move vertically on when the screen is touched. But the paddle isn't moving. I've double checked the onTouch Code but i'm still no closer to finding out what I am doing wrong.
package com.nblsoft.pong;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.View;
public class PongLogic extends View implements View.OnTouchListener {
//set screen constrains in dip
Configuration configuration = this.getResources().getConfiguration();
int dpHeight = configuration.screenHeightDp; //The current height of the available screen space, in dp units, corresponding to screen height resource qualifier.
int dpWidth = configuration.screenWidthDp; //The current width of the available screen space, in dp units, corresponding to screen width resource qualifier.
//int smallestScreenWidthDp = configuration.smallestScreenWidthDp; //The smallest screen size an application will see in normal operation, corresponding to smallest screen width resource qualifier.
//DisplayMetrics displayMetrics = this.getResources().getDisplayMetrics();
//float dpHeight = displayMetrics.heightPixels / displayMetrics.density;
//float dpWidth = displayMetrics.widthPixels / displayMetrics.density;
private int dptopixel(int DESIRED_DP_VALUE){
final float scale = getResources().getDisplayMetrics().density;
return (int)((DESIRED_DP_VALUE) * scale + 0.5f);
}
private int pixeltodp(int DESIRED_PIXEL_VALUE){
final float scale = getResources().getDisplayMetrics().density;
return (int) ((DESIRED_PIXEL_VALUE) - 0.5f / scale);
}
//set paddle size, speed, position vector
int paddle_pos_x = 4 * (dptopixel(dpWidth)/100); //3 for 320x480, 10 for 1080x1920 etc.
int paddle_width = (dptopixel(dpWidth)/10); //
int paddle_pos_y = (dptopixel(dpHeight)/10); //48 for 320x480, 190 for 1080x1920 etc.
int paddle_height = (dptopixel(dpHeight)/100) + 3; //the paddle is 100% of the total height of phone.
int user_paddle_pos_x = 4 * (dptopixel(dpWidth)/100) ;
int user_paddle_pos_y = dptopixel(dpHeight) - ((dptopixel(dpHeight)/10) + (dptopixel(dpHeight)/100) + 3) ;
//User Paddle
public Rect paddle_user = new Rect(user_paddle_pos_x,
user_paddle_pos_y,
user_paddle_pos_x + paddle_width,
user_paddle_pos_y + paddle_height);
//AI paddle
Rect paddle_AI = new Rect(paddle_pos_x,
paddle_pos_y,
paddle_pos_x + paddle_width,
paddle_pos_y + paddle_height);
//set ball position vector, Velocity vector, acceleration
//Override onDraw method
#Override
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
Paint mytext = new Paint();
mytext.setColor(Color.WHITE);
// Draw Middle point
canvas.drawRect(0, ((dptopixel(dpHeight)) / 2), (dptopixel(dpWidth)), (((dptopixel(dpHeight)) / 2) + 2), mytext);
canvas.drawRect(paddle_user,mytext);
canvas.drawRect(paddle_AI, mytext);
//Practise Methods
//canvas.drawText(Integer.toString(dptopixel(dpHeight)),300,300,mytext);
//canvas.drawText(Integer.toString(dptopixel(dpWidth)), 400, 400, mytext);
//canvas.drawText(Integer.toString(dpHeight),500,500,mytext);
//canvas.drawText(Integer.toString(dpWidth),600,600,mytext);
//canvas.drawText("Fuck", 700, 700, mytext);
//canvas.drawRect(0,0,dptopixel(dpWidth),dptopixel(dpHeight),mytext);
}
//Override Touch method
#Override
public boolean onTouch(View v, MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
paddle_user.offsetTo(10,0);
}
return true; //Event Handled
}
public PongLogic(Context context) {
super(context);
setBackgroundColor(Color.BLACK); //to set background
this.setFocusableInTouchMode(true); //to enable touch mode
this.setOnTouchListener(this);
}
}
You have to implement the OnTouchListener interface and do this.setOnTouchListener(this).
Edit:
public class CustomClass extends View implements View.OnTouchListener{}
then in your constructor you add this.setOnTouchListener(this);
Edit2:
Ok so I forgot to tell you but when you does some modification for exemple with you rect you have to call the draw method and to do that properly you call invalidate. So in your ontouch method add invalidate().
here is the code that I did if you wan to check (I just cut your code to have a easier exemple):
public class PongLogic extends View implements View.OnTouchListener {
//set ball position vector, Velocity vector, acceleration
Rect paddle_user = new Rect(0, 100, 100, 200);
public PongLogic(Context context, AttributeSet attrs) {
super(context, attrs);
this.setOnTouchListener(this);
setBackgroundColor(Color.BLACK); //to set background
this.setFocusableInTouchMode(true);
}
//Override onDraw method
#Override
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
Paint mytext = new Paint();
mytext.setColor(Color.WHITE);
mytext.setStyle(Paint.Style.STROKE);
mytext.setStrokeWidth(2);
canvas.drawRect(paddle_user, mytext);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
paddle_user.offsetTo(0,0);
invalidate();
return false;
}
}
So what you missed was basically the invalidate() method. So what you did in the begging was not wrong with the first OnTouch method but better to do with the interface if you make a custom view.
Using the code given below we can retrieve the height and the width of a particular screen size :
DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
float dpHeight = displayMetrics.heightPixels / displayMetrics.density;
float dpWidth = displayMetrics.widthPixels / displayMetrics.density;
I want to draw rectangles in a row as shown in the image. I have chosen default values for the rectangle width to be 100(in the drawRect() I have passed the difference between the right and left point to be 100, so that each rectangle is wide by 100). Each rectangle contains digits as shown in the picture. Now if the number of digits is small, the rectangles and the digits are rendered fine. But if the number of digits is large, the rectangles go outside the screen. For such a case I calculate the total width that the rectangles(including padding, commas, etc) will take, and compare it to the screen width. I am having issues with the code as the width that I get is in DP and the width that I calculate is not. How do I change the calculation of the total width covered by the rectangles, so that if this width is more than the width of the screen, I can reduce the dimensions of the rectangles, to render the rectangles within the scree.
Also, I want to render the rectangles "Centrally Aligned". How do I achieve that.
Given below is the image :
The code for the class is as follows :
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.text.StaticLayout;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
public class CounterWidget extends View {
private int DIGIT_SIZE = 120;
private int TEXT_SIZE = 75;
private int PADDING_LEFT=20;
private int PADDING_RIGHT=20;
private int RECT_HEIGHT=135;
private int RECT_WIDTH=100;
private int PADDING_BETWEEN_RECTS=10;
private int PADDING_BETWEEN_RECTANGLE_DIGIT_HORIZONTAL=12;
private int PADDING_BETWEEN_RECTANGLE_DIGIT_VERTICAL=25;
private int number = 1200000, rectColor, numColor, counter, noOfCommas, totalPadding;
private int digits[] = new int[15];
private Paint widgetPaint, numberPaint, textPaint, commaPaint;
private String defaultText;
public CounterWidget(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CounterWidget, 0, 0);
try {
rectColor = a.getInteger(R.styleable.CounterWidget_rectColor, 0);
numColor = a.getInteger(R.styleable.CounterWidget_numberColor, 1);
defaultText=a.getString(R.styleable.CounterWidget_defaultText);
} finally {
a.recycle();
}
init();
}
private void init() {
// Paint object for the rectangles.
widgetPaint = new Paint();
widgetPaint.setStyle(Paint.Style.FILL);
widgetPaint.setAntiAlias(true);
widgetPaint.setColor(rectColor);
// Paint object for the number.
numberPaint = new Paint();
numberPaint.setAntiAlias(true);
numberPaint.setColor(numColor);
numberPaint.setTextSize(DIGIT_SIZE);
// Paint object for the comma.
commaPaint = new Paint();
commaPaint.setAntiAlias(true);
commaPaint.setColor(rectColor);
commaPaint.setTextSize(DIGIT_SIZE);
//Calculation for the total number of digits.
int i = 0;
while (number > 0) {
digits[i] = number % 10;
number = number / 10;
i++;
}
counter = i - 1;
}
private void getNoOfCommas()
{
int a = counter+1;
if(a>0&&a<=3)
noOfCommas=0;
else if(a>3&&a<=6)
noOfCommas=1;
else if(a>6&&a<=9)
noOfCommas=2;
else if(a>9&&a<=12)
noOfCommas=3;
else
noOfCommas=4;
}
#Override
protected void onDraw(Canvas canvas) {
DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
float dpHeight = displayMetrics.heightPixels / displayMetrics.density;
float dpWidth = displayMetrics.widthPixels / displayMetrics.density;
float pix = dpToPx(getContext(),(int) dpWidth);
checkForWidth(pix);
// The text passed in the layout.
// Starting point for the rendering of the counter.
Rect rect = new Rect(20, 20, (20+RECT_WIDTH), (20+RECT_HEIGHT)); // Calculation of the starting point.
// Origin for rendering of the digit inside the rectangle.
int xx = rect.left + PADDING_BETWEEN_RECTANGLE_DIGIT_HORIZONTAL;
int yy = (rect.bottom - PADDING_BETWEEN_RECTANGLE_DIGIT_VERTICAL);
// Origin for rendering the first comma.
int xxx,yyy = (rect.bottom +10);
for (int i = counter,j=0; i >= 0; i--,j++) {
// Drawing the rectangle and using rectangle as reference, drawing the digit.
drawColoredDigit(canvas, rect, String.valueOf(digits[i]),xx,yy);
// Updating the reference values for the rectangle.
rect.left += (PADDING_BETWEEN_RECTS + RECT_WIDTH);
rect.right += (PADDING_BETWEEN_RECTS + RECT_WIDTH);
// Updating the reference values for the digits inside the rectangle.
xx = (rect.left+ PADDING_BETWEEN_RECTANGLE_DIGIT_HORIZONTAL);
xxx = (rect.left - PADDING_BETWEEN_RECTS/2);
if(((counter-j)%3==0)&&(counter!=j))
{
canvas.drawText(",",xxx,yyy,commaPaint);
rect.left += (2*PADDING_BETWEEN_RECTS);
rect.right += (2*PADDING_BETWEEN_RECTS);
xx = (rect.left + PADDING_BETWEEN_RECTANGLE_DIGIT_HORIZONTAL);
}
}
}
private void drawColoredDigit(Canvas canvas, Rect rect, String digit, int xx, int yy) {
canvas.drawRect(rect, widgetPaint);
canvas.drawText(digit, xx, yy, numberPaint);
}
public static int dpToPx(Context context, int dp) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
int px = Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
return px;
}
public static float convertPixelsToDp(float px, Context context){
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
float dp = px / (metrics.densityDpi / 160f);
return dp;
}
private void checkForWidth(float width)
{
getNoOfCommas();
totalPadding=(PADDING_BETWEEN_RECTS*(counter-1))+(noOfCommas*20)+PADDING_LEFT+PADDING_RIGHT;
int x = ((counter+1)*RECT_WIDTH)+totalPadding;
if( width<(x)) {
DIGIT_SIZE /= 3;
TEXT_SIZE /= 3;
RECT_HEIGHT-=30;
RECT_WIDTH-=30;
PADDING_BETWEEN_RECTANGLE_DIGIT_VERTICAL/=2;
PADDING_BETWEEN_RECTANGLE_DIGIT_HORIZONTAL/=2;
}
else {
Continue using the same dimensions.
}
}
public int convertToDp(int input)
{
float scale = getResources().getDisplayMetrics().density;
return (int) (input * scale + 0.5f);
}}
In the above code, I was trying to shrink the rectangles and the text within the rectangles.
How to customize a ProgressBar to look like a Thermometer ? with the possibility to change color.
My suggestion was to rotate the progressBar 90° to become vertical then have it overlay an image of an empty Thermometer but it's bad and messy solution.
I Think the best will be to either to extends View or ProgressBar class and customize the draw method but I have no idea how to draw Thermometer, any Help would be appreciated.
I created something like this for a project
package com.janslab.thermometer.widgets;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.Scroller;
import com.janslab.thermometer.R;
public class DummyThermometer extends View {
private Paint mInnerCirclePaint;
private Paint mOuterCirclePaint;
private Paint mFirstOuterCirclePaint;
//thermometer arc paint
private Paint mFirstOuterArcPaint;
//thermometer lines paints
private Paint mInnerLinePaint;
private Paint mOuterLinePaint;
private Paint mFirstOuterLinePaint;
//thermometer radii
private int mOuterRadius;
private int mInnerRadius;
private int mFirstOuterRadius;
//thermometer colors
private int mThermometerColor = Color.rgb(200, 115, 205);
//circles and lines variables
private float mLastCellWidth;
private int mStageHeight;
private float mCellWidth;
private float mStartCenterY; //center of first cell
private float mEndCenterY; //center of last cell
private float mStageCenterX;
private float mXOffset;
private float mYOffset;
// I 1st Cell I 2nd Cell I 3rd Cell I
private static final int NUMBER_OF_CELLS = 3; //three cells in all ie.stageHeight divided into 3 equal cells
//animation variables
private float mIncrementalTempValue;
private boolean mIsAnimating;
private Animator mAnimator;
public DummyThermometer(Context context) {
this(context, null);
}
public DummyThermometer(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DummyThermometer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (attrs != null) {
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Thermometer, defStyle, 0);
mThermometerColor = a.getColor(R.styleable.Thermometer_therm_color, mThermometerColor);
a.recycle();
}
init();
}
private void init() {
mInnerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mInnerCirclePaint.setColor(mThermometerColor);
mInnerCirclePaint.setStyle(Paint.Style.FILL);
mInnerCirclePaint.setStrokeWidth(17f);
mOuterCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mOuterCirclePaint.setColor(Color.WHITE);
mOuterCirclePaint.setStyle(Paint.Style.FILL);
mOuterCirclePaint.setStrokeWidth(32f);
mFirstOuterCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mFirstOuterCirclePaint.setColor(mThermometerColor);
mFirstOuterCirclePaint.setStyle(Paint.Style.FILL);
mFirstOuterCirclePaint.setStrokeWidth(60f);
mFirstOuterArcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mFirstOuterArcPaint.setColor(mThermometerColor);
mFirstOuterArcPaint.setStyle(Paint.Style.STROKE);
mFirstOuterArcPaint.setStrokeWidth(30f);
mInnerLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mInnerLinePaint.setColor(mThermometerColor);
mInnerLinePaint.setStyle(Paint.Style.FILL);
mInnerLinePaint.setStrokeWidth(17f);
mOuterLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mOuterLinePaint.setColor(Color.WHITE);
mOuterLinePaint.setStyle(Paint.Style.FILL);
mFirstOuterLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mFirstOuterLinePaint.setColor(mThermometerColor);
mFirstOuterLinePaint.setStyle(Paint.Style.FILL);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mStageCenterX = getWidth() / 2;
mStageHeight = getHeight();
mCellWidth = mStageHeight / NUMBER_OF_CELLS;
//center of first cell
mStartCenterY = mCellWidth / 2;
//move to 3rd cell
mLastCellWidth = (NUMBER_OF_CELLS * mCellWidth);
//center of last(3rd) cell
mEndCenterY = mLastCellWidth - (mCellWidth / 2);
// mOuterRadius is 1/4 of mCellWidth
mOuterRadius = (int) (0.25 * mCellWidth);
mInnerRadius = (int) (0.656 * mOuterRadius);
mFirstOuterRadius = (int) (1.344 * mOuterRadius);
mFirstOuterLinePaint.setStrokeWidth(mFirstOuterRadius);
mOuterLinePaint.setStrokeWidth(mFirstOuterRadius / 2);
mFirstOuterArcPaint.setStrokeWidth(mFirstOuterRadius / 4);
mXOffset = mFirstOuterRadius / 4;
mXOffset = mXOffset / 2;
//get the d/f btn firstOuterLine and innerAnimatedline
mYOffset = (mStartCenterY + (float) 0.875 * mOuterRadius) - (mStartCenterY + mInnerRadius);
mYOffset = mYOffset / 2;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawFirstOuterCircle(canvas);
drawOuterCircle(canvas);
drawInnerCircle(canvas);
drawFirstOuterLine(canvas);
drawOuterLine(canvas);
animateInnerLine(canvas);
drawFirstOuterCornerArc(canvas);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//take care of paddingTop and paddingBottom
int paddingY = getPaddingBottom() + getPaddingTop();
//get height and width
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
height += paddingY;
setMeasuredDimension(width, height);
}
private void drawInnerCircle(Canvas canvas) {
drawCircle(canvas, mInnerRadius, mInnerCirclePaint);
}
private void drawOuterCircle(Canvas canvas) {
drawCircle(canvas, mOuterRadius, mOuterCirclePaint);
}
private void drawFirstOuterCircle(Canvas canvas) {
drawCircle(canvas, mFirstOuterRadius, mFirstOuterCirclePaint);
}
private void drawCircle(Canvas canvas, float radius, Paint paint) {
canvas.drawCircle(mStageCenterX, mEndCenterY, radius, paint);
}
private void drawOuterLine(Canvas canvas) {
float startY = mEndCenterY - (float) (0.875 * mOuterRadius);
float stopY = mStartCenterY + (float) (0.875 * mOuterRadius);
drawLine(canvas, startY, stopY, mOuterLinePaint);
}
private void drawFirstOuterLine(Canvas canvas) {
float startY = mEndCenterY - (float) (0.875 * mFirstOuterRadius);
float stopY = mStartCenterY + (float) (0.875 * mOuterRadius);
drawLine(canvas, startY, stopY, mFirstOuterLinePaint);
}
private void drawLine(Canvas canvas, float startY, float stopY, Paint paint) {
canvas.drawLine(mStageCenterX, startY, mStageCenterX, stopY, paint);
}
//simulate temperature measurement for now
private void animateInnerLine(Canvas canvas) {
if (mAnimator == null)
measureTemperature();
if (!mIsAnimating) {
mIncrementalTempValue = mEndCenterY + (float) (0.875 * mInnerRadius);
mIsAnimating = true;
} else {
mIncrementalTempValue = mEndCenterY + (float) (0.875 * mInnerRadius) - mIncrementalTempValue;
}
if (mIncrementalTempValue > mStartCenterY + mInnerRadius) {
float startY = mEndCenterY + (float) (0.875 * mInnerRadius);
drawLine(canvas, startY, mIncrementalTempValue, mInnerCirclePaint);
} else {
float startY = mEndCenterY + (float) (0.875 * mInnerRadius);
float stopY = mStartCenterY + mInnerRadius;
drawLine(canvas, startY, stopY, mInnerCirclePaint);
mIsAnimating = false;
stopMeasurement();
}
}
private void drawFirstOuterCornerArc(Canvas canvas) {
float y = mStartCenterY - (float) (0.875 * mFirstOuterRadius);
RectF rectF = new RectF(mStageCenterX - mFirstOuterRadius / 2 + mXOffset, y + mFirstOuterRadius, mStageCenterX + mFirstOuterRadius / 2 - mXOffset, y + (2 * mFirstOuterRadius) + mYOffset);
canvas.drawArc(rectF, -180, 180, false, mFirstOuterArcPaint);
}
public void setThermometerColor(int thermometerColor) {
this.mThermometerColor = thermometerColor;
mInnerCirclePaint.setColor(mThermometerColor);
mFirstOuterCirclePaint.setColor(mThermometerColor);
mFirstOuterArcPaint.setColor(mThermometerColor);
mInnerLinePaint.setColor(mThermometerColor);
mFirstOuterLinePaint.setColor(mThermometerColor);
invalidate();
}
//simulate temperature measurement for now
private void measureTemperature() {
mAnimator = new Animator();
mAnimator.start();
}
private class Animator implements Runnable {
private Scroller mScroller;
private final static int ANIM_START_DELAY = 1000;
private final static int ANIM_DURATION = 4000;
private boolean mRestartAnimation = false;
public Animator() {
mScroller = new Scroller(getContext(), new AccelerateDecelerateInterpolator());
}
public void run() {
if (mAnimator != this)
return;
if (mRestartAnimation) {
int startY = (int) (mStartCenterY - (float) (0.875 * mInnerRadius));
int dy = (int) (mEndCenterY + mInnerRadius);
mScroller.startScroll(0, startY, 0, dy, ANIM_DURATION);
mRestartAnimation = false;
}
boolean isScrolling = mScroller.computeScrollOffset();
mIncrementalTempValue = mScroller.getCurrY();
if (isScrolling) {
invalidate();
post(this);
} else {
stop();
}
}
public void start() {
mRestartAnimation = true;
postDelayed(this, ANIM_START_DELAY);
}
public void stop() {
removeCallbacks(this);
mAnimator = null;
}
}
private void stopMeasurement() {
if (mAnimator != null)
mAnimator.stop();
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
measureTemperature();
}
#Override
protected void onDetachedFromWindow() {
stopMeasurement();
super.onDetachedFromWindow();
}
#Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
switch (visibility) {
case View.VISIBLE:
measureTemperature();
break;
default:
stopMeasurement();
break;
}
}
}
attrs.xml file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Thermometer">
<attr name="therm_color" format="color" />
</declare-styleable>
</resources>
First I would provide 2 setters, one for color and one for the temperature value, normalized from 0 ... 1, where 0 means no visible bar, and 1 means a fully visible bar.
public void setColor(int color) {
mColor = color;
invalidate(); // important, this triggers onDraw
}
public void setValue(float value) {
mValue = -(value - 1);
invalidate(); // important, this triggers onDraw
}
Notice for value, I reverse the value, since we draw the bar from bottom up, instead from top down. It makes sense in the canvas.drawRect method.
If your CustomView may have custom sizes, set your size of the progressBar (I refer to the inner bar as progressBar) in onSizeChanged, as this gets called when the View has changed it's size.
If it is a fixed size, you can just provide those values statically in an init function or the constructor.
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mProgressRect = new Rect(
/*your bar left offset relative to base bitmap*/,
/*your bar top offset relative to base bitmap*/,
/*your bar total width*/,
/*your max bar height*/
);
}
Then in ondraw, take these values into account and draw accordingly.
First draw the Bitmap, depending on your selected color (I would provide the thermometer base as a Bitmap, as long as it does not have to be completely dynamically drawn (special requirements)
Then draw the progress bar, with an height based on mValue * totalHeight of the bar, using the color provided in the setter.
For example:
#Override
protected void onDraw(Canvas canvas) {
// draw your thermometer base, bitmap based on color value
canvas.drawBitmap( /*your base thermometer bitmap here*/ );
// draw the "progress"
canvas.drawRect(mProgressRect.left, mProgressRect.top + (mValue * mProgressRect.bottom - mProgressRect.top), mProgressRect.right, mProgressRect.bottom, mPaint);
}
Hope that helps.
P.S.:
If you want to have the thermometer base image also dynamically drawn, it's a slightly different story, it would involve creating a path first and draw it with a Paint object, instead of drawing the bitmap.
EDIT:
Even better, if you want a simple solution for the "roundness" of the bar, draw a line instead a rect.
Define a line paint object like this:
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(20); // thickness of your bar
then in onDraw, instead drawRect:
// draw the "progress"
canvas.drawLine(mProgressRect.left, mProgressRect.top + (mValue * mProgressRect.bottom - mProgressRect.top), mProgressRect.left, mProgressRect.bottom, mPaint);
Be sure to adjust your mProgressRectaccordingly.