Is there a method to draw text with in a specified rectangle?
I am drawing directly to a canvas(ImageView) using
canvas.drawText(text,x,y,paint)
But this drew the entire text in a single line. I want to wrap the text with in the specified (x,y) ,(x1,y1) limit. I don't want to use textviews or any other views.
I just want to draw the text over an image.
Is there any method to do this?
Thanks in Advance
Firstly you have to determine the text size. The width of each character could be get by getTextWidths(), the height is same with text size. Try to estimate a initial text size, and use the height and width of text to adjust the final value.
Secondly you need to break lines. Paint.getTextWidths() or Paint.breakText() can all achieve this target.
Edit: add the code example.
public static class RectTextView extends View {
private int mWidth = 200;
private int mHeight = 100;
private String mText = "Hello world. Don't you know why, why you and I.";
private Paint mPaint;
private List<Integer> mTextBreakPoints;
public RectTextView(Context context) {
this(context, null);
}
public RectTextView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setColor(Color.WHITE);
mPaint.setAntiAlias(true);
setSuitableTextSize();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(mWidth, mHeight);
}
#Override
protected void onDraw(Canvas canvas) {
int start = 0;
int x = 0;
int y = 0;
for (int point : mTextBreakPoints) {
y += mPaint.getTextSize();
canvas.drawText(mText, start, point, x, y, mPaint);
start = point;
}
}
private void setSuitableTextSize() {
int textSize = getEstimateTextSize();
for (; textSize > 0; textSize--) {
if (isTextSizeSuitable(textSize))
return;
}
}
private boolean isTextSizeSuitable(int size) {
mTextBreakPoints = new ArrayList<Integer>();
mPaint.setTextSize(size);
int start = 0;
int end = mText.length();
while (start < end) {
int len = mPaint.breakText(mText, start, end, true, mWidth,
null);
start += len;
mTextBreakPoints.add(start);
}
return mTextBreakPoints.size() * size < mHeight;
}
private int getEstimateTextSize() {
return (int) Math.sqrt(mWidth * mHeight / mText.length() * 2);
}
}
Related
I'd like to implement a fading edge behavior to TextView like the Google Play Movie does:
As you can see the last letters of the third line have a fading edge effect.
Is there a way to achieve this for a specific line defined via android:maxLines? (For example android:maxLines="3")
I've tried the following but it works with the attribute android:singleLine only which is not my goal:
<TextView
...
android:requiresFadingEdge="horizontal"
android:fadingEdgeLength="30dp"
android:ellipsize="none"
android:singleLine="true" />
Setting android:maxLines here instead results in no fading at all.
Edit/Additional:
Previously I also tried a Shader with LinearGradient while extending TextView like here, but the described solution applies a background/foreground (and there were also some other issues with it ...).
I'd like to apply the Gradient to the last 3-4 characters of the maxLine line. Could this be possible?
Edit:
With Mike M.'s help (take a look into the comments) I could modify his answer to reach my wanted behavior. The final implementation with additions (or here as java file):
public class FadingTextView extends AppCompatTextView {
// Length
private static final float PERCENTAGE = .9f;
private static final int CHARACTERS = 6;
// Attribute for ObjectAnimator
private static final String MAX_HEIGHT_ATTR = "maxHeight";
private final Shader shader;
private final Matrix matrix;
private final Paint paint;
private final Rect bounds;
private int mMaxLines;
private boolean mExpanded = false;
public FadingTextView(Context context) {
this(context, null);
}
public FadingTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
}
public FadingTextView(Context context, AttributeSet attrs, int defStyleAttribute) {
super(context, attrs, defStyleAttribute);
matrix = new Matrix();
paint = new Paint();
bounds = new Rect();
shader = new LinearGradient(0f, 0f, PERCENTAGE, 0f, Color.TRANSPARENT, Color.BLACK, Shader.TileMode.CLAMP);
paint.setShader(shader);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
mMaxLines = getMaxLines();
}
#Override
protected void onDraw(Canvas canvas) {
if (getLineCount() > getMaxLines() && !mExpanded
&& getRootView() != null && getText() != null
) {
final Matrix m = matrix;
final Rect b = bounds;
final Layout l = getLayout();
int fadeLength = (int) (getPaint().measureText(getText(), getText().length() - CHARACTERS, getText().length()));
final int line = mMaxLines - 1;
getLineBounds(line, b);
final int lineStart = l.getLineStart(line);
final int lineEnd = l.getLineEnd(line);
final CharSequence text = getText().subSequence(lineStart, lineEnd);
final int measure = (int) (getPaint().measureText(text, 0, text.length()));
b.right = b.left + measure;
b.left = b.right - fadeLength;
final int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
super.onDraw(canvas);
m.reset();
m.setScale(fadeLength, 1f);
m.postTranslate(b.left, 0f);
shader.setLocalMatrix(matrix);
canvas.drawRect(b, paint);
canvas.restoreToCount(saveCount);
} else {
super.onDraw(canvas);
}
}
/**
* Makes the TextView expanding without any animation.
*/
public void expandCollapse() {
setMaxLines(mExpanded ? mMaxLines : getLineCount());
mExpanded = !mExpanded;
}
/**
* Makes the TextView expanding/collapsing with sliding animation (vertically)
*
* #param duration Duration in milliseconds from beginning to end of the animation
*/
public void expandCollapseAnimated(final int duration) {
// Height before the animation (either maxLine or lineCount, depending on current state)
final int startHeight = getMeasuredHeight();
// Set new maxLine value depending on current state
setMaxLines(mExpanded ? mMaxLines : getLineCount());
mExpanded = !mExpanded;
// Measuring new height
measure(View.MeasureSpec.makeMeasureSpec(
getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
);
final int endHeight = getMeasuredHeight();
ObjectAnimator animation = ObjectAnimator.ofInt(
this, // TextView
MAX_HEIGHT_ATTR, // maxHeight
startHeight, // height before animation
endHeight // height after animation
);
animation.setDuration(duration).start();
}
/**
* Sets maxLine value programmatically
*
* #param newValue new value for maxLines
*/
public void setNewMaxLine(int newValue) {
mMaxLines = newValue;
}
}
Try this out .. Hope this will work
public class ShadowSpan : Android.Text.Style.CharacterStyle
{
public float Dx;
public float Dy;
public float Radius;
public Android.Graphics.Color Color;
public ShadowSpan(float radius, float dx, float dy, Android.Graphics.Color color)
{
Radius = radius; Dx = dx; Dy = dy; Color = color;
}
public override void UpdateDrawState (TextPaint tp)
{
tp.SetShadowLayer(Radius, Dx, Dy, Color);
}
}
I am trying to create a custom view that has a Circle and in it, I have to have sections in run time as shown in the image below. I tried a lot of stuff in onDraw method but got no luck. I even tried https://github.com/donvigo/CustomProgressControls . Basically, I want to give a number of sections and then in each section I can select colors as per my need.
I am looking for ProgressBar that should have gap/space as shown in the image; in between circles. Say if I have given 5 sections, 3 of which should be "full", it should color the first 3 in red, and the other 2 in green, for example.
To draw I am doing like:
private void initExternalCirclePainter() {
internalCirclePaint = new Paint();
internalCirclePaint.setAntiAlias(true);
internalCirclePaint.setStrokeWidth(internalStrokeWidth);
internalCirclePaint.setColor(color);
internalCirclePaint.setStyle(Paint.Style.STROKE);
internalCirclePaint.setPathEffect(new DashPathEffect(new float[]{dashWith, dashSpace}, dashSpace));
}
I might be a little late to the party, but I actually wrote a custom component that has 2 rings that look quite similar to what you're trying to achieve. You can just remove the outer ring easily. The image of what I got in the end:
Here's the class:
public class RoundedSectionProgressBar extends View {
// The amount of degrees that we wanna reserve for the divider between 2 sections
private static final float DIVIDER_ANGLE = 7;
public static final float DEGREES_IN_CIRCLE = 360;
public static final int PADDING = 18;
public static final int PADDING2 = 12;
protected final Paint paint = new Paint();
protected final Paint waitingPaint = new Paint();
protected final Paint backgroundPaint = new Paint();
private int totalSections = 5;
private int fullSections = 2;
private int waiting = 3; // The outer ring. You can omit this
private RectF rect = new RectF();
public RoundedSectionProgressBar(Context context) {
super(context);
init(context, null);
}
public RoundedSectionProgressBar(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public RoundedSectionProgressBar(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
// Can come from attrs if need be?
int strokeWidth = 3;
setupPaint(context, strokeWidth, paint, R.color.filled_color_inner_ring);
setupPaint(context, strokeWidth, waitingPaint, R.color.empty_color_inner_ring);
setupPaint(context, strokeWidth, backgroundPaint, R.color.filled_color_outer_ring);
}
private void setupPaint(Context context, int strokeWidth, Paint backgroundPaint, int colorRes) {
backgroundPaint.setStrokeCap(Paint.Cap.SQUARE);
backgroundPaint.setColor(context.getResources().getColor(colorRes));
backgroundPaint.setAntiAlias(true);
backgroundPaint.setStrokeWidth(strokeWidth);
backgroundPaint.setStyle(Paint.Style.STROKE);
}
public int getTotalSections() {
return totalSections;
}
public void setTotalSections(int totalSections) {
this.totalSections = totalSections;
invalidate();
}
public int getFullSections() {
return fullSections;
}
public void setNumberOfSections(int fullSections, int totalSections, int waiting) {
this.fullSections = fullSections;
this.totalSections = totalSections;
this.waiting = waiting;
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
rect.set(getLeft() + PADDING, getTop() + PADDING, getRight() - PADDING, getBottom() - PADDING);
float angleOfSection = (DEGREES_IN_CIRCLE / totalSections) - DIVIDER_ANGLE;
// Drawing the inner ring
for (int i = 0; i < totalSections; i++) {
// -90 because it doesn't start at the top, so rotate by -90
// divider_angle/2 especially in 2 sections, it's visibly rotated by Divider angle, so we split this between last and first
float startAngle = -90 + i * (angleOfSection + DIVIDER_ANGLE) + DIVIDER_ANGLE / 2;
if (i < fullSections) {
canvas.drawArc(rect, startAngle, angleOfSection, false, paint);
} else {
canvas.drawArc(rect, startAngle, angleOfSection, false, backgroundPaint);
}
}
// Drawing the outer ring
rect.set(getLeft() + PADDING2, getTop() + PADDING2, getRight() - PADDING2, getBottom() - PADDING2);
for (int i = 0; i < waiting; i++) {
float startAngle = -90 + i * (angleOfSection + DIVIDER_ANGLE) + DIVIDER_ANGLE / 2;
canvas.drawArc(rect, startAngle, angleOfSection, false, waitingPaint);
}
}
}
Notice that this code won't give you the outer ring's 'empty' slots, since we decided against them in the end. The inner circle will have both the empty and filled slots. The whole class can be reused, and it's responsible just for the 2 rings that are drawn, the 6/6, +3 and the red circle are parts of another view.
The most important piece of the code is the onDraw method. It contains the logic for drawing the arcs in the for loop, as well as the logic for calculating the angles and adding spaces between them. Everything is rotated by -90 degrees, because I needed it to start at the top, rather than on the right, as it is the 0-degree angle in Android. It's not that complex, and you can modify it to fit your needs better should you need to.
I find it easier to do math for drawArc(operating on angle values based on number of sections) rather than computing the arc length.
Here's a quick idea, with a lot of hard-coded properties, but you should be able to get the idea:
public class MyStrokeCircleView extends View {
private Paint mPaint;
private RectF mRect;
private int mPadding;
private int mSections;
private int mFullArcSliceLength;
private int mColorArcLineLength;
private int mArcSectionGap;
public MyStrokeCircleView(Context context) {
super(context);
init(null, 0);
}
public MyStrokeCircleView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs, 0);
}
public MyStrokeCircleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs, defStyle);
}
private void init(AttributeSet attrs, int defStyle) {
mPaint = new Paint();
mPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(10);
mPaint.setColor(ContextCompat.getColor(getContext(), android.R.color.darker_gray));
mPadding = 5;
mRect = new RectF(mPadding, mPadding, mPadding, mPadding);
mSections = 4;
mFullArcSliceLength = 360 / mSections;
mArcSectionGap = mFullArcSliceLength / 10;
mColorArcLineLength = mFullArcSliceLength - 2 * mArcSectionGap;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mRect.right = getWidth() - mPadding;
mRect.bottom = getHeight() - mPadding;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < mSections; i++) {
canvas.drawArc(mRect, i * mFullArcSliceLength + mArcSectionGap, mColorArcLineLength, false, mPaint);
}
}
}
Im trying to implement a textview with each character bounded in a box with a background color.
A sample is such as below
The text will be totally dynamic.
Im thinking of adding a list of TextViews to a LinearLayout in activity class, and customize their background colors and borders.
Is this is the better approach ?
Please suggest a good solution, if there's any.
here is a simple example, to do this I merely write a view extends TextView.
public class CustomText extends View {
private int borderWidthLeft = dp(4);
private int borderWidthRight = dp(4);
private int borderWidthTop = dp(4);
private int borderWidthBottom = dp(4);
private int boderColor = Color.BLACK;
private int backgroundColor = Color.BLUE;
private int textColor = Color.WHITE;
private int textSize = sp(30);
private Paint backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
private Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
private int backgroundRectWidth = dp(35);
private int backgroundRectHeight = dp(35);
private Rect textBgRect = new Rect();
private String defaultText = "A";
public CustomText(Context context) {
this(context, null);
}
public CustomText(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
backgroundPaint.setColor(backgroundColor);
textPaint.setColor(textColor);
textPaint.setTextAlign(Align.CENTER);
textPaint.setTextSize(textSize);
textPaint.setFakeBoldText(true);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBackground(canvas);
drawText(canvas);
}
private void drawBackground(Canvas canvas) {
canvas.drawColor(boderColor);
int left = borderWidthLeft;
int top = borderWidthTop;
int right = borderWidthLeft + backgroundRectWidth;
int bottom = borderWidthTop + backgroundRectHeight;
textBgRect.set(left, top, right, bottom);
canvas.save();
canvas.clipRect(textBgRect, Op.REPLACE);
canvas.drawRect(textBgRect, backgroundPaint);
canvas.restore();
}
private void drawText(Canvas canvas) {
int bgCenterX = borderWidthLeft + backgroundRectWidth / 2;
int bgCenterY = borderWidthTop + backgroundRectHeight / 2;
FontMetrics metric = textPaint.getFontMetrics();
int textHeight = (int) Math.ceil(metric.descent - metric.ascent);
int x = bgCenterX;
int y = (int) (bgCenterY + textHeight / 2 - metric.descent);
System.out.println(textHeight);
System.out.println(y);
System.out.println(bgCenterY);
canvas.drawText(defaultText, x, y, textPaint);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(backgroundRectWidth + borderWidthLeft + borderWidthRight,
backgroundRectHeight + borderWidthTop + borderWidthBottom);
}
private int dp(int value) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, getResources().getDisplayMetrics());
}
private int sp(int value) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, value, getResources().getDisplayMetrics());
}
}
Using separate TextViews is an expensive approach. If your text has many characters, it will cost a lot of memory and time due to the numerous TextView instances' layout and measure passes. Since this is a very specific component, I would recommend creating a custom View and drawing the contents on the canvas manually.
You could use Spannable
Here is a good tutorial https://blog.stylingandroid.com/introduction-to-spans/
I have a custom view that I want to use to display the amplitude of audio coming in through the microphone in a line graph.
Getting the amplitude and all that I have no problem with, and drawing the lines is not really a problem either.
What I want to do is show the amplitude starting at the far right edge, moving left. So with each new sample I want to translate the bitmap to the left, then draw a line from the last point to the new point. I'm not sure what the easiest way to achieve this is. I originally was able to do it by drawing Paths and just adding a new point to the path with each sample, the problem was that after like a minute the path was too big to be drawn. So I thought about it and wanted to switch to using a cached bitmap, translate that on each iteration, and draw from the last point to the new point. However this is tricky to do as (after experimentation). When I translate the bitmap it doesn't move the far left pixels off the bitmap, it just moves the entire bitmap in the canvas and I have no way to write pixels to the right side.
Below is a description of what I'm trying to do:
Given this:
I want to translate that to the left:
Then draw a line to a new point the space space on the right
Of course, step 2 and 3 should happen at essentially the same time.
How can I achieve this? I'm open to new ideas altogether, like perhaps saving all the points for up to 1 screen worth and drawing them out on each onDraw call. I'd prefer to just save them in a bitmap and do some kind of translation/clipping etc to achieve the same thing with perhaps less overhead.
private static final int MAX_AMPLITUDE = 32767;
float lx, ly;
private Paint mPaint;
private Bitmap mBitmap;
private Canvas mCanvas;
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5);
mPaint.setColor(Color.Black);
}
#Override
public void onSizeChanged(int w, int h, int oldw, int oldh) {
if (mBitmap != null) {
mBitmap.recycle();
}
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
height = h;
width = w;
ly = height;
lx = width;
amplitudeDivisor = ((float) MAX_AMPLITUDE / (float) height);
}
#Override
public void onDraw(Canvas canvas) {
mAmplitude = (float)(MAX_AMPLITUDE * Math.random());
float dx = width - delta;
float dy = height - (mAmplitude / amplitudeDivisor);
mCanvas.drawLine(lx, ly, dx, dy, mPaint);
mCanvas.translate(-delta, 0);
canvas.drawBitmap(mBitmap, 0, 0, mPaint);
lx = dx;
ly = dy;
delta+=10;
postInvalidateDelayed(200);
}
The above is just a sample, I'm just using a random value for the amplitude to simplify for now. I've tried a bunch of things with no luck. Any help would be greatly appreciated.
I ended up getting this working by saving the points to an array. I draw a white line before the recording starts. Note that I use an EvictingQueue from the Guava library as a circular buffer of points to render on a line. To use this, once a recording starts call start() and when it ends call stop. From your activity you will need to send MediaRecorder getMaxAmplitude() values to the updateAmplitude() method of this class, and do so at an interval of say 50 ms. The view also supports rotation.
public class AmplitudeWaveFormView extends View {
private static final String TAG = AmplitudeWaveFormView.class.getSimpleName();
private static final int MAX_AMPLITUDE = 32767;
private static final int SAMPLES_PER_SCREEN = 100;
private float mAmplitude = 0;
private Paint mRecordingPaint, mNotRecordingPaint;
private int height = -1;
private int width = -1;
private boolean mIsStarted;
private float[] lastPoints;
private int oldWidth = -1, oldHeight = -1;
private int mCurrentSample;
private float amplitudeDivisor = 1;
private float lx,ly, deltaX;
private EvictingQueue<Float> mPointQueue;
private int recordColor;
private int notRecordingColor;
public AmplitudeWaveFormView(Context context) {
super(context);
init();
}
public AmplitudeWaveFormView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public AmplitudeWaveFormView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public void start() {
mIsStarted = true;
}
public void stop() {
mIsStarted = false;
}
public void updateAmplitude(float amplitude) {
mAmplitude = amplitude;
postInvalidate();
}
private void init() {
recordColor = getResources().getColor(R.color.mint);
notRecordingColor = getResources().getColor(R.color.alpine);
mRecordingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mRecordingPaint.setStyle(Paint.Style.STROKE);
mRecordingPaint.setStrokeWidth(5);
mRecordingPaint.setColor(recordColor);
mNotRecordingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mNotRecordingPaint.setStyle(Paint.Style.STROKE);
mNotRecordingPaint.setStrokeWidth(5);
mNotRecordingPaint.setColor(notRecordingColor);
}
#Override
public void onSizeChanged(int w, int h, int oldw, int oldh) {
height = h;
width = w;
ly = height;
lx = width;
deltaX = (float)width / (float)SAMPLES_PER_SCREEN;
amplitudeDivisor = ((float) MAX_AMPLITUDE / (float) height);
mPointQueue = EvictingQueue.create(SAMPLES_PER_SCREEN * 4);
if (lastPoints != null && lastPoints.length > 0) {
float xScale = (float) width/oldWidth;
float yScale = (float) height/oldHeight;
Matrix matrix = new Matrix();
matrix.setScale(xScale, yScale);
matrix.mapPoints(lastPoints);
mPointQueue.addAll(Floats.asList(lastPoints));
ly = lastPoints[lastPoints.length-1];
lx= lastPoints[lastPoints.length -2];
lastPoints = null;
}
}
#Override
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
mCurrentSample = bundle.getInt("sample");
lastPoints = bundle.getFloatArray("lines");
oldWidth = bundle.getInt("oldWidth");
oldHeight = bundle.getInt("oldHeight");
state = ((Bundle) state).getParcelable("parent");
}
super.onRestoreInstanceState(state);
}
#Override
public Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putFloatArray("lines", Floats.toArray(mPointQueue));
bundle.putInt("sample", mCurrentSample);
bundle.putParcelable("parent", super.onSaveInstanceState());
bundle.putInt("oldWidth", width);
bundle.putInt("oldHeight", height);
return bundle;
}
#Override
public void onDraw(Canvas canvas) {
if (mIsStarted) {
float x = lx + deltaX;
float y = height - (mAmplitude / amplitudeDivisor);
mPointQueue.add(lx);
mPointQueue.add(ly);
mPointQueue.add(x);
mPointQueue.add(y);
lastPoints = Floats.toArray(mPointQueue);
lx = x;
ly = y;
}
if (lastPoints != null && lastPoints.length > 0) {
int len = mPointQueue.size() / 4 >= SAMPLES_PER_SCREEN ? SAMPLES_PER_SCREEN * 4 : mPointQueue.size();
float translateX = width - lastPoints[lastPoints.length - 2];
canvas.translate(translateX, 0);
canvas.drawLines(lastPoints, 0, len, mRecordingPaint);
}
if (mCurrentSample <= SAMPLES_PER_SCREEN) {
drawNotRecordingLine(canvas);
}
mCurrentSample++;
}
private void drawNotRecordingLine(Canvas canvas) {
canvas.drawLine(0,height, width, height, mNotRecordingPaint);
}
}
I have written a custom imageview to show a credit card. To create the credit card I have a base image and I have setters to set the PAN, Card holder, expiry. This text needs to be drawn on top of the base card image. My problem is maintaining the position and size of the text so that it will always look correct no matter the changing size of the base image. The only thing I can rely on is the aspect ratio of the image being the same as a normal credit card.
My custom ImageView
public class CardView extends ImageView {
private String mPan = "4321 0123 4567 8910";
private String mExpiry = "01/16";
private String mCardholder = "MR JOHN SMITH";
private float mPanTextSize = 22;
private float mOtherTextSize = 14;
private Paint mPanPaint = new Paint();
private Paint mCardholderPaint = new Paint();
public CardView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initCardView();
}
public CardView(Context context, AttributeSet attrs) {
super(context, attrs);
initCardView();
}
public CardView(Context context) {
super(context);
initCardView();
}
private final void initCardView() {
mPanPaint.setColor(0xFFFFFFFF);
mPanPaint.setShadowLayer(1, 1, 1, 0xAA000000);
mPanPaint.setAntiAlias(true);
mPanPaint.setTextSize(mPanTextSize * getResources().getDisplayMetrics().scaledDensity);
mPanPaint.setTypeface(Typeface.MONOSPACE);
mCardholderPaint.setColor(0xFFFFFFFF);
mCardholderPaint.setShadowLayer(1, 1, 1, 0xAA000000);
mCardholderPaint.setAntiAlias(true);
mCardholderPaint.setTextSize(mOtherTextSize * getResources().getDisplayMetrics().scaledDensity);
mCardholderPaint.setTypeface(Typeface.MONOSPACE);
setPadding(0,0,0,0);
//setAdjustViewBounds(true);
setScaleType(ImageView.ScaleType.CENTER_INSIDE);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float panLength = mPanPaint.measureText(mPan);
float x = (getWidth() - panLength)/2;
float y = -mPanPaint.ascent() + (getHeight() * 0.46f);
canvas.drawText(mPan, x, y, mPanPaint);
x = (getWidth() - panLength)/1.5f;
y = y - mCardholderPaint.ascent();
canvas.drawText(mExpiry, x, y, mCardholderPaint);
y = y - mCardholderPaint.ascent();
canvas.drawText(mCardholder, x, y, mCardholderPaint);
//super.onDraw(canvas);
}
public void setPan(String pan) {
mPan = pan;
invalidate();
}
public String getPan() {
return mPan;
}
public void setExpiry(String expiry) {
mExpiry = expiry;
invalidate();
}
public String getExpiry() {
return mExpiry;
}
public void setCardholder(String cardholder) {
mCardholder = cardholder;
invalidate();
}
public String getCardholder() {
return mCardholder;
}
}
So sometimes this looks ok but as you get to 10 inch screens the text is way too small, right in the center of the image (imagine looking at a credit card but the number only takes up the space of the middle 8 digits), and as you get to small screens the text is too big, going right up to the image sides or past them.
Any solutions? Any explanation why?
You need to make the text sizes dependent on the size of your image.
try to experiment with different values for yourPanFactor and yourCardholderFactor until you get the desired result
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
final int height = bottom-top;
mPanPaint.setTextSize(height*yourPanFactor);
mCardholderPaint.setTextSize(height*yourCardholderFactor);
}
}
I managed to make this work in this lines of code
if (imageView.getWidth() <= resource.getWidth()) {
ratio = (float) resource.getWidth() / (float) imageView.getWidth();
} else {
ratio = (float) imageView.getWidth() / (float) resource.getWidth();
}
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setTextSize(editText.getTextSize() * ratio);
Note that the editText has the default text size which is 14 sp.
I hope it help you. and correct me if am wrong