美文网首页
开源项目Plaid学习(四)BaselineGridTextVi

开源项目Plaid学习(四)BaselineGridTextVi

作者: akak18183 | 来源:发表于2017-03-06 04:44 被阅读0次

源码

BaselineGridTextView是继承FontTextView的一个自定义控件:

/**
 * An extension to {@link android.widget.TextView} which aligns text to a 4dp baseline grid.
 * <p>
 * To achieve this we expose a {@code lineHeightHint} allowing you to specify the desired line
 * height (alternatively a {@code lineHeightMultiplierHint} to use a multiplier of the text size).
 * This line height will be adjusted to be a multiple of 4dp to ensure that baselines sit on
 * the grid.
 * <p>
 * We also adjust spacing above and below the text to ensure that the first line's baseline sits on
 * the grid (relative to the view's top) & that this view's height is a multiple of 4dp so that
 * subsequent views start on the grid.
 */
public class BaselineGridTextView extends FontTextView {

    private final float FOUR_DIP;

    private float lineHeightMultiplierHint = 1f;
    private float lineHeightHint = 0f;
    private boolean maxLinesByHeight = false;
    private int extraTopPadding = 0;
    private int extraBottomPadding = 0;

    public BaselineGridTextView(Context context) {
        this(context, null);
    }

    public BaselineGridTextView(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.textViewStyle);
    }

    public BaselineGridTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public BaselineGridTextView(Context context, AttributeSet attrs,
                                int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        final TypedArray a = context.obtainStyledAttributes(
                attrs, R.styleable.BaselineGridTextView, defStyleAttr, defStyleRes);

        lineHeightMultiplierHint =
                a.getFloat(R.styleable.BaselineGridTextView_lineHeightMultiplierHint, 1f);
        lineHeightHint =
                a.getDimensionPixelSize(R.styleable.BaselineGridTextView_lineHeightHint, 0);
        maxLinesByHeight = a.getBoolean(R.styleable.BaselineGridTextView_maxLinesByHeight, false);
        a.recycle();

        FOUR_DIP = TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics());
        computeLineHeight();
    }

    public float getLineHeightMultiplierHint() {
        return lineHeightMultiplierHint;
    }

    public void setLineHeightMultiplierHint(float lineHeightMultiplierHint) {
        this.lineHeightMultiplierHint = lineHeightMultiplierHint;
        computeLineHeight();
    }

    public float getLineHeightHint() {
        return lineHeightHint;
    }

    public void setLineHeightHint(float lineHeightHint) {
        this.lineHeightHint = lineHeightHint;
        computeLineHeight();
    }

    public boolean getMaxLinesByHeight() {
        return maxLinesByHeight;
    }

    public void setMaxLinesByHeight(boolean maxLinesByHeight) {
        this.maxLinesByHeight = maxLinesByHeight;
        requestLayout();
    }

    @Override
    public int getCompoundPaddingTop() {
        // include extra padding to place the first line's baseline on the grid
        return super.getCompoundPaddingTop() + extraTopPadding;
    }

    @Override
    public int getCompoundPaddingBottom() {
        // include extra padding to make the height a multiple of 4dp
        return super.getCompoundPaddingBottom() + extraBottomPadding;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        extraTopPadding = 0;
        extraBottomPadding = 0;
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int height = getMeasuredHeight();
        height += ensureBaselineOnGrid();
        height += ensureHeightGridAligned(height);
        setMeasuredDimension(getMeasuredWidth(), height);
        checkMaxLines(height, MeasureSpec.getMode(heightMeasureSpec));
    }

    /**
     * Ensures line height is a multiple of 4dp.
     */
    private void computeLineHeight() {
        final Paint.FontMetricsInt fm = getPaint().getFontMetricsInt();
        final int fontHeight = Math.abs(fm.ascent - fm.descent) + fm.leading;
        final float desiredLineHeight = (lineHeightHint > 0)
                ? lineHeightHint
                : lineHeightMultiplierHint * fontHeight;

        final int baselineAlignedLineHeight =
                (int) (FOUR_DIP * (float) Math.ceil(desiredLineHeight / FOUR_DIP));
        setLineSpacing(baselineAlignedLineHeight - fontHeight, 1f);
    }

    /**
     * Ensure that the first line of text sits on the 4dp grid.
     */
    private int ensureBaselineOnGrid() {
        float baseline = getBaseline();
        float gridAlign = baseline % FOUR_DIP;
        if (gridAlign != 0) {
            extraTopPadding = (int) (FOUR_DIP - Math.ceil(gridAlign));
        }
        return extraTopPadding;
    }

    /**
     * Ensure that height is a multiple of 4dp.
     */
    private int ensureHeightGridAligned(int height) {
        float gridOverhang = height % FOUR_DIP;
        if (gridOverhang != 0) {
            extraBottomPadding = (int) (FOUR_DIP - Math.ceil(gridOverhang));
        }
        return extraBottomPadding;
    }

    /**
     * When measured with an exact height, text can be vertically clipped mid-line. Prevent
     * this by setting the {@code maxLines} property based on the available space.
     */
    private void checkMaxLines(int height, int heightMode) {
        if (!maxLinesByHeight || heightMode != MeasureSpec.EXACTLY) return;

        int textHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
        int completeLines = (int) Math.floor(textHeight / getLineHeight());
        setMaxLines(completeLines);
    }
}

还需要一个自定义属性的xml文件,即attrs_baseline_grid_text_view.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="BaselineGridTextView">
        <attr name="lineHeightMultiplierHint" format="float" />
        <attr name="lineHeightHint" format="dimension"/>
        <attr name="maxLinesByHeight" format="boolean" />
    </declare-styleable>
</resources>

解析

理解FontMetrics

如果说FontTextView的目的很好理解的话,这个控件的目的就不是那么直白,因为可能都没接触过TextView的FontMetrics。既然说到Baseline,那么就照搬stackOverflow上面的一个回答吧:

  • Top - The maximum distance above the baseline for the tallest glyph in the font at a given text size.
  • Ascent - The recommended distance above the baseline for singled spaced text.
  • Descent - The recommended distance below the baseline for singled spaced text.
  • Bottom - The maximum distance below the baseline for the lowest glyph in the font at a given text size.
  • Leading - The recommended additional space to add between lines of text.

Note that the Baseline is what the first four are measured from. It is line which forms the base that the text sits on, even though some characters (like g, y, j, etc.) might have parts that go below the line. It is comparable to the lines you write on in a lined notebook.
Here is a picture to help visualize these things:


效果图

如上图,一共三个view,第一个是普通的TextView,后面两个长的分别是FontTextView和BaselineGridTextView。通过观察不难看出,确实是行间距变了。没有影响横向的对齐。

   <FontTextView
        android:id="@+id/tv_2"
        android:layout_below="@+id/tv_1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/long_text"
        app:font="roboto-mono-regular"
        android:textSize="14sp"/>

    <BaselineGridTextView
        android:id="@+id/tv_3"
        android:layout_below="@+id/tv_2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/long_text"
        app:font="roboto-mono-regular"
        app:lineHeightHint="24sp"/>

此外,BaselineGridTextView可以直接设置行高度而无需再设置textSize,在某些时候也会很方便。

相关文章

网友评论

      本文标题:开源项目Plaid学习(四)BaselineGridTextVi

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