TextView

getTextSize()和setTextSize()的单位设置

我们都已经知道getTextView取的值是像素值,看过源码就会知道它实际上是调用Paint类的native方法。在这一点上无可争议。但是在setTextSize()处,就会有个小说法,这里我先把结论说下:
根据已有sp值直接设置setTextSize(),请这样设置

tv.setTextSize(17);

而针对想要把sp转化成px进行传入,或者使用dimens文件中已有值进行设置的话

tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, ScreenUtils.dp2px(this, 17));
tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.font_size_sp));

好,下面说下原理:

setTextSize()源码分析

我们知道,TextView的单位有很多,什么sp啊,dp啊,px等等。那么google在android.util包下就专门设置了一个类用来管理这些文字单位:TypedValue。并且设置了几个静态变量来规定类型:

    /** {@link #TYPE_DIMENSION} complex unit: Value is raw pixels. */
    public static final int COMPLEX_UNIT_PX = 0;
    /** {@link #TYPE_DIMENSION} complex unit: Value is Device Independent
     *  Pixels. */
    public static final int COMPLEX_UNIT_DIP = 1;
    /** {@link #TYPE_DIMENSION} complex unit: Value is a scaled pixel. */
    public static final int COMPLEX_UNIT_SP = 2;
    /** {@link #TYPE_DIMENSION} complex unit: Value is in points. */
    public static final int COMPLEX_UNIT_PT = 3;
    /** {@link #TYPE_DIMENSION} complex unit: Value is in inches. */
    public static final int COMPLEX_UNIT_IN = 4;
    /** {@link #TYPE_DIMENSION} complex unit: Value is in millimeters. */
    public static final int COMPLEX_UNIT_MM = 5;

而我们调用setTextSize()方法时,上面的类型是作为一个参数传入的。

    /**
     * Set the default text size to a given unit and value.  See {@link
     * TypedValue} for the possible dimension units.
     *
     * @param unit The desired dimension unit.
     * @param size The desired size in the given units.
     *
     * @attr ref android.R.styleable#TextView_textSize
     */
    public void setTextSize(int unit, float size) {
        Context c = getContext();
        Resources r;

        if (c == null)
            r = Resources.getSystem();
        else
            r = c.getResources();

        setRawTextSize(TypedValue.applyDimension(
                unit, size, r.getDisplayMetrics()));
    }

而实际上我们平时使用的setTextSize()方法就是上面方法的重载,而且它也为我们设置好了默认类型

    /**
     * Set the default text size to the given value, interpreted as "scaled
     * pixel" units.  This size is adjusted based on the current density and
     * user font size preference.
     *
     * @param size The scaled pixel size.
     *
     * @attr ref android.R.styleable#TextView_textSize
     */
    @android.view.RemotableViewMethod
    public void setTextSize(float size) {
        setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
    }

这也就是为什么setTextSize(float size)的传入值是sp类型的原因。而将字体单位传入也不是没有用的,我们看到此方法中将单位值传给了TypedValue.applyDimension(unit, size, r.getDisplayMetrics())。我们可以跟进去看看:

    /**
     * Converts an unpacked complex data value holding a dimension to its final floating 
     * point value. The two parameters <var>unit</var> and <var>value</var>
     * are as in {@link #TYPE_DIMENSION}.
     *  
     * @param unit The unit to convert from.
     * @param value The value to apply the unit to.
     * @param metrics Current display metrics to use in the conversion -- 
     *                supplies display density and scaling information.
     * 
     * @return The complex floating point value multiplied by the appropriate 
     * metrics depending on its unit. 
     */
    public static float applyDimension(int unit, float value,
                                       DisplayMetrics metrics)
    {
        switch (unit) {
        case COMPLEX_UNIT_PX:
            return value;
        case COMPLEX_UNIT_DIP:
            return value * metrics.density;
        case COMPLEX_UNIT_SP:
            return value * metrics.scaledDensity;
        case COMPLEX_UNIT_PT:
            return value * metrics.xdpi * (1.0f/72);
        case COMPLEX_UNIT_IN:
            return value * metrics.xdpi;
        case COMPLEX_UNIT_MM:
            return value * metrics.xdpi * (1.0f/25.4f);
        }
        return 0;
    }

可以看到,系统根据文字单位的不同对字号大小进行了屏幕适配。sp类型会乘以scaledDensity值,dp类型会乘以density,像素则不作处理。
看来,系统对于字体大小没有进行过多的处理,仅仅是根据文字单位作出了适配处理。那么就和我们传入的值有很大关系了。

setTextSize()传入参数比较

1.直接调用setTextSize(),只需要传入我们已经计算好的sp值就可以
2.通过dp2px将sp值转化为像素值,我们在setTextSize()时,把传入单位设置成TypedValue.COMPLEX_UNIT_PX也可达到相同效果。
3.但当我们使用dimens文件进行适配时,就要小心,因为系统会针对文件中dp或sp乘上density或scaledDensity,使得getResources().getDimension(R.dimen.font_size_dp)取值就是适配好的像素值,如果我们这时再去调用文字单位为TypedValue.COMPLEX_UNIT_DIP或者TypedValue.COMPLEX_UNIT_SP,文字最终效果将会翻倍变大。所以在使用该文件时应该TypedValue.COMPLEX_UNIT_PX
以小米红米Note 1 LTE机型为例,屏幕分辨率在1280×720,屏幕尺寸在5.5寸,density为2.

    <dimen name="font_size_sp">17sp</dimen>
    <dimen name="font_size_dp">17dp</dimen>
    <dimen name="font_size_px">34px</dimen>
        int i = (int)getResources().getDimension(R.dimen.font_size_sp);
        int x = (int)getResources().getDimension(R.dimen.font_size_dp);
        int y = (int)getResources().getDimension(R.dimen.font_size_px);

debug取值后:

跑马灯效果实现

xml文件代码:

        android:singleLine="true"
        android:ellipsize="marquee"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:marqueeRepeatLimit="marquee_forever"

仅仅设置这几行代码就可以实现一个永久滑动的TextView的跑马灯效果。但需要注意几点:
1.上面前四个属性是必需的,最后一个属性是可添加的,没有这条属性,跑马灯效果依然可以实现,只不过仅仅显示一次而已。
2.界面上有多个TextView想要显示跑马灯的效果,仅仅在xml中设置是不够的,需要对每个TextView在java代码中设置:

tv.setSelected(true);

3.文本长度应该超出屏幕宽度,否则无效果。

android:ellipsize

属性是对TextView文本进行省略设置, 值有五个:none代表不省略(默认值)start在开始处省略,middle代表在中间处省略,end代表在结尾处省略,marquee代表以跑马灯的形式展示。
对应的java代码是:

setEllipsize(TextUtils.TruncateAt)

android:marqueeRepeatLimit

该属性是对TextView跑马灯效果次数的限制,单位为一个int值,例如android:marqueeRepeatLimit="marquee_forever"就是跑马灯效果展示100次。值marquee_forever为永远展示。对应java代码:setMarqueeRepeatLimit(int)

android:singleLine

该属性是对TextView的文本内容进行单行显示的限制,使用时默认会自带android:ellipsize="end"的属性,所以显示效果会是“XXXXXXXXXXXX...”。但是自从api23时谷歌就不推荐使用这个属性了,在官方网站上也没有找到推荐替换的属性。一直不明白这样一个很方便的属性为什么会被弃掉。当然,不推荐不代表不能使用,而且可替换的属性有maxLines,lines。java代码中对应setSingleLine ()

阴影效果

使用下面四个属性,可以使TextView具有阴影效果:

        android:shadowDx="5"
        android:shadowDy="5"
        android:shadowRadius="5"
        android:shadowColor="@color/colorAccent"
  • android:shadowDx——设置阴影横向坐标开始位置(相对于文本内容)
  • android:shadowDy——设置阴影纵向坐标开始位置(相对于文本内容)
  • android:shadowRadius——设置阴影的半径(以真正打印的单个字形的边界为基准点,向外辐射的半径)
  • android:shadowColor——指定文本阴影的颜色 对应java代码为setShadowLayer(float radius, float dx, float dy, int color)
    注意:xml属性中的值不需要加单位,x和y值可以为负值。

同时改变TextView中部分文字的显示字号和字色

ColorStateList redColors = ColorStateList.valueOf(0xffff0000);
SpannableStringBuilder spanBuilder = new SpannableStringBuilder("这是一个测试");
//style 为0 即是正常的,还有Typeface.BOLD(粗体) Typeface.ITALIC(斜体)等
//size  为0 即采用原始的正常的 size大小 
spanBuilder.setSpan(new TextAppearanceSpan(null, 0, 60, redColors, null), 0, 3, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);

TextView tv_test = (TextView) findViewById(R.id.tv_test);
tv_test.setText(spanBuilder);

改变TextView中部分文字的显示字色以及可点击

 SpannableString builder = new SpannableString(commentText);
                            builder.setSpan(new NoUnderLineSpan() {
                                @Override
                                public void onClick(View widget) {
                                    UserInfoActivityFactory.start(activity, articleComment.getQuote().getUserId(), false);
                                }
                            }, start, end, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
                            holder.textUserComment.setText(builder);
                            holder.textUserComment.setMovementMethod(LinkMovementMethod.getInstance());


class NoUnderLineSpan extends ClickableSpan {
        @Override
        public void onClick(View widget) {

        }

        @Override
        public void updateDrawState(TextPaint ds) {
            super.updateDrawState(ds);
            ds.setUnderlineText(false);
            ds.setColor(activity.getResources().getColor(R.color.iOS7_d__district));
        }
    }

可是这样会带来两个隐藏问题:

1.只有build设置过的部分可点击,build之外的地方不可点击,即使点击事件设在了view上。

2.如果文本过长,textView没有显示完全,那么默认的LinkMovementMethod在点击完成后会将未显示完的文本内容滑动出来。

解决:1.给build分段设置Spannable。2自定义LinkMovementMethod,覆写其中逻辑。

                            commentText = "这是一段测试文本";
                            int start = commentText.indexOf(atUserName);
                            int end = start + atUserName.length();
                            SpannableString builder = new SpannableString(commentText);
                            builder.setSpan(new NoUnderLineSpan() {
                                @Override
                                public void onClick(View widget) {
                                    UserInfoActivityFactory.start(activity, articleComment.getQuote().getUserId(), false);
                                }
                            }, start, end, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
                            builder.setSpan(new NoUnderLineNormalSpan(){
                              @Override
                                public void onClick(View view) {

                                }
                            },end, commentText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                            holder.textUserComment.setText(builder);
                            holder.textUserComment.setMovementMethod(new NoScrollLinkMovementMethod());

NoScrollLinkMovementMethod的逻辑:

public class NoScrollLinkMovementMethod extends LinkMovementMethod {

    public NoScrollLinkMovementMethod() {
    }

    @Override
    public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
        int action = event.getAction();

        if (action == MotionEvent.ACTION_UP ||
                action == MotionEvent.ACTION_DOWN) {
            int x = (int) event.getX();
            int y = (int) event.getY();

            x -= widget.getTotalPaddingLeft();
            y -= widget.getTotalPaddingTop();

            x += widget.getScrollX();
            y += widget.getScrollY();

            Layout layout = widget.getLayout();
            int line = layout.getLineForVertical(y);
            int off = layout.getOffsetForHorizontal(line, x);

            ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);

            if (link.length != 0) {
                if (action == MotionEvent.ACTION_UP) {
                    link[0].onClick(widget);
                }

                return true;
            } else {
                Selection.removeSelection(buffer);
            }
        }

        return Touch.onTouchEvent(widget, buffer, event);
    }
}

代码中直接设置资源图片

.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.ico_btn_retracted, 0, 0);

设置字体

android:fontFamily="monospace"

results matching ""

    No results matching ""