多くの場合、TextViewのフォントを指定された境界に自動調整する必要があります。
残念ながら、この問題について多くのスレッドや投稿(および推奨される解決策)があっても(例 ここ 、 ここ と ここ )では、どれも実際にはうまくいきません。
だからこそ、私は本当の取引が見つかるまでそれらのそれぞれをテストすることにしました。
私はそのようなtextViewからの要件は以下のようになるべきだと思います。
フォント、書体、スタイル、および文字セットの使用を許可する必要があります。
幅と高さの両方を処理する必要があります
制限のためにテキストが収まらない限り切り捨てはありません(例:長すぎるテキスト、小さすぎる利用可能なサイズ)。ただし、必要に応じて、水平/垂直スクロールバーを要求することもできます。その場合だけです。
複数行または単一行を許可する必要があります。複数行の場合は、最大行数と最小行数を許可します。
計算が遅くなるべきではありません。最適なサイズを見つけるためにループを使用しますか?少なくともそれを最適化し、毎回あなたのサンプリングを1ずつ増加させないでください。
複数行の場合、サイズ変更や他の行の使用を優先したり、 "\ n"文字を使用して自分自身で行を選択したりできます。
私はたくさんのサンプル(私が書いたリンクのサンプルを含む)を試してみました、そしてまた、私はそれらをケースを処理するために修正しようとしました、私は話しました、しかし、どれも本当にうまくいきません。
TextViewが正しく自動収まるかどうかを視覚的に確認できるサンプルプロジェクトを作成しました。
現在のところ、私のサンプルプロジェクトはテキスト(英語のアルファベットと数字)とtextViewのサイズをランダム化し、それを1行に留めていますが、これでも私が試したサンプルではうまくいきません。
ここにコードがあります(こちらも利用できます)。
res/layout/activity_main.xml
<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="match_parent" tools:context=".MainActivity">
<Button Android:id="@+id/button1" Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
Android:layout_alignParentBottom="true"
Android:layout_centerHorizontal="true" Android:text="Button" />
<FrameLayout Android:layout_width="match_parent"
Android:layout_height="wrap_content" Android:layout_above="@+id/button1"
Android:layout_alignParentLeft="true" Android:background="#ffff0000"
Android:layout_alignParentRight="true" Android:id="@+id/container"
Android:layout_alignParentTop="true" />
</RelativeLayout>
src/.../MainActivity.Java
public class MainActivity extends Activity
{
private final Random _random =new Random();
private static final String ALLOWED_CHARACTERS ="qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890";
@Override
protected void onCreate(final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ViewGroup container=(ViewGroup)findViewById(R.id.container);
findViewById(R.id.button1).setOnClickListener(new OnClickListener()
{
@Override
public void onClick(final View v)
{
container.removeAllViews();
final int maxWidth=container.getWidth();
final int maxHeight=container.getHeight();
final FontFitTextView fontFitTextView=new FontFitTextView(MainActivity.this);
final int width=_random.nextInt(maxWidth)+1;
final int height=_random.nextInt(maxHeight)+1;
fontFitTextView.setLayoutParams(new LayoutParams(width,height));
fontFitTextView.setSingleLine();
fontFitTextView.setBackgroundColor(0xff00ff00);
final String text=getRandomText();
fontFitTextView.setText(text);
container.addView(fontFitTextView);
Log.d("DEBUG","width:"+width+" height:"+height+" text:"+text);
}
});
}
private String getRandomText()
{
final int textLength=_random.nextInt(20)+1;
final StringBuilder builder=new StringBuilder();
for(int i=0;i<textLength;++i)
builder.append(ALLOWED_CHARACTERS.charAt(_random.nextInt(ALLOWED_CHARACTERS.length())));
return builder.toString();
}
}
誰もが実際にうまくいくこの共通の問題に対する解決策を知っていますか?
私が書いたものよりもはるかに少ない機能を持った解決策でさえ、例えば一定数のテキストの行を持ち、そのサイズに従ってそのフォントを調整するが、奇妙なグリッチを持たず、テキストを得ることもない。その利用可能なスペースと比較して大きい/小さい。
これはとても重要なTextViewなので、私は誰もが簡単にそれを使用して貢献できるようにライブラリを公開することにしました。 ここ .
MartinHの簡単な修正 ここ のおかげで、このコードはAndroid:drawableLeft
、Android:drawableRight
、Android:drawableTop
およびAndroid:drawableBottom
タグも処理します。
ここでの私の答えはあなたを幸せにするはずです Auto Scale TextViewテキストを範囲内に収める
テストケースを修正しました。
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ViewGroup container = (ViewGroup) findViewById(R.id.container);
findViewById(R.id.button1).setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
container.removeAllViews();
final int maxWidth = container.getWidth();
final int maxHeight = container.getHeight();
final AutoResizeTextView fontFitTextView = new AutoResizeTextView(MainActivity.this);
final int width = _random.nextInt(maxWidth) + 1;
final int height = _random.nextInt(maxHeight) + 1;
fontFitTextView.setLayoutParams(new FrameLayout.LayoutParams(
width, height));
int maxLines = _random.nextInt(4) + 1;
fontFitTextView.setMaxLines(maxLines);
fontFitTextView.setTextSize(500);// max size
fontFitTextView.enableSizeCache(false);
fontFitTextView.setBackgroundColor(0xff00ff00);
final String text = getRandomText();
fontFitTextView.setText(text);
container.addView(fontFitTextView);
Log.d("DEBUG", "width:" + width + " height:" + height
+ " text:" + text + " maxLines:" + maxLines);
}
});
}
私はAndroid開発者のリクエストごとにコードをここに投稿しています。
最終効果:
サンプルレイアウトファイル:
<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
Android:orientation="vertical"
Android:padding="16dp" >
<com.vj.widgets.AutoResizeTextView
Android:layout_width="match_parent"
Android:layout_height="100dp"
Android:ellipsize="none"
Android:maxLines="2"
Android:text="Auto Resized Text, max 2 lines"
Android:textSize="100sp" /> <!-- maximum size -->
<com.vj.widgets.AutoResizeTextView
Android:layout_width="match_parent"
Android:layout_height="100dp"
Android:ellipsize="none"
Android:gravity="center"
Android:maxLines="1"
Android:text="Auto Resized Text, max 1 line"
Android:textSize="100sp" /> <!-- maximum size -->
<com.vj.widgets.AutoResizeTextView
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:text="Auto Resized Text"
Android:textSize="500sp" /> <!-- maximum size -->
</LinearLayout>
そしてJavaコード:
import Android.annotation.TargetApi;
import Android.content.Context;
import Android.content.res.Resources;
import Android.graphics.RectF;
import Android.os.Build;
import Android.text.Layout.Alignment;
import Android.text.StaticLayout;
import Android.text.TextPaint;
import Android.util.AttributeSet;
import Android.util.SparseIntArray;
import Android.util.TypedValue;
import Android.widget.TextView;
public class AutoResizeTextView extends TextView {
private interface SizeTester {
/**
*
* @param suggestedSize
* Size of text to be tested
* @param availableSpace
* available space in which text must fit
* @return an integer < 0 if after applying {@code suggestedSize} to
* text, it takes less space than {@code availableSpace}, > 0
* otherwise
*/
public int onTestSize(int suggestedSize, RectF availableSpace);
}
private RectF mTextRect = new RectF();
private RectF mAvailableSpaceRect;
private SparseIntArray mTextCachedSizes;
private TextPaint mPaint;
private float mMaxTextSize;
private float mSpacingMult = 1.0f;
private float mSpacingAdd = 0.0f;
private float mMinTextSize = 20;
private int mWidthLimit;
private static final int NO_LINE_LIMIT = -1;
private int mMaxLines;
private boolean mEnableSizeCache = true;
private boolean mInitializedDimens;
public AutoResizeTextView(Context context) {
super(context);
initialize();
}
public AutoResizeTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
private void initialize() {
mPaint = new TextPaint(getPaint());
mMaxTextSize = getTextSize();
mAvailableSpaceRect = new RectF();
mTextCachedSizes = new SparseIntArray();
if (mMaxLines == 0) {
// no value was assigned during construction
mMaxLines = NO_LINE_LIMIT;
}
}
@Override
public void setTextSize(float size) {
mMaxTextSize = size;
mTextCachedSizes.clear();
adjustTextSize();
}
@Override
public void setMaxLines(int maxlines) {
super.setMaxLines(maxlines);
mMaxLines = maxlines;
adjustTextSize();
}
public int getMaxLines() {
return mMaxLines;
}
@Override
public void setSingleLine() {
super.setSingleLine();
mMaxLines = 1;
adjustTextSize();
}
@Override
public void setSingleLine(boolean singleLine) {
super.setSingleLine(singleLine);
if (singleLine) {
mMaxLines = 1;
} else {
mMaxLines = NO_LINE_LIMIT;
}
adjustTextSize();
}
@Override
public void setLines(int lines) {
super.setLines(lines);
mMaxLines = lines;
adjustTextSize();
}
@Override
public void setTextSize(int unit, float size) {
Context c = getContext();
Resources r;
if (c == null)
r = Resources.getSystem();
else
r = c.getResources();
mMaxTextSize = TypedValue.applyDimension(unit, size,
r.getDisplayMetrics());
mTextCachedSizes.clear();
adjustTextSize();
}
@Override
public void setLineSpacing(float add, float mult) {
super.setLineSpacing(add, mult);
mSpacingMult = mult;
mSpacingAdd = add;
}
/**
* Set the lower text size limit and invalidate the view
*
* @param minTextSize
*/
public void setMinTextSize(float minTextSize) {
mMinTextSize = minTextSize;
adjustTextSize();
}
private void adjustTextSize() {
if (!mInitializedDimens) {
return;
}
int startSize = (int) mMinTextSize;
int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom()
- getCompoundPaddingTop();
mWidthLimit = getMeasuredWidth() - getCompoundPaddingLeft()
- getCompoundPaddingRight();
mAvailableSpaceRect.right = mWidthLimit;
mAvailableSpaceRect.bottom = heightLimit;
super.setTextSize(
TypedValue.COMPLEX_UNIT_PX,
efficientTextSizeSearch(startSize, (int) mMaxTextSize,
mSizeTester, mAvailableSpaceRect));
}
private final SizeTester mSizeTester = new SizeTester() {
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public int onTestSize(int suggestedSize, RectF availableSPace) {
mPaint.setTextSize(suggestedSize);
String text = getText().toString();
boolean singleline = getMaxLines() == 1;
if (singleline) {
mTextRect.bottom = mPaint.getFontSpacing();
mTextRect.right = mPaint.measureText(text);
} else {
StaticLayout layout = new StaticLayout(text, mPaint,
mWidthLimit, Alignment.ALIGN_NORMAL, mSpacingMult,
mSpacingAdd, true);
// Return early if we have more lines
if (getMaxLines() != NO_LINE_LIMIT
&& layout.getLineCount() > getMaxLines()) {
return 1;
}
mTextRect.bottom = layout.getHeight();
int maxWidth = -1;
for (int i = 0; i < layout.getLineCount(); i++) {
if (maxWidth < layout.getLineWidth(i)) {
maxWidth = (int) layout.getLineWidth(i);
}
}
mTextRect.right = maxWidth;
}
mTextRect.offsetTo(0, 0);
if (availableSPace.contains(mTextRect)) {
// May be too small, don't worry we will find the best match
return -1;
} else {
// too big
return 1;
}
}
};
/**
* Enables or disables size caching, enabling it will improve performance
* where you are animating a value inside TextView. This stores the font
* size against getText().length() Be careful though while enabling it as 0
* takes more space than 1 on some fonts and so on.
*
* @param enable
* Enable font size caching
*/
public void enableSizeCache(boolean enable) {
mEnableSizeCache = enable;
mTextCachedSizes.clear();
adjustTextSize(getText().toString());
}
private int efficientTextSizeSearch(int start, int end,
SizeTester sizeTester, RectF availableSpace) {
if (!mEnableSizeCache) {
return binarySearch(start, end, sizeTester, availableSpace);
}
int key = getText().toString().length();
int size = mTextCachedSizes.get(key);
if (size != 0) {
return size;
}
size = binarySearch(start, end, sizeTester, availableSpace);
mTextCachedSizes.put(key, size);
return size;
}
private static int binarySearch(int start, int end, SizeTester sizeTester,
RectF availableSpace) {
int lastBest = start;
int lo = start;
int hi = end - 1;
int mid = 0;
while (lo <= hi) {
mid = (lo + hi) >>> 1;
int midValCmp = sizeTester.onTestSize(mid, availableSpace);
if (midValCmp < 0) {
lastBest = lo;
lo = mid + 1;
} else if (midValCmp > 0) {
hi = mid - 1;
lastBest = hi;
} else {
return mid;
}
}
// Make sure to return the last best.
// This is what should always be returned.
return lastBest;
}
@Override
protected void onTextChanged(final CharSequence text, final int start,
final int before, final int after) {
super.onTextChanged(text, start, before, after);
adjustTextSize();
}
@Override
protected void onSizeChanged(int width, int height, int oldwidth,
int oldheight) {
mInitializedDimens = true;
mTextCachedSizes.clear();
super.onSizeChanged(width, height, oldwidth, oldheight);
if (width != oldwidth || height != oldheight) {
adjustTextSize();
}
}
}
警告:
両側の複合ドロアブルを考慮に入れるようにM-WaJeEhの答えを少し修正しました。
getCompoundPaddingXXXX()
メソッドはpadding of the view + drawable space
を返します。たとえば、参照してください。 TextView.getCompoundPaddingLeft()
問題:これにより、テキストに使用可能なTextViewスペースの幅と高さの測定が修正されます。描画可能サイズを考慮に入れないと、それは無視され、テキストは描画可能サイズと重なってしまいます。
更新されたセグメントadjustTextSize(String)
:
private void adjustTextSize(final String text) {
if (!mInitialized) {
return;
}
int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom() - getCompoundPaddingTop();
mWidthLimit = getMeasuredWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight();
mAvailableSpaceRect.right = mWidthLimit;
mAvailableSpaceRect.bottom = heightLimit;
int maxTextSplits = text.split(" ").length;
AutoResizeTextView.super.setMaxLines(Math.min(maxTextSplits, mMaxLines));
super.setTextSize(
TypedValue.COMPLEX_UNIT_PX,
binarySearch((int) mMinTextSize, (int) mMaxTextSize,
mSizeTester, mAvailableSpaceRect));
}
わかりました先週は、コードを正確にテストに合わせて大幅に書き換えました。これで1:1をコピーすることができ、すぐに動作します - setSingleLine()
を含みます。極端な値を使用する場合は、MIN_TEXT_SIZE
とMAX_TEXT_SIZE
を調整することを忘れないでください。
収束アルゴリズムは次のようになります。
for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {
// Go to the mean value...
testSize = (upperTextSize + lowerTextSize) / 2;
// ... inflate the dummy TextView by setting a scaled textSize and the text...
mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
mTestView.setText(text);
// ... call measure to find the current values that the text WANTS to occupy
mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int tempHeight = mTestView.getMeasuredHeight();
// ... decide whether those values are appropriate.
if (tempHeight >= targetFieldHeight) {
upperTextSize = testSize; // Font is too big, decrease upperSize
}
else {
lowerTextSize = testSize; // Font is too small, increase lowerSize
}
}
そして、クラス全体がここにあります 。
結果は現在非常に柔軟です。これは、xmlで宣言されているものと同じように機能します。
<com.example.myProject.AutoFitText
Android:id="@+id/textView"
Android:layout_width="match_parent"
Android:layout_height="0dp"
Android:layout_weight="4"
Android:text="@string/LoremIpsum" />
...テストのようにプログラム的に構築されています。
私はあなたが今これを使えるようになることを本当に望みます。ちなみに今setText(CharSequence text)
を呼び出して使用することができます。このクラスは、非常にまれな例外を処理し、しっかりしている必要があります。このアルゴリズムでnotがサポートされているのは、これだけです。
setMaxLines(x)
への呼び出しx >= 2
しかし、私はあなたが望むならこれを構築するのを助けるために広範囲のコメントを加えました!
ご注意ください:
これを1行に限定せずに通常どおりに使用するだけであれば、前述したようにWord-breakingが発生する可能性があります。これはAndroidの機能、notAutoFitText
のせいです。 AndroidはTextViewには長すぎる言葉を常に壊してしまうので、実際には非常に便利です。あなたがここに介入したいのであれば、203行目以降の私のコメントとコードを見てください。私はすでに十分な分割と認識をあなたに代わって書いています。 。
結論として、スペース文字もサポートするようにテストを書き直すことを強くお勧めします。
final Random _random = new Random();
final String ALLOWED_CHARACTERS = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890";
final int textLength = _random.nextInt(80) + 20;
final StringBuilder builder = new StringBuilder();
for (int i = 0; i < textLength; ++i) {
if (i % 7 == 0 && i != 0) {
builder.append(" ");
}
builder.append(ALLOWED_CHARACTERS.charAt(_random.nextInt(ALLOWED_CHARACTERS.length())));
}
((AutoFitText) findViewById(R.id.textViewMessage)).setText(builder.toString());
これは非常に肌触りの良い(そしてより現実的な)結果を生み出すでしょう。
あなたもこの件に関してあなたを始めさせるためのコメントを見つけるでしょう。
頑張ってよろしく
私の要求は
私はリンクを参照しました: Auto Scale TextViewテキストを範囲内に収める (コメントを含む)そして DialogTitle.Java
私は、提供された解決策が素晴らしく単純であることを見つけました、しかしそれは動的にテキストボックスのサイズを変えません。それは素晴らしいときに リストビューから選択されたテキストの長さは、ウィンドウ内の既存のテキストの長さより大きい ScalableTextView
。 ScalableTextView
内の既存のテキストよりも短い長さのテキストを選択した場合、テキストのサイズは大きくならず、テキストは小さいサイズで表示されます。
ScalableTextView.Javaを修正して、テキストの長さに基づいてテキストサイズを再調整しました。これが私のScalableTextView.Java
です
public class ScalableTextView extends TextView
{
float defaultTextSize = 0.0f;
public ScalableTextView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
setSingleLine();
setEllipsize(TruncateAt.END);
defaultTextSize = getTextSize();
}
public ScalableTextView(Context context, AttributeSet attrs)
{
super(context, attrs);
setSingleLine();
setEllipsize(TruncateAt.END);
defaultTextSize = getTextSize();
}
public ScalableTextView(Context context)
{
super(context);
setSingleLine();
setEllipsize(TruncateAt.END);
defaultTextSize = getTextSize();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
setTextSize(TypedValue.COMPLEX_UNIT_PX, defaultTextSize);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final Layout layout = getLayout();
if (layout != null)
{
final int lineCount = layout.getLineCount();
if (lineCount > 0)
{
int ellipsisCount = layout.getEllipsisCount(lineCount - 1);
while (ellipsisCount > 0)
{
final float textSize = getTextSize();
// textSize is already expressed in pixels
setTextSize(TypedValue.COMPLEX_UNIT_PX, (textSize - 1));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
ellipsisCount = layout.getEllipsisCount(lineCount - 1);
}
}
}
}
}
ハッピーコーディング....
警告、 Android 3 (ハニカム)および Android 4.0 (アイスクリームサンドイッチ)のバグ
Androidのバージョン:3.1 - 4.04にはバグがあり、TextViewの中のsetTextSize()は最初の時だけ動作します(最初の呼び出し)。
バグは/で説明されています 問題22493:アンドロイド4.0のTextViewの高さのバグおよび/問題17343:ボタンの高さとテキストが元の状態に戻らないHoneyCombのテキストサイズを増減した後。
これを回避するには、サイズを変更する前に、TextViewに割り当てられているテキストに改行文字を追加します。
final String DOUBLE_BYTE_SPACE = "\u3000";
textView.append(DOUBLE_BYTE_SPACE);
私は以下のように私のコードでそれを使用します。
final String DOUBLE_BYTE_SPACE = "\u3000";
AutoResizeTextView textView = (AutoResizeTextView) view.findViewById(R.id.aTextView);
String fixString = "";
if (Android.os.Build.VERSION.SDK_INT >= Android.os.Build.VERSION_CODES.HONEYCOMB_MR1
&& Android.os.Build.VERSION.SDK_INT <= Android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
fixString = DOUBLE_BYTE_SPACE;
}
textView.setText(fixString + "The text" + fixString);
中央に配置するために、この "\ u3000"文字をテキストの左右に追加します。左に揃えて配置している場合は、右にのみ追加してください。もちろん、AutoResizeTextViewウィジェットに埋め込むこともできますが、修正コードを外部に置いておきたいと思いました。
この問題に対する公式の解決策があります。 Android Oで導入された自動サイズ調整TextViewはサポートライブラリ26で利用可能で、Android 4.0までずっと後方互換性があります。
https://developer.Android.com/preview/features/autosizing-textview.html
私はなぜこの情報も含んでいる https://stackoverflow.com/a/42940171/47680 が管理者によって削除された理由がわかりません。
私はこの属性がどのように動作するかを段階的に説明します。
1 - プロジェクト許可ファイルにAndroidサポートライブラリ26.x.xをインポートします。 IDEにサポートライブラリがない場合は、自動的にダウンロードされます。
dependencies {
compile 'com.Android.support:support-v4:26.1.0'
compile 'com.Android.support:appcompat-v7:26.1.0'
compile 'com.Android.support:support-v13:26.1.0' }
allprojects {
repositories {
jcenter()
maven {
url "https://maven.google.com"
}
} }
2-あなたのレイアウトXMLファイルを開き、あなたのTextViewにこのタグを付けるようにリファクタリングしてください。このシナリオは、次のとおりです。システムでフォントサイズを増やした場合、テキストを折り返しではなく、適切な幅に合わせます。
<Android.support.v7.widget.AppCompatTextView
Android:id="@+id/textViewAutoSize"
Android:layout_width="match_parent"
Android:layout_height="25dp"
Android:ellipsize="none"
Android:text="Auto size text with compatible lower Android versions."
Android:textSize="12sp"
app:autoSizeMaxTextSize="14sp"
app:autoSizeMinTextSize="4sp"
app:autoSizeStepGranularity="0.5sp"
app:autoSizeTextType="uniform" />
2018年6月からAndroidは公式にサポート Android 4.0(APIレベル14)以降でこの機能 をサポートします。
Android 8.0(APIレベル26)以降:
setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize,
int autoSizeStepGranularity, int unit);
Android 8.0より前のAndroidバージョン(APIレベル26):
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(TextView textView,
int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)
私の詳細 の答え をチェックしてください。
テキストビューを画像に変換し、画像を境界内で拡大縮小します。
ビューを画像に変換する方法の例を次に示します。ビューをAndroidに表示せずにビットマップに変換しますか?
問題は、あなたのテキストは選択できないということですが、うまくいくはずです。試したことがないので、どのように見えるかわからない(スケーリングのため)。
LayoutParams
にMaxWidth
とMaxHeight
とTextView
を追加してみてください。レイアウトが親コンテナを尊重し、オーバーフローしないように強制します。
textview.setLayoutParams(new LayoutParams(LinearLayout.MATCH_PARENT,LinearLayout.WRAP_CONTENT));
int GeneralApproxWidthOfContainer = 400;
int GeneralApproxHeightOfContainer = 600;
textview.setMaxWidth(400);
textview.setMaxHeight(600);`
@Malachiaszによって説明されている問題に対するクイックフィックス
自動サイズ変更クラスにこれのカスタムサポートを追加することで問題を解決しました。
public void setTextCompat(final CharSequence text) {
setTextCompat(text, BufferType.NORMAL);
}
public void setTextCompat(final CharSequence text, BufferType type) {
// Quick fix for Android Honeycomb and Ice cream sandwich which sets the text only on the first call
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 &&
Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
super.setText(DOUBLE_BYTE_WORDJOINER + text + DOUBLE_BYTE_WORDJOINER, type);
} else {
super.setText(text, type);
}
}
@Override
public CharSequence getText() {
String originalText = super.getText().toString();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 &&
Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
// We try to remove the Word joiners we added using compat method - if none found - this will do nothing.
return originalText.replaceAll(DOUBLE_BYTE_WORDJOINER, "");
} else {
return originalText;
}
}
yourView.setTextCompat(newTextValue)
の代わりにyourView.setText(newTextValue)
を呼び出すだけです。
Android O以降、xmlのテキストを自動サイズ変更することが可能です。
https://developer.Android.com/preview/features/autosizing-textview.html
<TextView
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
app:autoSizeTextType="uniform"
app:autoSizeMinTextSize="12sp"
app:autoSizeMaxTextSize="100sp"
app:autoSizeStepGranularity="2sp"
/>
Android Oを使用すると、TextViewの特性と境界に基づいて、テキストサイズを自動的に拡大または縮小してレイアウトに合わせることができます。この設定により、動的コンテンツを含むさまざまな画面でテキストサイズを最適化するのが簡単になります。
Support Library 26.0 Betaは、Android Oより前のAndroidバージョンを実行しているデバイスでの自動サイズ調整TextView機能を完全にサポートしています。このライブラリーは、Android 4.0(APIレベル14)以降をサポートしています。 Android.support.v4.widgetパッケージには、下位互換性のある方法で機能にアクセスするためのTextViewCompatクラスが含まれています。
以下は、カスタムフォントの機能を追加したavalancha TextViewです。
使用法:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:foo="http://schemas.Android.com/apk/res-auto"
Android:layout_width="wrap_content"
Android:layout_height="match_parent" >
<de.meinprospekt.androidhd.view.AutoFitText
Android:layout_width="wrap_content"
Android:layout_height="10dp"
Android:text="Small Text"
Android:textColor="#FFFFFF"
Android:textSize="100sp"
foo:customFont="fonts/Roboto-Light.ttf" />
</FrameLayout>
追加を忘れないでください:xmlns:foo = "http://schemas.Android.com/apk/res-auto"。フォントは資産内にある必要があります
import Java.util.ArrayList;
import Java.util.List;
import Android.annotation.SuppressLint;
import Android.content.Context;
import Android.content.res.TypedArray;
import Android.graphics.Paint;
import Android.graphics.Typeface;
import Android.os.Build;
import Android.util.AttributeSet;
import Android.util.Log;
import Android.util.TypedValue;
import Android.view.View;
import Android.view.ViewGroup.LayoutParams;
import Android.view.ViewTreeObserver;
import Android.view.ViewTreeObserver.OnGlobalLayoutListener;
import Android.widget.TextView;
import de.meinprospekt.androidhd.R;
import de.meinprospekt.androidhd.adapter.BrochuresHorizontalAdapter;
import de.meinprospekt.androidhd.util.LOG;
/**
* https://stackoverflow.com/a/16174468/2075875 This class builds a new Android Widget named AutoFitText which can be used instead of a TextView to
* have the text font size in it automatically fit to match the screen width. Credits go largely to Dunni, gjpc, gregm and speedplane from
* Stackoverflow, method has been (style-) optimized and rewritten to match Android coding standards and our MBC. This version upgrades the original
* "AutoFitTextView" to now also be adaptable to height and to accept the different TextView types (Button, TextClock etc.)
*
* @author pheuschk
* @createDate: 18.04.2013
*
* combined with: https://stackoverflow.com/a/7197867/2075875
*/
@SuppressWarnings("unused")
public class AutoFitText extends TextView {
private static final String TAG = AutoFitText.class.getSimpleName();
/** Global min and max for text size. Remember: values are in pixels! */
private final int MIN_TEXT_SIZE = 10;
private final int MAX_TEXT_SIZE = 400;
/** Flag for singleLine */
private boolean mSingleLine = false;
/**
* A dummy {@link TextView} to test the text size without actually showing anything to the user
*/
private TextView mTestView;
/**
* A dummy {@link Paint} to test the text size without actually showing anything to the user
*/
private Paint mTestPaint;
/**
* Scaling factor for fonts. It's a method of calculating independently (!) from the actual density of the screen that is used so users have the
* same experience on different devices. We will use DisplayMetrics in the Constructor to get the value of the factor and then calculate SP from
* pixel values
*/
private float mScaledDensityFactor;
/**
* Defines how close we want to be to the factual size of the Text-field. Lower values mean higher precision but also exponentially higher
* computing cost (more loop runs)
*/
private final float mThreshold = 0.5f;
/**
* Constructor for call without attributes --> invoke constructor with AttributeSet null
*
* @param context
*/
public AutoFitText(Context context) {
this(context, null);
}
public AutoFitText(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public AutoFitText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
//TextViewPlus part https://stackoverflow.com/a/7197867/2075875
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoFitText);
String customFont = a.getString(R.styleable.AutoFitText_customFont);
setCustomFont(context, customFont);
a.recycle();
// AutoFitText part
mScaledDensityFactor = context.getResources().getDisplayMetrics().scaledDensity;
mTestView = new TextView(context);
mTestPaint = new Paint();
mTestPaint.set(this.getPaint());
this.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// make an initial call to onSizeChanged to make sure that refitText is triggered
onSizeChanged(AutoFitText.this.getWidth(), AutoFitText.this.getHeight(), 0, 0);
// Remove the LayoutListener immediately so we don't run into an infinite loop
//AutoFitText.this.getViewTreeObserver().removeOnGlobalLayoutListener(this);
removeOnGlobalLayoutListener(AutoFitText.this, this);
}
});
}
public boolean setCustomFont(Context ctx, String asset) {
Typeface tf = null;
try {
tf = Typeface.createFromAsset(ctx.getAssets(), asset);
} catch (Exception e) {
LOG.e(TAG, "Could not get typeface: "+e.getMessage());
return false;
}
setTypeface(tf);
return true;
}
@SuppressLint("NewApi")
public static void removeOnGlobalLayoutListener(View v, ViewTreeObserver.OnGlobalLayoutListener listener){
if (Build.VERSION.SDK_INT < 16) {
v.getViewTreeObserver().removeGlobalOnLayoutListener(listener);
} else {
v.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
}
}
/**
* Main method of this widget. Resizes the font so the specified text fits in the text box assuming the text box has the specified width. This is
* done via a dummy text view that is refit until it matches the real target width and height up to a certain threshold factor
*
* @param targetFieldWidth The width that the TextView currently has and wants filled
* @param targetFieldHeight The width that the TextView currently has and wants filled
*/
private void refitText(String text, int targetFieldWidth, int targetFieldHeight) {
// Variables need to be visible outside the loops for later use. Remember size is in pixels
float lowerTextSize = MIN_TEXT_SIZE;
float upperTextSize = MAX_TEXT_SIZE;
// Force the text to wrap. In principle this is not necessary since the dummy TextView
// already does this for us but in rare cases adding this line can prevent flickering
this.setMaxWidth(targetFieldWidth);
// Padding should not be an issue since we never define it programmatically in this app
// but just to to be sure we cut it off here
targetFieldWidth = targetFieldWidth - this.getPaddingLeft() - this.getPaddingRight();
targetFieldHeight = targetFieldHeight - this.getPaddingTop() - this.getPaddingBottom();
// Initialize the dummy with some params (that are largely ignored anyway, but this is
// mandatory to not get a NullPointerException)
mTestView.setLayoutParams(new LayoutParams(targetFieldWidth, targetFieldHeight));
// maxWidth is crucial! Otherwise the text would never line wrap but blow up the width
mTestView.setMaxWidth(targetFieldWidth);
if (mSingleLine) {
// the user requested a single line. This is very easy to do since we primarily need to
// respect the width, don't have to break, don't have to measure...
/*************************** Converging algorithm 1 ***********************************/
for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {
// Go to the mean value...
testSize = (upperTextSize + lowerTextSize) / 2;
mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
mTestView.setText(text);
mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
if (mTestView.getMeasuredWidth() >= targetFieldWidth) {
upperTextSize = testSize; // Font is too big, decrease upperSize
} else {
lowerTextSize = testSize; // Font is too small, increase lowerSize
}
}
/**************************************************************************************/
// In rare cases with very little letters and width > height we have vertical overlap!
mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
if (mTestView.getMeasuredHeight() > targetFieldHeight) {
upperTextSize = lowerTextSize;
lowerTextSize = MIN_TEXT_SIZE;
/*************************** Converging algorithm 1.5 *****************************/
for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {
// Go to the mean value...
testSize = (upperTextSize + lowerTextSize) / 2;
mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
mTestView.setText(text);
mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
if (mTestView.getMeasuredHeight() >= targetFieldHeight) {
upperTextSize = testSize; // Font is too big, decrease upperSize
} else {
lowerTextSize = testSize; // Font is too small, increase lowerSize
}
}
/**********************************************************************************/
}
} else {
/*********************** Converging algorithm 2 ***************************************/
// Upper and lower size converge over time. As soon as they're close enough the loop
// stops
// TODO probe the algorithm for cost (ATM possibly O(n^2)) and optimize if possible
for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {
// Go to the mean value...
testSize = (upperTextSize + lowerTextSize) / 2;
// ... inflate the dummy TextView by setting a scaled textSize and the text...
mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
mTestView.setText(text);
// ... call measure to find the current values that the text WANTS to occupy
mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int tempHeight = mTestView.getMeasuredHeight();
// int tempWidth = mTestView.getMeasuredWidth();
// LOG.debug("Measured: " + tempWidth + "x" + tempHeight);
// LOG.debug("TextSize: " + testSize / mScaledDensityFactor);
// ... decide whether those values are appropriate.
if (tempHeight >= targetFieldHeight) {
upperTextSize = testSize; // Font is too big, decrease upperSize
} else {
lowerTextSize = testSize; // Font is too small, increase lowerSize
}
}
/**************************************************************************************/
// It is possible that a single Word is wider than the box. The Android system would
// wrap this for us. But if you want to decide fo yourself where exactly to break or to
// add a hyphen or something than you're going to want to implement something like this:
mTestPaint.setTextSize(lowerTextSize);
List<String> words = new ArrayList<String>();
for (String s : text.split(" ")) {
Log.i("tag", "Word: " + s);
words.add(s);
}
for (String Word : words) {
if (mTestPaint.measureText(Word) >= targetFieldWidth) {
List<String> pieces = new ArrayList<String>();
// pieces = breakWord(Word, mTestPaint.measureText(Word), targetFieldWidth);
// Add code to handle the pieces here...
}
}
}
/**
* We are now at most the value of threshold away from the actual size. To rather undershoot than overshoot use the lower value. To match
* different screens convert to SP first. See {@link http://developer.Android.com/guide/topics/resources/more-resources.html#Dimension} for
* more details
*/
this.setTextSize(TypedValue.COMPLEX_UNIT_SP, lowerTextSize / mScaledDensityFactor);
return;
}
/**
* This method receives a call upon a change in text content of the TextView. Unfortunately it is also called - among others - upon text size
* change which means that we MUST NEVER CALL {@link #refitText(String)} from this method! Doing so would result in an endless loop that would
* ultimately result in a stack overflow and termination of the application
*
* So for the time being this method does absolutely nothing. If you want to notify the view of a changed text call {@link #setText(CharSequence)}
*/
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
// Super implementation is also intentionally empty so for now we do absolutely nothing here
super.onTextChanged(text, start, lengthBefore, lengthAfter);
}
@Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
if (width != oldWidth && height != oldHeight) {
refitText(this.getText().toString(), width, height);
}
}
/**
* This method is guaranteed to be called by {@link TextView#setText(CharSequence)} immediately. Therefore we can safely add our modifications
* here and then have the parent class resume its work. So if text has changed you should always call {@link TextView#setText(CharSequence)} or
* {@link TextView#setText(CharSequence, BufferType)} if you know whether the {@link BufferType} is normal, editable or spannable. Note: the
* method will default to {@link BufferType#NORMAL} if you don't pass an argument.
*/
@Override
public void setText(CharSequence text, BufferType type) {
int targetFieldWidth = this.getWidth();
int targetFieldHeight = this.getHeight();
if (targetFieldWidth <= 0 || targetFieldHeight <= 0 || text.equals("")) {
// Log.v("tag", "Some values are empty, AutoFitText was not able to construct properly");
} else {
refitText(text.toString(), targetFieldWidth, targetFieldHeight);
}
super.setText(text, type);
}
/**
* TODO add sensibility for {@link #setMaxLines(int)} invocations
*/
@Override
public void setMaxLines(int maxLines) {
// TODO Implement support for this. This could be relatively easy. The idea would probably
// be to manipulate the targetHeight in the refitText-method and then have the algorithm do
// its job business as usual. Nonetheless, remember the height will have to be lowered
// dynamically as the font size shrinks so it won't be a walk in the park still
if (maxLines == 1) {
this.setSingleLine(true);
} else {
throw new UnsupportedOperationException("MaxLines != 1 are not implemented in AutoFitText yet, use TextView instead");
}
}
@Override
public void setSingleLine(boolean singleLine) {
// save the requested value in an instance variable to be able to decide later
mSingleLine = singleLine;
super.setSingleLine(singleLine);
}
}
既知のバグ:Android 4.03では動作しません - フォントが見えないか非常に小さい(オリジナルのavalanchaも動作しません)以下はそのバグに対する回避策です: https://stackoverflow.com/ a/21851239/2075875
Android公式 TextViewの自動化 を試した後、AndroidバージョンがAndroid 8.0(APIレベル26)より前かどうかがわかりました。 Android.support.v7.widget.AppCompatTextView
を使用する必要があり、サポートライブラリのバージョンが26.0.0以上であることを確認してください。例:
<Android.support.v7.widget.AppCompatTextView
Android:layout_width="130dp"
Android:layout_height="32dp"
Android:maxLines="1"
app:autoSizeMaxTextSize="22sp"
app:autoSizeMinTextSize="12sp"
app:autoSizeStepGranularity="2sp"
app:autoSizeTextType="uniform" />
update:
@ Android-developerの返信によると、AppCompatActivity
ソースコードを確認し、onCreate
にこれらの2行が見つかりました
final AppCompatDelegate delegate = getDelegate(); delegate.installViewFactory();
およびAppCompatDelegateImpl
のcreateView
if (mAppCompatViewInflater == null) {
mAppCompatViewInflater = new AppCompatViewInflater();
}
AppCompatViewInflater
インフレータビューを使用し、AppCompatViewInflater
createViewの場合、「TextView」にAppCompatTextViewを使用します。
public final View createView(){
...
View view = null;
switch (name) {
case "TextView":
view = new AppCompatTextView(context, attrs);
break;
case "ImageView":
view = new AppCompatImageView(context, attrs);
break;
case "Button":
view = new AppCompatButton(context, attrs);
break;
...
}
私のプロジェクトではAppCompatActivity
を使用しないため、xmlで<Android.support.v7.widget.AppCompatTextView>
を使用する必要があります。
これを試して
TextWatcher changeText = new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
tv3.setText(et.getText().toString());
tv3.post(new Runnable() {
@Override
public void run() {
while(tv3.getLineCount() >= 3){
tv3.setTextSize((tv3.getTextSize())-1);
}
}
});
}
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override public void afterTextChanged(Editable s) { }
};
もっと簡単なものを探しているなら:
public MyTextView extends TextView{
public void resize(String text, float textViewWidth, float textViewHeight) {
Paint p = new Paint();
Rect bounds = new Rect();
p.setTextSize(1);
p.getTextBounds(text, 0, text.length(), bounds);
float widthDifference = (textViewWidth)/bounds.width();
float heightDifference = (textViewHeight);
textSize = Math.min(widthDifference, heightDifference);
setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
}