美文网首页Android开发其他Android技术知识
解决Android开发中经常与设计稿不吻合的问题

解决Android开发中经常与设计稿不吻合的问题

作者: 9711922c6b29 | 来源:发表于2018-04-13 22:09 被阅读160次

    一个正常的开发流程中会由设计同学给到设计稿,再有开发同学根据标注完成应用页面的开发。不过开发一段时间就会发现在做一些长页面,有时候元素已经超出屏幕范围了,然而在设计稿上却可以刚好放满一个页面。其实除了这些还有一些控件,也会感觉出来的效果要比设计稿大打折扣,明明都是按照设计稿的尺寸做的,为什么会有人眼可以明显分辨的差距呢。

    不看下面的废话,直接看结论点这里(简书跳转不了,直接翻到最下面就好)

    尝试解决问题

    第一次发现这个问题还是去年年初的时候,发现问题之后就是通过搜索引擎去查询有没有类似的问题,然后找到一个线索就是Android TextView有默认的顶部和底部边距,所以如果通过上下的Margin去做就会导致一定的误差。里面也给出了一个解决方案,就是这个边距的值大概为字体的0.1倍大小,虽然这个经验方案很有效。但是如果手机更换了比较特殊的字体的话,那么这个经验值也会有较大偏差。

    寻求问题原因

    昨天发现又有同事因为这个问题再花费大量精力调整界面,看来这个问题其实大部分都没注意到。所以有了写一篇博客简单分享的想法,查找更正规的设置方法

    为了找到问题出现的原因,做出了两种假设:

    1. 在Java层TextView绘制文字时造成的
    2. native层文字绘制的实现中就有这个问题

    分析Android java层绘制流程

    简单分析TextView代码,可以发现实际控制文字绘制的是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) {
            /*省略无关代码*/
            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;
            }
    
           /*省略无关代码*/
    
            mLineCount++;
            return v;
        }
    

    上面方法中的mTopPaddingmBottomPadding一看就是很可疑的变量。把这两个等式有关的变量找出来如下(我们不关心真实的绘制逻辑, 只找出对这个问题有影响的变量就好了)

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

    很明显这个值的大小跟字体的不同也会有关系,这和我之前遇到经验法不能解决的问题是一致的。关于字体参数的意义可以查看FontMetrics(fm就是FontMetrics类型)。

    看来上面代码就是问题的原因了,但我们更希望能在TextView中找到解决问题的方法,查询调用了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的值是外部参数传递过来的(trackpad是判断是否设置mTopPadding/mBottomPadding的条件,这也是我们的线索),搜索generate方法,发现是在构造函数中调用,所以下一步查询TextView中构建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();
    

    再结合Builder的代码,我们会发现mIncludePad的值即trackpad的值。查询mIncludePad的值我们会发现两个方法与之有关:

        /**
         * 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;
        }
    

    根据注释也知道了,这就是所有问题的答案了,遗憾的是没有通过xml中设置属性去掉这个默认头部和底部的距离,xml中可以通过android:includeFontPadding="false"设置该属性。

    总结

    造成实际输出和设计稿不同的原因是TextView的默认上下边距,可以通过调用下面的方法来移除这个默认的上下边距:

    TextView#setIncludeFontPadding(false)
    

    或者xml中设置includeFontPadding为false

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            android:includeFontPadding="false" />
    

    相关文章

      网友评论

      本文标题:解决Android开发中经常与设计稿不吻合的问题

      本文链接:https://www.haomeiwen.com/subject/qbmhkftx.html