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的结果
- Android 获取textView高度的N种方式
- 获取TextView的高度
- Android TextView的高度
- 获取TextView的内容高度
- android获取textview展开渲染后的高度
- Android下在onCreate方法中获取TextView的高度
- 获取android顶部状态栏高度的两种方式
- Android 获取View的高度或TextView的行数, 实现自适应的textview
- 获取View高度的几种方式
- 几种获取高度的方式
- Android 在OnCreate获取需要控件的高度,宽度,textview的行数等等
- Android 获取控件的宽度和高度的几种方式
- Android在onCreate()方法中动态获取TextView控件的高度
- 天天记录 - Android TextView setMaxLines后获取完整高度
- 老邓的android学习笔记(2)-Android 获取屏幕的分辨率的N种方式
- Android Textview 高度问题
- TextView 精确获取各种高度
- Android获取TextView的行数
- js拖拽(二)仿iGoogle自定义首页模块拖拽
- Ubuntu下安装boost库
- 《软件架构设计》学习笔记--7--6大步骤3:确定关键需求
- Spring总结
- [4] OFDM符号的生成与解析
- Android 获取textView高度的N种方式
- Centos 6.4源码安装mysql-5.6.28.tar.gz
- LeetCode *** 13. Roman to Integer
- PAT BASIC 1004
- 图标
- Python调用postgreSQL(使用psycopg2)
- Java设计模式之初学者笔记——设计模式基础讲解
- 回调函数解析
- vc合并CDockPane视图窗口