你真的懂Android的TextView吗?

576 阅读5分钟

TextView

在实际开发过程中,都会遇到一些问题。或多或少自定义控件的情况。而我们大多数喜欢用的是系统自带的控件,比方说TextView,ImageView等,这里讲的是就是使用TextView达到一些效果

  1. TextView的图片大小可控【因为正常的TextView对于图片的大小需要通过代码来控制,这里实现为xml来设置】
  2. TextView的图片和文本需要在点击的时候有效果变化【正常的Textview的点击是没有效果的】

以下为实现的效果图:

以下为代码:

public class TextImageView extends android.support.v7.widget.AppCompatTextView {

    private int mLeftWidth;
    private int mLeftHeight;
    private int mTopWidth;
    private int mTopHeight;
    private int mRightWidth;
    private int mRightHeight;
    private int mBottomWidth;
    private int mBottomHeight;

    /**
     * 是否需要点击效果,默认为透明度变暗50%
     */
    private boolean isNeedClickEffect;

    public TextImageView(Context context) {
        super(context);
    }

    public TextImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public TextImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }


    public void init(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TextImageView);

        mLeftWidth = typedArray.getDimensionPixelOffset(R.styleable.TextImageView_drawableLeftWidth, 0);
        mLeftHeight = typedArray.getDimensionPixelOffset(R.styleable.TextImageView_drawableLeftHeight, 0);
        mTopWidth = typedArray.getDimensionPixelOffset(R.styleable.TextImageView_drawableTopWidth, 0);
        mTopHeight = typedArray.getDimensionPixelOffset(R.styleable.TextImageView_drawableTopHeight, 0);
        mRightWidth = typedArray.getDimensionPixelOffset(R.styleable.TextImageView_drawableRightWidth, 0);
        mRightHeight = typedArray.getDimensionPixelOffset(R.styleable.TextImageView_drawableRightHeight, 0);
        mBottomWidth = typedArray.getDimensionPixelOffset(R.styleable.TextImageView_drawableBottomWidth, 0);
        mBottomHeight = typedArray.getDimensionPixelOffset(R.styleable.TextImageView_drawableBottomHeight, 0);
        isNeedClickEffect = typedArray.getBoolean(R.styleable.TextImageView_clickEffect, false);
        typedArray.recycle();
        setDrawablesSize();
    }

    private void setDrawablesSize() {
        Drawable[] compoundDrawables = getCompoundDrawables();
        for (int i = 0; i < compoundDrawables.length; i++) {
            switch (i) {
                case 0:
                    setDrawableBounds(compoundDrawables[0], mLeftWidth, mLeftHeight);
                    break;
                case 1:
                    setDrawableBounds(compoundDrawables[1], mTopWidth, mTopHeight);
                    break;
                case 2:
                    setDrawableBounds(compoundDrawables[2], mRightWidth, mRightHeight);
                    break;
                case 3:
                    setDrawableBounds(compoundDrawables[3], mBottomWidth, mBottomHeight);
                    break;
                default:

                    break;
            }

        }
        setCompoundDrawables(compoundDrawables[0], compoundDrawables[1], compoundDrawables[2], compoundDrawables[3]);
    }

    private void setDrawableBounds(Drawable drawable, int width, int height) {
        if (drawable != null) {
            double scale = ((double) drawable.getIntrinsicHeight()) / ((double) drawable.getIntrinsicWidth());
            drawable.setBounds(0, 0, width, height);
            Rect bounds = drawable.getBounds();
            //高宽只给一个值时,自适应
            if (bounds.right != 0 || bounds.bottom != 0) {
                if (bounds.right == 0) {
                    bounds.right = (int) (bounds.bottom / scale);
                    drawable.setBounds(bounds);
                }
                if (bounds.bottom == 0) {
                    bounds.bottom = (int) (bounds.right * scale);
                    drawable.setBounds(bounds);
                }
            }

        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean intercept = super.onTouchEvent(event);
        if (isNeedClickEffect) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    setAlpha(0.5f);
                    intercept = true;
                    break;
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    setAlpha(1f);
                    break;
                default:
                    break;
            }
        }
        return intercept;
    }
}

如果对于点击有不一样的效果,可以使用上一篇文章讲的this.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY);来进行设置与改进

但是这样就能满足所有的情况了吗?

然后在网上看到了这样的一种效果图。原文在这里

TextView右下角的小图片在这里就不能实现,但是bbs中提供的解决方案就非常的不错,这里做个备案。

public class MyText extends android.support.v7.widget.AppCompatTextView {


    public MyText(Context context) {
        super(context);
    }

    public MyText(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //得到Drawable集合  分别对应 左上右下
        Drawable[] drawables = getCompoundDrawables();
        if (drawables != null) {
            //获取右边图片
            Drawable drawableRight = drawables[2];
            if (drawableRight != null) {
                //获取文字占用长宽
                int textWidth = (int) getPaint().measureText(getText().toString());
                int textHeight = (int) getPaint().getTextSize();
                //获取图片实际长宽
                int drawableWidth = drawableRight.getIntrinsicWidth();
                int drawableHeight = drawableRight.getIntrinsicHeight();
                //setBounds修改Drawable在View所占的位置和大小,对应参数同样的 左上右下()
                drawableRight.setBounds(((textWidth - getWidth()) / 2), (textHeight - drawableHeight) / 2, ((textWidth - getWidth()) / 2) + drawableWidth, (textHeight + drawableHeight) / 2);
            }
        }
        super.onDraw(canvas);
    }
}

以下是在使用TextView的时候发生的一些问题:

  1. 需要滚动
  2. 需要显示不同的颜色的文本不同的字号
  3. TextView自动换行?
  4. 能不能显示黑字白边?
  5. 能不能增加行间距?字太挤了。

下面对这三个问题分别提出解决方案

  1. 基本上采用这个style就可以,如果你不能引用style可以采用将属性移植到TextView中的方式
   <style name="TextView_Marquee">
        <item name="android:focusable">true</item>
        <item name="android:ellipsize">marquee</item>
        <item name="android:focusableInTouchMode">true</item>
        <item name="android:marqueeRepeatLimit">marquee_forever</item>
        <item name="android:singleLine">true</item>
    </style>

note:

  1. 这里建议将相同的属性都提取出来,有助于增加开发效率,也许刚开始提出来的时候会很麻烦但是,写的多了,你就会发现这样极大的提升了效率,二来也能对于以后设计或者产品想要对样式进行修改可以一改就全都修改了,而不用每个地方都去查找害怕遗漏了。

  2. 如果使用以上的方式在字体超过文本大小的时候还不能滚动,你就需要强制去调用tvName.requestFocus();这个方法了

  3. 还有可能产生不滚动的原因是因为TextView的位置是动态计算出来的,比方说之前遇到的一个问题:

    <LinearLayout
            android:id="@+id/ll_info"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:orientation="horizontal">
    
            <TextView
                android:id="@+id/tv_title"
                android:layout_width="0dp"
                android:textColor="@color/white"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:ellipsize="marquee"
                android:focusable="true"
                android:singleLine="true"
                android:textSize="?attr/text_size_h2" />
    
    
            <TextView
                android:id="@+id/tv_time"
                android:layout_width="@dimen/m150"
                android:layout_height="wrap_content"
                android:layout_marginLeft="@dimen/m5"
                android:gravity="right"
                android:text="0:00/0:00"
                android:textColor="@color/white"
                android:textSize="?attr/text_size_h4" />
    
        </LinearLayout>
    

    如果将tv_time的宽度设置为wrap_content 就会导致不能滚动,因为TextView不能够计算自己的宽度导致不能滚动

  1. Q:需要显示不同的颜色的文本不同的字号

    1. 我记得很久以前查了半天为啥用Html.fromHtml() 怎么实现不同颜色,因为我发现字号是可以的,后来才发现这么写,既影响性能,还不能实现效果,为啥网上的博客都说可以呢,因为Android的设备不一样了啊,现在都是4.4+的天下了。

    2. 当然是使用SpannableString 来进行实现,他可以实现非常多的功能,我的使用时比较的简单

      public static SpannableString getTitleForAudioNameWithArtists(String title, String subTitle) {
              SpannableString spannableString;
              int songColor =  0;
              int artistColor = 0;
              songColor = Color.WHITE;
              artistColor = Color.parseColor("#7FFFFFFF");
      
              String name = String.format(Locale.getDefault(), "%s - %s", title, subTitle);
              spannableString = new SpannableString(name);
              spannableString.setSpan(new ForegroundColorSpan(songColor), 0, title.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
              spannableString.setSpan(new ForegroundColorSpan(artistColor)
                      , title.length() + 1, name.length()
                      , Spanned.SPAN_INCLUSIVE_INCLUSIVE);
      
              return spannableString;
          }
      

      我记得我当初看到很多Api的更换,我会产生一个疑问,为啥当初好好的api要换掉呢?比方说Html就是一个不错的选择,因为本人当初大学的时候学的是javaweb遇到这个东西,理论上来说是有点奇怪的,因为我宁愿写写html的东西上去,现在还换成了SpannableString 这个难用的东西,因为本来我要写一行的东西现在要写好几行?而很多东西真的越变越复杂,同时也越变越安全。

  2. Q:TextView自动换行?

    每当测试为我:为啥明明一行还有很多空间,为啥后面就显示“...”了呢?为啥明明还可以显示几个字的,为啥就换行了呢?我表示我不知道啊,我只是简单调用TextView的api而已。

    文本对齐,详看,参考资料的第4条

  3. Q: 能不能显示黑字白边?

    参看资料的第二条

  4. Q: 能不能增加行间距?字太挤了。

  5. 参看资料的第一条,android提供的有的属性letterSpacing lineSpacingExtra lineSpacingMultiplier

参考资料:

  1. 开发中的 TextView 技巧点整理
  2. 文字描边
  3. TextView emoj的等图标展示以及其他属性
  4. 文本自动换行,整齐排版