This code helps me to draw text in EditText element.
public class LimitedEditText extends EditText
{
private Paint limit_text_paint;
/**
* Current size of text displayed in the EditView.
*/
private int current_text_size;
/**
* Maximum text size allowed.
*/
private int maximum_text_size;
/**
* Default limit indicator X margin used.
*/
private final int DEFAULT_X_MARGIN= 10;
/**
* Default limit indicator Y margin used.
*/
private final int DEFAULT_Y_MARGIN= 20;
/**
* Flag indicating text size limit is unlimited.
*/
public static final int UNLIMITED= -100;
/**
* LimitedEditText constructor.
* #param context Context that will display this view.
* #param attrs Set of attributes defined for this view.
* #param defStyle Style defined for this view.
*/
public LimitedEditText(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
initComponents(attrs);
}
/**
* LimitedEditText constructor.
* #param context Context that will display this view.
* #param attrs Set of attributes defined for this view.
*/
public LimitedEditText(Context context, AttributeSet attrs)
{
super(context, attrs);
initComponents(attrs);
}
/**
* LimitedEditText constructor.
* #param context Context that will display this view.
*/
public LimitedEditText(Context context)
{
super(context);
initComponents(null);
}
/**
* Initialize the UI components.
* #param attrs Set of attributes defined for this view.
*/
private void initComponents(AttributeSet attrs)
{
limit_text_paint= new Paint();
limit_text_paint.setColor(Color.GRAY);
limit_text_paint.setTextSize(20);
maximum_text_size= UNLIMITED;
current_text_size= 0;
if(attrs!=null) //android:maxLength
maximum_text_size= attrs.getAttributeIntValue("android", "maxLength", UNLIMITED);
super.addTextChangedListener(new TextWatcher(){
#Override
public void afterTextChanged(Editable s)
{
if(maximum_text_size != UNLIMITED)
current_text_size= getText().toString().length();
invalidate();
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
#Override
public void onTextChanged(CharSequence s, int start, int before,int count) {}
});
}
/**
* Set maximum text size for given EditText.
* #param max Maximum number of characters allowed.
*/
public void setMaxTextSize(int max)
{
maximum_text_size= max;
if(max== UNLIMITED)
setFilters(null);
else
setFilters(new InputFilter[]{new InputFilter.LengthFilter(max)});
}
/**
* Remove text size limit.
* This will stop drawing the amount of characters left.
*/
public void removeTextSizeLimit()
{
setMaxTextSize(UNLIMITED);
}
/**
* Get the X position of limit indicator.
* #param text Text to be drawn on the limit indicator.
* #return X position of limit indicator.
*/
protected float getLimitIndicatorX(String text)
{
float widths[]= new float1;
limit_text_paint.getTextWidths(text, widths);
float sum= 0;
for(float w: widths)
sum+= w;
return getWidth() + getScrollX() - sum - DEFAULT_X_MARGIN;
}
/**
* Get the Y position of limit indicator.
* #param text Text to be drawn on the limit indicator.
* #return Y position of limit indicator.
*/
protected float getLimitIndicatorY(String text)
{
return DEFAULT_Y_MARGIN + getScrollY();
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
if(maximum_text_size== UNLIMITED)
return;
String text= current_text_size+"/"+maximum_text_size;
canvas.drawText(text, getLimitIndicatorX(text), getLimitIndicatorY(text), limit_text_paint);
}
}
my result
I need to draw it under the text(and blue line), but i can't do it , because after blue line is end of edittext. How could i do it? Of course, i can draw line myself, but i think it will be not correct solution.
Is there a way to animate scaleType="centerCrop"? I need to scale my image to certerCrop so the image can cover the entire area of the imageView. But I need to animate it to show the user the actual look of the image before it is being scaled I was planning to do it this way
imageView.animate().scaleX(2.0f).setDuration(2000)
but the problem is that I will be doing this on multiple image for a slide show so each image will have a different size and orientation.
I also found this library https://github.com/flavioarfaria/KenBurnsView but still having problems using it.
Any help will be much appreciated thanks.
EDITED
Started working on the KenBurnsView still not able to pan the image to center.
I wrote a custom ImageView to support animation between ScaleTypes, Hope it is helpful for you. And forgive my bad English, It is not my first language.
Usage:
AnimatedImageView animatedImageView = new AnimatedImageView(context) or findViewById(R.id.animated_image_view);
animatedImageView.setAnimatedScaleType(ImageView.ScaleType.CENTER_CROP);
animatedImageView.setAnimDuration(1000); // default is 500
animatedImageView.setStartDelay(500);
animatedImageView.startAnimation;
AnimatedImageView
/**
* Custom ImageView that can animate ScaleType
* Originally created by Wflei on 16/5/31.
* Updated by Mike Miller (https://mikemiller.design) on 6/15/2017.
* Original StackOverflow Post - https://stackoverflow.com/a/37539692/2415921
*/
public class AnimatedImageView extends android.support.v7.widget.AppCompatImageView {
// Listener values;
private ScaleType mFromScaleType, mToScaleType;
private ValueAnimator mValueAnimator;
private int mStartDelay = 0;
private boolean isViewLayedOut = false;
// Constructors
public AnimatedImageView(Context context) {
this(context, null);
}
public AnimatedImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AnimatedImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// Set default original scale type
mFromScaleType = getScaleType();
// Set default scale type for animation
mToScaleType = getScaleType();
// Init value animator
mValueAnimator = ValueAnimator.ofFloat(0f, 1f);
// Set resource
updateScaleType(mFromScaleType, false);
}
/**
* Sets the scale type we want to animate to
*
* #param toScaleType
*/
public void setAnimatedScaleType(ScaleType toScaleType) {
mToScaleType = toScaleType;
}
/**
* Duration of the animation
*
* #param animationDuration
*/
public void setAnimDuration(int animationDuration) {
mValueAnimator.setDuration(animationDuration);
}
/**
* Set the time delayed for the animation
*
* #param startDelay The delay (in milliseconds) before the animation
*/
public void setStartDelay(Integer startDelay) {
mStartDelay = startDelay == null ? 0 : startDelay;
}
#Override
public void setScaleType(ScaleType scaleType) {
super.setScaleType(scaleType);
mFromScaleType = scaleType;
}
#Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
updateScaleType(mFromScaleType, false);
}
#Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
updateScaleType(mFromScaleType, false);
}
#Override
public void setImageResource(int resId) {
super.setImageResource(resId);
updateScaleType(mFromScaleType, false);
}
#Override
public void setImageURI(Uri uri) {
super.setImageURI(uri);
updateScaleType(mFromScaleType, false);
}
/**
* Animates the current view
* and updates it's current asset
*/
public void startAnimation() {
// This will run the animation with a delay (delay is defaulted at 0)
postDelayed(startDelay, mStartDelay);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// View has been laid out
isViewLayedOut = true;
// Animate change when bounds are official
if (changed) {
updateScaleType(mToScaleType, false);
}
}
/**
* animate to scaleType
*
* #param toScaleType
*/
private void updateScaleType(final ScaleType toScaleType, boolean animated) {
// Check if view is laid out
if (!isViewLayedOut) {
return;
}
// Cancel value animator if its running
if (mValueAnimator != null && mValueAnimator.isRunning()) {
mValueAnimator.cancel();
mValueAnimator.removeAllUpdateListeners();
}
// Set the scale type
super.setScaleType(mFromScaleType);
// Get values for the current image matrix
setFrame(getLeft(), getTop(), getRight(), getBottom());
Matrix srcMatrix = getImageMatrix();
final float[] srcValues = new float[9], destValues = new float[9];
srcMatrix.getValues(srcValues);
// Set the scale type to the new type
super.setScaleType(toScaleType);
setFrame(getLeft(), getTop(), getRight(), getBottom());
Matrix destMatrix = getImageMatrix();
if (toScaleType == ScaleType.FIT_XY) {
float sX = ((float) getWidth()) / getDrawable().getIntrinsicWidth();
float sY = ((float) getHeight()) / getDrawable().getIntrinsicHeight();
destMatrix.postScale(sX, sY);
}
destMatrix.getValues(destValues);
// Get translation values
final float transX = destValues[2] - srcValues[2];
final float transY = destValues[5] - srcValues[5];
final float scaleX = destValues[0] - srcValues[0];
final float scaleY = destValues[4] - srcValues[4];
// Set the scale type to a matrix
super.setScaleType(ScaleType.MATRIX);
// Listen to value animator changes
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = animation.getAnimatedFraction();
float[] currValues = Arrays.copyOf(srcValues, srcValues.length);
currValues[2] = srcValues[2] + transX * value;
currValues[5] = srcValues[5] + transY * value;
currValues[0] = srcValues[0] + scaleX * value;
currValues[4] = srcValues[4] + scaleY * value;
Matrix matrix = new Matrix();
matrix.setValues(currValues);
setImageMatrix(matrix);
}
});
// Save the newly set scale type after animation completes
mValueAnimator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
// Set the from scale type to the newly used scale type
mFromScaleType = toScaleType;
}
});
// Start the animation
if (animated) {
mValueAnimator.start();
}
}
}
I am writing a custom view for 3D page turning which extends from View. Inside this custom view, I declare two views for foreground and background of page. I have declared different layouts for each foreground and background of page. Each layout has a Relativelayout and some elements inside that.
Inside custom view, I inflate the layouts and assign them to foreground and background view.
The RelativeLayout is seen but elements inside the layout is not showing up.
Can someone please suggest me how to achieve this. Am stuck ..
My customview code:
public class PageCurlView extends View {
/** Our Log tag */
private final static String TAG = "PageCurlView";
private Context myAppContext;
/** The context which owns us */
private WeakReference<Context> mContext;
/** LAGACY The current foreground */
//private Bitmap mForeground;
public View mForeground;
/** LAGACY The current background */
//private Bitmap mBackground;
public View mBackground;
/** LAGACY Current selected page */
private int mIndex = 0;
public Integer[] mViewIds = {
R.layout.view_1,
R.layout.view_2,
R.layout.view_3,
R.layout.view_4,
R.layout.view_5,
R.layout.view_6,
R.layout.view_7,
R.layout.view_8,
R.layout.view_9,
R.layout.view_10
};
private int mTotalViews = mViewIds.length;
//Variables for inline sliders
public ViewPager mInlinePager;
public AwesomePagerAdapter mInlineAdapter;
/**
* Base
* #param context
*/
public PageCurlView(Context context) {
super(context);
init(context);
ResetClipEdge();
}
/**
* Construct the object from an XML file. Valid Attributes:
*
* #see android.view.View#View(android.content.Context, android.util.AttributeSet)
*/
public PageCurlView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
// Get the data from the XML AttributeSet
{
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PageCurlView);
// Get data
bEnableDebugMode = a.getBoolean(R.styleable.PageCurlView_enableDebugMode, bEnableDebugMode);
mCurlSpeed = a.getInt(R.styleable.PageCurlView_curlSpeed, mCurlSpeed);
mUpdateRate = a.getInt(R.styleable.PageCurlView_updateRate, mUpdateRate);
mInitialEdgeOffset = a.getInt(R.styleable.PageCurlView_initialEdgeOffset, mInitialEdgeOffset);
mCurlMode = a.getInt(R.styleable.PageCurlView_curlMode, mCurlMode);
// recycle object (so it can be used by others)
a.recycle();
}
ResetClipEdge();
}
/**
* Initialize the view
*/
private final void init(Context context) {
myAppContext = context;
// Cache the context
mContext = new WeakReference<Context>(context);
// Base padding
setPadding(3, 3, 3, 3);
// The focus flags are needed
setFocusable(true);
setFocusableInTouchMode(true);
mMovement = new Vector2D(0,0);
mFinger = new Vector2D(0,0);
mOldMovement = new Vector2D(0,0);
// Set the default props, those come from an XML :D
// Create some sample images
LayoutInflater inflater = (LayoutInflater) myAppContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view1 = inflater.inflate(mViewIds[0], null);
View view2 = inflater.inflate(mViewIds[1], null);
//Fix after coming back from vacation
//For inline sliders
mForeground = view1;
mBackground = view2;
}
/**
* Reset points to it's initial clip edge state
*/
/**
* Render the text
*
* #see android.view.View#onDraw(android.graphics.Canvas)
*/
//#Override
//protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
// canvas.drawText(mText, getPaddingLeft(), getPaddingTop() - mAscent, mTextPaint);
//}
//---------------------------------------------------------------
// Curling. This handles touch events, the actual curling
// implementations and so on.
//---------------------------------------------------------------
/**
* Swap between the fore and back-ground.
*/
#Deprecated
private void SwapViews() {
/*Bitmap temp = mForeground;
mForeground = mBackground;
mBackground = temp;*/
View temp = mForeground;
mForeground = mBackground;
mBackground = temp;
}
/**
* Swap to next view
*/
private void nextView() { //Sushil need to uncomment
int foreIndex = mIndex + 1;
if(foreIndex >= /*mPages.size()*/mTotalViews) {
//foreIndex = 0;
foreIndex = mTotalViews-1;
}
int backIndex = foreIndex + 1;
if(backIndex >= /*mPages.size()*/mTotalViews) {
//backIndex = 0;
backIndex = mTotalViews-1;
}
mIndex = foreIndex;
setViews(foreIndex, backIndex);
}
/**
* Swap to previous view
*/
private void previousView() { //Sushil need to uncomment
Log.i("Sushil", "....previousView()....");
int backIndex = mIndex;
int foreIndex = backIndex - 1;
if(foreIndex < 0) {
foreIndex = /*mPages.size()*/0;
}
mIndex = foreIndex;
setViews(foreIndex, backIndex);
}
/**
* Set current fore and background
* #param foreground - Foreground view index
* #param background - Background view index
*/
private void setViews(int foreground, int background) {
LayoutInflater inflater = (LayoutInflater) myAppContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view1 = inflater.inflate(mViewIds[foreground], null);
View view2 = inflater.inflate(mViewIds[background], null);
mForeground = view1;//(WebView)mPages.get(foreground);
mBackground = view2;//(WebView)mPages.get(background);
}
//---------------------------------------------------------------
// Drawing methods
//---------------------------------------------------------------
#Override
protected void onDraw(Canvas canvas) {
// Always refresh offsets
mCurrentLeft = getLeft();
mCurrentTop = getTop();
// Translate the whole canvas
//canvas.translate(mCurrentLeft, mCurrentTop);
// We need to initialize all size data when we first draw the view
if ( !bViewDrawn ) {
bViewDrawn = true;
onFirstDrawEvent(canvas);
}
canvas.drawColor(Color.WHITE);
// Curl pages
//DoPageCurl();
// TODO: This just scales the views to the current
// width and height. We should add some logic for:
// 1) Maintain aspect ratio
// 2) Uniform scale
// 3) ...
Rect rect = new Rect();
rect.left = 0;
rect.top = 0;
rect.bottom = getHeight();
rect.right = getWidth();
// First Page render
Paint paint = new Paint();
// Draw our elements
drawForeground(canvas, rect, paint);
drawBackground(canvas, rect, paint);
drawCurlEdge(canvas);
// Draw any debug info once we are done
if ( bEnableDebugMode )
drawDebug(canvas);
// Check if we can re-enable input
if ( bEnableInputAfterDraw )
{
bBlockTouchInput = false;
bEnableInputAfterDraw = false;
}
// Restore canvas
//canvas.restore();
super.onDraw(canvas);
}
/**
* Called on the first draw event of the view
* #param canvas
*/
protected void onFirstDrawEvent(Canvas canvas) {
mFlipRadius = getWidth();
ResetClipEdge();
DoPageCurl();
}
/**
* Draw the foreground
* #param canvas
* #param rect
* #param paint
*/
private void drawForeground( Canvas canvas, Rect rect, Paint paint ) {
//canvas.drawBitmap(mForeground, null, rect, paint);
//mForeground.loadUrl("file:///android_asset/WebContent/Section01.html");
mForeground.layout(rect.left, rect.top, rect.right, rect.bottom);
mForeground.draw(canvas);
// Draw the page number (first page is 1 in real life :D
// there is no page number 0 hehe)
//drawPageNum(canvas, mIndex);
}
/**
* Create a Path used as a mask to draw the background page
* #return
*/
private Path createBackgroundPath() {
Path path = new Path();
path.moveTo(mA.x, mA.y);
path.lineTo(mB.x, mB.y);
path.lineTo(mC.x, mC.y);
path.lineTo(mD.x, mD.y);
path.lineTo(mA.x, mA.y);
return path;
}
/**
* Draw the background image.
* #param canvas
* #param rect
* #param paint
*/
private void drawBackground( Canvas canvas, Rect rect, Paint paint ) {
Path mask = createBackgroundPath();
// Save current canvas so we do not mess it up
canvas.save();
canvas.clipPath(mask);
//canvas.drawBitmap(mBackground, null, rect, paint);
//mBackground.loadUrl("file:///android_asset/WebContent/Section01.html");
mBackground.layout(rect.left, rect.top, rect.right, rect.bottom);
mBackground.draw(canvas);
// Draw the page number (first page is 1 in real life :D
// there is no page number 0 hehe)
drawPageNum(canvas, mIndex);
canvas.restore();
}
}
One of my layout file:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/sampletextview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/red"
android:text="VIEW 1" />
</RelativeLayout>
"You're doing it wrong"
Your custom View contains bunch of another views, so logically it should be ViewGroup (or subclass of it) with proper implementation, not just View.
set layout params after inflating views.
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams([width], [heigth]);
//assign additional params like below, above, to right or to left or others relative layout params
inflatedView.setLayoutParams(lp);
I'm working on a project which needs me to display some dynamic texts based on users' selection on a button.
I know how to do the text display part, but I was stuck on how I can display different text properly on a fixed size button.
For example: "Apple" and "I have an Apple". How can I achieve the result that when displaying "Apple", the text size will be bigger and fit the button, and when "I have an Apple" the text will be smaller and may become three lines?
Thank you!
Android 8.0 supports Autosizing TextViews so you just have to specify android:autoSizeTextType="uniform". For older versions, you can use android.support.v7.widget.AppCompatTextView with app:autoSizeTextType="uniform".
By chance, it also works for buttons and for older versions just use android.support.v7.widget.AppCompatButton instead.
Hope this helped.
Take a look at this question Auto Scale TextView Text to Fit within Bounds. The same technique should apply to a button.
(yes, it is much more complicated than it seems like it should be.)
Style.xml:
<style name="Widget.Button.CustomStyle" parent="Widget.MaterialComponents.Button">
<item name="android:minHeight">50dp</item>
<item name="android:maxWidth">300dp</item>
<item name="android:textStyle">bold</item>
<item name="android:textSize">16sp</item>
<item name="backgroundTint">#color/white</item>
<item name="cornerRadius">25dp</item>
<item name="autoSizeTextType">uniform</item>
<item name="autoSizeMinTextSize">10sp</item>
<item name="autoSizeMaxTextSize">16sp</item>
<item name="autoSizeStepGranularity">2sp</item>
<item name="android:maxLines">1</item>
<item name="android:textColor">#color/colorPrimary</item>
<item name="android:insetTop">0dp</item>
<item name="android:insetBottom">0dp</item>
<item name="android:lineSpacingExtra">4sp</item>
<item name="android:gravity">center</item>
</style>
Usage:
<com.google.android.material.button.MaterialButton
android:id="#+id/blah"
style="#style/Widget.Button.CustomStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="Your long text, to the infinity and beyond!!! Why not :)" />
Result:
[Source: https://stackoverflow.com/a/59302886/421467 ]
I know this question is a few years old, but I want to add a full solution for future reference.
This code is based on AutoFitTextView and has been adapted for Buttons. Specifically it also considers the text width to avoid word-breaks when resizing.
For all licensing information visit the above link.
You'll need at least to java files:
AutoSizeTextButton.java
public class AutoSizeTextButton extends android.support.v7.widget.AppCompatButton implements AutofitHelper.OnTextSizeChangeListener{
private AutofitHelper mHelper;
public AutoSizeTextButton(Context context) {
this(context, null, 0);
}
public AutoSizeTextButton(Context context, AttributeSet attrs) {
super(context, attrs, 0);
}
public AutoSizeTextButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs, defStyleAttr);
}
private void init(AttributeSet attrs, int defStyleAttr) {
mHelper = AutofitHelper.create(this, attrs, defStyleAttr)
.addOnTextSizeChangeListener(this);
}
// Getters and Setters
/**
* {#inheritDoc}
*/
#Override
public void setTextSize(int unit, float size) {
super.setTextSize(unit, size);
if (mHelper != null) {
mHelper.setTextSize(unit, size);
}
}
/**
* {#inheritDoc}
*/
#Override
public void setLines(int lines) {
super.setLines(lines);
if (mHelper != null) {
mHelper.setMaxLines(lines);
}
}
/**
* {#inheritDoc}
*/
#Override
public void setMaxLines(int maxLines) {
super.setMaxLines(maxLines);
if (mHelper != null) {
mHelper.setMaxLines(maxLines);
}
}
/**
* Returns the {#link AutofitHelper} for this View.
*/
public AutofitHelper getAutofitHelper() {
return mHelper;
}
/**
* Returns whether or not the text will be automatically re-sized to fit its constraints.
*/
public boolean isSizeToFit() {
return mHelper.isEnabled();
}
/**
* Sets the property of this field (sizeToFit), to automatically resize the text to fit its
* constraints.
*/
public void setSizeToFit() {
setSizeToFit(true);
}
/**
* If true, the text will automatically be re-sized to fit its constraints; if false, it will
* act like a normal View.
*
* #param sizeToFit
*/
public void setSizeToFit(boolean sizeToFit) {
mHelper.setEnabled(sizeToFit);
}
/**
* Returns the maximum size (in pixels) of the text in this View.
*/
public float getMaxTextSize() {
return mHelper.getMaxTextSize();
}
/**
* Set the maximum text size to the given value, interpreted as "scaled pixel" units. This size
* is adjusted based on the current density and user font size preference.
*
* #param size The scaled pixel size.
*
* #attr ref android.R.styleable#TextView_textSize
*/
public void setMaxTextSize(float size) {
mHelper.setMaxTextSize(size);
}
/**
* Set the maximum text size to a given unit and value. See TypedValue for the possible
* dimension units.
*
* #param unit The desired dimension unit.
* #param size The desired size in the given units.
*
* #attr ref android.R.styleable#TextView_textSize
*/
public void setMaxTextSize(int unit, float size) {
mHelper.setMaxTextSize(unit, size);
}
/**
* Returns the minimum size (in pixels) of the text in this View.
*/
public float getMinTextSize() {
return mHelper.getMinTextSize();
}
/**
* Set the minimum text size to the given value, interpreted as "scaled pixel" units. This size
* is adjusted based on the current density and user font size preference.
*
* #param minSize The scaled pixel size.
*
* #attr ref R.styleable#AutofitButton_minTextSize
*/
public void setMinTextSize(int minSize) {
mHelper.setMinTextSize(TypedValue.COMPLEX_UNIT_SP, minSize);
}
/**
* Set the minimum text size to a given unit and value. See TypedValue for the possible
* dimension units.
*
* #param unit The desired dimension unit.
* #param minSize The desired size in the given units.
*
* #attr ref R.styleable#AutofitButton_minTextSize
*/
public void setMinTextSize(int unit, float minSize) {
mHelper.setMinTextSize(unit, minSize);
}
/**
* Returns the amount of precision used to calculate the correct text size to fit within its
* bounds.
*/
public float getPrecision() {
return mHelper.getPrecision();
}
/**
* Set the amount of precision used to calculate the correct text size to fit within its
* bounds. Lower precision is more precise and takes more time.
*
* #param precision The amount of precision.
*/
public void setPrecision(float precision) {
mHelper.setPrecision(precision);
}
#Override
public void onTextSizeChange(float textSize, float oldTextSize) {
// do nothing
}
}
AutofitHelper.java
/**
* A helper class to enable automatically resizing a {#link android.widget.Button}`s {#code textSize} to fit
* within its bounds.
*
* #attr ref R.styleable.AutofitButton_sizeToFit
* #attr ref R.styleable.AutofitButton_minTextSize
* #attr ref R.styleable.AutofitButton_precision
*/
public class AutofitHelper {
private static final String TAG = "AutoFitTextHelper";
private static final boolean SPEW = false;
// Minimum size of the text in pixels
private static final int DEFAULT_MIN_TEXT_SIZE = 8; //sp
// How precise we want to be when reaching the target textWidth size
private static final float DEFAULT_PRECISION = 0.5f;
/**
* Creates a new instance of {#code AutofitHelper} that wraps a {#link android.widget.Button} and enables
* automatically sizing the text to fit.
*/
public static AutofitHelper create(Button view) {
return create(view, null, 0);
}
/**
* Creates a new instance of {#code AutofitHelper} that wraps a {#link android.widget.Button} and enables
* automatically sizing the text to fit.
*/
public static AutofitHelper create(Button view, AttributeSet attrs) {
return create(view, attrs, 0);
}
/**
* Creates a new instance of {#code AutofitHelper} that wraps a {#link android.widget.Button} and enables
* automatically sizing the text to fit.
*/
public static AutofitHelper create(Button view, AttributeSet attrs, int defStyle) {
AutofitHelper helper = new AutofitHelper(view);
boolean sizeToFit = true;
if (attrs != null) {
Context context = view.getContext();
int minTextSize = (int) helper.getMinTextSize();
float precision = helper.getPrecision();
TypedArray ta = context.obtainStyledAttributes(
attrs,
R.styleable.AutofitButton,
defStyle,
0);
sizeToFit = ta.getBoolean(R.styleable.AutofitButton_sizeToFit, sizeToFit);
minTextSize = ta.getDimensionPixelSize(R.styleable.AutofitButton_minTextSize,
minTextSize);
precision = ta.getFloat(R.styleable.AutofitButton_precision, precision);
ta.recycle();
helper.setMinTextSize(TypedValue.COMPLEX_UNIT_PX, minTextSize)
.setPrecision(precision);
}
helper.setEnabled(sizeToFit);
return helper;
}
/**
* Re-sizes the textSize of the TextView so that the text fits within the bounds of the View.
*/
private static void autofit(Button view, TextPaint paint, float minTextSize, float maxTextSize,
int maxLines, float precision) {
if (maxLines <= 0 || maxLines == Integer.MAX_VALUE) {
// Don't auto-size since there's no limit on lines.
return;
}
int targetWidth = view.getWidth() - view.getPaddingLeft() - view.getPaddingRight();
if (targetWidth <= 0) {
return;
}
CharSequence text = view.getText();
TransformationMethod method = view.getTransformationMethod();
if (method != null) {
text = method.getTransformation(text, view);
}
Context context = view.getContext();
Resources r = Resources.getSystem();
DisplayMetrics displayMetrics;
float size = maxTextSize;
float high = size;
float low = 0;
if (context != null) {
r = context.getResources();
}
displayMetrics = r.getDisplayMetrics();
paint.set(view.getPaint());
paint.setTextSize(size);
if ((maxLines == 1 && paint.measureText(text, 0, text.length()) > targetWidth)
|| getLineCount(text, paint, size, targetWidth, displayMetrics) > maxLines
|| getMaxWordWidth(text, paint, size, displayMetrics) > targetWidth) {
size = getAutofitTextSize(text, getMaxWord(text, paint), paint, targetWidth, maxLines, low, high, precision,
displayMetrics);
}
if (size < minTextSize) {
size = minTextSize;
}
view.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
}
/**
* Recursive binary search to find the best size for the text.
*/
private static float getAutofitTextSize(CharSequence text, String widestWord, TextPaint paint,
float targetWidth, int maxLines, float low, float high, float precision,
DisplayMetrics displayMetrics) {
float mid = (low + high) / 2.0f;
int lineCount = 1;
StaticLayout layout = null;
paint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, mid,
displayMetrics));
if (maxLines != 1) {
layout = new StaticLayout(text, paint, (int)targetWidth, Layout.Alignment.ALIGN_NORMAL,
1.0f, 0.0f, true);
lineCount = layout.getLineCount();
}
if (SPEW) Log.d(TAG, "low=" + low + " high=" + high + " mid=" + mid +
" target=" + targetWidth + " maxLines=" + maxLines + " lineCount=" + lineCount);
if (lineCount > maxLines) {
// For the case that `text` has more newline characters than `maxLines`.
if ((high - low) < precision) {
return low;
}
return getAutofitTextSize(text, widestWord, paint, targetWidth, maxLines, low, mid, precision,
displayMetrics);
}
else if (lineCount < maxLines) {
return getAutofitTextSize(text, widestWord, paint, targetWidth, maxLines, mid, high, precision,
displayMetrics);
}
else {
float maxLineWidth = 0;
if (maxLines == 1) {
maxLineWidth = paint.measureText(text, 0, text.length());
} else {
for (int i = 0; i < lineCount; i++) {
if (layout.getLineWidth(i) > maxLineWidth) {
maxLineWidth = layout.getLineWidth(i);
}
}
}
float maxWordWidth = paint.measureText(widestWord, 0, widestWord.length());
if(maxWordWidth > maxLineWidth){
maxLineWidth = maxWordWidth;
}
if ((high - low) < precision) {
return low;
} else if (maxLineWidth > targetWidth) {
return getAutofitTextSize(text, widestWord, paint, targetWidth, maxLines, low, mid, precision,
displayMetrics);
} else if (maxLineWidth < targetWidth) {
return getAutofitTextSize(text, widestWord, paint, targetWidth, maxLines, mid, high, precision,
displayMetrics);
} else {
return mid;
}
}
}
private static int getLineCount(CharSequence text, TextPaint paint, float size, float width,
DisplayMetrics displayMetrics) {
paint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, size,
displayMetrics));
StaticLayout layout = new StaticLayout(text, paint, (int)width,
Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true);
return layout.getLineCount();
}
private static int getMaxLines(Button view) {
int maxLines = -1; // No limit (Integer.MAX_VALUE also means no limit)
TransformationMethod method = view.getTransformationMethod();
if (method != null && method instanceof SingleLineTransformationMethod) {
maxLines = 1;
}
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
// setMaxLines() and getMaxLines() are only available on android-16+
maxLines = view.getMaxLines();
}
return maxLines;
}
private static float getMaxWordWidth(CharSequence text, TextPaint paint, float size,
DisplayMetrics displayMetrics) {
paint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, size,
displayMetrics));
String maxWord = getMaxWord(text, paint);
return paint.measureText(maxWord, 0, maxWord.length());
}
private static String getMaxWord(CharSequence text, TextPaint paint) {
String textStr = text.toString();
textStr = textStr.replace("-", "- ");
String[] words = textStr.split("[ \u00AD\u200B]");
String maxWord = "";
float maxWordWidth = 0;
for (String word : words) {
float wordWidth = paint.measureText(word, 0, word.length());
if (wordWidth > maxWordWidth){
maxWordWidth = wordWidth;
maxWord = word;
}
}
return maxWord;
}
// Attributes
private Button mButton;
private TextPaint mPaint;
/**
* Original textSize of the TextView.
*/
private float mTextSize;
private int mMaxLines;
private float mMinTextSize;
private float mMaxTextSize;
private float mPrecision;
private boolean mEnabled;
private boolean mIsAutofitting;
private ArrayList<OnTextSizeChangeListener> mListeners;
private TextWatcher mTextWatcher = new AutofitTextWatcher();
private View.OnLayoutChangeListener mOnLayoutChangeListener =
new AutofitOnLayoutChangeListener();
private AutofitHelper(Button view) {
final Context context = view.getContext();
float scaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
mButton = view;
mPaint = new TextPaint();
setRawTextSize(view.getTextSize());
mMaxLines = getMaxLines(view);
mMinTextSize = scaledDensity * DEFAULT_MIN_TEXT_SIZE;
mMaxTextSize = mTextSize;
mPrecision = DEFAULT_PRECISION;
}
/**
* Adds an {#link OnTextSizeChangeListener} to the list of those whose methods are called
* whenever the {#link android.widget.Button}'s {#code textSize} changes.
*/
public AutofitHelper addOnTextSizeChangeListener(OnTextSizeChangeListener listener) {
if (mListeners == null) {
mListeners = new ArrayList<OnTextSizeChangeListener>();
}
mListeners.add(listener);
return this;
}
/**
* Removes the specified {#link OnTextSizeChangeListener} from the list of those whose methods
* are called whenever the {#link android.widget.Button}'s {#code textSize} changes.
*/
public AutofitHelper removeOnTextSizeChangeListener(OnTextSizeChangeListener listener) {
if (mListeners != null) {
mListeners.remove(listener);
}
return this;
}
/**
* Returns the amount of precision used to calculate the correct text size to fit within its
* bounds.
*/
public float getPrecision() {
return mPrecision;
}
/**
* Set the amount of precision used to calculate the correct text size to fit within its
* bounds. Lower precision is more precise and takes more time.
*
* #param precision The amount of precision.
*/
public AutofitHelper setPrecision(float precision) {
if (mPrecision != precision) {
mPrecision = precision;
autofit();
}
return this;
}
/**
* Returns the minimum size (in pixels) of the text.
*/
public float getMinTextSize() {
return mMinTextSize;
}
/**
* Set the minimum text size to the given value, interpreted as "scaled pixel" units. This size
* is adjusted based on the current density and user font size preference.
*
* #param size The scaled pixel size.
*
* #attr ref me.grantland.R.styleable#AutofitTextView_minTextSize
*/
public AutofitHelper setMinTextSize(float size) {
return setMinTextSize(TypedValue.COMPLEX_UNIT_SP, size);
}
/**
* Set the minimum text size to a given unit and value. See TypedValue for the possible
* dimension units.
*
* #param unit The desired dimension unit.
* #param size The desired size in the given units.
*
* #attr ref me.grantland.R.styleable#AutofitTextView_minTextSize
*/
public AutofitHelper setMinTextSize(int unit, float size) {
Context context = mButton.getContext();
Resources r = Resources.getSystem();
if (context != null) {
r = context.getResources();
}
setRawMinTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()));
return this;
}
private void setRawMinTextSize(float size) {
if (size != mMinTextSize) {
mMinTextSize = size;
autofit();
}
}
/**
* Returns the maximum size (in pixels) of the text.
*/
public float getMaxTextSize() {
return mMaxTextSize;
}
/**
* Set the maximum text size to the given value, interpreted as "scaled pixel" units. This size
* is adjusted based on the current density and user font size preference.
*
* #param size The scaled pixel size.
*
* #attr ref android.R.styleable#TextView_textSize
*/
public AutofitHelper setMaxTextSize(float size) {
return setMaxTextSize(TypedValue.COMPLEX_UNIT_SP, size);
}
/**
* Set the maximum text size to a given unit and value. See TypedValue for the possible
* dimension units.
*
* #param unit The desired dimension unit.
* #param size The desired size in the given units.
*
* #attr ref android.R.styleable#TextView_textSize
*/
public AutofitHelper setMaxTextSize(int unit, float size) {
Context context = mButton.getContext();
Resources r = Resources.getSystem();
if (context != null) {
r = context.getResources();
}
setRawMaxTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()));
return this;
}
private void setRawMaxTextSize(float size) {
if (size != mMaxTextSize) {
mMaxTextSize = size;
autofit();
}
}
/**
* #see TextView#getMaxLines()
*/
public int getMaxLines() {
return mMaxLines;
}
/**
* #see TextView#setMaxLines(int)
*/
public AutofitHelper setMaxLines(int lines) {
if (mMaxLines != lines) {
mMaxLines = lines;
autofit();
}
return this;
}
/**
* Returns whether or not automatically resizing text is enabled.
*/
public boolean isEnabled() {
return mEnabled;
}
/**
* Set the enabled state of automatically resizing text.
*/
public AutofitHelper setEnabled(boolean enabled) {
if (mEnabled != enabled) {
mEnabled = enabled;
if (enabled) {
mButton.addTextChangedListener(mTextWatcher);
mButton.addOnLayoutChangeListener(mOnLayoutChangeListener);
autofit();
} else {
mButton.removeTextChangedListener(mTextWatcher);
mButton.removeOnLayoutChangeListener(mOnLayoutChangeListener);
mButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
}
}
return this;
}
/**
* Returns the original text size of the View.
*
* #see TextView#getTextSize()
*/
public float getTextSize() {
return mTextSize;
}
/**
* Set the original text size of the View.
*
* #see TextView#setTextSize(float)
*/
public void setTextSize(float size) {
setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
}
/**
* Set the original text size of the View.
*
* #see TextView#setTextSize(int, float)
*/
public void setTextSize(int unit, float size) {
if (mIsAutofitting) {
// We don't want to update the TextView's actual textSize while we're autofitting
// since it'd get set to the autofitTextSize
return;
}
Context context = mButton.getContext();
Resources r = Resources.getSystem();
if (context != null) {
r = context.getResources();
}
setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()));
}
private void setRawTextSize(float size) {
if (mTextSize != size) {
mTextSize = size;
}
}
private void autofit() {
float oldTextSize = mButton.getTextSize();
float textSize;
mIsAutofitting = true;
autofit(mButton, mPaint, mMinTextSize, mMaxTextSize, mMaxLines, mPrecision);
mIsAutofitting = false;
textSize = mButton.getTextSize();
if (textSize != oldTextSize) {
sendTextSizeChange(textSize, oldTextSize);
}
}
private void sendTextSizeChange(float textSize, float oldTextSize) {
if (mListeners == null) {
return;
}
for (OnTextSizeChangeListener listener : mListeners) {
listener.onTextSizeChange(textSize, oldTextSize);
}
}
private class AutofitTextWatcher implements TextWatcher {
#Override
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
// do nothing
}
#Override
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
autofit();
}
#Override
public void afterTextChanged(Editable editable) {
// do nothing
}
}
private class AutofitOnLayoutChangeListener implements View.OnLayoutChangeListener {
#Override
public void onLayoutChange(View view, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
autofit();
}
}
/**
* When an object of a type is attached to an {#code AutofitHelper}, its methods will be called
* when the {#code textSize} is changed.
*/
public interface OnTextSizeChangeListener {
/**
* This method is called to notify you that the size of the text has changed to
* {#code textSize} from {#code oldTextSize}.
*/
public void onTextSizeChange(float textSize, float oldTextSize);
}
}
Then add the needed attributes in
values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="AutofitButton">
<!-- Minimum size of the text. -->
<attr name="minTextSize" format="dimension" />
<!-- Amount of precision used to calculate the correct text size to fit within its
bounds. Lower precision is more precise and takes more time. -->
<attr name="precision" format="float" />
<!-- Defines whether to automatically resize text to fit to the view's bounds. -->
<attr name="sizeToFit" format="boolean" />
</declare-styleable>
<!-- Your other attributes -->
</resources>
And you're done! You can now use the AutoSizeTextButton class.
<your.package.name.AutoSizeTextButton
android:layout_width="..."
android:layout_height="..."
android:maxLines="2" />
And be sure to add the android:maxLines attribute with a value larger than 0, otherwise it won't do anything!
Additional Notes:
The text is shrunken until the longest word fits into the button without wrapping (or the minimum size is reached). The words have to be seperated by either a normal space, or a hyphen. This algorithm also considers a SOFT HYPHEN or a ZERO WIDTH SPACE a word seperator, however I would strongly advise to test them before using them, because the Android Text Engine used in buttons ignores these characters (at least in API 19), which could lead to weird word-wraps.
It's better you use this library named AutoScaleTextView
https://bitbucket.org/ankri/autoscaletextview
This will definitely help you to achieve your desired task.
There is no built-in way of doing this, you will need to create/use a custom view that adapts the inner text to its bounds. Don't worry, this is not the first time its been asked, see the Custom View provided at Auto Scale TextView Text to Fit within Bounds to get the working code.
if any one is looking on how to disable auto text size, it can be done by
<TextView
app:autoSizeTextType="none" <!-- disabled -->
adding the above line to your text view