在規定區域內自動調整文字位置

forward123_發表於2019-01-31

問題

最近在做一個需求,就是塗鴉時,在繪製的圖形旁邊通過文字顯示相關資訊.先給大家看看最終的效果:

效果圖

一開始的時候被這個問題難倒了,怎麼找到合適的位置顯示文字資訊,讓它們既不超出顯示範圍,又互不重疊?

解決方案

如果考慮不能重疊的問題,就需要在圖形四周的位置遍歷一遍直到沒有跟其他文字重疊,加上繪製的圖形是可以移動問題,這樣問題就更復雜了.於是我只能簡單化,尋找代價低又基本能滿足要求的方式.

首先,定義一個適配規則:文字優先顯示在左邊,如果超出顯示區域則直接顯示在右邊,這種右邊的情況下如果也超出了顯示區域,則往左邊偏移文字文字,使其剛好在顯示區域邊上.確定了左右位置後,接著處理垂直方向,文字優先顯示在跟圖形最上邊位置齊平的位置,如果文字底部超出顯示區域,則往上偏移文字,使其底部剛好在顯示區域底邊.

以矩形為例,顯示效果如下:

分析圖

對照規則,標註如下:

文字優先顯示在左邊(圖1,2,4,6,7,10),如果超出顯示區域則直接顯示在右邊(圖3,5),這種右邊的情況下如果也超出了顯示區域,則往左邊偏移文字文字,使其剛好在顯示區域邊上(圖9).確定了左右位置後,接著處理垂直方向,文字優先顯示在跟圖形最上邊位置齊平的位置(除圖8外的所有圖形),如果文字底部超出顯示區域,則往上偏移文字,使其底部剛好在顯示區域底邊(圖8).

這樣就限定了影像顯示在規定區域內啦!

實現

最後給出主要程式碼的程式碼實現,包括了矩形,圓,線的文字位置調整:

String msg = getInfo();
        StaticLayout textStaticLayout = new StaticLayout(msg, mTextPaint, (int) getMaxWidth(msg, mTextPaint) + 1, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
        mTextBounds.set(0, 0, textStaticLayout.getWidth(), textStaticLayout.getHeight());
        float x = 0, y = 0;
        if (getShape() == DoodleShape.HOLLOW_RECT || getShape() == DoodleShape.HOLLOW_CIRCLE) {
            // 左右限定
            float diff = -mTextPaint.getTextSize() / 3 ;
            x = mShapeBounds.right - diff;  // 優先在右
            y = mShapeBounds.top;
            if (x + getLocation().x + mTextBounds.width() > getDoodle().getBitmap().getWidth()) { // 超過右邊邊界,移到左邊
                x = mShapeBounds.left + diff - textStaticLayout.getWidth();
                if (x + getLocation().x < 0) {  // 左邊邊界限定
                  x = 0;
              }
            }

            // 上下限定,優先在上
            if (y + getLocation().y + mTextBounds.height() > getDoodle().getBitmap().getHeight()) {
                y = mShapeBounds.bottom - textStaticLayout.getHeight();
            }
            if (y + getLocation().y < 0) {
                y = 0;
            }
        } else { //  線
            PointF sxy = this.mSxy; // 起點
            PointF dxy = this.mDxy; // 終點
            if (this.mSxy.x > this.mDxy.x) { // 保證起點在左,終點在右的相對位置
                sxy = this.mDxy;
                dxy = this.mSxy;
            }
            // 左右限定
            x = dxy.x + mTextPaint.getTextSize() / 3;  // 優先在右
            y = dxy.y;
            if (x + getLocation().x + mTextBounds.width() > getDoodle().getBitmap().getWidth()) { // 超過右邊邊界,移到左邊
                x = sxy.x - mTextPaint.getTextSize() / 3 - textStaticLayout.getWidth();
                y = sxy.y;
                if (x + getLocation().x < 0) {  // 左邊邊界限定
                    x = 0;
                }
            }
           
            // 上下限定, 優先在上
            if (y + getLocation().y + mTextBounds.height() > getDoodle().getBitmap().getHeight()) {
                y = y - ((y + getLocation().y + mTextBounds.height()) - getDoodle().getBitmap().getHeight());
            }
            if (y + getLocation().y < 0) {
                y = 0;
            }
        }
        canvas.save();
        canvas.translate(x, y);
        textStaticLayout.draw(canvas);
        canvas.restore();

總結

上面僅是處理文字邊界範圍內顯示,除此之外還會存在文字之間重疊的問題.這裡我們可以參照上面的思路,優化邏輯,比如在左右限定時,如果滿足右邊顯示,則增加判斷是否跟其他圖形的文字重疊,如果不重疊則確定右邊顯示,如果有重疊則換成在左邊顯示.思路很簡單即在每一步判斷後再增加一條更嚴格的判斷,當然我們無法做到所有文字都不重疊(不然需要太多判斷條件),只要能做到儘量不重疊即可.

文中一開始的效果的塗鴉程式碼在我的開源框架的開源專案>>>>開源專案Doodle!一個功能強大,可自定義和可擴充套件的塗鴉框架。謝謝大家支援!!!

相關文章