การวัดความสูงของข้อความที่จะวาดบน Canvas (Android)


132

วิธีใดตรงไปตรงมาในการวัดความสูงของข้อความ? วิธีที่ฉันทำตอนนี้คือการใช้ Paint measureText()เพื่อรับความกว้างจากนั้นลองผิดลองถูกเพื่อหาค่าเพื่อให้ได้ความสูงโดยประมาณ ฉันก็ยุ่งอยู่FontMetricsเหมือนกัน แต่ทั้งหมดนี้ดูเหมือนวิธีการโดยประมาณที่ดูด

ฉันพยายามปรับขนาดของสิ่งต่างๆเพื่อความละเอียดที่แตกต่างกัน ฉันทำได้ แต่จบลงด้วยรหัส verbose อย่างไม่น่าเชื่อพร้อมการคำนวณจำนวนมากเพื่อกำหนดขนาดสัมพัทธ์ ฉันเกลียดมัน! จะต้องมีวิธีที่ดีกว่านี้

คำตอบ:


136

สิ่งที่เกี่ยวกับpaint.getTextBounds () (วิธีวัตถุ)


1
สิ่งนี้ให้ผลลัพธ์ที่แปลกมากเมื่อฉันประเมินความสูงของข้อความ ข้อความสั้นส่งผลให้มีความสูง 12 ในขณะที่ข้อความยาวจริง ๆ ให้ผลลัพธ์ที่ความสูง 16 (กำหนดขนาดตัวอักษรเป็น 16) ไม่มีเหตุผลสำหรับฉัน (android 2.3.3)
AgentKnopf

36
ความแปรปรวนของความสูงคือการที่คุณมีลูกหลานในข้อความกล่าวคือ 'สูง' สูงกว่า 'ต่ำ' เนื่องจากส่วนของ g ใต้เส้น
FrinkTheBrave

209

มีหลายวิธีในการวัดความสูงขึ้นอยู่กับสิ่งที่คุณต้องการ

getTextBounds

getTextBoundsถ้าคุณกำลังทำสิ่งที่ต้องการได้อย่างแม่นยำตรงกลางขนาดเล็กจำนวนข้อความแก้ไขคุณอาจต้องการ คุณจะได้สี่เหลี่ยมผืนผ้าล้อมรอบแบบนี้

Rect bounds = new Rect();
mTextPaint.getTextBounds(mText, 0, mText.length(), bounds);
int height = bounds.height();

ดังที่คุณเห็นในภาพต่อไปนี้สตริงที่แตกต่างกันจะให้ความสูงต่างกัน (แสดงเป็นสีแดง)

ป้อนคำอธิบายภาพที่นี่

ความสูงที่แตกต่างกันเหล่านี้อาจเป็นข้อเสียในบางสถานการณ์เมื่อคุณต้องการความสูงคงที่ไม่ว่าข้อความจะเป็นอย่างไร ดูหัวข้อถัดไป

Paint.FontMetrics

คุณสามารถคำนวณความสูงของแบบอักษรจากเมตริกแบบอักษร ความสูงจะเท่ากันเสมอเนื่องจากได้มาจากแบบอักษรไม่ใช่สตริงข้อความใด ๆ

Paint.FontMetrics fm = mTextPaint.getFontMetrics();
float height = fm.descent - fm.ascent;

เส้นฐานคือบรรทัดที่ข้อความอยู่ โดยทั่วไปแล้วการสืบเชื้อสายจะอยู่ไกลที่สุดที่อักขระจะอยู่ต่ำกว่าเส้นและโดยทั่วไปการขึ้นจะอยู่ที่ไกลที่สุดที่อักขระจะอยู่เหนือเส้น เพื่อให้ได้ความสูงคุณต้องลบการเพิ่มขึ้นเนื่องจากเป็นค่าลบ (เส้นฐานคือy=0และyลดขนาดหน้าจอ)

ดูภาพต่อไปนี้ 234.375ความสูงของทั้งสองสายที่มี

ป้อนคำอธิบายภาพที่นี่

หากคุณต้องการความสูงของบรรทัดมากกว่าความสูงของข้อความคุณสามารถดำเนินการดังต่อไปนี้:

float height = fm.bottom - fm.top + fm.leading; // 265.4297

นี่คือbottomและtopของเส้น โดยทั่วไปแล้วค่านำหน้า (ระยะห่างระหว่างบรรทัด) จะเป็นศูนย์ แต่คุณควรเพิ่มเข้าไป

ภาพด้านบนมาจากโครงการนี้ คุณสามารถเล่นกับมันเพื่อดูว่า Font Metrics ทำงานอย่างไร

StaticLayout

สำหรับการวัดความสูงของข้อความหลายบรรทัดคุณควรใช้ไฟล์StaticLayout. ฉันพูดถึงรายละเอียดบางอย่างในคำตอบนี้แต่วิธีพื้นฐานในการรับความสูงนี้เป็นดังนี้:

String text = "This is some text. This is some text. This is some text. This is some text. This is some text. This is some text.";

TextPaint myTextPaint = new TextPaint();
myTextPaint.setAntiAlias(true);
myTextPaint.setTextSize(16 * getResources().getDisplayMetrics().density);
myTextPaint.setColor(0xFF000000);

int width = 200;
Layout.Alignment alignment = Layout.Alignment.ALIGN_NORMAL;
float spacingMultiplier = 1;
float spacingAddition = 0;
boolean includePadding = false;

StaticLayout myStaticLayout = new StaticLayout(text, myTextPaint, width, alignment, spacingMultiplier, spacingAddition, includePadding);

float height = myStaticLayout.getHeight(); 

คำอธิบายที่ดี ภาพหน้าจอมาจากแอปอะไร
Micheal Johnson

1
@MichealJohnson ฉันเพิ่ม app เป็นโครงการ GitHub ที่นี่
Suragch

1
แล้ว "getTextSize" ให้อะไรแก่คุณ?
นักพัฒนา Android

1
Paint getTextSize()ช่วยให้คุณมีขนาดตัวอักษรในหน่วยพิกเซล (ตรงข้ามกับspหน่วย) @androiddeveloper
Suragch

2
ความสัมพันธ์ของขนาดแบบอักษรในหน่วยพิกเซลกับความสูงที่วัดได้และมิติข้อมูล FontMetricsคืออะไร นั่นเป็นคำถามที่ฉันต้องการสำรวจเพิ่มเติม
Suragch

85

คำตอบของ @ bramp ถูกต้อง - บางส่วนซึ่งไม่ได้กล่าวถึงว่าขอบเขตที่คำนวณได้จะเป็นสี่เหลี่ยมผืนผ้าขั้นต่ำที่มีข้อความเต็มโดยมีพิกัดเริ่มต้นโดยนัยเป็น 0, 0

ซึ่งหมายความว่าความสูงของตัวอย่างเช่น "Py" จะแตกต่างจากความสูงของ "py" หรือ "hi" หรือ "oi" หรือ "aw" เนื่องจากการใช้พิกเซลนั้นต้องการความสูงที่แตกต่างกัน

สิ่งนี้ไม่เทียบเท่ากับ FontMetrics ใน java แบบคลาสสิก

แม้ว่าความกว้างของข้อความจะไม่น่าปวดหัวมากนัก แต่ความสูงก็คือ

โดยเฉพาะอย่างยิ่งหากคุณต้องการจัดแนวข้อความที่วาดไว้ตรงกลางในแนวตั้งให้ลองกำหนดขอบเขตของข้อความ "a" (โดยไม่ใส่เครื่องหมายอัญประกาศ) แทนที่จะใช้ข้อความที่คุณต้องการวาด ใช้ได้ผลกับฉัน ...

นี่คือสิ่งที่ฉันหมายถึง:

Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.LINEAR_TEXT_FLAG);

paint.setStyle(Paint.Style.FILL);
paint.setColor(color);
paint.setTextAlign(Paint.Align.CENTER);
paint.setTextSize(textSize);

Rect bounds = new Rect();
paint.getTextBounds("a", 0, 1, bounds);

buffer.drawText(this.myText, canvasWidth >> 1, (canvasHeight + bounds.height()) >> 1, paint);
// remember x >> 1 is equivalent to x / 2, but works much much faster

การจัดข้อความตรงกลางในแนวตั้งหมายถึงกึ่งกลางในแนวตั้งจัดแนวสี่เหลี่ยมผืนผ้าที่มีขอบซึ่งจะแตกต่างกันไปตามข้อความต่างๆ (ตัวพิมพ์ใหญ่ตัวอักษรยาว ฯลฯ ) แต่สิ่งที่เราต้องการทำจริงๆคือจัดแนวเส้นฐานของข้อความที่แสดงผลเช่นเดียวกันเพื่อไม่ให้ปรากฏสูงขึ้นหรือเป็นร่อง ดังนั้นตราบใดที่เรารู้จุดศูนย์กลางของตัวอักษรที่เล็กที่สุด (เช่น "a") เราก็สามารถนำการจัดตำแหน่งของมันกลับมาใช้ซ้ำกับส่วนที่เหลือของข้อความได้ สิ่งนี้จะจัดแนวข้อความทั้งหมดให้อยู่กึ่งกลางและจัดแนวพื้นฐาน


25
ไม่ได้เห็นx >> 1มานานแล้ว อัพโหวตเท่านั้น :)
keaukraine

62
คอมไพเลอร์สมัยใหม่ที่ดีจะเห็นx / 2และปรับให้เหมาะสมx >> 1
intrepidis

37
@keaukraine x / 2เป็นมิตรมากขึ้นเมื่ออ่านโค้ดโดยพิจารณาจากความคิดเห็นของ Chris
d3dave

สิ่งที่อยู่bufferในตัวอย่างนี้? มันcanvasถูกส่งผ่านไปยังdraw(Canvas)วิธีการหรือไม่?
AutonomousApps

@AutonomousApps ใช่มันคือผืนผ้าใบ
Chisko

16

ความสูงคือขนาดข้อความที่คุณตั้งค่าไว้ในตัวแปร Paint

อีกวิธีหนึ่งในการหาความสูงคือ

mPaint.getTextSize();

3

คุณสามารถใช้ระดับการระบุขอบเขตที่จำเป็นแล้วโทรandroid.text.StaticLayout getHeight()คุณสามารถวาดข้อความ (อยู่ในเค้าโครง) โดยเรียกdraw(Canvas)ใช้เมธอด


2

คุณสามารถรับขนาดตัวอักษรสำหรับวัตถุ Paint โดยใช้เมธอด getTextSize () ตัวอย่างเช่น:

Paint mTextPaint = new Paint (Paint.ANTI_ALIAS_FLAG);
//use densityMultiplier to take into account different pixel densities
final float densityMultiplier = getContext().getResources()
            .getDisplayMetrics().density;  
mTextPaint.setTextSize(24.0f*densityMultiplier);

//...

float size = mTextPaint.getTextSize();

ที่ไหนที่ไม่24.0fมาจากไหน?
Erigami

24.0f เป็นเพียงตัวอย่างขนาดตัวอักษร
moondroid

1

คุณต้องใช้Rect.width()และRect.Height()ที่ส่งคืนจากgetTextBounds()แทน ที่เหมาะกับฉัน


2
ไม่ใช่หากคุณกำลังจัดการกับข้อความหลายกลุ่ม เหตุผลอยู่ในคำตอบของฉันข้างต้น
Nar Gar

0

หากใครยังมีปัญหานี่คือรหัสของฉัน

ฉันมีมุมมองที่กำหนดเองซึ่งเป็นสี่เหลี่ยมจัตุรัส (กว้าง = สูง) และฉันต้องการกำหนดอักขระให้กับมัน onDraw()แสดงวิธีรับความสูงของตัวอักษรแม้ว่าฉันจะไม่ได้ใช้ก็ตาม ตัวละครจะแสดงขึ้นตรงกลางมุมมอง

public class SideBarPointer extends View {

    private static final String TAG = "SideBarPointer";

    private Context context;
    private String label = "";
    private int width;
    private int height;

    public SideBarPointer(Context context) {
        super(context);
        this.context = context;
        init();
    }

    public SideBarPointer(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        init();
    }

    public SideBarPointer(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.context = context;
        init();
    }

    private void init() {
//        setBackgroundColor(0x64FF0000);
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        height = this.getMeasuredHeight();
        width = this.getMeasuredWidth();

        setMeasuredDimension(width, width);
    }

    protected void onDraw(Canvas canvas) {
        float mDensity = context.getResources().getDisplayMetrics().density;
        float mScaledDensity = context.getResources().getDisplayMetrics().scaledDensity;

        Paint previewPaint = new Paint();
        previewPaint.setColor(0x0C2727);
        previewPaint.setAlpha(200);
        previewPaint.setAntiAlias(true);

        Paint previewTextPaint = new Paint();
        previewTextPaint.setColor(Color.WHITE);
        previewTextPaint.setAntiAlias(true);
        previewTextPaint.setTextSize(90 * mScaledDensity);
        previewTextPaint.setShadowLayer(5, 1, 2, Color.argb(255, 87, 87, 87));

        float previewTextWidth = previewTextPaint.measureText(label);
//        float previewTextHeight = previewTextPaint.descent() - previewTextPaint.ascent();
        RectF previewRect = new RectF(0, 0, width, width);

        canvas.drawRoundRect(previewRect, 5 * mDensity, 5 * mDensity, previewPaint);
        canvas.drawText(label, (width - previewTextWidth)/2, previewRect.top - previewTextPaint.ascent(), previewTextPaint);

        super.onDraw(canvas);
    }

    public void setLabel(String label) {
        this.label = label;
        Log.e(TAG, "Label: " + label);

        this.invalidate();
    }
}

3
-1 สำหรับการปันส่วนใน onDraw (): จะให้ประโยชน์ด้านประสิทธิภาพมากมายหากคุณต้องประกาศฟิลด์ในคลาส (เริ่มต้นในตัวสร้าง) และใช้ซ้ำใน onDraw ()
zoltish
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.