I've created a subclass of TextView, and it is used alongside standard TextViews. The problem is that the style that is applied by default to the standard TextView isn't applied to my custom TextView. Is there a way for my custom TextView to inherit it's parent's style?
Custom view:
import android.content.Context;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;
/**
* Text view that auto adjusts text size to fit within the view.
* If the text size equals the minimum text size and still does not
* fit, append with an ellipsis.
*/
public class AutoResizeTextView extends TextView {
// Minimum text size for this text view
public static final float MIN_TEXT_SIZE = 20;
// Interface for resize notifications
public interface OnTextResizeListener {
void onTextResize(TextView textView, float oldSize, float newSize);
}
// Our ellipse string
private static final String mEllipsis = "...";
// Registered resize listener
private OnTextResizeListener mTextResizeListener;
// Flag for text and/or size changes to force a resize
private boolean mNeedsResize = false;
// Text size that is set from code. This acts as a starting point for resizing
private float mTextSize;
// Temporary upper bounds on the starting text size
private float mMaxTextSize = 0;
// Lower bounds for text size
private float mMinTextSize = MIN_TEXT_SIZE;
// Text view line spacing multiplier
private float mSpacingMult = 1.0f;
// Text view additional line spacing
private float mSpacingAdd = 0.0f;
// Add ellipsis to text that overflows at the smallest text size
private boolean mAddEllipsis = true;
private float mDefaultTextSize;
// Default constructor override
public AutoResizeTextView(Context context) {
this(context, null);
}
// Default constructor when inflating from XML file
public AutoResizeTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
// Default constructor override
public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs);
mTextSize = getTextSize();
mDefaultTextSize = mTextSize;
}
/**
* When text changes, set the force resize flag to true and reset the text size.
*/
#Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
mNeedsResize = true;
// Since this view may be reused, it is good to reset the text size
resetTextSize();
}
/**
* If the text view size changed, set the force resize flag to true
*/
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw || h != oldh) {
mNeedsResize = true;
}
}
/**
* Register listener to receive resize notifications
* #param listener
*/
public void setOnResizeListener(OnTextResizeListener listener) {
mTextResizeListener = listener;
}
/**
* Override the set text size to update our internal reference values
*/
#Override
public void setTextSize(float size) {
super.setTextSize(size);
mTextSize = getTextSize();
}
/**
* Override the set text size to update our internal reference values
*/
#Override
public void setTextSize(int unit, float size) {
super.setTextSize(unit, size);
mTextSize = getTextSize();
}
/**
* Override the set line spacing to update our internal reference values
*/
#Override
public void setLineSpacing(float add, float mult) {
super.setLineSpacing(add, mult);
mSpacingMult = mult;
mSpacingAdd = add;
}
/**
* Set the upper text size limit and invalidate the view
* #param maxTextSize
*/
public void setMaxTextSize(float maxTextSize) {
mMaxTextSize = maxTextSize;
requestLayout();
invalidate();
}
/**
* Return upper text size limit
* #return
*/
public float getMaxTextSize() {
return mMaxTextSize;
}
/**
* Set the lower text size limit and invalidate the view
* #param minTextSize
*/
public void setMinTextSize(float minTextSize) {
mMinTextSize = minTextSize;
requestLayout();
invalidate();
}
/**
* Return lower text size limit
* #return
*/
public float getMinTextSize() {
return mMinTextSize;
}
/**
* Set flag to add ellipsis to text that overflows at the smallest text size
* #param addEllipsis
*/
public void setAddEllipsis(boolean addEllipsis) {
mAddEllipsis = addEllipsis;
}
/**
* Return flag to add ellipsis to text that overflows at the smallest text size
* #return
*/
public boolean getAddEllipsis() {
return mAddEllipsis;
}
/**
* Reset the text to the original size
*/
public void resetTextSize() {
if (mTextSize > 0) {
mTextSize = mDefaultTextSize;
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
}
}
/**
* Resize text after measuring
*/
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (changed || mNeedsResize) {
int widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
int heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
resizeText(widthLimit, heightLimit);
}
super.onLayout(changed, left, top, right, bottom);
}
/**
* Resize the text size with default width and height
*/
public void resizeText() {
int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
resizeText(widthLimit, heightLimit);
}
/**
* Resize the text size with specified width and height
* #param widthLimit
* #param heightLimit
*/
public void resizeText(int widthLimit, int heightLimit) {
CharSequence text = getText();
// Do not resize if the view does not have dimensions or there is no text
if (text == null || text.length() == 0 || heightLimit <= 0 || widthLimit <= 0 || mTextSize == 0) {
return;
}
if (getTransformationMethod() != null) {
text = getTransformationMethod().getTransformation(text, this);
}
// Get the text view's paint object
TextPaint textPaint = getPaint();
// Store the current text size
float currentTextSize = textPaint.getTextSize();
// If there is a max text size set, use the lesser of that and the default text size
float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;
// Get the required text height
int textHeight = getTextHeight(text, textPaint, widthLimit, targetTextSize);
// Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
while (textHeight > heightLimit && targetTextSize > mMinTextSize) {
targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
textHeight = getTextHeight(text, textPaint, widthLimit, targetTextSize);
}
// If we had reached our minimum text size and still don't fit, append an ellipsis
if (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > heightLimit) {
// Draw using a static layout
// modified: use a copy of TextPaint for measuring
TextPaint paint = new TextPaint(textPaint);
// Draw using a static layout
StaticLayout layout = new StaticLayout(text, paint, widthLimit, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
// Check that we have a least one line of rendered text
if (layout.getLineCount() > 0) {
// Since the line at the specific vertical position would be cut off,
// we must trim up to the previous line
int lastLine = layout.getLineForVertical(heightLimit) - 1;
// If the text would not even fit on a single line, clear it
if (lastLine < 0) {
setText("");
}
// Otherwise, trim to the previous line and add an ellipsis
else {
int start = layout.getLineStart(lastLine);
int end = layout.getLineEnd(lastLine);
float lineWidth = layout.getLineWidth(lastLine);
float ellipseWidth = textPaint.measureText(mEllipsis);
// Trim characters off until we have enough room to draw the ellipsis
while (widthLimit < lineWidth + ellipseWidth) {
lineWidth = textPaint.measureText(text.subSequence(start, --end + 1).toString());
}
setText(text.subSequence(0, end) + mEllipsis);
}
}
}
// Some devices try to auto adjust line spacing, so force default line spacing
// and invalidate the layout as a side effect
setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
setLineSpacing(mSpacingAdd, mSpacingMult);
// Notify the listener if registered
if (mTextResizeListener != null) {
mTextResizeListener.onTextResize(this, currentTextSize, targetTextSize);
}
// Reset force resize flag
mNeedsResize = false;
}
// Set the text size of the text paint object and use a static layout to render text off screen before measuring
private int getTextHeight(CharSequence source, TextPaint paint, int width, float textSize) {
// modified: make a copy of the original TextPaint object for measuring
// (apparently the object gets modified while measuring, see also the
// docs for TextView.getPaint() (which states to access it read-only)
TextPaint paintCopy = new TextPaint(paint);
// Update the text paint object
paintCopy.setTextSize(textSize);
// Measure using a static layout
StaticLayout layout = new StaticLayout(source, paintCopy, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
return layout.getHeight();
}
}
Related
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 have developed a custom 3D ListView by following this tutorial. I am able to create the listview successfully. However, now I wish to scroll down and up in the listview by using Button events. I have created following functions for scrolling down and up in the listview.
//scroll list down
public void scrollListDown(){
int count = 20;
int currentSelected = mListView.getSelectedItemPosition();
if (currentSelected != (count -1) && currentSelected < (count - 1)) {
int count1 = currentSelected + 1;
mListView.setSelection(count1);
}
}
However, in this example, they have not written any implementation for the setSelection method. Due to this the scrolling does not work. I wish to move the selection from one item to another when a button is clicked. My function works on a normal ListView in Android
Currently, I have written a logic for the setSelection method as follows:
#Override
public void setSelection(int position) {
if (mAdapter == null) {
return;
}
if (!isInTouchMode()) {
position = lookForSelectablePosition(position, true);
if (position >= 0) {
setNextSelectedPositionInt(position);
}
} else {
mResurrectToPosition = position;
}
if (position >= 0) {
mLayoutMode = LAYOUT_SPECIFIC;
mSpecificTop = 10;
if (mNeedSync) {
mSyncPosition = position;
mSyncRowId = mAdapter.getItemId(position);
}
requestLayout();
}
}
This is not working as expected. Can anyone suggest how I should use the setSelection method in this case?
My Adapter class
/**
* A simple list view that displays the items as 3D blocks
*/
public class MyListView extends AdapterView<ListAdapter> {
/** Width of the items compared to the width of the list */
private static final float ITEM_WIDTH = 0.85f;
/** Space occupied by the item relative to the height of the item */
private static final float ITEM_VERTICAL_SPACE = 1.45f;
/** Ambient light intensity */
private static final int AMBIENT_LIGHT = 55;
/** Diffuse light intensity */
private static final int DIFFUSE_LIGHT = 200;
/** Specular light intensity */
private static final float SPECULAR_LIGHT = 70;
/** Shininess constant */
private static final float SHININESS = 200;
/** The max intensity of the light */
private static final int MAX_INTENSITY = 0xFF;
/** Amount of down scaling */
private static final float SCALE_DOWN_FACTOR = 0.15f;
/** Amount to rotate during one screen length */
private static final int DEGREES_PER_SCREEN = 270;
/** Represents an invalid child index */
private static final int INVALID_INDEX = -1;
/** Distance to drag before we intercept touch events */
private static final int TOUCH_SCROLL_THRESHOLD = 10;
/** Children added with this layout mode will be added below the last child */
private static final int LAYOUT_MODE_BELOW = 0;
/** Children added with this layout mode will be added above the first child */
private static final int LAYOUT_MODE_ABOVE = 1;
/** User is not touching the list */
private static final int TOUCH_STATE_RESTING = 0;
/** User is touching the list and right now it's still a "click" */
private static final int TOUCH_STATE_CLICK = 1;
/** User is scrolling the list */
private static final int TOUCH_STATE_SCROLL = 2;
/** The adapter with all the data */
private ListAdapter mAdapter;
/** Current touch state */
private int mTouchState = TOUCH_STATE_RESTING;
/** X-coordinate of the down event */
private int mTouchStartX;
/** Y-coordinate of the down event */
private int mTouchStartY;
/**
* The top of the first item when the touch down event was received
*/
private int mListTopStart;
/** The current top of the first item */
private int mListTop;
/**
* The offset from the top of the currently first visible item to the top of
* the first item
*/
private int mListTopOffset;
/** Current rotation */
private int mListRotation;
/** The adaptor position of the first visible item */
private int mFirstItemPosition;
/** The adaptor position of the last visible item */
private int mLastItemPosition;
/** A list of cached (re-usable) item views */
private final LinkedList<View> mCachedItemViews = new LinkedList<View>();
/** Used to check for long press actions */
private Runnable mLongPressRunnable;
/** Reusable rect */
private Rect mRect;
/** Camera used for 3D transformations */
private Camera mCamera;
/** Re-usable matrix for canvas transformations */
private Matrix mMatrix;
/** Paint object to draw with */
private Paint mPaint;
/** true if rotation of the items is enabled */
private boolean mRotationEnabled = true;
/** true if lighting of the items is enabled */
private boolean mLightEnabled = true;
int mSyncPosition;
long mSyncRowId = INVALID_ROW_ID;
long mNextSelectedRowId;
boolean mNeedSync = false;
int mLayoutHeight;
private boolean mAreAllItemsSelectable = true;
private int mResurrectToPosition;
private int mLayoutMode, mSpecificTop,mNextSelectedPosition;
static final int SYNC_SELECTED_POSITION = 0;
int mSyncMode;
static final int LAYOUT_SPECIFIC = 4;
/**
* Constructor
*
* #param context The context
* #param attrs Attributes
*/
public MyListView(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
#Override
public void setAdapter(ListAdapter adapter) {
mAdapter = adapter;
removeAllViewsInLayout();
requestLayout();
}
#Override
public ListAdapter getAdapter() {
return mAdapter;
}
#Override
public void setSelection(int position) {
//throw new UnsupportedOperationException("Not supported");
if (mAdapter == null) {
return;
}
if (!isInTouchMode()) {
position = lookForSelectablePosition(position, true);
if (position >= 0) {
setNextSelectedPositionInt(position);
}
} else {
mResurrectToPosition = position;
}
if (position >= 0) {
mLayoutMode = LAYOUT_SPECIFIC;
mSpecificTop = 10;
if (mNeedSync) {
mSyncPosition = position;
mSyncRowId = mAdapter.getItemId(position);
}
requestLayout();
}
}
public void setNextSelectedPositionInt(int position) {
mNextSelectedPosition = position;
mNextSelectedRowId = getItemIdAtPosition(position);
// If we are trying to sync to the selection, update that too
if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {
mSyncPosition = position;
mSyncRowId = mNextSelectedRowId;
}
}
/**
* Find a position that can be selected (i.e., is not a separator).
*
* #param position The starting position to look at.
* #param lookDown Whether to look down for other positions.
* #return The next selectable position starting at position and then searching either up or
* down. Returns {#link #INVALID_POSITION} if nothing can be found.
*/
public int lookForSelectablePosition(int position, boolean lookDown) {
final ListAdapter adapter = mAdapter;
if (adapter == null || isInTouchMode()) {
return INVALID_POSITION;
}
final int count = adapter.getCount();
if (!mAreAllItemsSelectable) {
if (lookDown) {
position = Math.max(0, position);
while (position < count && !adapter.isEnabled(position)) {
position++;
}
} else {
position = Math.min(position, count - 1);
while (position >= 0 && !adapter.isEnabled(position)) {
position--;
}
}
if (position < 0 || position >= count) {
return INVALID_POSITION;
}
return position;
} else {
if (position < 0 || position >= count) {
return INVALID_POSITION;
}
return position;
}
}
/* #Override
public View getSelectedView() {
throw new UnsupportedOperationException("Not supported");
}*/
/**
* Enables and disables individual rotation of the items.
*
* #param enable If rotation should be enabled or not
*/
public void enableRotation(final boolean enable) {
mRotationEnabled = enable;
if (!mRotationEnabled) {
mListRotation = 0;
}
invalidate();
}
/**
* Checks whether rotation is enabled
*
* #return true if rotation is enabled
*/
public boolean isRotationEnabled() {
return mRotationEnabled;
}
/**
* Enables and disables lighting of the items.
*
* #param enable If lighting should be enabled or not
*/
public void enableLight(final boolean enable) {
mLightEnabled = enable;
if (!mLightEnabled) {
mPaint.setColorFilter(null);
} else {
mPaint.setAlpha(0xFF);
}
invalidate();
}
/**
* Checks whether lighting is enabled
*
* #return true if rotation is enabled
*/
public boolean isLightEnabled() {
return mLightEnabled;
}
#Override
public boolean onInterceptTouchEvent(final MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startTouch(event);
return false;
case MotionEvent.ACTION_MOVE:
return startScrollIfNeeded(event);
default:
endTouch();
return false;
}
}
#Override
public boolean onTouchEvent(final MotionEvent event) {
if (getChildCount() == 0) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startTouch(event);
break;
case MotionEvent.ACTION_MOVE:
if (mTouchState == TOUCH_STATE_CLICK) {
startScrollIfNeeded(event);
}
if (mTouchState == TOUCH_STATE_SCROLL) {
scrollList((int)event.getY() - mTouchStartY);
}
break;
case MotionEvent.ACTION_UP:
if (mTouchState == TOUCH_STATE_CLICK) {
clickChildAt((int)event.getX(), (int)event.getY());
}
endTouch();
break;
default:
endTouch();
break;
}
return true;
}
#Override
protected void onLayout(final boolean changed, final int left, final int top, final int right,
final int bottom) {
super.onLayout(changed, left, top, right, bottom);
// if we don't have an adapter, we don't need to do anything
if (mAdapter == null) {
return;
}
if (getChildCount() == 0) {
mLastItemPosition = -1;
fillListDown(mListTop, 0);
} else {
final int offset = mListTop + mListTopOffset - getChildTop(getChildAt(0));
removeNonVisibleViews(offset);
fillList(offset);
}
positionItems();
invalidate();
}
#Override
protected boolean drawChild(final Canvas canvas, final View child, final long drawingTime) {
// get the bitmap
final Bitmap bitmap = child.getDrawingCache();
if (bitmap == null) {
// if the is null for some reason, default to the standard
// drawChild implementation
return super.drawChild(canvas, child, drawingTime);
}
// get top left coordinates
final int top = child.getTop();
final int left = child.getLeft();
// get centerX and centerY
final int childWidth = child.getWidth();
final int childHeight = child.getHeight();
final int centerX = childWidth / 2;
final int centerY = childHeight / 2;
// get scale
final float halfHeight = getHeight() / 2;
final float distFromCenter = (top + centerY - halfHeight) / halfHeight;
final float scale = (float)(1 - SCALE_DOWN_FACTOR * (1 - Math.cos(distFromCenter)));
// get rotation
float childRotation = mListRotation - 20 * distFromCenter;
childRotation %= 90;
if (childRotation < 0) {
childRotation += 90;
}
// draw the item
if (childRotation < 45) {
drawFace(canvas, bitmap, top, left, centerX, centerY, scale, childRotation - 90);
drawFace(canvas, bitmap, top, left, centerX, centerY, scale, childRotation);
} else {
drawFace(canvas, bitmap, top, left, centerX, centerY, scale, childRotation);
drawFace(canvas, bitmap, top, left, centerX, centerY, scale, childRotation - 90);
}
return false;
}
/**
* Draws a face of the 3D block
*
* #param canvas The canvas to draw on
* #param view A bitmap of the view to draw
* #param top Top placement of the view
* #param left Left placement of the view
* #param centerX Center x-coordinate of the view
* #param centerY Center y-coordinate of the view
* #param scale The scale to draw the view in
* #param rotation The rotation of the view
*/
private void drawFace(final Canvas canvas, final Bitmap view, final int top, final int left,
final int centerX, final int centerY, final float scale, final float rotation) {
// create the camera if we haven't before
if (mCamera == null) {
mCamera = new Camera();
}
// save the camera state
mCamera.save();
// translate and then rotate the camera
mCamera.translate(0, 0, centerY);
mCamera.rotateX(rotation);
mCamera.translate(0, 0, -centerY);
// create the matrix if we haven't before
if (mMatrix == null) {
mMatrix = new Matrix();
}
// get the matrix from the camera and then restore the camera
mCamera.getMatrix(mMatrix);
mCamera.restore();
// translate and scale the matrix
mMatrix.preTranslate(-centerX, -centerY);
mMatrix.postScale(scale, scale);
mMatrix.postTranslate(left + centerX, top + centerY);
// create and initialize the paint object
if (mPaint == null) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setFilterBitmap(true);
}
// set the light
if (mLightEnabled) {
mPaint.setColorFilter(calculateLight(rotation));
} else {
mPaint.setAlpha(0xFF - (int)(2 * Math.abs(rotation)));
}
// draw the bitmap
canvas.drawBitmap(view, mMatrix, mPaint);
}
/**
* Calculates the lighting of the item based on rotation.
*
* #param rotation The rotation of the item
* #return A color filter to use
*/
private LightingColorFilter calculateLight(final float rotation) {
final double cosRotation = Math.cos(Math.PI * rotation / 180);
int intensity = AMBIENT_LIGHT + (int)(DIFFUSE_LIGHT * cosRotation);
int highlightIntensity = (int)(SPECULAR_LIGHT * Math.pow(cosRotation, SHININESS));
if (intensity > MAX_INTENSITY) {
intensity = MAX_INTENSITY;
}
if (highlightIntensity > MAX_INTENSITY) {
highlightIntensity = MAX_INTENSITY;
}
final int light = Color.rgb(intensity, intensity, intensity);
final int highlight = Color.rgb(highlightIntensity, highlightIntensity, highlightIntensity);
return new LightingColorFilter(light, highlight);
}
/**
* Sets and initializes all things that need to when we start a touch
* gesture.
*
* #param event The down event
*/
private void startTouch(final MotionEvent event) {
// save the start place
mTouchStartX = (int)event.getX();
mTouchStartY = (int)event.getY();
mListTopStart = getChildTop(getChildAt(0)) - mListTopOffset;
// start checking for a long press
startLongPressCheck();
// we don't know if it's a click or a scroll yet, but until we know
// assume it's a click
mTouchState = TOUCH_STATE_CLICK;
}
/**
* Resets and recycles all things that need to when we end a touch gesture
*/
private void endTouch() {
// remove any existing check for longpress
removeCallbacks(mLongPressRunnable);
// reset touch state
mTouchState = TOUCH_STATE_RESTING;
}
/**
* Scrolls the list. Takes care of updating rotation (if enabled) and
* snapping
*
* #param scrolledDistance The distance to scroll
*/
public void scrollList(final int scrolledDistance) {
mListTop = mListTopStart + scrolledDistance;
//mRotationEnabled = false;
if (mRotationEnabled) {
mListRotation = -(DEGREES_PER_SCREEN * mListTop) / getHeight();
}
requestLayout();
}
public void scrollListViewUp(final int scrolledDistance) {
mListTop = mListTopStart + scrolledDistance;
//mRotationEnabled = false;
if (mRotationEnabled) {
mListRotation = -(DEGREES_PER_SCREEN * mListTop) / getHeight();
}
requestLayout();
}
public void scrollListViewDown(final int scrolledDistance) {
mListTop = mListTopStart + scrolledDistance;
//mRotationEnabled = false;
if (mRotationEnabled) {
mListRotation = -(DEGREES_PER_SCREEN * mListTop) / getHeight();
}
requestLayout();
}
/**
* Posts (and creates if necessary) a runnable that will when executed call
* the long click listener
*/
private void startLongPressCheck() {
// create the runnable if we haven't already
if (mLongPressRunnable == null) {
mLongPressRunnable = new Runnable() {
public void run() {
if (mTouchState == TOUCH_STATE_CLICK) {
final int index = getContainingChildIndex(mTouchStartX, mTouchStartY);
if (index != INVALID_INDEX) {
longClickChild(index);
}
}
}
};
}
// then post it with a delay
postDelayed(mLongPressRunnable, ViewConfiguration.getLongPressTimeout());
}
/**
* Checks if the user has moved far enough for this to be a scroll and if
* so, sets the list in scroll mode
*
* #param event The (move) event
* #return true if scroll was started, false otherwise
*/
public boolean startScrollIfNeeded(final MotionEvent event) {
final int xPos = (int)event.getX();
final int yPos = (int)event.getY();
if (xPos < mTouchStartX - TOUCH_SCROLL_THRESHOLD
|| xPos > mTouchStartX + TOUCH_SCROLL_THRESHOLD
|| yPos < mTouchStartY - TOUCH_SCROLL_THRESHOLD
|| yPos > mTouchStartY + TOUCH_SCROLL_THRESHOLD) {
// we've moved far enough for this to be a scroll
removeCallbacks(mLongPressRunnable);
mTouchState = TOUCH_STATE_SCROLL;
return true;
}
return false;
}
/**
* Returns the index of the child that contains the coordinates given.
*
* #param x X-coordinate
* #param y Y-coordinate
* #return The index of the child that contains the coordinates. If no child
* is found then it returns INVALID_INDEX
*/
private int getContainingChildIndex(final int x, final int y) {
if (mRect == null) {
mRect = new Rect();
}
for (int index = 0; index < getChildCount(); index++) {
getChildAt(index).getHitRect(mRect);
if (mRect.contains(x, y)) {
return index;
}
}
return INVALID_INDEX;
}
/**
* Calls the item click listener for the child with at the specified
* coordinates
*
* #param x The x-coordinate
* #param y The y-coordinate
*/
private void clickChildAt(final int x, final int y) {
final int index = getContainingChildIndex(x, y);
if (index != INVALID_INDEX) {
final View itemView = getChildAt(index);
final int position = mFirstItemPosition + index;
final long id = mAdapter.getItemId(position);
performItemClick(itemView, position, id);
}
}
/**
* Calls the item long click listener for the child with the specified index
*
* #param index Child index
*/
private void longClickChild(final int index) {
final View itemView = getChildAt(index);
final int position = mFirstItemPosition + index;
final long id = mAdapter.getItemId(position);
final OnItemLongClickListener listener = getOnItemLongClickListener();
if (listener != null) {
listener.onItemLongClick(this, itemView, position, id);
}
}
/**
* Removes view that are outside of the visible part of the list. Will not
* remove all views.
*
* #param offset Offset of the visible area
*/
private void removeNonVisibleViews(final int offset) {
// We need to keep close track of the child count in this function. We
// should never remove all the views, because if we do, we loose track
// of were we are.
int childCount = getChildCount();
// if we are not at the bottom of the list and have more than one child
if (mLastItemPosition != mAdapter.getCount() - 1 && childCount > 1) {
// check if we should remove any views in the top
View firstChild = getChildAt(0);
while (firstChild != null && getChildBottom(firstChild) + offset < 0) {
// remove the top view
removeViewInLayout(firstChild);
childCount--;
mCachedItemViews.addLast(firstChild);
mFirstItemPosition++;
// update the list offset (since we've removed the top child)
mListTopOffset += getChildHeight(firstChild);
// Continue to check the next child only if we have more than
// one child left
if (childCount > 1) {
firstChild = getChildAt(0);
} else {
firstChild = null;
}
}
}
// if we are not at the top of the list and have more than one child
if (mFirstItemPosition != 0 && childCount > 1) {
// check if we should remove any views in the bottom
View lastChild = getChildAt(childCount - 1);
while (lastChild != null && getChildTop(lastChild) + offset > getHeight()) {
// remove the bottom view
removeViewInLayout(lastChild);
childCount--;
mCachedItemViews.addLast(lastChild);
mLastItemPosition--;
// Continue to check the next child only if we have more than
// one child left
if (childCount > 1) {
lastChild = getChildAt(childCount - 1);
} else {
lastChild = null;
}
}
}
}
/**
* Fills the list with child-views
*
* #param offset Offset of the visible area
*/
private void fillList(final int offset) {
final int bottomEdge = getChildBottom(getChildAt(getChildCount() - 1));
fillListDown(bottomEdge, offset);
final int topEdge = getChildTop(getChildAt(0));
fillListUp(topEdge, offset);
}
/**
* Starts at the bottom and adds children until we've passed the list bottom
*
* #param bottomEdge The bottom edge of the currently last child
* #param offset Offset of the visible area
*/
private void fillListDown(int bottomEdge, final int offset) {
while (bottomEdge + offset < getHeight() && mLastItemPosition < mAdapter.getCount() - 1) {
mLastItemPosition++;
final View newBottomchild = mAdapter.getView(mLastItemPosition, getCachedView(), this);
addAndMeasureChild(newBottomchild, LAYOUT_MODE_BELOW);
bottomEdge += getChildHeight(newBottomchild);
}
}
/**
* Starts at the top and adds children until we've passed the list top
*
* #param topEdge The top edge of the currently first child
* #param offset Offset of the visible area
*/
private void fillListUp(int topEdge, final int offset) {
while (topEdge + offset > 0 && mFirstItemPosition > 0) {
mFirstItemPosition--;
final View newTopCild = mAdapter.getView(mFirstItemPosition, getCachedView(), this);
addAndMeasureChild(newTopCild, LAYOUT_MODE_ABOVE);
final int childHeight = getChildHeight(newTopCild);
topEdge -= childHeight;
// update the list offset (since we added a view at the top)
mListTopOffset -= childHeight;
}
}
/**
* Adds a view as a child view and takes care of measuring it
*
* #param child The view to add
* #param layoutMode Either LAYOUT_MODE_ABOVE or LAYOUT_MODE_BELOW
*/
private void addAndMeasureChild(final View child, final int layoutMode) {
LayoutParams params = child.getLayoutParams();
if (params == null) {
params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
final int index = layoutMode == LAYOUT_MODE_ABOVE ? 0 : -1;
child.setDrawingCacheEnabled(true);
addViewInLayout(child, index, params, true);
final int itemWidth = (int)(getWidth() * ITEM_WIDTH);
child.measure(MeasureSpec.EXACTLY | itemWidth, MeasureSpec.UNSPECIFIED);
}
/**
* Positions the children at the "correct" positions
*/
private void positionItems() {
int top = mListTop + mListTopOffset;
for (int index = 0; index < getChildCount(); index++) {
final View child = getChildAt(index);
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
final int left = (getWidth() - width) / 2;
final int margin = getChildMargin(child);
final int childTop = top + margin;
child.layout(left, childTop, left + width, childTop + height);
top += height + 2 * margin;
}
}
/**
* Checks if there is a cached view that can be used
*
* #return A cached view or, if none was found, null
*/
private View getCachedView() {
if (mCachedItemViews.size() != 0) {
return mCachedItemViews.removeFirst();
}
return null;
}
/**
* Returns the margin of the child view taking into account the
* ITEM_VERTICAL_SPACE
*
* #param child The child view
* #return The margin of the child view
*/
private int getChildMargin(final View child) {
return (int)(child.getMeasuredHeight() * (ITEM_VERTICAL_SPACE - 1) / 2);
}
/**
* Returns the top placement of the child view taking into account the
* ITEM_VERTICAL_SPACE
*
* #param child The child view
* #return The top placement of the child view
*/
private int getChildTop(final View child) {
return child.getTop() - getChildMargin(child);
}
/**
* Returns the bottom placement of the child view taking into account the
* ITEM_VERTICAL_SPACE
*
* #param child The child view
* #return The bottom placement of the child view
*/
private int getChildBottom(final View child) {
return child.getBottom() + getChildMargin(child);
}
/**
* Returns the height of the child view taking into account the
* ITEM_VERTICAL_SPACE
*
* #param child The child view
* #return The height of the child view
*/
private int getChildHeight(final View child) {
return child.getMeasuredHeight() + 2 * getChildMargin(child);
}
#Override
public View getSelectedView() {
// TODO Auto-generated method stub
return null;
}
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
I want to fit the text size of a TextView and an EditText to to match their bounds.
I searched a bit and found the code below. So I created a new class with the code below:
package com.example.test;
import android.content.Context;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;
public class AutoResizeTextView extends TextView {
// Minimum text size for this text view
public static final float MIN_TEXT_SIZE = 20;
// Interface for resize notifications
public interface OnTextResizeListener {
public void onTextResize(TextView textView, float oldSize, float newSize);
}
// Our ellipse string
private static final String mEllipsis = "...";
// Registered resize listener
private OnTextResizeListener mTextResizeListener;
// Flag for text and/or size changes to force a resize
private boolean mNeedsResize = false;
// Text size that is set from code. This acts as a starting point for resizing
private float mTextSize;
// Temporary upper bounds on the starting text size
private float mMaxTextSize = 0;
// Lower bounds for text size
private float mMinTextSize = MIN_TEXT_SIZE;
// Text view line spacing multiplier
private float mSpacingMult = 1.0f;
// Text view additional line spacing
private float mSpacingAdd = 0.0f;
// Add ellipsis to text that overflows at the smallest text size
private boolean mAddEllipsis = true;
// Default constructor override
public AutoResizeTextView(Context context) {
this(context, null);
}
// Default constructor when inflating from XML file
public AutoResizeTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
// Default constructor override
public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mTextSize = getTextSize();
}
/**
* When text changes, set the force resize flag to true and reset the text size.
*/
#Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
mNeedsResize = true;
// Since this view may be reused, it is good to reset the text size
resetTextSize();
}
/**
* If the text view size changed, set the force resize flag to true
*/
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw || h != oldh) {
mNeedsResize = true;
}
}
/**
* Register listener to receive resize notifications
* #param listener
*/
public void setOnResizeListener(OnTextResizeListener listener) {
mTextResizeListener = listener;
}
/**
* Override the set text size to update our internal reference values
*/
#Override
public void setTextSize(float size) {
super.setTextSize(size);
mTextSize = getTextSize();
}
/**
* Override the set text size to update our internal reference values
*/
#Override
public void setTextSize(int unit, float size) {
super.setTextSize(unit, size);
mTextSize = getTextSize();
}
/**
* Override the set line spacing to update our internal reference values
*/
#Override
public void setLineSpacing(float add, float mult) {
super.setLineSpacing(add, mult);
mSpacingMult = mult;
mSpacingAdd = add;
}
/**
* Set the upper text size limit and invalidate the view
* #param maxTextSize
*/
public void setMaxTextSize(float maxTextSize) {
mMaxTextSize = maxTextSize;
requestLayout();
invalidate();
}
/**
* Return upper text size limit
* #return
*/
public float getMaxTextSize() {
return mMaxTextSize;
}
/**
* Set the lower text size limit and invalidate the view
* #param minTextSize
*/
public void setMinTextSize(float minTextSize) {
mMinTextSize = minTextSize;
requestLayout();
invalidate();
}
/**
* Return lower text size limit
* #return
*/
public float getMinTextSize() {
return mMinTextSize;
}
/**
* Set flag to add ellipsis to text that overflows at the smallest text size
* #param addEllipsis
*/
public void setAddEllipsis(boolean addEllipsis) {
mAddEllipsis = addEllipsis;
}
/**
* Return flag to add ellipsis to text that overflows at the smallest text size
* #return
*/
public boolean getAddEllipsis() {
return mAddEllipsis;
}
/**
* Reset the text to the original size
*/
public void resetTextSize() {
if(mTextSize > 0) {
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
mMaxTextSize = mTextSize;
}
}
/**
* Resize text after measuring
*/
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if(changed || mNeedsResize) {
int widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
int heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
resizeText(widthLimit, heightLimit);
}
super.onLayout(changed, left, top, right, bottom);
}
/**
* Resize the text size with default width and height
*/
public void resizeText() {
int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
resizeText(widthLimit, heightLimit);
}
/**
* Resize the text size with specified width and height
* #param width
* #param height
*/
public void resizeText(int width, int height) {
CharSequence text = getText();
// Do not resize if the view does not have dimensions or there is no text
if(text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) {
return;
}
// Get the text view's paint object
TextPaint textPaint = getPaint();
// Store the current text size
float oldTextSize = textPaint.getTextSize();
// If there is a max text size set, use the lesser of that and the default text size
float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;
// Get the required text height
int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
// Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
while(textHeight > height && targetTextSize > mMinTextSize) {
targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
textHeight = getTextHeight(text, textPaint, width, targetTextSize);
}
// If we had reached our minimum text size and still don't fit, append an ellipsis
if(mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
// Draw using a static layout
StaticLayout layout = new StaticLayout(text, textPaint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
// Check that we have a least one line of rendered text
if(layout.getLineCount() > 0) {
// Since the line at the specific vertical position would be cut off,
// we must trim up to the previous line
int lastLine = layout.getLineForVertical(height) - 1;
// If the text would not even fit on a single line, clear it
if(lastLine < 0) {
setText("");
}
// Otherwise, trim to the previous line and add an ellipsis
else {
int start = layout.getLineStart(lastLine);
int end = layout.getLineEnd(lastLine);
float lineWidth = layout.getLineWidth(lastLine);
float ellipseWidth = textPaint.measureText(mEllipsis);
// Trim characters off until we have enough room to draw the ellipsis
while(width < lineWidth + ellipseWidth) {
lineWidth = textPaint.measureText(text.subSequence(start, --end + 1).toString());
}
setText(text.subSequence(0, end) + mEllipsis);
}
}
}
// Some devices try to auto adjust line spacing, so force default line spacing
// and invalidate the layout as a side effect
textPaint.setTextSize(targetTextSize);
setLineSpacing(mSpacingAdd, mSpacingMult);
// Notify the listener if registered
if(mTextResizeListener != null) {
mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
}
// Reset force resize flag
mNeedsResize = false;
}
// Set the text size of the text paint object and use a static layout to render text off screen before measuring
private int getTextHeight(CharSequence source, TextPaint paint, int width, float textSize) {
// Update the text paint object
paint.setTextSize(textSize);
// Measure using a static layout
StaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
return layout.getHeight();
}
}
But what do I have to do from there in order for the code to work? This is my code and my XML file. What do I do wrong and what should I do?
Java:
package com.example.test;
import android.app.Activity;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnPreparedListener;
import android.net.Uri;
import android.os.Bundle;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.VideoView;
import com.example.test.AutoResizeTextView;
public class MainActivity extends Activity {
public VideoView vv;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, // Set Fullscreen mode, overiding title and BATTERY
WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // SCREEN NEVER GOES TO SLEEP MODE
//*** BACKGROUND MOVIE, LOOPING AND SETTING LOCAL PATH ***
vv = (VideoView) findViewById(R.id.videoView);
vv.setOnPreparedListener(new OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
mp.setLooping(true);
}
});
Uri url=Uri.parse("android.resource://" + getPackageName() +"/"+ R.raw.bubblessd );
vv.setVideoURI(url);
vv.start();
vv.requestFocus();
//*** BACKGROUND MOVIE, LOOPING AND SETTING LOCAL PATH ***
}
final EditText myet = (EditText) findViewById(R.id.editText1);
myet.resizeText();
}
and XML:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
style="#style/AppTheme"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fitsSystemWindows="true"
android:gravity="center"
android:soundEffectsEnabled="false"
tools:context=".MainActivity" >
<VideoView
android:id="#+id/videoView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true" />
<LinearLayout
android:id = "#+id/main_menu_wrapper"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical"
android:weightSum="100">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="20"
android:fitsSystemWindows="true"
android:gravity="center"
android:orientation="horizontal"
android:weightSum="100" >
<com.example.test.AutoResizeTextView
android:id="#+id/textView1"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="20"
android:background="#55000000"
android:text="TextView"
/>
<EditText
android:id="#+id/editText1"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="20"
android:background="#55000000"
android:inputType="text"
android:maxLength="10"
android:shadowColor="#000"
android:textColor="#FFFFFF"
android:textStyle="bold" >
</EditText>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
You are already using it correctly. By defining it in your XML, you tell the inflater to use that class.
If you want to access it in Java, treat it like any other view:
AutoResizeTextView textView = (AutoResizeTextView) findViewById(R.id.textView1);
textView.setText("Hello, user!");
It will behave just like any other text view, except it is supposed to automatically resize the text.