Android布局之View.measure()动态量取高度并设置布局--(例:动态计算评论高度并显示)

需求是这样的:

在应用程序的详情介绍时,有评论的版块,该页评论最多显示5条,而每条最大字数是140个字符,每条评论可能根据字数不同,所占据的高度也不一样,如有的是1行,有的是2、3行,且评论可以翻页。

图片效果如下:

如何解决这样的问题呢?

首先必须知道的是评论控件不要固定不变,而是需要动态计算并动态添加到显示面板中的。

下面通过实例来说一下。

1.定义布局

定义布局的时候,可以用AbsoluteLayout,因为高度是动态量取的,所以具体坐标是可以求得的。参考如下:

 <AbsoluteLayout
        android:layout_width="1386px"
        android:layout_height="wrap_content"
        android:layout_marginTop="60px"
        android:id="@+id/comment_content">

 </AbsoluteLayout>

该布局文件宽度设置不变,高度根据内容填充。只需将这个布局方在需要显示评论内容的地方。

2.显示数据

显示数据,需要根据数据来计算高度,首先把数据设置到控件中,然后通过View.measure(int
widthMeasureSpec, int heightMeasureSpec)量取高度,并为该View设置坐标。参考代码:

<span style="font-size:14px;">/**
	 * 初始化、动态计算高度
	 */
	public void initCommentView() {
		record_temp = new CommentRecord();
		mPositionRecord = new ArrayList<Integer>();
		mHeightRecord = new ArrayList<Integer>();
		int currentHeight = 0;

		int maxNum = cachedComments.size();
		int sum = 0;
		for (int i = comment_begin_index; i < maxNum; i++) {
			if (null != mCommentCache && !mCommentCache.empty()) {
				comment_temp = mCommentCache.pop();
			} else {
				comment_temp = new CommentSimpleView(mContext);
			}
			mCommentUI.add(comment_temp);

			comment_temp.setData(cachedComments.get(i));
			comment_temp.measure(width, height);
			if (MAX_COMMENT_HEIGHT > currentHeight) {
				comment_content.addView(
						comment_temp,
						new AbsoluteLayout.LayoutParams(1386, comment_temp
								.getMeasuredHeight(), 0, FIRST_COMMENT_INTERVAL
								+ currentHeight));
				mPositionRecord.add(FIRST_COMMENT_INTERVAL + currentHeight);
				mHeightRecord.add(comment_temp.getMeasuredHeight());
				currentHeight = currentHeight
						+ comment_temp.getMeasuredHeight();
				comment_end_index++;
				sum++;
				if (sum < 5) {
					comment_temp.show_Divider();
				} else if (sum == 5) {
					comment_temp.hide_Divider();
				}
			}
			if (MAX_COMMENT_HEIGHT < currentHeight) {
				compareHeight = 1;
				isEnd = false;
				RelativeLayout.LayoutParams rl = (LayoutParams) comment_content
						.getLayoutParams();
				rl.setMargins(0, 60, 0, 0);
				rl.width = 1386;
				rl.height = MAX_COMMENT_HEIGHT+20;
				comment_content.setLayoutParams(rl);
				break;
			}
			if (MAX_COMMENT_HEIGHT == currentHeight) {
				compareHeight = 0;
				if (maxNum == comment_end_index) {
					isEnd = true;
					comment_pagedown.setFocusStatus(false);
				} else {
					isEnd = false;
				}
			}

		}
		record_temp.setHeightRecord(mHeightRecord);
		record_temp.setPositionRecord(mPositionRecord);
		record_temp.setBegin_index(comment_begin_index);
		record_temp.setEnd_index(comment_end_index);
		if (MAX_COMMENT_HEIGHT > currentHeight) {
			isEnd = true;
			comment_pagedown.setFocusStatus(false);
		}

	}</span>

其中全局的宽、高定义如下:

int width = MeasureSpec.makeMeasureSpec(1386, MeasureSpec.EXACTLY);

int height = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);

3.翻页动态计算实现

/**
	 * 点击下一页时,判断是否有空间剩余,若有剩余则用上一页数据补充,false:无剩余
	 * @return
	 */
	private boolean hasExtraSpace() {
		if(null != cachedComments && cachedComments.size()-comment_end_index >=5){ //剩下的评论大于5条
			return false;
		}
		int beginIndex = 0;
		if(1 == compareHeight){
			beginIndex = comment_end_index;
		}else if(0 == compareHeight){
			beginIndex = comment_end_index+1;
		}
		int maxSize = cachedComments.size();
		int HeightSum = 0;
		for(int i = beginIndex;i<maxSize;i++){
			comment_temp = new CommentSimpleView(mContext);
			comment_temp.setData(cachedComments.get(i));
			comment_temp.measure(width, height);
			HeightSum += comment_temp.getMeasuredHeight();
			if(MAX_COMMENT_HEIGHT <= HeightSum){
				return false;
			}

		}
		lastPageHeight = HeightSum;
		return true;
	}
	/**
	 * 最后一页不满一屏,为倒数第一页,从最后一条倒序排列,
	 */
	public void showLastPage(){
		int lastCommentNum = cachedComments.size() - comment_end_index;
		int copy_last_index = comment_end_index;
		while(128 <= MAX_COMMENT_HEIGHT - lastPageHeight){
			lastCommentNum ++;
			comment_temp = new CommentSimpleView(mContext);
			comment_temp.setData(cachedComments.get(--copy_last_index));
			comment_temp.measure(width, height);
			lastPageHeight+=comment_temp.getMeasuredHeight();
		}
		if(MAX_COMMENT_HEIGHT < lastPageHeight){
			lastCommentNum -- ;
			lastPageHeight -= comment_temp.getMeasuredHeight();
			copy_last_index ++;
		}
		int sum = cachedComments.size();
		int current_H = FIRST_COMMENT_INTERVAL + MAX_COMMENT_HEIGHT - lastPageHeight;
		for(int i= copy_last_index;i<sum;i++){

			if(null != mCommentCache && !mCommentCache.empty()){
				comment_temp = mCommentCache.pop();
			}else{
				comment_temp = new CommentSimpleView(mContext);
			}
			mCommentUI.add(comment_temp);
			comment_temp.setData(cachedComments.get(i));
			comment_temp.measure(width, height);
			comment_content.addView(comment_temp, new AbsoluteLayout.LayoutParams(1386, comment_temp.getMeasuredHeight(), 0, current_H));
			current_H = current_H + comment_temp.getMeasuredHeight();
			if(i == sum -1){
				comment_temp.hide_Divider();
			}else{
				comment_temp.show_Divider();
			}
		}
		isEnd = true;
		comment_pagedown.setFocusStatus(false);
	}
	/**
	 * 点击上一页,出栈,恢复数据并显示
	 */
	public void showPageUp(){
		if(mCommentRecord.empty()){
			Toast.makeText(mContext, "已是第一页", Toast.LENGTH_SHORT).show();
			return;
		}
		record_temp = mCommentRecord.pop();
		int begin = record_temp.getBegin_index();
		int end = record_temp.getEnd_index();
		List<Integer> position = record_temp.getPositionRecord();
		List<Integer> height = record_temp.getHeightRecord();
		if(null == position || null == height){
			return;
		}
		int m = 0;
		for(int i= begin;i<=end;i++){
			if(null != mCommentCache && !mCommentCache.empty()){
				comment_temp = mCommentCache.pop();
			}else{
				comment_temp = new CommentSimpleView(mContext);
			}
			mCommentUI.add(comment_temp);
			comment_temp.setData(cachedComments.get(i));
			comment_content.addView(comment_temp, new AbsoluteLayout.LayoutParams(1386,height.get(m), 0, position.get(m)));
			m++;
			if(5 == m){
				comment_temp.hide_Divider();
			}else{
				comment_temp.show_Divider();
			}
		}
		isEnd = false;
		comment_begin_index = begin;
		comment_end_index = end;
	}

4.点击上一页、下一页事件处理

public class comment_pageup_click implements OkButtonViewClick {

		@Override
		public void onOkButtonViewClick() {
			if(1 == currentPage){
//				Toast.makeText(mContext, "已是第一页", Toast.LENGTH_SHORT).show();
				comment_pageup.setFocusStatus(false);
				return;
			}else{
				currentPage = currentPage - 1;
				comment_content.removeAllViews();
				while (!mCommentUI.isEmpty()) {
					mCommentCache.push(mCommentUI.remove(0));
				}
				showPageUp();
				comment_pagedown.setFocusStatus(true);
			}

		}
	}

	class comment_pagedown_click implements OkButtonViewClick {

		@Override
		public void onOkButtonViewClick() {
			if(isEnd){
//				Toast.makeText(mContext, "已是最后一页", Toast.LENGTH_SHORT).show();
				comment_pagedown.setFocusStatus(false);
				return;
			}
			else if(!isEnd){ //不到最后一页
				if(!hasExtraSpace()){ //下一页无剩余空间,即该页不是倒数第二页
					mCommentRecord.push(record_temp);
					currentPage = currentPage + 1;
					if(1 == compareHeight){
						comment_begin_index = comment_end_index;
						comment_end_index --;
					}else{
						comment_begin_index = comment_end_index+1;
					}
					if(currentPage%4 ==0){ //预加载数据
						ParserHelper.getParserHelper().requestComment("1", "30", "9", callback);
					}
					comment_content.removeAllViews();
					while (!mCommentUI.isEmpty()) {
						mCommentCache.push(mCommentUI.remove(0));
					}
					initCommentView();
				}else {    //下一页有剩余空间,即该页为倒数第二页
					mCommentRecord.push(record_temp);
					currentPage = currentPage + 1;
					comment_content.removeAllViews();
					while (!mCommentUI.isEmpty()) {
						mCommentCache.push(mCommentUI.remove(0));
					}
					showLastPage();
				}
				comment_pageup.setFocusStatus(true);
			}
		}
	}

5.下一页点击时保存状态,用作恢复。(用栈保存,入栈出栈)

package com.helios.module.commentData;

import java.util.List;

public class CommentRecord {
		int begin_index;
		int end_index;
		List <Integer> positionRecord;
		List <Integer> heightRecord;

		public CommentRecord() {
			super();
		}
		public int getBegin_index() {
			return begin_index;
		}
		public void setBegin_index(int begin_index) {
			this.begin_index = begin_index;
		}
		public int getEnd_index() {
			return end_index;
		}
		public void setEnd_index(int end_index) {
			this.end_index = end_index;
		}
		public List<Integer> getPositionRecord() {
			return positionRecord;
		}
		public void setPositionRecord(List<Integer> positionRecord) {
			this.positionRecord = positionRecord;
		}
		public List<Integer> getHeightRecord() {
			return heightRecord;
		}
		public void setHeightRecord(List<Integer> heightRecord) {
			this.heightRecord = heightRecord;
		}
}

6.总结语

动态计算,动态设置布局还是挺常用的。

其中的关键就是,measure()方法的使用,设置好数据就可以measure(),量好高度后,再设置到显示面板中,即调用:

ViewGroup.addView(View child,
LayoutParams params)

第一次写动态布局,写的不好,希望大家勿喷,有疑问欢迎留言讨论,大家互相学习,慢慢进步。

时间: 2024-08-28 22:28:39

Android布局之View.measure()动态量取高度并设置布局--(例:动态计算评论高度并显示)的相关文章

Android View measure流程详解

Android View measure流程详解 Android中View绘制的流程包括:measure(测量)->layout(布局)->draw(绘制). 因为Android中每个View都占据了一块矩形的空间,当我们要在屏幕上显示这个矩形的View的时候 首先我们需要知道这个矩形的大小(宽和高)这就对应了View的measure流程. 有了View的宽和高,我们还需要知道View左上角的起点在哪里,右下角的终点在哪里,这就对应了View的layout流程. 当矩形的区域在屏幕上确定之后,

Android View measure (二) 自定义UI控件measure相关

本篇模拟三个角色:Android 架构师-小福.Android  控件开发工程师-小黑. Android 开发工程师-小白,下面按照三个角色不同角度分析measure过程. 小福负责分享: measure的本质 measure代码流程 onMeasure方法与MeasureSpec 提出问题 小黑负责分享: 布局控件开发中覆写Measure例子 - ok 从遇到的一个异常说起 什么时候需要覆写onMeaure? - ok view.getWidth与view.getMeasureWidth区别

android 用java动态设置布局(增添删除修改布局)

XML对开发者来说十分的方便,不仅使用起来简单,而且能够及时调试,修改界面之后马上能看到效果. Java设置布局不具有这个优势.但是java却可以动态对布局进行操作,这是xml所做不到的.笔者认为,新手索要掌握的java动态设置布局主要有两点,一方面是对布局的属性进行修改,另一方面是增添和删除控件. 首先说一下动态设置布局在项目中的应用,拿高德地图举个例子,如下图:    我们可以看到,高德地图的默认界面与点击地图之后的界面是不一样的,上面同样的控件在layout中的位置也不一样,这个用xml便

Android View measure (一) 流程分析

本篇模拟三个角色:Android 架构师-小福.Android  控件开发工程师-小黑. Android 开发工程师-小白,下面按照三个角色不同角度分析measure过程. 小福负责分享: measure的本质 - ok measure代码流程 - 分析FrameLayout.onMeasure onMeasure方法与MeasureSpec - ok 提出问题 Android 架构师-小福的分享 一.Measure本质 小福:我今天分享是的measure架构设计相关的,先问一个问题,measu

Android View measure (五) 支持margin属性,从一个异常说起

先来看下代码 一.查看夏目 1. 自定义控件 public class CustomViewGroup extends ViewGroup { ...... @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 遍历所有子视图,进行measure操作 for (int i =

Android开发:View的几种布局及实践

引言 View的布局显示方式有下面几种:线性布局(Linear Layout).相对布局(Relative Layout).表格布局(Table Layout).网格视图(Grid View).标签布局(Tab Layout).列表视图(List View).绝对布局(AbsoluteLayout).本文虽然是介绍View的布局方式,但不仅仅是这样,其中涉及了很多小的知识点,绝对能给你带来Android大餐! 本文的主要内容就是分别介绍以上视图的七种布局显示方式效果及实现,大纲如下: 1.Vie

android新手关于左右滑动的问题,布局把&lt;android.support.v4.view.ViewPager/&gt;&lt;ImageView/&gt; 放在上面就不行了。

============问题描述============ main.xml代码: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:umadsdk="http://schemas.android.com/apk/res/com.Love

Android中将布局文件/View添加至窗口过程分析 ---- 从setContentView()谈起

本文主要内容是讲解一个视图View或者一个ViewGroup对象是如何添加至应用程序窗口中的.下文中提到的窗口可泛指我们能看到的界面,包括一个Activity呈现的界面(我们可以将之理解为应用程序窗口),一个Dialog,一个Toast,一个Menu菜单等. 首先对相关类的作用进行一下简单介绍: Window 类   位于 /frameworks/base/core/java/android/view/Window.java 说明:该类是一个抽象类,提供了绘制窗口的一组通用API.可以将之理解为

Android精通:View与ViewGroup,LinearLayout线性布局,RelativeLayout相对布局,ListView列表组件

UI的描述 对于Android应用程序中,所有用户界面元素都是由View和ViewGroup对象构建的.View是绘制在屏幕上能与用户进行交互的一个对象.而对于ViewGroup来说,则是一个用于存放其他View和ViewGroup对象的布局容器! Android为我们提供了View和ViewGroup的两个子类的集合,提供常用的一些输入控件(比如按钮,图片和文本域等)和各种各样的布局模式(比如线程布局,相对布局,绝对布局,帧布局,表格布局等). 用户界面布局 在你APP软件上的,用户界面上显示