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

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

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

嘗試解決問題

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

尋求問題原因

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

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

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

分析Android java層繪制流程

簡單分析TextView代碼霸琴,可以發(fā)現(xiàn)實(shí)際控制文字繪制的是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一看就是很可疑的變量澎迎。把這兩個(gè)等式有關(guān)的變量找出來如下(我們不關(guān)心真實(shí)的繪制邏輯, 只找出對這個(gè)問題有影響的變量就好了)

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

很明顯這個(gè)值的大小跟字體的不同也會有關(guān)系选调,這和我之前遇到經(jīng)驗(yàn)法不能解決的問題是一致的夹供。關(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)兩個(gè)方法與之有關(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è)置屬性去掉這個(gè)默認(rèn)頭部和底部的距離再层,xml中可以通過android:includeFontPadding="false"設(shè)置該屬性贸铜。

總結(jié)

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

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)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市聂受,隨后出現(xiàn)的幾起案子萨脑,更是在濱河造成了極大的恐慌,老刑警劉巖饺饭,帶你破解...
    沈念sama閱讀 212,185評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件渤早,死亡現(xiàn)場離奇詭異,居然都是意外死亡瘫俊,警方通過查閱死者的電腦和手機(jī)鹊杖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,445評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扛芽,“玉大人骂蓖,你說我怎么就攤上這事〈猓” “怎么了登下?”我有些...
    開封第一講書人閱讀 157,684評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長叮喳。 經(jīng)常有香客問我被芳,道長,這世上最難降的妖魔是什么馍悟? 我笑而不...
    開封第一講書人閱讀 56,564評論 1 284
  • 正文 為了忘掉前任畔濒,我火速辦了婚禮,結(jié)果婚禮上锣咒,老公的妹妹穿的比我還像新娘侵状。我一直安慰自己,他們只是感情好毅整,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,681評論 6 386
  • 文/花漫 我一把揭開白布趣兄。 她就那樣靜靜地躺著,像睡著了一般悼嫉。 火紅的嫁衣襯著肌膚如雪艇潭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,874評論 1 290
  • 那天,我揣著相機(jī)與錄音暴区,去河邊找鬼闯团。 笑死,一個(gè)胖子當(dāng)著我的面吹牛仙粱,可吹牛的內(nèi)容都是我干的房交。 我是一名探鬼主播,決...
    沈念sama閱讀 39,025評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼伐割,長吁一口氣:“原來是場噩夢啊……” “哼候味!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起隔心,我...
    開封第一講書人閱讀 37,761評論 0 268
  • 序言:老撾萬榮一對情侶失蹤白群,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后硬霍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體帜慢,經(jīng)...
    沈念sama閱讀 44,217評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,545評論 2 327
  • 正文 我和宋清朗相戀三年唯卖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了粱玲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,694評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拜轨,死狀恐怖抽减,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情橄碾,我是刑警寧澤卵沉,帶...
    沈念sama閱讀 34,351評論 4 332
  • 正文 年R本政府宣布,位于F島的核電站法牲,受9級特大地震影響史汗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜皆串,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,988評論 3 315
  • 文/蒙蒙 一淹办、第九天 我趴在偏房一處隱蔽的房頂上張望眉枕。 院中可真熱鬧恶复,春花似錦、人聲如沸速挑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,778評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽姥宝。三九已至翅萤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間腊满,已是汗流浹背套么。 一陣腳步聲響...
    開封第一講書人閱讀 32,007評論 1 266
  • 我被黑心中介騙來泰國打工培己, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人胚泌。 一個(gè)月前我還...
    沈念sama閱讀 46,427評論 2 360
  • 正文 我出身青樓省咨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親玷室。 傳聞我的和親對象是個(gè)殘疾皇子零蓉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,580評論 2 349

推薦閱讀更多精彩內(nèi)容

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