Я пытаюсь добиться такого результата:
Сейчас у меня есть ConstraintLayout, который включает три Views: TextView, ImageView, ImageView.
Требования к дизайну:
TextView;TextView имеет фиксированный размер 80dpImageViews должен менять положение по горизонтали в зависимости от положения текста внутри TextView. См. Изображения с делом №1 / №2 для справки.Можно ли достичь пункта 4 в XML? А программно?
[Дополнительная информация]
Автоматическое изменение размера не работает должным образом с шириной wrap_content, см. документация по автоподбору:
Note: If you set autosizing in an XML file, it is not recommended to use the value "wrap_content" for the
layout_widthorlayout_heightattributes of a TextView. It may produce unexpected results.
Мое решение работает хорошо, но я не уверен в эффективности. И я хочу получить больше взглядов и мнений по этому поводу. У нас есть такая простая штука, как layout_constraintBaseline_toBaselineOf, для выравнивания видов по горизонтали. А как насчет вертикального? Может, в будущем добавят :) Как лучше это сделать? @Cheticamp
Итак, после расследования было найдено два решения для этого:
ImageView;AutoResizeTextView;В первый случай мы получаем фактический width текста в TextView:
Rect bounds = new Rect();
textView.getPaint().getTextBounds(textView.getText().toString(), 0, textView.getText().length(), bounds);
float width = bounds.width();
А затем примените простую математику, чтобы вычислить horizontal bias для ImageView.
Второй вариант - добавить настраиваемый вид. Он отличается от autosizing-textview. Размер текста изменится после измерения в onLayout. А в onTextChanged мы сбрасываем размер текста без изменения размера. Эллипсирование - это дополнительная функция, которая может быть полезна в некоторых случаях.
/**
* 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 android.support.v7.widget.AppCompatTextView {
// 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(AutoResizeTextView.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;
}
if (getTransformationMethod() != null) {
text = getTransformationMethod().getTransformation(text, this);
}
// 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
// modified: use a copy of TextPaint for measuring
TextPaint paint = new TextPaint(textPaint);
// Draw using a static layout
StaticLayout layout = new StaticLayout(text, paint, width, Layout.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
setTextSize(TypedValue.COMPLEX_UNIT_PX, 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) {
// 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, Layout.Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
return layout.getHeight();
}
}
Источник кода: https://gist.github.com/chathudan/61fd2f6e5919738d7821
Играя с некоторыми заданными значениями размеров, которые вы можете настроить в соответствии с вашим приложением, я добился этого, используя только xml.
Изображения глухие, я вырезал их из оригинального изображения. Я использовал компоновку ограничений и относительную компоновку. Текст реагирует на изменение размера и поворот.
Вертикальный вид
Горизонтальный вид
Мелкий текст
Большой текст
Конечно, поиграйте с размерами, чтобы они соответствовали вашим потребностям.
<?xml version = "1.0" encoding = "utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android = "http://schemas.android.com/apk/res/android"
xmlns:app = "http://schemas.android.com/apk/res-auto"
xmlns:tools = "http://schemas.android.com/tools"
android:layout_width = "match_parent"
android:layout_height = "match_parent"
app:layout_behavior = "@string/appbar_scrolling_view_behavior"
tools:context = "com.yvettecolomb.myapplication.MainActivity"
tools:showIn = "@layout/activity_main">
<RelativeLayout
xmlns:android = "http://schemas.android.com/apk/res/android"
android:id = "@+id/ll1"
android:layout_width = "match_parent"
android:layout_height = "wrap_content"
android:layout_centerHorizontal = "true"
android:layout_marginLeft = "0dp"
android:layout_marginRight = "0dp"
android:layout_marginTop = "10dp"
android:padding = "0dp"
app:layout_constraintLeft_toLeftOf = "parent"
app:layout_constraintRight_toRightOf = "parent">
<TextView
android:id = "@+id/tv1"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:layout_centerHorizontal = "true"
android:layout_marginTop = "10dp"
android:autoSizeTextType = "uniform"
android:gravity = "center"
android:padding = "2dp"
android:text = "TEST"/>
<ImageView
android:id = "@+id/image1"
android:layout_width = "wrap_content"
android:layout_height = "30dp"
android:layout_margin = "-10dp"
android:layout_toLeftOf = "@+id/tv1"
android:src = "@drawable/a"/>
<ImageView
android:id = "@+id/image2"
android:layout_width = "wrap_content"
android:layout_height = "30dp"
android:layout_toRightOf = "@+id/tv1"
android:src = "@drawable/b"/>
<ImageView
android:id = "@+id/image3"
android:layout_width = "wrap_content"
android:layout_height = "30dp"
android:layout_below = "@+id/tv1"
android:layout_margin = "-10dp"
android:layout_toLeftOf = "@+id/tv2"
android:src = "@drawable/c"/>
<TextView
android:id = "@+id/tv2"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:layout_below = "@+id/tv1"
android:layout_centerHorizontal = "true"
android:layout_marginTop = "10dp"
android:autoSizeTextType = "uniform"
android:gravity = "center"
android:padding = "2dp"
android:text = "test test test test test"/>
<ImageView
android:id = "@+id/image4"
android:layout_width = "wrap_content"
android:layout_height = "30dp"
android:layout_below = "@+id/tv1"
android:layout_toRightOf = "@+id/tv2"
android:src = "@drawable/d"/>
</RelativeLayout>
</android.support.constraint.ConstraintLayout>
Используйте AppCompatTextView. Измените значения autoSizeMaxTextSize, autoSizeMinTextSize и autoSizeStepGranularity.
<RelativeLayout
xmlns:app = "http://schemas.android.com/apk/res-auto"
android:id = "@+id/rl_flashcard_back"
android:layout_width = "match_parent"
android:layout_height = "match_parent"
android:orientation = "vertical">
<android.support.v7.widget.AppCompatTextView
android:id = "@+id/tv_text"
android:layout_width = "match_parent"
android:layout_height = "match_parent"
android:layout_centerInParent = "true"
android:gravity = "center"
app:autoSizeMaxTextSize = "16sp"
app:autoSizeMinTextSize = "8sp"
app:autoSizeStepGranularity = "1sp"
app:autoSizeTextType = "uniform"
/>
android.support.v7.widget.AppCompatTextView tv_text = mRootView.findViewById(R.id.tv_text);
https://developer.android.com/guide/topics/ui/look-and-feel/autosizing-textview
Вы ответили на свой вопрос, и это здорово. Похоже, вы применили награду после ответа на свой вопрос. Если да, то как насчет вашего ответа вас не устраивает?