상세 컨텐츠

본문 제목

안드로이드 android 텍스트 길이 영역 크기 관련

스마트기기개발관련/안드로이드 개발

by AlrepondTech 2016. 6. 1. 11:48

본문

반응형
728x170


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



출처: http://m.blog.naver.com/gh2501/132705119



**

     * 화면 크기에 따른 적당한 가로크기의 제목 설정

     * @param title

     * @param rowTitle

     * @return

     */

    private String getTitleFittedWidth(String title, TextView rowTitle) {

        DisplayMetrics displayMatrics = new DisplayMetrics();

        int width = getWindowManager().getDefaultDisplay().getWidth();

        // 기기 해상도에 맞는 텍스트 영역만의 크기

        float txtWidth = width - 다른 위젯들의 가로길이(pix) 를 합친 값;

        String adjustText = title;


        for (int i = 0; i < title.length(); i++) {

            rowTitle.setText(title.substring(0, i + 1));

            if (rowTitle.getPaint().measureText(title.substring(0, i + 1)) > txtWidth) {

                Log.d(Constants.LOG_TAG, "title.substring(0, i+1) : " + title.substring(0, i + 1));

                adjustText = title.substring(0, i - 2) + "...";

                break;

            }

        }

        

        return adjustText;

    }




////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



I'm measuring text using Paint.getTextBounds(), since I'm interested in getting both the height and width of the text to be rendered. However, the actual text rendered is always a bit wider than the .width() of the Rect information filled by getTextBounds().

To my surprise, I tested .measureText(), and found that it returns a different (higher) value. I gave it a try, and found it correct.

Why do they report different widths? How can I correctly obtain the height and width? I mean, I canuse .measureText(), but then I wouldn't know if I should trust the .height() returned by getTextBounds().

As requested, here is minimal code to reproduce the problem:

final String someText = "Hello. I believe I'm some text!";

Paint p = new Paint();
Rect bounds = new Rect();

for (float f = 10; f < 40; f += 1f) {
    p.setTextSize(f);

    p.getTextBounds(someText, 0, someText.length(), bounds);

    Log.d("Test", String.format(
        "Size %f, measureText %f, getTextBounds %d",
        f,
        p.measureText(someText),
        bounds.width())
    );
}

The output shows that the difference not only gets greater than 1 (and is no last-minute rounding error), but also seems to increase with size (I was about to draw more conclusions, but it may be entirely font-dependent):

D/Test    (  607): Size 10.000000, measureText 135.000000, getTextBounds 134
D/Test    (  607): Size 11.000000, measureText 149.000000, getTextBounds 148
D/Test    (  607): Size 12.000000, measureText 156.000000, getTextBounds 155
D/Test    (  607): Size 13.000000, measureText 171.000000, getTextBounds 169
D/Test    (  607): Size 14.000000, measureText 195.000000, getTextBounds 193
D/Test    (  607): Size 15.000000, measureText 201.000000, getTextBounds 199
D/Test    (  607): Size 16.000000, measureText 211.000000, getTextBounds 210
D/Test    (  607): Size 17.000000, measureText 225.000000, getTextBounds 223
D/Test    (  607): Size 18.000000, measureText 245.000000, getTextBounds 243
D/Test    (  607): Size 19.000000, measureText 251.000000, getTextBounds 249
D/Test    (  607): Size 20.000000, measureText 269.000000, getTextBounds 267
D/Test    (  607): Size 21.000000, measureText 275.000000, getTextBounds 272
D/Test    (  607): Size 22.000000, measureText 297.000000, getTextBounds 294
D/Test    (  607): Size 23.000000, measureText 305.000000, getTextBounds 302
D/Test    (  607): Size 24.000000, measureText 319.000000, getTextBounds 316
D/Test    (  607): Size 25.000000, measureText 330.000000, getTextBounds 326
D/Test    (  607): Size 26.000000, measureText 349.000000, getTextBounds 346
D/Test    (  607): Size 27.000000, measureText 357.000000, getTextBounds 354
D/Test    (  607): Size 28.000000, measureText 369.000000, getTextBounds 365
D/Test    (  607): Size 29.000000, measureText 396.000000, getTextBounds 392
D/Test    (  607): Size 30.000000, measureText 401.000000, getTextBounds 397
D/Test    (  607): Size 31.000000, measureText 418.000000, getTextBounds 414
D/Test    (  607): Size 32.000000, measureText 423.000000, getTextBounds 418
D/Test    (  607): Size 33.000000, measureText 446.000000, getTextBounds 441
D/Test    (  607): Size 34.000000, measureText 455.000000, getTextBounds 450
D/Test    (  607): Size 35.000000, measureText 468.000000, getTextBounds 463
D/Test    (  607): Size 36.000000, measureText 474.000000, getTextBounds 469
D/Test    (  607): Size 37.000000, measureText 500.000000, getTextBounds 495
D/Test    (  607): Size 38.000000, measureText 506.000000, getTextBounds 501
D/Test    (  607): Size 39.000000, measureText 521.000000, getTextBounds 515


-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------




261down voteaccepted
+100

You can do what I did to inspect such problem:

Study Android source code, Paint.java source, see both measureText and getTextBounds methods. You'd learn that measureText calls native_measureText, and getTextBounds calls nativeGetStringBounds, which are native methods implemented in C++.

So you'd continue to study Paint.cpp, which implements both.

native_measureText -> SkPaintGlue::measureText_CII

nativeGetStringBounds -> SkPaintGlue::getStringBounds

Now your study checks where these methods differ. After some param checks, both call function SkPaint::measureText in Skia Lib (part of Android), but they both call different overloaded form.

Digging further into Skia, I see that both calls result into same computation in same function, only return result differently.

To answer your question: Both your calls do same computation. Possible difference of result lies in fact that getTextBounds returns bounds as integer, while measureText returns float value.

So what you get is rounding error during conversion of float to int, and this happens in Paint.cpp in SkPaintGlue::doTextBounds in call to function SkRect::roundOut.

The difference between computed width of those two calls may be maximally 1.

EDIT 4 Oct 2011

What may be better than visualization. I took the effort, for own exploring, and for deserving bounty :) 


This is font size 60, in red is bounds rectangle, in purple is result of measureText.

It's seen that bounds left part starts some pixels from left, and value of measureText is incremented by this value on both left and right. This is something called Glyph's AdvanceX value. (I've discovered this in Skia sources in SkPaint.cpp)

So the outcome of the test is that measureText adds some advance value to the text on both sides, while getTextBounds computes minimal bounds where given text will fit.

Hope this result is useful to you.

Testing code:

  protected void onDraw(Canvas canvas){
     final String s = "Hello. I'm some text!";

     Paint p = new Paint();
     Rect bounds = new Rect();
     p.setTextSize(60);

     p.getTextBounds(s, 0, s.length(), bounds);
     float mt = p.measureText(s);
     int bw = bounds.width();

     Log.i("LCG", String.format(
          "measureText %f, getTextBounds %d (%s)",
          mt,
          bw, bounds.toShortString())
      );
     bounds.offset(0, -bounds.top);
     p.setStyle(Style.STROKE);
     canvas.drawColor(0xff000080);
     p.setColor(0xffff0000);
     canvas.drawRect(bounds, p);
     p.setColor(0xff00ff00);
     canvas.drawText(s, 0, bounds.bottom, p);
  }


-------------------------------------------------------------------------------------------------------------------------------------------------------------------------



The mice answer is great... And here is the description of the real problem:

The short simple answer is that Paint.getTextBounds(String text, int start, int end, Rect bounds) returns Rect which doesn't starts at (0,0). That is, to get actual width of text that will be set by calling Canvas.drawText(String text, float x, float y, Paint paint) with the same Paint object from getTextBounds() you should add the left position of Rect. Something like that:

public int getTextWidth(String text, Paint paint) {
    Rect bounds = new Rect();
    paint.getTextBounds(text, 0, end, bounds);
    int width = bounds.left + bounds.width();
    return width;
}

Notice this bounds.left - this the key of the problem.

In this way you will receive the same width of text, that you would receive using Canvas.drawText().

And the same function should be for getting height of the text:

public int getTextHeight(String text, Paint paint) {
    Rect bounds = new Rect();
    paint.getTextBounds(text, 0, end, bounds);
    int height = bounds.bottom + bounds.height();
    return height;
}

P.s.: I didn't test this exact code, but tested the conception.

------------------------------------------------------------------------------------------------------------------------------------------------------------------



I think the results @mice found are missleading. The observations might be correct for the font size of 60 but they turn much more different when the text is smaller. Eg. 10px. In that case the text is actually drawn BEYOND the bounds.


Sourcecode of the screenshot:

  @Override
  protected void onDraw( Canvas canvas ) {
    for( int i = 0; i < 20; i++ ) {
      int startSize = 10;
      int curSize = i + startSize;
      paint.setTextSize( curSize );
      String text = i + startSize + " - " + TEXT_SNIPPET;
      Rect bounds = new Rect();
      paint.getTextBounds( text, 0, text.length(), bounds );
      float top = STEP_DISTANCE * i + curSize;
      bounds.top += top;
      bounds.bottom += top;
      canvas.drawRect( bounds, bgPaint );
      canvas.drawText( text, 0, STEP_DISTANCE * i + curSize, paint );
    }
  }


I was also figuring out how to measure text on a canvas. After reading the great post from mice i had some problems on how to measure multiline text. There is no obvious way from these contributions but after some research i cam across the StaticLayout class. It allows you to measure multiline text (text with "\n") and configure much more properties of your text via the associated Paint.

Here is a snippet showing how to measure multiline text:

private StaticLayout measure( TextPaint textPaint, String text, Integer wrapWidth ) {
int boundedWidth = Integer.MAX_VALUE;
if( wrapWidth != null && wrapWidth > 0 ) {
  boundedWidth = wrapWidth;
}
StaticLayout layout = new StaticLayout( text, textPaint, boundedWidth, Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false );
return layout;

}

The wrapwitdh is able to determin if you want to limit your multiline text to a certain width.

Since the StaticLayout.getWidth() only returns this boundedWidth you have to take another step to get the maximum width required by your multiline text. You are able to determine each lines width and the max width is the highest line width of course:

private float getMaxLineWidth( StaticLayout layout ) {
float maxLine = 0.0f;
int lineCount = layout.getLineCount();
for( int i = 0; i < lineCount; i++ ) {
  if( layout.getLineWidth( 0 ) > maxLine ) {
    maxLine = layout.getLineWidth( 0 );
  }
}
return maxLine;

}


---------------------------------------------------------------------------------------------------------------------------------------------------------------------------


There is another way to measure the text bounds precisely, first you should get the path for the current Paint and text. In your case it should be like this:

p.getTextPath(someText, 0, someText.length(), 0.0f, 0.0f, mPath);

After that you can call:

mPath.computeBounds(mBoundsPath, true);

In my code it always returns correct and expected values. But, not sure if it works faster than your approach.



-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

This is how I calculated the real dimensions for the first letter (you can change the method header to suit your needs, i.e. instead of char[] use String):

private void calculateTextSize(char[] text, PointF outSize) {
    // use measureText to calculate width
    float width = mPaint.measureText(text, 0, 1);

    // use height from getTextBounds()
    Rect textBounds = new Rect();
    mPaint.getTextBounds(text, 0, 1, textBounds);
    float height = textBounds.height();
    outSize.x = width;
    outSize.y = height;
}

Note that I'm using TextPaint instead of the original Paint class.



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


출처: http://baeksupervisor.tistory.com/107



이런 상황이 아주 자주 있다.

TextView 의 layout_height 를 wrap_content 로 한다면 width 를 가득 채운 후 개행 하게 된다.. 대략난감 ㅡㅡ;

1줄로만 나오고 싶다면.. maxLine 을 1 로 하면 된다.
하지만 지가 알아서 나올 수 있는 만큼만 나오고 뒷부분은 짤리게 된다..
사용자로 하여금 "다 나왔구나.." 라는 생각이 들게 한다.

그래서 보통 뒤에 text 가 더 있다는 표시로 "..." 더 찍어주는게 된다.

JAVA 단에서 str.length 로 체크 하자니.. 숫자와 영문과 한글은 그 width 가 각각 다르기 때문에 맞지 않는다.

------------------------- 결론적으로 느껴 졌어~ 넌 나의 사람이 된다는게.. ㅡㅡ; ----------------

TextView 의 Paint 객체를 얻어와 measureText 메소드를 사용하여 현재 TextView 의 text 의 width 값을 얻어온다.
이를 가지고 check 한다.


tv_bookname = (TextView)findViewById(R.id.tv_bookname);
String name = b.getString(BaseInterface.INTENT_KEY_BOOKNAME);
for(int i=0 ; i<name.length() ; i++) {
	tv_bookname.setText(name.substring(0, i+1));
	if(tv_bookname.getPaint().measureText(name.substring(0, i+1), 0, name.substring(0, i+1).length()) > 320.0f) {
		tv_bookname.setText(name.substring(0, i)+"...");
		break;
	}
}




////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



출처:  http://bsscco.tistory.com/category/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C%20%EA%B0%9C%EB%B0%9C


TextView에 들어가는 텍스트가 TextView의 최대 가로길이를 넘으면 자동으로 개행되는데 문자단위로 개행되기 때문에 깔끔하지 않습니다. 그래서 TextView에 텍스트를 넣기 전에 텍스트를 단어단위로 개행되도록 수정할 겁니다.


public final class TextViewHelper {

    private static Paint mStaticMasurePaint;


    public static String shrinkWithWordUnit(TextView textView, String s, float maxWidth) {

        if (mStaticMasurePaint == null) {

            mStaticMasurePaint = new TextPaint();

        }


        mStaticMasurePaint.setTextSize(textView.getTextSize());

        mStaticMasurePaint.setTypeface(textView.getTypeface());

        String[] tokens = s.replace("\n", "").split(" ");

        String newStr = "";

        int currLineWidth = 0;

        for (String token : tokens) {

            if (currLineWidth + mStaticMasurePaint.measureText(" " + token) > maxWidth) {

                newStr += "\n" + token;

                currLineWidth = (int) mStaticMasurePaint.measureText(token);

            } else {

                if (newStr.equals("")) {

                    newStr = token;

                    currLineWidth = (int) mStaticMasurePaint.measureText(token);

                } else {

                    newStr += " " + token;

                    currLineWidth += mStaticMasurePaint.measureText(" " + token);

                }

            }

        }

        return newStr;

    }


    private TextViewHelper() {

    }

}


아래와 같이 사용합니다.

textView.setText(TextViewHelper.shrinkWithWordUnit(textView, "배수지 걸스데이 트와이스 시스타 여자친구 소녀시대 아이유", 800));





///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


반응형
그리드형


관련글 더보기

댓글 영역