Android开发之深入理解RectF和Rect之间的区别

2017-01-15 22:11 阅读 6,784 次 评论 0 条
版权声明:本文著作权归TeachCourse所有,未经许可禁止转载,谢谢支持!
转载请注明出处:http://teachcourse.cn/2268.html

摘要:

Rect是“Rectangle”简写的英文单词,中文意思“矩形或长方形”,Rect对象持有一个矩形的四个integer坐标值,RectF对象持有一个矩形的四个float坐标值,这是两者最大的区别。从实现的方式上看,Rect是一个final类实现Parcelable接口,RectF是一个普通类实现Parcelable接口,Rect和RectF除了记录的坐标数据类型不一样外,两个类提供的方法大体上都是一样的,比如:equals()比较两个矩形的坐标是否一样、isEmpty()判断矩形是否为空、width()获取矩形的宽、height()获取矩形的高、centerX()获取矩形水平中心点坐标、centerY()获取矩形垂直中心点坐标等等

封装Rect和RectF类的目的,使用Rect或RectF对象保存坐标数据,使用封装的方法方便计算或比较两个矩形的关系。

每一个View子类或者控件在屏幕中占据一块矩形区域,用于绘制和事件处理,Rect或RectF通常被用于记录这样的一块矩形区域,TeachCourse习惯将left、top、right和bottom划分为左上角坐标(left,top)和右下角坐标(right,bottom)

一、Rect或RectF基础总结

RectF和Rect类提供的方法大部分是一样的,这里着重详细介绍Rect类的使用,在布局文件放置一个TextView,然后获取控件的矩形局域的坐标,使用Rect对象保存坐标的数据,最后调用Rect提供的一些方法计算矩形的宽、高,演示Rect类的简单使用。

17-0115-2149-rect-rectf-different

  1. final TextView textView = new TextView(this);  
  2. textView.setText("显示Rect存储坐标数据");  
  3. /** 
  4.  * 设置TextView的宽度和高度,最后计算TextView的左上角和右下角的坐标 
  5.  */  
  6. textView.setLayoutParams(new ViewGroup.LayoutParams(400400));  
  7. textView.setBackgroundColor(Color.parseColor("#00BFFF"));  
  8. textView.setGravity(Gravity.CENTER);  
  9. textView.setOnClickListener(new View.OnClickListener() {  
  10.     @Override  
  11.     public void onClick(View v) {  
  12.     int top = v.getTop();  
  13.     int left = v.getLeft();  
  14.     int right = v.getRight();  
  15.     int bottom = v.getBottom();  
  16.     /** 
  17.      * 将TextView相对父控件的坐标保存在Rect对象 
  18.      */  
  19.     mRect.left = left;  
  20.     mRect.right = right;  
  21.     mRect.top = top;  
  22.     mRect.bottom = bottom;  
  23.   
  24.     textView.setText(mRect.toShortString());  
  25.     }  
  26. });  
  1. final Button button = new Button(this);  
  2. /** 
  3.  * 设置button的宽度和高度,最后计算矩形局域的宽和高 
  4.  */  
  5. ViewGroup.MarginLayoutParams params=new ViewGroup.MarginLayoutParams(800300);  
  6. /** 
  7.  * 设置button的margin属性值 
  8.  */  
  9. params.setMargins(100,DensityUtil.dip2px(this,100),100,100);  
  10. button.setLayoutParams(params);  
  11. button.setText("计算Rect坐标");  
  12. button.setBackgroundColor(Color.parseColor("#7FFFAA"));  
  13. button.setOnClickListener(new View.OnClickListener() {  
  14.     @Override  
  15.     public void onClick(View v) {  
  16.     int top = v.getTop();  
  17.     int left = v.getLeft();  
  18.     int right = v.getRight();  
  19.     int bottom = v.getBottom();  
  20.     /** 
  21.      * 将TextView相对父控件的坐标保存在Rect对象 
  22.      */  
  23.     mRect.left = left;  
  24.     mRect.right = right;  
  25.     mRect.top = top;  
  26.     mRect.bottom = bottom;  
  27.   
  28.     button.setText("宽度:"+mRect.width()+"\n"+"高度:"+mRect.height());  
  29.     }  
  30. });  
  1. final Button anim_btn =new Button(this);  
  2. /** 
  3.  * 设置button的宽度和高度 
  4.  */  
  5. params=new ViewGroup.MarginLayoutParams(800300);  
  6. /** 
  7.  * 设置button的margin属性值,计算矩形局域的中心点坐标 
  8.  */  
  9. params.setMargins(100,DensityUtil.dip2px(this,100),100,100);  
  10. anim_btn.setLayoutParams(params);  
  11. anim_btn.setText("计算Rect坐标");  
  12. anim_btn.setBackgroundColor(Color.parseColor("#DDA0DD"));  
  13. anim_btn.setGravity(Gravity.RIGHT);  
  14. anim_btn.setOnClickListener(new View.OnClickListener() {  
  15.     @Override  
  16.     public void onClick(View v) {  
  17.     int top = v.getTop();  
  18.     int left = v.getLeft();  
  19.     int right = v.getRight();  
  20.     int bottom = v.getBottom();  
  21.     /** 
  22.      * 将TextView相对父控件的坐标保存在Rect对象 
  23.      */  
  24.     mRect.left = left;  
  25.     mRect.right = right;  
  26.     mRect.top = top;  
  27.     mRect.bottom = bottom;  
  28.     anim_btn.setText("水平中心点:"+mRect.centerX()+"\n垂直中心点:"+mRect.centerY());  
  29.     }  
  30. });  

正是因为每一个矩形局域包含着left、top、right和bottom四个顶点坐标,getLeft()、getTop()、getRight()和getBottom()属于View声明的方法,因此每一个View子类或者控件继承上述方法,Rect或RectF类似一个工具类,封装四个顶点坐标的计算关系,使用getLeft()、getTop()、getRight()和getBottom()需要注意两个问题:

第一个问题:getLeft()、getTop()、getRight()和getBottom()计算相对其父容器的位置

第二个问题:getLeft()、getTop()、getRight()和getBottom()计算结果为0,是因为当前View子类或控件没有绘制完成。解决办法,onClick方法点击的时候计算或者使用线程的延时计算

  1. /** 
  2.  * 延时获取控件相对父容器的left、top、right、bottom坐标,否则为0 
  3.  */  
  4. new Thread(new Runnable() {  
  5.     @Override  
  6.     public void run() {  
  7.     try {  
  8.         Thread.sleep(1000);  
  9.     } catch (InterruptedException e) {  
  10.         e.printStackTrace();  
  11.     }  
  12.     saveCoordinateToRect();  
  13.     }  
  14. }).start();  

二、Rect或RectF方法深入理解

Rect是一个final类,不属于被继承,实现Parcelable接口执行序列化,声明public作用域的四个整型属性:left、top、right和bottom,用来记录View矩形局域的四个顶点坐标。

  1. public Rect() {}  

1、创建一个空的Rect对象,left、top、right和bottom的默认值为0

  1. public Rect(int left, int top, int right, int bottom) {  
  2.     this.left = left;  
  3.     this.top = top;  
  4.     this.right = right;  
  5.     this.bottom = bottom;  
  6. }  

2、创建一个指定坐标值的Rect对象,left、top、right和bottom为指定值

  1. public Rect(Rect r) {  
  2.     if (r == null) {  
  3.         left = top = right = bottom = 0;  
  4.     } else {  
  5.         left = r.left;  
  6.         top = r.top;  
  7.         right = r.right;  
  8.         bottom = r.bottom;  
  9.     }  
  10. }  

3、使用已知的Rect,创建一个新的Rect对象,left、top、right和bottom为已知的Rect包含的值

  1. @Override  
  2. public boolean equals(Object o) {  
  3.     if (this == o) return true;  
  4.     if (o == null || getClass() != o.getClass()) return false;  
  5.   
  6.     Rect r = (Rect) o;  
  7.     return left == r.left && top == r.top && right == r.right && bottom == r.bottom;  
  8. }  

4、判断当前Rect与指定的o是否同一个,相同的条件:属于同一个对象或者两者left、top、right或bottom属性值一样

  1. @Override  
  2. public int hashCode() {  
  3.     int result = left;  
  4.     result = 31 * result + top;  
  5.     result = 31 * result + right;  
  6.     result = 31 * result + bottom;  
  7.     return result;  
  8. }  

5、计算Rect属性值的散列码

  1. @Override  
  2. public String toString() {  
  3.     StringBuilder sb = new StringBuilder(32);  
  4.     sb.append("Rect("); sb.append(left); sb.append(", ");  
  5.     sb.append(top); sb.append(" - "); sb.append(right);  
  6.     sb.append(", "); sb.append(bottom); sb.append(")");  
  7.     return sb.toString();  
  8. }  

6、以Rect(left,top-right,bottom)的格式返回矩形四个坐标值

  1. public String toShortString(StringBuilder sb) {  
  2.     sb.setLength(0);  
  3.     sb.append('['); sb.append(left); sb.append(',');  
  4.     sb.append(top); sb.append("]["); sb.append(right);  
  5.     sb.append(','); sb.append(bottom); sb.append(']');  
  6.     return sb.toString();  
  7. }  

7、以[left,top] [right,bottom]的格式返回矩形四个坐标值,即矩形区域左上角和右下角坐标

  1. public String toShortString() {  
  2.     return toShortString(new StringBuilder(32));  
  3. }  

8、以[left,top] [right,bottom]的格式返回矩形四个坐标值,即矩形区域左上角和右下角坐标,和上述方法一样

  1. public String flattenToString() {  
  2.     StringBuilder sb = new StringBuilder(32);  
  3.     // WARNING: Do not change the format of this string, it must be  
  4.     // preserved because Rects are saved in this flattened format.  
  5.     sb.append(left);  
  6.     sb.append(' ');  
  7.     sb.append(top);  
  8.     sb.append(' ');  
  9.     sb.append(right);  
  10.     sb.append(' ');  
  11.     sb.append(bottom);  
  12.     return sb.toString();  
  13. }  

9、以left top right bottom的格式返回矩形四个坐标值,即平铺的格式,比如:0 0 400 400或 100 100 800 300

  1. public static Rect unflattenFromString(String str) {  
  2.     Matcher matcher = UnflattenHelper.getMatcher(str);  
  3.     if (!matcher.matches()) {  
  4.         return null;  
  5.     }  
  6.     return new Rect(Integer.parseInt(matcher.group(1)),  
  7.             Integer.parseInt(matcher.group(2)),  
  8.             Integer.parseInt(matcher.group(3)),  
  9.             Integer.parseInt(matcher.group(4)));  
  10. }  

10、给定一个平铺格式的字符串,比如:0 0 400 400,判断是否合法,然后转换为一个Rect对象

  1. public void printShortString(PrintWriter pw) {  
  2.     pw.print('['); pw.print(left); pw.print(',');  
  3.     pw.print(top); pw.print("]["); pw.print(right);  
  4.     pw.print(','); pw.print(bottom); pw.print(']');  
  5. }  

11、将Rect包含的属性值以[left,top] [right,bottom]的格式写入给定的PrintWriter流中

  1. public final boolean isEmpty() {  
  2.     return left >= right || top >= bottom;  
  3. }  

12、判断Rect是否一个空对象,即包含的属性值是否不为0

  1. public final int width() {  
  2.     return right - left;  
  3. }  

13、计算矩形区域的宽度

  1. public final int height() {  
  2.     return bottom - top;  
  3. }  

14、计算矩形区域的高度

  1. public final int centerX() {  
  2.     return (left + right) >> 1;  
  3. }  

15、计算矩形区域的水平中心点,计算结果为分数则返回最接近的整型数,例如:水平中心点400

  1. public final int centerY() {  
  2.     return (top + bottom) >> 1;  
  3. }  

16、计算矩形区域的垂直中心点,计算结果为分数则返回最接近的整型数,例如:垂直中心点850

  1. public final float exactCenterX() {  
  2.     return (left + right) * 0.5f;  
  3. }  

17、计算矩形区域的水平中心点,返回结果float类型,例如:水平中心点400.0

  1. public final float exactCenterY() {  
  2.     return (top + bottom) * 0.5f;  
  3. }  

18、计算矩形区域的垂直中心点,返回结果float类型,例如:垂直中心点850.0

  1. public void setEmpty() {  
  2.     left = right = top = bottom = 0;  
  3. }  

19、将Rect对象包含的属性值设置为0

  1. public void set(int left, int top, int right, int bottom) {  
  2.     this.left = left;  
  3.     this.top = top;  
  4.     this.right = right;  
  5.     this.bottom = bottom;  
  6. }  

20、将Rect的属性值设置为指定的值

  1. public void set(Rect src) {  
  2.     this.left = src.left;  
  3.     this.top = src.top;  
  4.     this.right = src.right;  
  5.     this.bottom = src.bottom;  
  6. }  

21、复制指定的Rect对象包含的属性值

  1. public void offset(int dx, int dy) {  
  2.     left += dx;  
  3.     top += dy;  
  4.     right += dx;  
  5.     bottom += dy;  
  6. }  

22、在当前矩形区域的水平方向、垂直方向分别增加dx、dy距离,即扩展

  1. public void offsetTo(int newLeft, int newTop) {  
  2.     right += newLeft - left;  
  3.     bottom += newTop - top;  
  4.     left = newLeft;  
  5.     top = newTop;  
  6. }  

23、在当前矩形区域的水平方向、垂直方向分别偏移dx、dy距离,即水平平移dx、垂直平移dy

  1. public void inset(int dx, int dy) {  
  2.     left += dx;  
  3.     top += dy;  
  4.     right -= dx;  
  5.     bottom -= dy;  
  6. }  

24、在当前矩形区域的水平方向、垂直方向分别减少dx、dy距离,即缩小

  1. public boolean contains(int x, int y) {  
  2.     return left < right && top < bottom  // check for empty first  
  3.            && x >= left && x < right && y >= top && y < bottom;  
  4. }  

25、计算指定的坐标(x,y)是否包含在矩形区域范围内,包含返回true,否则返回false

  1. public boolean contains(int left, int top, int right, int bottom) {  
  2.            // check for empty first  
  3.     return this.left < this.right && this.top < this.bottom  
  4.            // now check for containment  
  5.             && this.left <= left && this.top <= top  
  6.             && this.right >= right && this.bottom >= bottom;  
  7. }  

26、计算指定的left、top、right、bottom顶点是否包含在矩形区域范围内,包含返回true,否则返回false

  1. public boolean contains(Rect r) {  
  2.            // check for empty first  
  3.     return this.left < this.right && this.top < this.bottom  
  4.            // now check for containment  
  5.            && left <= r.left && top <= r.top && right >= r.right && bottom >= r.bottom;  
  6. }  

27、计算指定的Rect是否包含在矩形区域范围内,包含返回true,否则返回false

  1. public boolean intersect(int left, int top, int right, int bottom) {  
  2.     if (this.left < right && left < this.right && this.top < bottom && top < this.bottom) {  
  3.         if (this.left < left) this.left = left;  
  4.         if (this.top < top) this.top = top;  
  5.         if (this.right > right) this.right = right;  
  6.         if (this.bottom > bottom) this.bottom = bottom;  
  7.         return true;  
  8.     }  
  9.     return false;  
  10. }  

28、计算当前Rect与指定的left、top、right、bottom顶点是否存在交集区域,存在返回true并且返回指定坐标,否则返回false

  1. public boolean intersect(Rect r) {  
  2.     return intersect(r.left, r.top, r.right, r.bottom);  
  3. }  

29、计算当前Rect与指定的Rect是否存在交集区域,存在返回true并且返回指定坐标,否则返回false

  1. public boolean setIntersect(Rect a, Rect b) {  
  2.     if (a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom) {  
  3.         left = Math.max(a.left, b.left);  
  4.         top = Math.max(a.top, b.top);  
  5.         right = Math.min(a.right, b.right);  
  6.         bottom = Math.min(a.bottom, b.bottom);  
  7.         return true;  
  8.     }  
  9.     return false;  
  10. }  

30、计算指定的a、b是否存在交集区域,存在返回true并且返回最大坐标,否则返回false

  1. public boolean intersects(int left, int top, int right, int bottom) {  
  2.     return this.left < right && left < this.right && this.top < bottom && top < this.bottom;  
  3. }  

31、计算当前Rect与指定的left、top、right、bottom顶点是否存在交集区域,存在返回true并且不返回指定坐标,否则返回false

  1. public static boolean intersects(Rect a, Rect b) {  
  2.     return a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom;  
  3. }  

32、计算指定的a、b是否存在交集区域,存在返回true并且不返回最大坐标,否则返回false

  1. public void union(int left, int top, int right, int bottom) {  
  2.     if ((left < right) && (top < bottom)) {  
  3.         if ((this.left < this.right) && (this.top < this.bottom)) {  
  4.             if (this.left > left) this.left = left;  
  5.             if (this.top > top) this.top = top;  
  6.             if (this.right < right) this.right = right;  
  7.             if (this.bottom < bottom) this.bottom = bottom;  
  8.         } else {  
  9.             this.left = left;  
  10.             this.top = top;  
  11.             this.right = right;  
  12.             this.bottom = bottom;  
  13.         }  
  14.     }  
  15. }  

33、计算当前Rect与指定的left、top、right、bottom顶点是否存在集区域,存在更新当前矩形区域,否则不更新

  1. public void union(Rect r) {  
  2.     union(r.left, r.top, r.right, r.bottom);  
  3. }  

34、计算当前Rect与指定的Rect是否存在集区域,存在更新当前矩形区域,否则不更新

  1. public void union(int x, int y) {  
  2.     if (x < left) {  
  3.         left = x;  
  4.     } else if (x > right) {  
  5.         right = x;  
  6.     }  
  7.     if (y < top) {  
  8.         top = y;  
  9.     } else if (y > bottom) {  
  10.         bottom = y;  
  11.     }  
  12. }  

35、计算当前Rect与指定的坐标(x,y)是否存在集区域,存在更新当前矩形区域,否则不更新

  1. public void sort() {  
  2.     if (left > right) {  
  3.         int temp = left;  
  4.         left = right;  
  5.         right = temp;  
  6.     }  
  7.     if (top > bottom) {  
  8.         int temp = top;  
  9.         top = bottom;  
  10.         bottom = temp;  
  11.     }  
  12. }  

36、排序当前矩形区域,符合:left

  1. public void scale(float scale) {  
  2.     if (scale != 1.0f) {  
  3.         left = (int) (left * scale + 0.5f);  
  4.         top = (int) (top * scale + 0.5f);  
  5.         right = (int) (right * scale + 0.5f);  
  6.         bottom = (int) (bottom * scale + 0.5f);  
  7.     }  
  8. }  

37、按照指定的值缩放当前矩形区域

  1. public void scaleRoundIn(float scale) {  
  2.     if (scale != 1.0f) {  
  3.         left = (int) Math.ceil(left * scale);  
  4.         top = (int) Math.ceil(top * scale);  
  5.         right = (int) Math.floor(right * scale);  
  6.         bottom = (int) Math.floor(bottom * scale);  
  7.     }  
  8. }  

38、按照指定的值缩放当前矩形区域

三、从源码分析Rect或RectF得使用

查看View子类或控件的源码,发现基本都运用到Rect或RectF,它的作用记录View或控件的矩形区域,最后重新绘制视图。这里以TextView控件为例,首先查看Rect或RectF在源码中的使用。

  1. private static final RectF TEMP_RECTF = new RectF();  

打开TextView控件源码,创建一个RectF对象TEMP_RECTF,Ctrl+F快速遍历当前源码使用到TEMP_RECTF的地方:

  1. /** 
  2.  *第一处使用到TEMP_RECTF的地方 
  3.  */  
  4. ...  
  5.   
  6.                 synchronized (TEMP_RECTF) {  
  7.                     /* 
  8.                      * The reason for this concern about the thickness of the 
  9.                      * cursor and doing the floor/ceil on the coordinates is that 
  10.                      * some EditTexts (notably textfields in the Browser) have 
  11.                      * anti-aliased text where not all the characters are 
  12.                      * necessarily at integer-multiple locations.  This should 
  13.                      * make sure the entire cursor gets invalidated instead of 
  14.                      * sometimes missing half a pixel. 
  15.                      */  
  16.                     float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());  
  17.                     if (thick < 1.0f) {  
  18.                         thick = 1.0f;  
  19.                     }  
  20.   
  21.                     thick /= 2.0f;  
  22.   
  23.                     // mHighlightPath is guaranteed to be non null at that point.  
  24.                     mHighlightPath.computeBounds(TEMP_RECTF, false);  
  25.   
  26.                     invalidate((int) FloatMath.floor(horizontalPadding + TEMP_RECTF.left - thick),  
  27.                             (int) FloatMath.floor(verticalPadding + TEMP_RECTF.top - thick),  
  28.                             (int) FloatMath.ceil(horizontalPadding + TEMP_RECTF.right + thick),  
  29.                             (int) FloatMath.ceil(verticalPadding + TEMP_RECTF.bottom + thick));  
  30.                 }  
  31.   
  32. ...  

TEMP_RECTF对象拥有矩形区域的四个顶点,调用Path提供的computeBounds()计算路径控制点的边界,然后将计算的结果写入TEMP_RECTF中,调用invalidate()重新绘制。关于invalidate()方法的使用,查看View源码,中文意思“是无效,使作废”,也就是使原来绘制的视图作废,根据新left、top、right、bottom的值重新绘制视图。

在TeachCourse文章开头提供的例子中,将第三个Button点击执行的代码修改一下,点击一次就改变当前Button的矩形区域的大小,然后调用invalidate重绘,呈现一种类似动画的效果。

  1. ...  
  2.   
  3.         anim_btn.setOnClickListener(new View.OnClickListener() {  
  4.             @Override  
  5.             public void onClick(View v) {  
  6.                 int top = v.getTop();  
  7.                 int left = v.getLeft();  
  8.                 int right = v.getRight();  
  9.                 int bottom = v.getBottom();  
  10.                 /** 
  11.                  * 将TextView相对父控件的坐标保存在Rect对象 
  12.                  */  
  13.                 mRect.left = left;  
  14.                 mRect.right = right;  
  15.                 mRect.top = top;  
  16.                 mRect.bottom = bottom;  
  17.                 /** 
  18.          *在当前矩形区域的水平方向、垂直方向分别减少dx、dy距离,即缩小 
  19.          */  
  20.                 mRect.inset(10,10);  
  21.         anim_btn.setLeft(mRect.left);  
  22.                 anim_btn.setTop(mRect.top);  
  23.                 anim_btn.setRight(mRect.right);  
  24.                 anim_btn.setBottom(mRect.bottom);  
  25.                 anim_btn.invalidate();  
  26.             }  
  27.         });  
  28.   
  29. ...  

17-0115-2150-rect-rectf-different

  1. /** 
  2.  *第二处使用到TEMP_RECTF的地方 
  3.  */  
  4. ...  
  5.   
  6.                 synchronized (TEMP_RECTF) {  
  7.                     mHighlightPath.computeBounds(TEMP_RECTF, true);  
  8.                     r.left = (int)TEMP_RECTF.left-1;  
  9.                     r.right = (int)TEMP_RECTF.right+1;  
  10.                 }  
  11.   
  12. ...  

这里的作用同理,调用Path提供的computeBounds()计算路径控制点的边界,然后将计算的结果写入TEMP_RECTF中,然后将将TEMP_RECTF左边界减1、右边界加1赋值给另一个Rect。重点理解Rect或RectF声明的各个方法的作用后,在分析源码中使用到的Rect或RectF,发现已经很容易理解。

关注公众号 扫一扫二维码,加我QQ

如果文章对你有帮助,欢迎点击上方按钮关注作者

来源:TeachCourse每周一次,深入学习Android教程,关注(QQ1589359239或公众号TeachCourse)
转载请注明出处:http://teachcourse.cn/2268.html
分类:Android, 开发基础 标签:, ,
Eclipse开发项目中红色感叹号问题解决办法 Eclipse开发项目中红色感叹号问
Android Studio如何快速更改目录结构和包名? Android Studio如何快速更改目
生活杂谈之阿里云免费虚拟主机申请过程详解 生活杂谈之阿里云免费虚拟主机申
Android学习笔记八:Java常用数据结构 Android学习笔记八:Java常用数

发表评论

呲牙 憨笑 坏笑 偷笑 色 微笑 抓狂 睡觉 酷 流汗 鼓掌 大哭 可怜 疑问 晕 惊讶 得意 尴尬 发怒 奋斗 衰 骷髅 啤酒 吃饭 礼物 强 弱 握手 OK NO 勾引 拳头 差劲 爱你

表情