Android 获取textView高度的N种方式

来源:互联网 发布:怎么下架淘宝宝贝 编辑:程序博客网 时间:2024/05/16 12:08

很多控件在onCreate()方法中获取到之后并不能马上获取到高度,原因很简单,控件还没有绘制完毕,那么应该在何种时机获取控件的高度,本文以TextView为例重点讨论一下,首先看一个小例子

首先在一个activity的布局中只放一个TextView,然后在布局中规定好文本,宽高都是“wrap_content”,布局文件如下

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="#000000"    tools:context="com.example.administrator.measuretest.TestTextViewActivity">    <TextView        android:id="@+id/tv1"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:textColor="#FFFFFF"        android:textSize="60sp"        android:text="Hello World!" /></RelativeLayout>

现在我们在onStart()方法中获取其高度

public class TestTextViewActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_test_text_view);    }    @Override    protected void onStart() {        super.onStart();        TextView tv1 = (TextView) findViewById(R.id.tv1);        Log.i("Alex","getMeasuredHeight="+tv1.getMeasuredHeight());        Log.i("Alex","getMeasuredState="+tv1.getMeasuredState());        Log.i("Alex","getHeight="+tv1.getHeight());        Log.i("Alex","getWidth="+tv1.getWidth());        Log.i("Alex","getMeasuredWidth="+tv1.getMeasuredWidth());        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)tv1.getLayoutParams();        Log.i("Alex","layoutParams.height="+layoutParams.height+"  layoutParams.width="+layoutParams.width);    }}
08-08 16:01:20.489 7573-7573/com.example.administrator.measuretest I/Alex: getMeasuredHeight=0
08-08 16:01:20.489 7573-7573/com.example.administrator.measuretest I/Alex: getMeasuredState=0
08-08 16:01:20.489 7573-7573/com.example.administrator.measuretest I/Alex: getHeight=0
08-08 16:01:20.489 7573-7573/com.example.administrator.measuretest I/Alex: getWidth=0
08-08 16:01:20.489 7573-7573/com.example.administrator.measuretest I/Alex: getMeasuredWidth=0
08-08 16:01:20.489 7573-7573/com.example.administrator.measuretest I/Alex: layoutParams.height=-2  layoutParams.width=-2


我们发现,不管是getMeasuredHeight,布局的高度还是getHeight都是0,也就是说,getMeasuredHeight并没有进行测量。下面有这样几个解决方案

1、等控件绘制完毕之后进行测量

2、观察者模式

3、调用onMeasure()方法进行测量

首先看第1、2中方法,其实原理是一样的,都是等控件绘制完毕之后进行测量,代码如下

 @Override    protected void onStart() {        super.onStart();        final TextView tv1 = (TextView) findViewById(R.id.tv1);        tv1.post(new Runnable() {            @Override            public void run() {                Log.i("Alex","getMeasuredHeight="+tv1.getMeasuredHeight());                Log.i("Alex","getMeasuredState="+tv1.getMeasuredState());                Log.i("Alex","getHeight="+tv1.getHeight());                Log.i("Alex","getWidth="+tv1.getWidth());                Log.i("Alex","getMeasuredWidth="+tv1.getMeasuredWidth());                RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)tv1.getLayoutParams();                Log.i("Alex","layoutParams.height="+layoutParams.height+"  layoutParams.width="+layoutParams.width);            }        });    }
结果如下

08-08 16:05:13.301 10558-10558/com.example.administrator.measuretest I/Alex: getMeasuredHeight=161
08-08 16:05:13.302 10558-10558/com.example.administrator.measuretest I/Alex: getMeasuredState=0
08-08 16:05:13.302 10558-10558/com.example.administrator.measuretest I/Alex: getHeight=161
08-08 16:05:13.302 10558-10558/com.example.administrator.measuretest I/Alex: getWidth=647
08-08 16:05:13.302 10558-10558/com.example.administrator.measuretest I/Alex: getMeasuredWidth=647
08-08 16:05:13.303 10558-10558/com.example.administrator.measuretest I/Alex: layoutParams.height=-2  layoutParams.width=-2

从这里可以看出,getHeight,getMeasuredHeight都已经有值了,也就是说onMeasure方法已经不知道被谁调用过了,但是 layoutParams.height依然是-2,其实这样非常的正确,因为 layoutParams.height = -2就意味着是wrap_content

观察者模式:

首先,我们观察这个控件被放到window上时候的情况,也就是使用OnAttachStateChangeListener,方法如下

final TextView tv1 = (TextView) findViewById(R.id.tv1);        tv1.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {            @Override            public void onViewAttachedToWindow(View v) {                Log.i("Alex","控件被添加到window上拉");                Log.i("Alex","getMeasuredHeight="+tv1.getMeasuredHeight());                Log.i("Alex","getMeasuredState="+tv1.getMeasuredState());                Log.i("Alex","getHeight="+tv1.getHeight());                Log.i("Alex","getWidth="+tv1.getWidth());                Log.i("Alex","getMeasuredWidth="+tv1.getMeasuredWidth());                RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)tv1.getLayoutParams();                Log.i("Alex","layoutParams.height="+layoutParams.height+"  layoutParams.width="+layoutParams.width);            }            @Override            public void onViewDetachedFromWindow(View v) {                Log.i("Alex","控件被window移除了");            }        });
结果如下:

08-08 16:10:43.030 14649-14649/com.example.administrator.measuretest I/Alex: 控件被添加到window上拉
08-08 16:10:43.030 14649-14649/com.example.administrator.measuretest I/Alex: getMeasuredHeight=0
08-08 16:10:43.030 14649-14649/com.example.administrator.measuretest I/Alex: getMeasuredState=0
08-08 16:10:43.030 14649-14649/com.example.administrator.measuretest I/Alex: getHeight=0
08-08 16:10:43.030 14649-14649/com.example.administrator.measuretest I/Alex: getWidth=0
08-08 16:10:43.031 14649-14649/com.example.administrator.measuretest I/Alex: getMeasuredWidth=0
08-08 16:10:43.031 14649-14649/com.example.administrator.measuretest I/Alex: layoutParams.height=-2  layoutParams.width=-2

从这里可以看出来,当TextView被attach到window上的时候,还是没能拿到宽高,所以不能监听这个事件

注:onViewDetachedFromWindow在Activity的onPause,onStop,onDestroy均不会调用


那让我们再来试试OnLayoutChangeListener

final TextView tv1 = (TextView) findViewById(R.id.tv1);        tv1.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {            @Override            public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {                Log.i("Alex","控件布局改变啦");                Log.i("Alex","getMeasuredHeight="+tv1.getMeasuredHeight());                Log.i("Alex","getMeasuredState="+tv1.getMeasuredState());                Log.i("Alex","getHeight="+tv1.getHeight());                Log.i("Alex","getWidth="+tv1.getWidth());                Log.i("Alex","getMeasuredWidth="+tv1.getMeasuredWidth());                RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)tv1.getLayoutParams();                Log.i("Alex","layoutParams.height="+layoutParams.height+"  layoutParams.width="+layoutParams.width);            }        });

08-08 16:16:27.261 18112-18112/com.example.administrator.measuretest I/Alex: 控件布局改变啦
08-08 16:16:27.262 18112-18112/com.example.administrator.measuretest I/Alex: getMeasuredHeight=161
08-08 16:16:27.262 18112-18112/com.example.administrator.measuretest I/Alex: getMeasuredState=0
08-08 16:16:27.262 18112-18112/com.example.administrator.measuretest I/Alex: getHeight=161
08-08 16:16:27.262 18112-18112/com.example.administrator.measuretest I/Alex: getWidth=647
08-08 16:16:27.262 18112-18112/com.example.administrator.measuretest I/Alex: getMeasuredWidth=647
08-08 16:16:27.262 18112-18112/com.example.administrator.measuretest I/Alex: layoutParams.height=-2  layoutParams.width=-2
08-08 16:16:27.330 18112-18112/com.example.administrator.measuretest I/Alex: 控件布局改变啦
08-08 16:16:27.330 18112-18112/com.example.administrator.measuretest I/Alex: getMeasuredHeight=161
08-08 16:16:27.330 18112-18112/com.example.administrator.measuretest I/Alex: getMeasuredState=0
08-08 16:16:27.330 18112-18112/com.example.administrator.measuretest I/Alex: getHeight=161
08-08 16:16:27.330 18112-18112/com.example.administrator.measuretest I/Alex: getWidth=647
08-08 16:16:27.330 18112-18112/com.example.administrator.measuretest I/Alex: getMeasuredWidth=647
08-08 16:16:27.330 18112-18112/com.example.administrator.measuretest I/Alex: layoutParams.height=-2  layoutParams.width=-2


没错,这个布局改变打印了两编,说明调用了两次。而且两次测量的结果都一样

另外还有一种监听器:ViewTreeObserver,代码跟上面的差不多,结果也一样,也是打印了两遍

final TextView tv1 = (TextView) findViewById(R.id.tv1);        tv1.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {            @Override            public void onGlobalLayout() {                Log.i("Alex","控件布局改变啦");                Log.i("Alex","getMeasuredHeight="+tv1.getMeasuredHeight());                Log.i("Alex","getMeasuredState="+tv1.getMeasuredState());                Log.i("Alex","getHeight="+tv1.getHeight());                Log.i("Alex","getWidth="+tv1.getWidth());                Log.i("Alex","getMeasuredWidth="+tv1.getMeasuredWidth());                RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)tv1.getLayoutParams();                Log.i("Alex","layoutParams.height="+layoutParams.height+"  layoutParams.width="+layoutParams.width);            }        });


上面都是等绘制完毕后被动测量,但是有时候我们等不了这么久,下面我们来试试主动测量的方法

主动测量方式1:使用View.Measure(int,int)函数

这个函数两个参数都是有父view传递过来的,也就是代表了父view的大小。其实说大小不太对,应该说是建议“规格”。他有两部分组成,第一部分:高16位表示MODE,定义在MeasureSpec类中,有三种类型,

MeasureSpec.EXACTLY:表示确定大小,

MeasureSpec.AT_MOST:表示最大大小, 

MeasureSpec.UNSPECIFIED:不确定。

第二部分:低16位表示size,既父view的大小,这就是为什么,我们在重写onmeasure方法是需要:int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec);这样调用,因为MeasureSpec知道怎么读取。

protected void onStart() {        super.onStart();        final TextView tv1 = (TextView) findViewById(R.id.tv1);        tv1.measure(View.MeasureSpec.getMode(0,0);        Log.i("Alex","getSize="+ View.MeasureSpec.getSize(View.MeasureSpec.EXACTLY));        Log.i("Alex","getMeasuredHeight="+tv1.getMeasuredHeight());        Log.i("Alex","getMeasuredState="+tv1.getMeasuredState());        Log.i("Alex","getHeight="+tv1.getHeight());        Log.i("Alex","getWidth="+tv1.getWidth());        Log.i("Alex","getMeasuredWidth="+tv1.getMeasuredWidth());        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)tv1.getLayoutParams();        Log.i("Alex","layoutParams.height="+layoutParams.height+"  layoutParams.width="+layoutParams.width);    }
结果是:

08-08 16:56:22.881 10543-10543/com.example.administrator.measuretest I/Alex: getMeasuredHeight=161
08-08 16:56:22.881 10543-10543/com.example.administrator.measuretest I/Alex: getMeasuredState=0
08-08 16:56:22.881 10543-10543/com.example.administrator.measuretest I/Alex: getHeight=0
08-08 16:56:22.881 10543-10543/com.example.administrator.measuretest I/Alex: getWidth=0
08-08 16:56:22.881 10543-10543/com.example.administrator.measuretest I/Alex: getMeasuredWidth=647
08-08 16:56:22.881 10543-10543/com.example.administrator.measuretest I/Alex: layoutParams.height=-2  layoutParams.width=-2

从这里可以看出getMeasuredHeight,和getMeasuredWidth均有值,getHeight,getWidth没有值,说明测量是合适的,只是没有画出来

注意,这个方法所获取的width和height可能跟实际draw后的不一样。官方文档解释了不同的原因:
View的大小由width和height决定。一个View实际上同时有两种width和height值。
第一种是measure width和measure height。他们定义了view想要在父View中占用多少width和height(详情见Layout)。measured height和width可以通过getMeasuredWidth() 和 getMeasuredHeight()获得。
第二种是width和height,有时候也叫做drawing width和drawing height。这些值定义了view在屏幕上绘制和Layout完成后的实际大小。这些值有可能跟measure width和height不同。width和height可以通过getWidth()和getHeight获得

measure完后,并不会实际改变View的尺寸,需要调用View.layout方法去进行布局。


textView在setText()执行之后也不能马上通过getHeight得到当前textView的高度,

更高级的,如果我们不仅需要textView的高度,还需要文本所占的行数,那么应该怎么做?

如果textView绘制完毕,可以通过TextView.getLineCount()获取当前文本所占得行数,那在没有绘制的情况下怎么办?

textView是基于一个叫StaticLayout的布局来绘制的,这个布局就有很多android开发者喜闻乐见的api,比如获取高度和某行字符下标什么的。使用这个布局,开发者不需要等到空间绘制完毕之后才能得到绘制的行数和某一行最后一个字符,想象一下如果给某个人一段话和一张纸,让他在不写字的情况下迅速的说出自己这段话究竟能写多少行也是很难的,让他在写之前就告诉你自己在写到某一行的结尾的字符那就更难,而StaticLayout很容易就能做到这一点,而且非常有效率。

另一方面,使用StaticLayout代替textView也是很好的一个选择,StaticLayout本身绘制的速度就要比textView快很多,尤其是在listVIew这种滑动频繁的场景下,StaticLayout拥有更流畅的表现

下面是一个封装了StaticLayout的一个控件,不仅可以实现普通textView的功能,而且能做一些在不绘制的情况下计算行数和字符的方法。

import android.content.Context;import android.graphics.Canvas;import android.text.Layout;import android.text.Layout.Alignment;import android.text.StaticLayout;import android.text.TextPaint;import android.util.Log;import android.view.View;import android.widget.TextView;public class AlxTextView extends View {    TextPaint textPaint = null;    StaticLayout staticLayout = null;    int lineCount = 0;    int width = 720;    int height = 0;    String txt = null;    public AlxTextView(Context context, String content,int width,float textSize) {        super(context);        textPaint = new TextPaint();        textPaint.setAntiAlias(true);        textPaint.setTextSize(textSize);        txt = content;        this.width = width;        staticLayout = new StaticLayout(txt, textPaint, width, Alignment.ALIGN_NORMAL, 1, 0, false);        height = staticLayout.getHeight();    }    public int getLayoutHeight(){        return height;    }    public int getLineCount(){        return staticLayout.getLineCount();    }    @Override    protected void onDraw(Canvas canvas) {        staticLayout.draw(canvas);        super.onDraw(canvas);    }    /**     * 在不绘制textView的情况下算出textView的高度,并且根据最大行数得到应该显示最后一个字符的下标,请在主线程顺序执行,第一个返回值是最后一个字符的下标,第二个返回值是高度     * @param textView     * @param content     * @param width     * @param maxLine     * @return     */    public static int[] measureTextViewHeight(TextView textView, String content, int width, int maxLine){        Log.i("Alex","宽度是"+width);        TextPaint textPaint  = textView.getPaint();        StaticLayout staticLayout = new StaticLayout(content, textPaint, width, Layout.Alignment.ALIGN_NORMAL, 1, 0, false);        int[] result = new int[2];        if(staticLayout.getLineCount()>maxLine) {//如果行数超出限制            int lastIndex = staticLayout.getLineStart(maxLine) - 1;            result[0] = lastIndex;            result[1] = new StaticLayout(content.substring(0, lastIndex), textPaint, width, Layout.Alignment.ALIGN_NORMAL, 1, 0, false).getHeight();            return result;        }else {//如果行数没有超出限制            result[0] = -1;            result[1] = staticLayout.getHeight();            return result;        }    }}


看到这,在TextView没有绘制完之前获取textView的高度,行数,随便哪一行的第一个和最后一个字符以及他们下标都可以轻松的拿到了。


刚才讨论的都是只有TextView一个控件的情况,如果包裹一个控件会怎样的,把布局改成下面的试一试

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="#000000"    tools:context="com.example.administrator.measuretest.TestTextViewActivity">    <LinearLayout        android:id="@+id/ll1"        android:layout_width="200dp"        android:layout_height="100dp">        <TextView            android:id="@+id/tv1"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:textColor="#FFFFFF"            android:textSize="60sp"            android:text="Hello World!" />    </LinearLayout></RelativeLayout>


protected void onStart() {        super.onStart();        final LinearLayout ll1 = (LinearLayout) findViewById(R.id.ll1);        ll1.measure(0,0);        Log.i("Alex","getMeasuredHeight="+ll1.getMeasuredHeight());        Log.i("Alex","getMeasuredState="+ll1.getMeasuredState());        Log.i("Alex","getHeight="+ll1.getHeight());        Log.i("Alex","getWidth="+ll1.getWidth());        Log.i("Alex","getMeasuredWidth="+ll1.getMeasuredWidth());        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)ll1.getLayoutParams();        Log.i("Alex","layoutParams.height="+layoutParams.height+"  layoutParams.width="+layoutParams.width);    }



08-08 19:48:01.107 12288-12288/com.example.administrator.measuretest I/Alex: getMeasuredHeight=240
08-08 19:48:01.107 12288-12288/com.example.administrator.measuretest I/Alex: getMeasuredState=0
08-08 19:48:01.108 12288-12288/com.example.administrator.measuretest I/Alex: getHeight=0
08-08 19:48:01.108 12288-12288/com.example.administrator.measuretest I/Alex: getWidth=0
08-08 19:48:01.108 12288-12288/com.example.administrator.measuretest I/Alex: getMeasuredWidth=972

08-08 19:48:01.108 12288-12288/com.example.administrator.measuretest I/Alex: layoutParams.height=300  layoutParams.width=600

非常有意思的事情发生了,因为LinerLayout的宽高被写死,所以layoutParams.height和layoutParams.width都是根据dp转换出来的px值,但是getMeasuredWidth和getMeasuredHeight都要比布局要大的多,而且这个宽度明显过大了,布局的宽度才是600,measure的宽度居然就变成了972,而且measured height反而要小了很多,虽然里面的textView高度超过了linearLayout

从实际上来说,layoutParams.height和layoutParams.width的值是正确的,测量的是不正确的,至于为什么不正确请向后看

下面看一下wrap_content的linearlayout的表现

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="#000000"    tools:context="com.example.administrator.measuretest.TestTextViewActivity">    <LinearLayout        android:id="@+id/ll1"        android:layout_width="wrap_content"        android:layout_height="wrap_content">        <TextView            android:id="@+id/tv1"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:textColor="#FFFFFF"            android:textSize="60sp"            android:text="Hello World!" />    </LinearLayout></RelativeLayout>

08-08 19:55:15.749 18342-18342/com.example.administrator.measuretest I/Alex: getMeasuredHeight=240
08-08 19:55:15.749 18342-18342/com.example.administrator.measuretest I/Alex: getMeasuredState=0
08-08 19:55:15.749 18342-18342/com.example.administrator.measuretest I/Alex: getHeight=0
08-08 19:55:15.749 18342-18342/com.example.administrator.measuretest I/Alex: getWidth=0
08-08 19:55:15.749 18342-18342/com.example.administrator.measuretest I/Alex: getMeasuredWidth=972
08-08 19:55:15.750 18342-18342/com.example.administrator.measuretest I/Alex: layoutParams.height=-2  layoutParams.width=-2

现在就真相大白了,LinearLayout进行measure的时候是默认自己是wrap_content,也就是说子控件要全部显示,如果LinearLayout比子控件还要小,那么measure的结果就是错误的,此时要用LayoutParameters的结果

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 苹果6p照相模糊怎么办 相框玻璃碎了怎么办 word文档加密后忘记密码怎么办 手机wps密码忘了怎么办 苹果手表忘了密码怎么办 苹果系统忘了密码怎么办 ps画板建小了怎么办 wps表格密码忘了怎么办 word文档变成虚的怎么办 wps论文中表格跨页怎么办 word文档复制过来有边框怎么办 wps表格跨页断开怎么办 锅的铆钉老是松怎么办 文胸不知道怎么染色了怎么办 未后的信息我该怎么办? 做leep手术后大出血怎么办 眼线笔出不了水怎么办 手机字体变成空心字怎么办 平安树树枝黑了怎么办 柳树被虫钻洞了怎么办 柳树叶子上有虫子怎么办 小金鱼翻肚皮了怎么办 秋天树叶没了小鸟怎么办 去国外旅游不会英语怎么办 橡皮树长了2米高怎么办 榕树盆景长的高怎么办? 2岁宝宝看书弯腰低头怎么办 excel表格打开很慢怎么办 3d模型有红线框怎么办 电视页面加载时错误怎么办 投屏显示加载视频错误怎么办 word遇到问题需要关闭怎么办 画眼线看不出来怎么办 14岁眼皮很松怎么办啊 ps存不了psd格式怎么办 花草上有白色物怎么办 ps抠出来有白边头发怎么办 脸上结痂掉了有红印怎么办 海棠花瓣干枯怎么办茎变软 微信上的图片打不开怎么办 口红吊兰老掉叶子怎么办