How to aspect fill programmatically? [Android] - android

I want to Aspect Fill my image. The size of my image is 1242x168. What I want is that if my screen is small my image will be cut and left align but it seems that it keeps on being aspect fit.
Here is my code
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
int device_width = displaymetrics.widthPixels;
float density = getResources().getDisplayMetrics().density;
float dpWidth = displaymetrics.widthPixels / density;
if(dpWidth >= 768) {
int aspectRatio = (int) (0.135 * device_width);
gifImageView.getLayoutParams().height = aspectRatio;
gifImageView.getLayoutParams().width = device_width;
}
Here is my xml
<pl.droidsonroids.gif.GifImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/displayImage"
android:layout_gravity="left"
android:scaleType="fitStart"
android:background="#color/red"
android:adjustViewBounds="true"
/>
I tried using android:scaleType="centerCrop" but it is crop in center I wanted to cut the right side that is why I wanted to be left align.

Try Scaling your image based on screen width:
public static float getScreenWidth(Activity activity) {
Display display = activity.getWindowManager().getDefaultDisplay();
DisplayMetrics outMetrics = new DisplayMetrics();
display.getMetrics(outMetrics);
float pxWidth = outMetrics.widthPixels;
return pxWidth;
}
float screenWidth=getScreenWidth(act)
float newHeight = screenWidth;
if (bitmap.getWidth() != 0 && bitmap.getHeight() != 0) {
newHeight = (screenWidth * bitmap.getHeight()) / bitmap.getWidth();
}
Bitmap scaledBitmap=Bitmap.createScaledBitmap(bitmap, (int) screenWidth, (int) newHeight, true);
android:adjustViewBounds=true.
android:scaleType="centerCrop"
Links: Resizing ImageView to fit to aspect ratio,
https://gist.github.com/JakeWharton/2856179

I tried doing this
//Get width of device
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
float density = getResources().getDisplayMetrics().density;
float dpWidth = displaymetrics.widthPixels / density;
if(dpWidth > 768 ) {
int aspectRatio = (int) (0.135 * device_width);
gifImageView.getLayoutParams().height = aspectRatio;
gifImageView.getLayoutParams().width = device_width;
gifImageView.setIsScale(false);
}else{
int aspectRatio = (int) (0.219 * device_width);
gifImageView.getLayoutParams().height = aspectRatio;
gifImageView.getLayoutParams().width = device_width;
gifImageView.setScaleType(ProportionalImageView.ScaleType.MATRIX);
gifImageView.setIsScale(true);
}
And create a cuztomized class that extends to ImageView / GifImageView
public class ProportionalImageView extends GifImageView {
Boolean isScale = false;
public ProportionalImageView(Context context) {
super(context);
if(isScale) {
setup();
}
}
public ProportionalImageView(Context context, AttributeSet attrs)
{
super(context, attrs);
if(isScale) {
setup();
}
}
public ProportionalImageView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
if(isScale) {
setup();
}
}
private void setup() {
setScaleType(ScaleType.MATRIX);
}
#Override
protected boolean setFrame(int l, int t, int r, int b)
{
if(isScale) {
computeMatrix();
}
return super.setFrame(l, t, r, b);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if(isScale) {
computeMatrix();
}
}
private void computeMatrix() {
Matrix matrix = getImageMatrix();
float scaleFactor, scaleFactorWidth, scaleFactorHeight;
scaleFactorWidth = (float) getWidth() / (float) getDrawable().getIntrinsicWidth();
scaleFactorHeight = (float) getHeight() / (float) getDrawable().getIntrinsicHeight();
if (scaleFactorHeight > scaleFactorWidth) {
scaleFactor = scaleFactorHeight;
} else {
scaleFactor = scaleFactorWidth;
}
matrix.setScale(scaleFactor, scaleFactor, 0, 0);
setImageMatrix(matrix);
}
public void setIsScale(Boolean isScale){
this.isScale = isScale;
}
public Boolean getIsScale() {
return isScale;
}
}

Related

Overlay TextureView with circle border View

Android newbie here but I'm trying to overlay a transparent circle view over a TextureView stream from the device camera, however no matter what I do I cannot get the overlay view to appear on top of the TextureView. I have the TextureView implemented as such:
package org.tensorflow.demo;
import android.content.Context;
import android.util.AttributeSet;
import android.view.TextureView;
/**
* A {#link TextureView} that can be adjusted to a specified aspect ratio.
*/
public class AutoFitTextureView extends TextureView {
private int ratioWidth = 0;
private int ratioHeight = 0;
public AutoFitTextureView(final Context context) {
this(context, null);
}
public AutoFitTextureView(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoFitTextureView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
}
/**
* Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
* calculated from the parameters. Note that the actual sizes of parameters don't matter, that
* is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
*
* #param width Relative horizontal size
* #param height Relative vertical size
*/
public void setAspectRatio(final int width, final int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Size cannot be negative.");
}
ratioWidth = width;
ratioHeight = height;
requestLayout();
}
#Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == ratioWidth || 0 == ratioHeight) {
setMeasuredDimension(width, height);
} else {
if (width < height * ratioWidth / ratioHeight) {
setMeasuredDimension(width, width * ratioHeight / ratioWidth);
} else {
setMeasuredDimension(height * ratioWidth / ratioHeight, height);
}
}
}
}
And my overlay currently looks like this (circle colour current set to black for debugging:
package org.tensorflow.demo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
public class OverlayView extends View {
private final Paint transparentPaint;
public OverlayView(final Context context, final AttributeSet attrs) {
super(context, attrs);
transparentPaint = new Paint();
transparentPaint.setColor(getResources().getColor(android.R.color.black));
}
#Override
public void onDraw(final Canvas canvas) {
int w = getWidth();
int h = getHeight();
int pl = getPaddingLeft();
int pr = getPaddingRight();
int pt = getPaddingTop();
int pb = getPaddingBottom();
int usableWidth = w - (pl + pr);
int usableHeight = h - (pt + pb);
int radius = Math.min(usableWidth, usableHeight) / 2;
int cx = pl + (usableWidth / 2);
int cy = pt + (usableHeight / 2);
canvas.drawCircle(cx, cy, radius, transparentPaint);
}
}
Lastly I've got my layout xml as such:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.tensorflow.demo.AutoFitTextureView
android:id="#+id/texture"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<org.tensorflow.demo.OverlayView
android:id="#+id/overlay"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<org.tensorflow.demo.RecognitionScoreView
android:id="#+id/results"
android:layout_width="match_parent"
android:layout_height="112dp" />
</FrameLayout>
Ended up solving this using a PorterDuff.Mode.SRC_OUT to cut out a circle from a full screen rectangle:
protected void createWindowFrame() {
int w = getWidth();
int h = getHeight();
windowFrame = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas osCanvas = new Canvas(windowFrame);
RectF outerRectangle = new RectF(0, 0, getWidth(), getHeight());
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.parseColor("#4885ed"));
osCanvas.drawRect(outerRectangle, paint);
paint.setColor(Color.TRANSPARENT);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
int pl = getPaddingLeft();
int pr = getPaddingRight();
int pt = getPaddingTop();
int pb = getPaddingBottom();
int usableWidth = w - (pl + pr);
int usableHeight = h - (pt + pb);
int radius = Math.min(usableWidth, usableHeight) / 2;
int cx = pl + (usableWidth / 2);
int cy = pt + (usableHeight / 2);
osCanvas.drawCircle(cx, cy, radius, paint);
}

How to get an Android Camera2 with 1:1 ratio like Instagram?

My question is very simple:
How to get an Android android.hardware.Camera2 with 1:1 ratio and without deformation like Instagram?
I tested with the GoogeSamples project android-Camera2Basic. But when I change the preview with a ratio of 1:1 image is deformed. Does anyone have an idea on this?
Thanks #CommonsWare.
I followed your advice using negative margin (top and bottom) and it works.
To do that, I just update AutoFitTextureView the GoogeSamples project android-Camera2Basic this way:
public class AutoFitTextureView extends TextureView {
//...
private boolean mWithMargin = false;
//...
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int margin = (height - width) / 2;
if(!mWithMargin) {
mWithMargin = true;
ViewGroup.MarginLayoutParams margins = ViewGroup.MarginLayoutParams.class.cast(getLayoutParams());
margins.topMargin = -margin;
margins.bottomMargin = -margin;
margins.leftMargin = 0;
margins.rightMargin = 0;
setLayoutParams(margins);
}
if (0 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, height);
} else {
if (width < height) {
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
} else {
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
}
}
}
}
Create custom texture view like this:
public class AutoFitTextureView extends TextureView {
private int mCameraWidth = 0;
private int mCameraHeight = 0;
private boolean mSquarePreview = false;
public AutoFitTextureView(Context context) {
this(context, null);
}
public AutoFitTextureView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setAspectRatio(int width, int height, boolean squarePreview) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Size cannot be negative.");
}
mCameraWidth = width;
mCameraHeight = height;
mSquarePreview = squarePreview;
requestLayout();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == mCameraWidth || 0 == mCameraHeight) {
setMeasuredDimension(width, height);
} else {
/**
* Vertical orientation
*/
if (width < height) {
if (mSquarePreview) {
setTransform(squareTransform(width, height));
setMeasuredDimension(width, width);
} else {
setMeasuredDimension(width, width * mCameraHeight / mCameraWidth);
}
}
/**
* Horizontal orientation
*/
else {
if (mSquarePreview) {
setTransform(squareTransform(width, height));
setMeasuredDimension(height, height);
} else {
setMeasuredDimension(height * mCameraWidth / mCameraHeight, height);
}
}
}
}
private Matrix setupTransform(int sw, int sh, int dw, int dh) {
Matrix matrix = new Matrix();
RectF src = new RectF(0, 0, sw, sh);
RectF dst = new RectF(0, 0, dw, dh);
RectF screen = new RectF(0, 0, dw, dh);
matrix.postRotate(-90, screen.centerX(), screen.centerY());
matrix.mapRect(dst);
matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
matrix.mapRect(src);
matrix.setRectToRect(screen, src, Matrix.ScaleToFit.FILL);
matrix.postRotate(-90, screen.centerX(), screen.centerY());
return matrix;
}
private Matrix squareTransform(int viewWidth, int viewHeight) {
Matrix matrix = new Matrix();
if (viewWidth < viewHeight) {
MyLogger.log(AutoFitTextureView.class, "Horizontal");
matrix.setScale(1, (float) mCameraHeight / (float) mCameraWidth, viewWidth / 2, viewHeight / 2);
} else {
MyLogger.log(AutoFitTextureView.class, "Vertical");
matrix.setScale((float) mCameraHeight / (float) mCameraWidth, 1, viewWidth / 2, viewHeight / 2);
}
return matrix;
}
}
And call setAspectRatio for your texture view in activity/fragment.
if (mVideoSize.width > mVideoSize.height) {
mTextureView.setAspectRatio(mVideoSize.height, mVideoSize.width, true);
} else {
mTextureView.setAspectRatio(mVideoSize.width, mVideoSize.height, true);
}
mCamera.setPreviewTexture(mTextureView.getSurfaceTexture());
mCamera.startPreview();
I did it with the Layout, in that way, google code can be keeped as it comes and automatically set a 1:1 preview based on the UI.
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="#id/footer"
android:layout_below="#id/header">
<com.example.android.camera2video.AutoFitTextureView
android:id="#+id/texture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintDimensionRatio="w,1:1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
/>
</android.support.constraint.ConstraintLayout>
Just put the AutoFitTextureView inside a ConstraintLayout and then
previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
width, height, videoSize);
does all the magic
For anybody looking for this, I tried the above answer. Adding a margin to hide part of textureview to make it look square looks good in preview. But when saving the image, you should remove the hidden areas from the output image also.
An Easier solution is to show a full textureview and to overlay some other layouts on it to make it look square.You can easily crop the image from output.
you can find the sample code here

Android ImageView scale bitmap to the width and crop the height

I have a fixed size ImageView. I want to achieve this :
Scale the Bitmap always to the width, if the Bitmap is wider or smaller than Imageview width.
Crop the height if taller than ImageView height else scale it to the height.
I want something like this answer, but the other way around (FitXCropY). I have tried changing this answer with no success.
Thank you.
Based on this answer ImageView to scale to fixed height, but crop excess width
public class FitXCropYImageView extends ImageView {
boolean done = false;
#SuppressWarnings("UnusedDeclaration")
public FitXCropYImageView(Context context) {
super(context);
setScaleType(ScaleType.MATRIX);
}
#SuppressWarnings("UnusedDeclaration")
public FitYCropXImageView(Context context, AttributeSet attrs) {
super(context, attrs);
setScaleType(ScaleType.MATRIX);
}
#SuppressWarnings("UnusedDeclaration")
public FitXCropYImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setScaleType(ScaleType.MATRIX);
}
private final RectF drawableRect = new RectF(0, 0, 0,0);
private final RectF viewRect = new RectF(0, 0, 0,0);
private final Matrix m = new Matrix();
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (done) {
return;//Already fixed drawable scale
}
final Drawable d = getDrawable();
if (d == null) {
return;//No drawable to correct for
}
int viewHeight = getMeasuredHeight();
int viewWidth = getMeasuredWidth();
int drawableWidth = d.getIntrinsicWidth();
int drawableHeight = d.getIntrinsicHeight();
drawableRect.set(0, 0, drawableWidth, drawableHeight);//Represents the original image
//Compute the left and right bounds for the scaled image
float viewHalfHeight = viewHeight / 2;
float scale = (float) viewWidth / (float) drawableWidth;
float scaledHeight = drawableHeight * scale;
float scaledHalfHeight = scaledHeight / 2;
viewRect.set(0, viewHalfHeight-scaledHalfHeight,viewWidth, viewHalfHeight+scaledHalfHeight);
m.setRectToRect(drawableRect, viewRect, Matrix.ScaleToFit.CENTER /* This constant doesn't matter? */);
setImageMatrix(m);
done = true;
requestLayout();
}
}
Try like this,
Add inside Oncreate
viewImage = (ImageView) findViewById(R.id.pictureImageview123);
viewImage.setScaleType(ScaleType.FIT_XY);

ImageView to scale to fixed height, but crop excess width

I would like my ImageView to scale in a particular fashion:
Scale so that the height of the image always fits the height of the ImageView
Crop any excess width
A picture speaks louder than a 1000 words, so here is a representation of how I want my ImageView to behave. Suppose it has a fixed height of say 100dp and suppose its width is match_parent.
Note that
on the phone layout, the image height is stretched, but the sides are cropped, akin to CROP_CENTER.
on the tablet layout, the image is also stretched to fit the ImageView height, behaving like FIT_CENTER
I suspect I need scaleType:matrix, but after that I'm lost. How can I make sure an image fits Y, but crops X?
In xml, use:
android:scaleType="centerCrop"
android:adjustViewBounds="true"
from & thanks to: https://stackoverflow.com/a/15600295/2162226
With a little help from my friends Carlos Robles and pskink, came up with the following custom ImageView:
public class FitYCropXImageView extends ImageView {
boolean done = false;
#SuppressWarnings("UnusedDeclaration")
public FitYCropXImageView(Context context) {
super(context);
setScaleType(ScaleType.MATRIX);
}
#SuppressWarnings("UnusedDeclaration")
public FitYCropXImageView(Context context, AttributeSet attrs) {
super(context, attrs);
setScaleType(ScaleType.MATRIX);
}
#SuppressWarnings("UnusedDeclaration")
public FitYCropXImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setScaleType(ScaleType.MATRIX);
}
private final RectF drawableRect = new RectF(0, 0, 0,0);
private final RectF viewRect = new RectF(0, 0, 0,0);
private final Matrix m = new Matrix();
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (done) {
return;//Already fixed drawable scale
}
final Drawable d = getDrawable();
if (d == null) {
return;//No drawable to correct for
}
int viewHeight = getMeasuredHeight();
int viewWidth = getMeasuredWidth();
int drawableWidth = d.getIntrinsicWidth();
int drawableHeight = d.getIntrinsicHeight();
drawableRect.set(0, 0, drawableWidth, drawableHeight);//Represents the original image
//Compute the left and right bounds for the scaled image
float viewHalfWidth = viewWidth / 2;
float scale = (float) viewHeight / (float) drawableHeight;
float scaledWidth = drawableWidth * scale;
float scaledHalfWidth = scaledWidth / 2;
viewRect.set(viewHalfWidth - scaledHalfWidth, 0, viewHalfWidth + scaledHalfWidth, viewHeight);
m.setRectToRect(drawableRect, viewRect, Matrix.ScaleToFit.CENTER /* This constant doesn't matter? */);
setImageMatrix(m);
done = true;
requestLayout();
}
}
If you use scaleType:matrix you will need to create your own Matrix and asign it to the view by means of setImageMatrix(Matrix) or manually modify the matrix at hen onMEasure method of a customImageView.
public class MyImageView extends ImageView {
boolean done=false;
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (done)
return;
final Drawable d = getDrawable();
final int drawableW = d.getIntrinsicWidth();
final int drawableH = d.getIntrinsicHeight();
float ratio = drawableW / drawableH;
//int width = getMeasuredWidth();
int height = getMeasuredHeight();
float scale=height/drawableH;
Matrix m = getImageMatrix();
float[] f = new float[9];
m.getValues(f);
f[Matrix.MSCALE_X]=scale;
f[Matrix.MSCALE_Y]=scale;
m.setValues(f);
done = true;
requestLayout();
}
}
LinearLayout ll = new LinearLayout(this);
ll.setOrientation(LinearLayout.VERTICAL);
LayoutParams params;
final ImageView iv0 = new ImageView(this);
//iv0.setBackgroundColor(0xffff0000);
params = new LayoutParams(LayoutParams.MATCH_PARENT, 100);
ll.addView(iv0, params);
final ImageView iv1 = new ImageView(this);
//iv1.setBackgroundColor(0xff00ff00);
params = new LayoutParams(60, 100);
ll.addView(iv1, params);
setContentView(ll);
Runnable action = new Runnable() {
#Override
public void run() {
Drawable d = getResources().getDrawable(R.drawable.layer0);
int dw = d.getIntrinsicWidth();
int dh = d.getIntrinsicHeight();
RectF src = new RectF(0, 0, dw, dh);
ImageView[] iviews = {iv0, iv1};
for (int i = 0; i < iviews.length; i++) {
ImageView iv = iviews[i];
iv.setImageDrawable(d);
iv.setScaleType(ScaleType.MATRIX);
float h = iv.getHeight();
float w = iv.getWidth();
float cx = w / 2;
float scale = h / dh;
float deltaw = dw * scale / 2;
RectF dst = new RectF(cx - deltaw, 0, cx + deltaw, h);
Matrix m = new Matrix();
m.setRectToRect(src, dst, ScaleToFit.FILL);
iv.setImageMatrix(m);
}
}
};
iv1.post(action);
If you want to display the center of the image, use:
android:scaleType="centerCrop"
android:adjustViewBounds="true"
If you want to show the edge of the image instead of the center, use:
android:scaleType="matrix"
android:adjustViewBounds="true"

android full screen textview [duplicate]

Is there any way in android to adjust the textsize in a textview to fit the space it occupies?
E.g. I'm using a TableLayout and adding several TextViews to each row. Since I don't want the TextViews to wrap the text I rather see that it lowers the font size of the content.
Any ideas?
I have tried measureText, but since I don't know the size of the column it seems troublesome to use.
This is the code where I want to change the font size to something that fits
TableRow row = new TableRow(this);
for (int i=0; i < ColumnNames.length; i++) {
TextView textColumn = new TextView(this);
textColumn.setText(ColumnNames[i]);
textColumn.setPadding(0, 0, 1, 0);
textColumn.setTextColor(getResources().getColor(R.drawable.text_default));
row.addView(textColumn, new TableRow.LayoutParams());
}
table.addView(row, new TableLayout.LayoutParams());
The solution below incorporates all of the suggestions here. It starts with what was originally posted by Dunni. It uses a binary search like gjpc's, but it is a bit more readable. It also include's gregm's bug fixes and a bug-fix of my own.
import android.content.Context;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;
public class FontFitTextView extends TextView {
public FontFitTextView(Context context) {
super(context);
initialise();
}
public FontFitTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initialise();
}
private void initialise() {
mTestPaint = new Paint();
mTestPaint.set(this.getPaint());
//max size defaults to the initially specified text size unless it is too small
}
/* Re size the font so the specified text fits in the text box
* assuming the text box is the specified width.
*/
private void refitText(String text, int textWidth)
{
if (textWidth <= 0)
return;
int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
float hi = 100;
float lo = 2;
final float threshold = 0.5f; // How close we have to be
mTestPaint.set(this.getPaint());
while((hi - lo) > threshold) {
float size = (hi+lo)/2;
mTestPaint.setTextSize(size);
if(mTestPaint.measureText(text) >= targetWidth)
hi = size; // too big
else
lo = size; // too small
}
// Use lo so that we undershoot rather than overshoot
this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int height = getMeasuredHeight();
refitText(this.getText().toString(), parentWidth);
this.setMeasuredDimension(parentWidth, height);
}
#Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
refitText(text.toString(), this.getWidth());
}
#Override
protected void onSizeChanged (int w, int h, int oldw, int oldh) {
if (w != oldw) {
refitText(this.getText().toString(), w);
}
}
//Attributes
private Paint mTestPaint;
}
I've written a class that extends TextView and does this. It just uses measureText as you suggest. Basically it has a maximum text size and minimum text size (which can be changed) and it just runs through the sizes between them in decrements of 1 until it finds the biggest one that will fit. Not particularly elegant, but I don't know of any other way.
Here is the code:
import android.content.Context;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.widget.TextView;
public class FontFitTextView extends TextView {
public FontFitTextView(Context context) {
super(context);
initialise();
}
public FontFitTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initialise();
}
private void initialise() {
testPaint = new Paint();
testPaint.set(this.getPaint());
//max size defaults to the intially specified text size unless it is too small
maxTextSize = this.getTextSize();
if (maxTextSize < 11) {
maxTextSize = 20;
}
minTextSize = 10;
}
/* Re size the font so the specified text fits in the text box
* assuming the text box is the specified width.
*/
private void refitText(String text, int textWidth) {
if (textWidth > 0) {
int availableWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
float trySize = maxTextSize;
testPaint.setTextSize(trySize);
while ((trySize > minTextSize) && (testPaint.measureText(text) > availableWidth)) {
trySize -= 1;
if (trySize <= minTextSize) {
trySize = minTextSize;
break;
}
testPaint.setTextSize(trySize);
}
this.setTextSize(trySize);
}
}
#Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
refitText(text.toString(), this.getWidth());
}
#Override
protected void onSizeChanged (int w, int h, int oldw, int oldh) {
if (w != oldw) {
refitText(this.getText().toString(), w);
}
}
//Getters and Setters
public float getMinTextSize() {
return minTextSize;
}
public void setMinTextSize(int minTextSize) {
this.minTextSize = minTextSize;
}
public float getMaxTextSize() {
return maxTextSize;
}
public void setMaxTextSize(int minTextSize) {
this.maxTextSize = minTextSize;
}
//Attributes
private Paint testPaint;
private float minTextSize;
private float maxTextSize;
}
This is speedplane's FontFitTextView, but it only decreases font size if needed to make the text fit, and keeps its font size otherwise. It does not increase the font size to fit height.
public class FontFitTextView extends TextView {
// Attributes
private Paint mTestPaint;
private float defaultTextSize;
public FontFitTextView(Context context) {
super(context);
initialize();
}
public FontFitTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
private void initialize() {
mTestPaint = new Paint();
mTestPaint.set(this.getPaint());
defaultTextSize = getTextSize();
}
/* Re size the font so the specified text fits in the text box
* assuming the text box is the specified width.
*/
private void refitText(String text, int textWidth) {
if (textWidth <= 0 || text.isEmpty())
return;
int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
// this is most likely a non-relevant call
if( targetWidth<=2 )
return;
// text already fits with the xml-defined font size?
mTestPaint.set(this.getPaint());
mTestPaint.setTextSize(defaultTextSize);
if(mTestPaint.measureText(text) <= targetWidth) {
this.setTextSize(TypedValue.COMPLEX_UNIT_PX, defaultTextSize);
return;
}
// adjust text size using binary search for efficiency
float hi = defaultTextSize;
float lo = 2;
final float threshold = 0.5f; // How close we have to be
while (hi - lo > threshold) {
float size = (hi + lo) / 2;
mTestPaint.setTextSize(size);
if(mTestPaint.measureText(text) >= targetWidth )
hi = size; // too big
else
lo = size; // too small
}
// Use lo so that we undershoot rather than overshoot
this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int height = getMeasuredHeight();
refitText(this.getText().toString(), parentWidth);
this.setMeasuredDimension(parentWidth, height);
}
#Override
protected void onTextChanged(final CharSequence text, final int start,
final int before, final int after) {
refitText(text.toString(), this.getWidth());
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw || h != oldh) {
refitText(this.getText().toString(), w);
}
}
}
Here is an example how it could be used in xml:
<com.your.package.activity.widget.FontFitTextView
android:id="#+id/my_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="My Text"
android:textSize="60sp" />
This would keep the font size to 60sp as long as the text fits in width. If the text is longer, it will decrease font size. In this case, the TextViews height will also change because of height=wrap_content.
If you find any bugs, feel free to edit.
Here is my solution which works on emulator and phones but not very well on Eclipse layout editor. It's inspired from kilaka's code but the size of the text is not obtained from the Paint but from measuring the TextView itself calling measure(0, 0).
The Java class :
public class FontFitTextView extends TextView
{
private static final float THRESHOLD = 0.5f;
private enum Mode { Width, Height, Both, None }
private int minTextSize = 1;
private int maxTextSize = 1000;
private Mode mode = Mode.None;
private boolean inComputation;
private int widthMeasureSpec;
private int heightMeasureSpec;
public FontFitTextView(Context context) {
super(context);
}
public FontFitTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FontFitTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray tAttrs = context.obtainStyledAttributes(attrs, R.styleable.FontFitTextView, defStyle, 0);
maxTextSize = tAttrs.getDimensionPixelSize(R.styleable.FontFitTextView_maxTextSize, maxTextSize);
minTextSize = tAttrs.getDimensionPixelSize(R.styleable.FontFitTextView_minTextSize, minTextSize);
tAttrs.recycle();
}
private void resizeText() {
if (getWidth() <= 0 || getHeight() <= 0)
return;
if(mode == Mode.None)
return;
final int targetWidth = getWidth();
final int targetHeight = getHeight();
inComputation = true;
float higherSize = maxTextSize;
float lowerSize = minTextSize;
float textSize = getTextSize();
while(higherSize - lowerSize > THRESHOLD) {
textSize = (higherSize + lowerSize) / 2;
if (isTooBig(textSize, targetWidth, targetHeight)) {
higherSize = textSize;
} else {
lowerSize = textSize;
}
}
setTextSize(TypedValue.COMPLEX_UNIT_PX, lowerSize);
measure(widthMeasureSpec, heightMeasureSpec);
inComputation = false;
}
private boolean isTooBig(float textSize, int targetWidth, int targetHeight) {
setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
measure(0, 0);
if(mode == Mode.Both)
return getMeasuredWidth() >= targetWidth || getMeasuredHeight() >= targetHeight;
if(mode == Mode.Width)
return getMeasuredWidth() >= targetWidth;
else
return getMeasuredHeight() >= targetHeight;
}
private Mode getMode(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if(widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY)
return Mode.Both;
if(widthMode == MeasureSpec.EXACTLY)
return Mode.Width;
if(heightMode == MeasureSpec.EXACTLY)
return Mode.Height;
return Mode.None;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if(!inComputation) {
this.widthMeasureSpec = widthMeasureSpec;
this.heightMeasureSpec = heightMeasureSpec;
mode = getMode(widthMeasureSpec, heightMeasureSpec);
resizeText();
}
}
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
resizeText();
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw || h != oldh)
resizeText();
}
public int getMinTextSize() {
return minTextSize;
}
public void setMinTextSize(int minTextSize) {
this.minTextSize = minTextSize;
resizeText();
}
public int getMaxTextSize() {
return maxTextSize;
}
public void setMaxTextSize(int maxTextSize) {
this.maxTextSize = maxTextSize;
resizeText();
}
}
The XML attribute file :
<resources>
<declare-styleable name="FontFitTextView">
<attr name="minTextSize" format="dimension" />
<attr name="maxTextSize" format="dimension" />
</declare-styleable>
</resources>
Check my github for the latest version of this class.
I hope it can be useful for someone.
If a bug is found or if the code needs explaination, feel free to open an issue on Github.
Thanks a lot to https://stackoverflow.com/users/234270/speedplane. Great answer!
Here is an improved version of his response that also take care of height and comes with a maxFontSize attribute to limit font size (was useful in my case, so I wanted to share it) :
package com.<your_package>;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;
public class FontFitTextView extends TextView
{
private Paint mTestPaint;
private float maxFontSize;
private static final float MAX_FONT_SIZE_DEFAULT_VALUE = 20f;
public FontFitTextView(Context context)
{
super(context);
initialise(context, null);
}
public FontFitTextView(Context context, AttributeSet attributeSet)
{
super(context, attributeSet);
initialise(context, attributeSet);
}
public FontFitTextView(Context context, AttributeSet attributeSet, int defStyle)
{
super(context, attributeSet, defStyle);
initialise(context, attributeSet);
}
private void initialise(Context context, AttributeSet attributeSet)
{
if(attributeSet!=null)
{
TypedArray styledAttributes = context.obtainStyledAttributes(attributeSet, R.styleable.FontFitTextView);
maxFontSize = styledAttributes.getDimension(R.styleable.FontFitTextView_maxFontSize, MAX_FONT_SIZE_DEFAULT_VALUE);
styledAttributes.recycle();
}
else
{
maxFontSize = MAX_FONT_SIZE_DEFAULT_VALUE;
}
mTestPaint = new Paint();
mTestPaint.set(this.getPaint());
//max size defaults to the initially specified text size unless it is too small
}
/* Re size the font so the specified text fits in the text box
* assuming the text box is the specified width.
*/
private void refitText(String text, int textWidth, int textHeight)
{
if (textWidth <= 0)
return;
int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
int targetHeight = textHeight - this.getPaddingTop() - this.getPaddingBottom();
float hi = maxFontSize;
float lo = 2;
// final float threshold = 0.5f; // How close we have to be
final float threshold = 1f; // How close we have to be
mTestPaint.set(this.getPaint());
Rect bounds = new Rect();
while ((hi - lo) > threshold)
{
float size = (hi + lo) / 2;
mTestPaint.setTextSize(size);
mTestPaint.getTextBounds(text, 0, text.length(), bounds);
if (bounds.width() >= targetWidth || bounds.height() >= targetHeight)
hi = size; // too big
else
lo = size; // too small
// if (mTestPaint.measureText(text) >= targetWidth)
// hi = size; // too big
// else
// lo = size; // too small
}
// Use lo so that we undershoot rather than overshoot
this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int height = getMeasuredHeight();
refitText(this.getText().toString(), parentWidth, height);
this.setMeasuredDimension(parentWidth, height);
}
#Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after)
{
refitText(text.toString(), this.getWidth(), this.getHeight());
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
if (w != oldw)
{
refitText(this.getText().toString(), w, h);
}
}
}
Corresponding /res/values/attr.xml file:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="FontFitTextView">
<attr name="maxFontSize" format="dimension" />
</declare-styleable>
</resources>
Example:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:res-auto="http://schemas.android.com/apk/res-auto"
android:id="#+id/home_Layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/background"
tools:ignore="ContentDescription" >
...
<com.<your_package>.FontFitTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="Sample Text"
android:textSize="28sp"
res-auto:maxFontSize="35sp"/>
...
</RelativeLayout>
To use the new maxFontSize attribute, don't forget to add xmlns:res-auto="http://schemas.android.com/apk/res-auto" as show in the example.
You can now do this without a third party library or a widget. It's built into TextView in API level 26. Add android:autoSizeTextType="uniform" to your TextView and set height to it. That's all.
https://developer.android.com/guide/topics/ui/look-and-feel/autosizing-textview.html
<?xml version="1.0" encoding="utf-8"?>
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:autoSizeTextType="uniform" />
You can also use TextViewCompat or app:autoSizeTextType="uniform" for backward compatibility.
I had the same problem and wrote a class that seems to work for me. Basically, I used a static layout to draw the text in a separate canvas and remeasure until I find a font size that fits. You can see the class posted in the topic below. I hope it helps.
Auto Scale TextView Text to Fit within Bounds
Use app:autoSizeTextType="uniform" for backward compatibility because android:autoSizeTextType="uniform" only work in API Level 26 and higher.
Slight modification to onMeasure:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
refitText(this.getText().toString(), parentWidth);
this.setMeasuredDimension(parentWidth, parentHeight);
}
And binary search on refitText:
private void refitText(String text, int textWidth)
{
if (textWidth > 0)
{
int availableWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
int trySize = (int)maxTextSize;
int increment = ~( trySize - (int)minTextSize ) / 2;
testPaint.setTextSize(trySize);
while ((trySize > minTextSize) && (testPaint.measureText(text) > availableWidth))
{
trySize += increment;
increment = ( increment == 0 ) ? -1 : ~increment / 2;
if (trySize <= minTextSize)
{
trySize = (int)minTextSize;
break;
}
testPaint.setTextSize(trySize);
}
this.setTextSize( TypedValue.COMPLEX_UNIT_PX, trySize);
}
}
I found the following to work nicely for me. It doesn't loop and accounts for both height and width. Note that it is important to specify the PX unit when calling setTextSize on the view. Thanks to the tip in a previous post for this!
Paint paint = adjustTextSize(getPaint(), numChars, maxWidth, maxHeight);
setTextSize(TypedValue.COMPLEX_UNIT_PX,paint.getTextSize());
Here is the routine I use, passing in the getPaint() from the view. A 10 character string with a 'wide' character is used to estimate the width independent from the actual string.
private static final String text10="OOOOOOOOOO";
public static Paint adjustTextSize(Paint paint, int numCharacters, int widthPixels, int heightPixels) {
float width = paint.measureText(text10)*numCharacters/text10.length();
float newSize = (int)((widthPixels/width)*paint.getTextSize());
paint.setTextSize(newSize);
// remeasure with font size near our desired result
width = paint.measureText(text10)*numCharacters/text10.length();
newSize = (int)((widthPixels/width)*paint.getTextSize());
paint.setTextSize(newSize);
// Check height constraints
FontMetricsInt metrics = paint.getFontMetricsInt();
float textHeight = metrics.descent-metrics.ascent;
if (textHeight > heightPixels) {
newSize = (int)(newSize * (heightPixels/textHeight));
paint.setTextSize(newSize);
}
return paint;
}
Works with modification
You need to set the text view size like this because otherwise setTextSize assumes the value is in SP units:
setTextSize(TypedValue.COMPLEX_UNIT_PX, trySize);
And you needed to explicitly add this code.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
refitText(this.getText().toString(), parentWidth);
}
I had this pain in my projects for soooo long until I found this library:
compile 'me.grantland:autofittextview:0.2.+'
You just need to add the xml by your needs and it's done. For example:
<me.grantland.widget.AutofitTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:maxLines="2"
android:textSize="40sp"
autofit:minTextSize="16sp"
/>
I used a variation of Dunni solution above, but that particular code didn't work for me. In particular, when trying to use the Paint object set to have the traits of the view's Paint object, and then calling measureText(), it doesn't return the same value as directly calling the view's Paint object. Perhaps there are some differences in the way my views are set up that make the behavior different.
My solution was to directly use the view's Paint, even though there might be some performance penalties in changing the font size for the view multiple times.
I've been working on improving the excellent solution from speedplane, and came up with this. It manages the height, including setting the margin such that the text should be centered correctly vertically.
This uses the same function to get the width, as it seems to work the best, but it uses a different function to get the height, as the height isn't provided anywhere. There are some corrections that need to be made, but I figured out a way to do that, while looking pleasing to the eye.
import android.content.Context;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;
public class FontFitTextView extends TextView {
public FontFitTextView(Context context) {
super(context);
initialize();
}
public FontFitTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
private void initialize() {
mTestPaint = new Paint();
mTestPaint.set(this.getPaint());
//max size defaults to the initially specified text size unless it is too small
}
/* Re size the font so the specified text fits in the text box
* assuming the text box is the specified width.
*/
private void refitText(String text, int textWidth,int textHeight)
{
if (textWidth <= 0)
return;
int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
int targetHeight = textHeight - this.getPaddingTop() - this.getPaddingBottom();
float hi = Math.min(targetHeight,100);
float lo = 2;
final float threshold = 0.5f; // How close we have to be
Rect bounds = new Rect();
mTestPaint.set(this.getPaint());
while((hi - lo) > threshold) {
float size = (hi+lo)/2;
mTestPaint.setTextSize(size);
mTestPaint.getTextBounds(text, 0, text.length(), bounds);
if((mTestPaint.measureText(text)) >= targetWidth || (1+(2*(size+(float)bounds.top)-bounds.bottom)) >=targetHeight)
hi = size; // too big
else
lo = size; // too small
}
// Use lo so that we undershoot rather than overshoot
this.setTextSize(TypedValue.COMPLEX_UNIT_PX,(float) lo);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
int height = getMeasuredHeight();
refitText(this.getText().toString(), parentWidth,height);
this.setMeasuredDimension(parentWidth, height);
}
#Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
refitText(text.toString(), this.getWidth(),this.getHeight());
}
#Override
protected void onSizeChanged (int w, int h, int oldw, int oldh) {
if (w != oldw) {
refitText(this.getText().toString(), w,h);
}
}
//Attributes
private Paint mTestPaint;
}
Google already made this feature.
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:autoSizeTextType="uniform" />
https://developer.android.com/guide/topics/ui/look-and-feel/autosizing-textview.html
Inspired by the previous posters I wanted to share my solution. It works with a scale factor which is applied to the previous font size to make it fit the available space. In addition to prevent unexpected behaviour of TextViews onDraw method, it simply draws the text on its own.
public class FontFitTextView extends TextView {
// How much of the available space should be used in percent.
private static final float MARGINHEIGHT = 0.8f;
private static final float MARGINWIDTH = 0.8f;
private Paint paint;
private int viewWidth;
private int viewHeight;
private float textHeight;
private float textWidth;
public FontFitTextView(Context c) {
this(c, null);
}
public FontFitTextView(Context c, AttributeSet attrs) {
super(c, attrs);
initComponent();
}
// Default constructor override
public FontFitTextView(Context c, AttributeSet attrs, int defStyle) {
super(c, attrs, defStyle);
initComponent();
}
private void initComponent() {
paint = new Paint();
paint.setTextSize(30);
paint.setTextAlign(Align.CENTER);
paint.setAntiAlias(true);
}
public void setFontColor(int c) {
paint.setColor(c);
}
private void calcTextSize(String s, Canvas c) {
float availableHeight = viewHeight;
float availableWidth = viewWidth;
// This value scales the old font up or down to match the available
// space.
float scale = 1.0f;
// Rectangle for measuring the text dimensions
Rect rect = new Rect();
float oldFontSize = paint.getTextSize();
// Calculate the space used with old font size
paint.getTextBounds(s, 0, s.length(), rect);
textWidth = rect.width();
textHeight = rect.height();
// find scale-value to fit the text horizontally
float scaleWidth = 1f;
if (textWidth > 0.0f) {
scaleWidth = (availableWidth) / textWidth * MARGINWIDTH;
}
// find scale-value to fit the text vertically
float scaleHeight = 1f;
if (textHeight > 0.0f) {
scaleHeight = (availableHeight) / textHeight * MARGINHEIGHT;
}
// We are always limited by the smaller one
if (scaleWidth < scaleHeight) {
scale = scaleWidth;
} else {
scale = scaleHeight;
}
// We apply the scale to the old font size to make it bigger or smaller
float newFontSize = (oldFontSize * scale);
paint.setTextSize(newFontSize);
}
/**
* Calculates the origin on the Y-Axis (width) for the text in this view.
*
* #return
*/
private float calcStartDrawingPosX() {
float left = getMeasuredWidth();
float centerY = left - (viewWidth / 2);
return centerY;
}
/**
* Calculates the origin on the Y-Axis (height) for the text in this view.
*
* #return
*/
private float calcStartDrawingPosY() {
float bottom = getMeasuredHeight();
// The paint only centers horizontally, origin on the Y-Axis stays at
// the bottom, thus we have to lift the origin additionally by the
// height of the font.
float centerX = bottom - (viewHeight / 2) + (textHeight / 2);
return centerX;
}
#Override
protected void onDraw(Canvas canvas) {
String text = getText().toString();
if (text.length() > 0) {
calcTextSize(text, canvas);
canvas.drawText(text, calcStartDrawingPosX(),
calcStartDrawingPosY(), paint);
}
};
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
viewWidth = w;
viewHeight = h;
super.onSizeChanged(w, h, oldw, oldh);
}
}
/* get your context */
Context c = getActivity().getApplicationContext();
LinearLayout l = new LinearLayout(c);
l.setOrientation(LinearLayout.VERTICAL);
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 0);
l.setLayoutParams(params);
l.setBackgroundResource(R.drawable.border);
TextView tv=new TextView(c);
tv.setText(" your text here");
/* set typeface if needed */
Typeface tf = Typeface.createFromAsset(c.getAssets(),"fonts/VERDANA.TTF");
tv.setTypeface(tf);
// LayoutParams lp = new LayoutParams();
tv.setTextColor(Color.parseColor("#282828"));
tv.setGravity(Gravity.CENTER | Gravity.BOTTOM);
// tv.setLayoutParams(lp);
tv.setTextSize(20);
l.addView(tv);
return l;
This should be a simple solution:
public void correctWidth(TextView textView, int desiredWidth)
{
Paint paint = new Paint();
Rect bounds = new Rect();
paint.setTypeface(textView.getTypeface());
float textSize = textView.getTextSize();
paint.setTextSize(textSize);
String text = textView.getText().toString();
paint.getTextBounds(text, 0, text.length(), bounds);
while (bounds.width() > desiredWidth)
{
textSize--;
paint.setTextSize(textSize);
paint.getTextBounds(text, 0, text.length(), bounds);
}
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
}
Extend TextView and override onDraw with the code below. It will keep text aspect ratio but size it to fill the space. You could easily modify code to stretch if necessary.
#Override
protected void onDraw(#NonNull Canvas canvas) {
TextPaint textPaint = getPaint();
textPaint.setColor(getCurrentTextColor());
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.drawableState = getDrawableState();
String text = getText().toString();
float desiredWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - 2;
float desiredHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom() - 2;
float textSize = textPaint.getTextSize();
for (int i = 0; i < 10; i++) {
textPaint.getTextBounds(text, 0, text.length(), rect);
float width = rect.width();
float height = rect.height();
float deltaWidth = width - desiredWidth;
float deltaHeight = height - desiredHeight;
boolean fitsWidth = deltaWidth <= 0;
boolean fitsHeight = deltaHeight <= 0;
if ((fitsWidth && Math.abs(deltaHeight) < 1.0)
|| (fitsHeight && Math.abs(deltaWidth) < 1.0)) {
// close enough
break;
}
float adjustX = desiredWidth / width;
float adjustY = desiredHeight / height;
textSize = textSize * (adjustY < adjustX ? adjustY : adjustX);
// adjust text size
textPaint.setTextSize(textSize);
}
float x = desiredWidth / 2f;
float y = desiredHeight / 2f - rect.top - rect.height() / 2f;
canvas.drawText(text, x, y, textPaint);
}
I wrote a short helper class that makes a textview fit within a certain width and adds ellipsize "..." at the end if the minimum textsize cannot be achieved.
Keep in mind that it only makes the text smaller until it fits or until the minimum text size is reached. To test with large sizes, set the textsize to a large number before calling the help method.
It takes Pixels, so if you are using values from dimen, you can call it like this:
float minTextSizePx = getResources().getDimensionPixelSize(R.dimen.min_text_size);
float maxTextWidthPx = getResources().getDimensionPixelSize(R.dimen.max_text_width);
WidgetUtils.fitText(textView, text, minTextSizePx, maxTextWidthPx);
This is the class I use:
public class WidgetUtils {
public static void fitText(TextView textView, String text, float minTextSizePx, float maxWidthPx) {
textView.setEllipsize(null);
int size = (int)textView.getTextSize();
while (true) {
Rect bounds = new Rect();
Paint textPaint = textView.getPaint();
textPaint.getTextBounds(text, 0, text.length(), bounds);
if(bounds.width() < maxWidthPx){
break;
}
if (size <= minTextSizePx) {
textView.setEllipsize(TextUtils.TruncateAt.END);
break;
}
size -= 1;
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
}
}
}
If a tranformation like allCaps is set, speedplane's approach is buggy. I fixed it, resulting in the following code (sorry, my reputation does not allow me to add this as a comment to speedplane's solution):
import android.content.Context;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;
public class FontFitTextView extends TextView {
public FontFitTextView(Context context) {
super(context);
initialise();
}
public FontFitTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initialise();
}
private void initialise() {
mTestPaint = new Paint();
mTestPaint.set(this.getPaint());
//max size defaults to the initially specified text size unless it is too small
}
/* Re size the font so the specified text fits in the text box
* assuming the text box is the specified width.
*/
private void refitText(String text, int textWidth)
{
if (getTransformationMethod() != null) {
text = getTransformationMethod().getTransformation(text, this).toString();
}
if (textWidth <= 0)
return;
int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
float hi = 100;
float lo = 2;
final float threshold = 0.5f; // How close we have to be
mTestPaint.set(this.getPaint());
while((hi - lo) > threshold) {
float size = (hi+lo)/2;
if(mTestPaint.measureText(text) >= targetWidth)
hi = size; // too big
else
lo = size; // too small
}
// Use lo so that we undershoot rather than overshoot
this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int height = getMeasuredHeight();
refitText(this.getText().toString(), parentWidth);
this.setMeasuredDimension(parentWidth, height);
}
#Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
refitText(text.toString(), this.getWidth());
}
#Override
protected void onSizeChanged (int w, int h, int oldw, int oldh) {
if (w != oldw) {
refitText(this.getText().toString(), w);
}
}
//Attributes
private Paint mTestPaint;
}
I don't known this is correct way or not bt its working ...take your view and check OnGlobalLayoutListener() and get textview linecount then set textSize.
yourView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (textView.getLineCount()>=3) {
textView.setTextSize(20);
}else{
//add somthing
}
}
});
Its very simple few line code..
In my case using app:autoSize was not solving all cases, for example it doesn't prevent word breaking
This is what I ended up using, it will resize down the text so that there are no word breaks on multiple lines
/**
* Resizes down the text size so that there are no word breaks
*/
class AutoFitTextView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr) {
private val paint = Paint()
private val bounds = Rect()
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
var shouldResize = false
paint.typeface = typeface
var textSize = textSize
paint.textSize = textSize
val biggestWord: String = text.split(" ").maxByOrNull { it.count() } ?: return
// Set bounds equal to the biggest word bounds
paint.getTextBounds(biggestWord, 0, biggestWord.length, bounds)
// Iterate to reduce the text size so that it makes the biggest word fit the line
while ((bounds.width() + paddingStart + paddingEnd + paint.fontSpacing) > measuredWidth) {
textSize--
paint.textSize = textSize
paint.getTextBounds(biggestWord, 0, biggestWord.length, bounds)
shouldResize = true
}
if (shouldResize) {
setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
}
}
}

Categories

Resources