解決Android開發(fā)中經(jīng)常與設(shè)計稿不吻合的問題

一個正常的開發(fā)流程中會由設(shè)計同學(xué)給到設(shè)計稿,再有開發(fā)同學(xué)根據(jù)標注完成應(yīng)用頁面的開發(fā)。不過開發(fā)一段時間就會發(fā)現(xiàn)在做一些長頁面,有時候元素已經(jīng)超出屏幕范圍了,然而在設(shè)計稿上卻可以剛好放滿一個頁面。其實除了這些還有一些控件,也會感覺出來的效果要比設(shè)計稿大打折扣,明明都是按照設(shè)計稿的尺寸做的,為什么會有人眼可以明顯分辨的差距呢。

不看下面的廢話,直接看結(jié)論點這里(簡書跳轉(zhuǎn)不了,直接翻到最下面就好)

嘗試解決問題

第一次發(fā)現(xiàn)這個問題還是去年年初的時候,發(fā)現(xiàn)問題之后就是通過搜索引擎去查詢有沒有類似的問題,然后找到一個線索就是Android TextView有默認的頂部和底部邊距,所以如果通過上下的Margin去做就會導(dǎo)致一定的誤差。里面也給出了一個解決方案,就是這個邊距的值大概為字體的0.1倍大小,雖然這個經(jīng)驗方案很有效。但是如果手機更換了比較特殊的字體的話,那么這個經(jīng)驗值也會有較大偏差。

尋求問題原因

昨天發(fā)現(xiàn)又有同事因為這個問題再花費大量精力調(diào)整界面,看來這個問題其實大部分都沒注意到。所以有了寫一篇博客簡單分享的想法,查找更正規(guī)的設(shè)置方法

為了找到問題出現(xiàn)的原因,做出了兩種假設(shè):

  1. 在Java層TextView繪制文字時造成的
  2. native層文字繪制的實現(xiàn)中就有這個問題

分析Android java層繪制流程

簡單分析TextView代碼,可以發(fā)現(xiàn)實際控制文字繪制的是StaticLayout。由于問題是TextView上下的間距,所以首先分析StaticLayout中對行的處理,搜索下對行有寫處理的方法:

private int out(CharSequence text, int start, int end,
                      int above, int below, int top, int bottom, int v,
                      float spacingmult, float spacingadd,
                      LineHeightSpan[] chooseHt, int[] chooseHtv,
                      Paint.FontMetricsInt fm, int flags,
                      boolean needMultiply, byte[] chdirs, int dir,
                      boolean easy, int bufEnd, boolean includePad,
                      boolean trackPad, char[] chs,
                      float[] widths, int widthStart, TextUtils.TruncateAt ellipsize,
                      float ellipsisWidth, float textWidth,
                      TextPaint paint, boolean moreChars) {
        /*省略無關(guān)代碼*/
        if (firstLine) {
            if (trackPad) {
                mTopPadding = top - above; // 看起來很可疑
            }

            if (includePad) {
                above = top;
            }
        }

        int extra;

        if (lastLine) {
            if (trackPad) {
                mBottomPadding = bottom - below; // 看起來很可疑
            }

            if (includePad) {
                below = bottom;
            }
        }


        if (needMultiply && !lastLine) {
            double ex = (below - above) * (spacingmult - 1) + spacingadd;
            if (ex >= 0) {
                extra = (int)(ex + EXTRA_ROUNDING);
            } else {
                extra = -(int)(-ex + EXTRA_ROUNDING);
            }
        } else {
            extra = 0;
        }

       /*省略無關(guān)代碼*/

        mLineCount++;
        return v;
    }

上面方法中的mTopPaddingmBottomPadding一看就是很可疑的變量。把這兩個等式有關(guān)的變量找出來如下(我們不關(guān)心真實的繪制邏輯, 只找出對這個問題有影響的變量就好了)

above = fm.ascent;
below = fm.descent;
top = fm.top;
bottom = fm.bottom;
...
mTopPadding = top - above; 
mBottomPadding = bottom - below; 

很明顯這個值的大小跟字體的不同也會有關(guān)系,這和我之前遇到經(jīng)驗法不能解決的問題是一致的。關(guān)于字體參數(shù)的意義可以查看FontMetrics(fm就是FontMetrics類型)。

看來上面代碼就是問題的原因了,但我們更希望能在TextView中找到解決問題的方法,查詢調(diào)用了out方法的地方:

void generate(Builder b, boolean includepad, boolean trackpad) {
    ...
    if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
                mLineCount < mMaximumVisibleLineCount) {
            // Log.e("text", "output last " + bufEnd);

            measured.setPara(source, bufEnd, bufEnd, textDir, b);

            paint.getFontMetricsInt(fm);

            v = out(source,
                    bufEnd, bufEnd, fm.ascent, fm.descent,
                    fm.top, fm.bottom,
                    v,
                    spacingmult, spacingadd, null,
                    null, fm, 0,
                    needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
                    includepad, trackpad, null,
                    null, bufStart, ellipsize,
                    ellipsizedWidth, 0, paint, false);
        }

trackpad的值是外部參數(shù)傳遞過來的(trackpad是判斷是否設(shè)置mTopPadding/mBottomPadding的條件,這也是我們的線索),搜索generate方法,發(fā)現(xiàn)是在構(gòu)造函數(shù)中調(diào)用,所以下一步查詢TextView中構(gòu)建StaticLayout的代碼:

            StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
                    0, mTransformed.length(), mTextPaint, wantWidth)
                    .setAlignment(alignment)
                    .setTextDirection(mTextDir)
                    .setLineSpacing(mSpacingAdd, mSpacingMult)
                    .setIncludePad(mIncludePad)
                    .setBreakStrategy(mBreakStrategy)
                    .setHyphenationFrequency(mHyphenationFrequency);
            if (shouldEllipsize) {
                builder.setEllipsize(effectiveEllipsize)
                        .setEllipsizedWidth(ellipsisWidth)
                        .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
            }
            // TODO: explore always setting maxLines
            result = builder.build();

再結(jié)合Builder的代碼,我們會發(fā)現(xiàn)mIncludePad的值即trackpad的值。查詢mIncludePad的值我們會發(fā)現(xiàn)兩個方法與之有關(guān):

    /**
     * Set whether the TextView includes extra top and bottom padding to make
     * room for accents that go above the normal ascent and descent.
     * The default is true.
     *
     * @see #getIncludeFontPadding()
     *
     * @attr ref android.R.styleable#TextView_includeFontPadding
     */
    public void setIncludeFontPadding(boolean includepad) {
        if (mIncludePad != includepad) {
            mIncludePad = includepad;

            if (mLayout != null) {
                nullLayouts();
                requestLayout();
                invalidate();
            }
        }
    }

    /**
     * Gets whether the TextView includes extra top and bottom padding to make
     * room for accents that go above the normal ascent and descent.
     *
     * @see #setIncludeFontPadding(boolean)
     *
     * @attr ref android.R.styleable#TextView_includeFontPadding
     */
    public boolean getIncludeFontPadding() {
        return mIncludePad;
    }

根據(jù)注釋也知道了,這就是所有問題的答案了,遺憾的是沒有通過xml中設(shè)置屬性去掉這個默認頭部和底部的距離,xml中可以通過android:includeFontPadding="false"設(shè)置該屬性。

總結(jié)

造成實際輸出和設(shè)計稿不同的原因是TextView的默認上下邊距,可以通過調(diào)用下面的方法來移除這個默認的上下邊距:

TextView#setIncludeFontPadding(false)

或者xml中設(shè)置includeFontPadding為false

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:includeFontPadding="false" />
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 記得友人曾問過我,還再相信愛情嗎?初聽這句話心里犯著嘀咕。想著難道友人情路坎坷?說罷,便成了友人的情感被傾訴對象。...
    隨風(fēng)而逝Wind閱讀 240評論 0 0
  • 在還是以女子溫文爾雅為美德的年代,在外沉默寡言的女人總是更能得到社會和家庭的認可。而現(xiàn)在的時代,沉默常常被打上“內(nèi)...
    郝小夕閱讀 256評論 0 0
  • 花開不敗 文/復(fù)旦 職燁 編輯/韓佑釋 整理/袁辰 01 我不知道應(yīng)該怎樣寫,準確地...
    韓佑釋閱讀 4,309評論 0 7
  • 繼續(xù)霧霾天,繼續(xù)的每天學(xué)習(xí)、然后吃飯、然后睡覺。 每天碼字,對于我來說,是必做的事情,不管我今天多累多忙。 搬過來...
    星星姑娘來自小鎮(zhèn)閱讀 317評論 0 0

友情鏈接更多精彩內(nèi)容