当前位置:   article > 正文

展开说说:Android之View基础知识解析

展开说说:Android之View基础知识解析

View虽不属于Android四代组件,但应用程度却非常非常广泛。在Android客户端,君所见之处皆是View。我们看到的Button、ImageView、TextView等等可视化的控件都是View,ViewGroup是View的子类因此它也是View。但是现在我们把View和ViewGroup当成两个类来看待,ViewGroup可以容纳View和ViewGroup,但View不可以再容纳其他View或ViewGroup,这种容纳的关系可以一直延伸仿佛一棵大树,从内而外有了父子关系,因此有个概念叫做ViewTree。

这篇文章一起总结一下View的基础知识:View的位置坐标、View宽高、View移动scrollTo和ScrollBy、手势追踪、显示隐藏、点击事件(包含单击事件、长按事件、双击事件)、ViewTreeObserver。

如果ViewA内部是ViewB,那么ViewA就被称为ViewB的父容器或父View。

1、View的位置坐标

位置坐标是View四个顶点相对于父容器的位置,因此View的坐标其实是相对坐标。

2、View宽高

View类中有mLeft-左、mTop-上、mRight-右、mBottom-下四个属性,分别对应左上角的横坐标、左上角的纵坐标、右下角的横坐标、右下角的纵坐标,利用View左上角有右下角两个顶点相对于父容器来计算顶点坐标到父容器的。View的宽高就是根据这四个属性计算出来的,公式如下:

Widht = mRight- mLeft;

Height = mBottom- mTop

这四个属性以及宽高都可以通过View类的get方法直接获取:以获取mLeft和高度为例看源码:

  1. /**
  2. * Return the height of your view.
  3. *
  4. * @return The height of your view, in pixels.
  5. */
  6. @ViewDebug.ExportedProperty(category = "layout")
  7. public final int getHeight() {
  8. return mBottom - mTop;
  9. }
  10. /**
  11. * Top position of this view relative to its parent.
  12. *
  13. * @return The top of this view, in pixels.
  14. */
  15. @ViewDebug.CapturedViewProperty
  16. public final int getTop() {
  17. return mTop;
  18. }

注意这里返回的像素PX哈,如果使用dp为单位需要自行转化。

3、View移动scrollTo和ScrollBy

scrollTo和scrollBy是View类内部的两个负责实现滑动的方法,两者的区别scrollBy是基于当前View自身位置的滑动,scrollTo是基于传递参数的绝对滑动。scrollBy内部调用了scrollTo只是累加了之前已经滑动的距离。先上源码:

  1. /**
  2. * Set the scrolled position of your view. This will cause a call to
  3. * {@link #onScrollChanged(int, int, int, int)} and the view will be
  4. * invalidated.
  5. * @param x the x position to scroll to
  6. * @param y the y position to scroll to
  7. */
  8. public void scrollTo(int x, int y) {
  9. if (mScrollX != x || mScrollY != y) {
  10. int oldX = mScrollX;
  11. int oldY = mScrollY;
  12. mScrollX = x;
  13. mScrollY = y;
  14. invalidateParentCaches();
  15. onScrollChanged(mScrollX, mScrollY, oldX, oldY);
  16. if (!awakenScrollBars()) {
  17. postInvalidateOnAnimation();
  18. }
  19. }
  20. }
  21. /**
  22. * Move the scrolled position of your view. This will cause a call to
  23. * {@link #onScrollChanged(int, int, int, int)} and the view will be
  24. * invalidated.
  25. * @param x the amount of pixels to scroll by horizontally
  26. * @param y the amount of pixels to scroll by vertically
  27. */
  28. public void scrollBy(int x, int y) {
  29. scrollTo(mScrollX + x, mScrollY + y);
  30. }

总结:

两个方法都是传入2个参数x和y,scrollTo中会把x、y分别赋值给mScrollX mScrollY ,scrollBy方法是在mScrollX mScrollY基础上累加了X、Y以后再调用scrollTo方法进行移动。

这里有两个概念,View和View内部的内容,scrollTo和scrollBy只能移动View内部的内容但不能移动View分毫。

mScrollX等于View左上角横坐标-去view内部内容的左上角横坐标;mScrollY等于View右下角纵坐标-view内部内容的右下角纵坐标。所以参数有正负之分,x参数为正说明内容向左移动x为负数向右移动,y参数为正说明内容向上移动y为负数向下移动

举个例子:
                textView2.scrollTo(20,0);   

 移动到相对于父view向左移动20PX,不累加,调用多少次也就是固定到这个位置不再移动了
                textView2.scrollBy(-20, 0);   

通过源码可知:移动到相对于父view向右移动20PX,内部调用的scrollTo移动都是先用当前位置加上要移动的距离,逐渐累加的,移动次数越多走的越远。

4、显示隐藏

编码过程中经常会遇到View的显示隐藏,但是这个很简单只需要调用setVisibility方法并传入对应的int参数。看一下源码:

  1. *
  2. * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
  3. * @attr ref android.R.styleable#View_visibility
  4. */
  5. @RemotableViewMethod
  6. public void setVisibility(@Visibility int visibility) {
  7. setFlags(visibility, VISIBILITY_MASK);
  8. }

四种int的flag, VISIBLE INVISIBLE GONE VISIBILITY_MASK


public static final int VISIBLE = 0x00000000;  设置View可见,正常显示

public static final int INVISIBLE = 0x00000004;   设置View不可见,不显示但会正常保留它的位置给它。

public static final int GONE = 0x00000008;   设置View不可见,不显示并且保留它的位置,给其他view占用。

5、手势追踪(包含单击事件、长按事件、双击事件)

GestureDetector用于辅助检测用户的单机、滑动、长按、双击等事件。

GestureDetector是一个类,它内部其中包含了两个接口OnGestureListener

OnDoubleTapListener是分别可以监听单击、长按和双击事件。

有一点需要注意GestureDetector的构造方法形参只有OnGestureListener没有OnDoubleTapListener,因此我们如果要接收双击事件需要先通过OnGestureListener创建GestureDetector实例,然后再调用setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener)方法。

第一步 在布局文件写个控件,这里选用TextView,然后给他设置clickable和onTouchListener;所在Activity要先实现View.OnTouchListener并重写onTouch方法

  1. <TextView
  2. android:id="@+id/gestureAct_content"
  3. android:layout_width="300dp"
  4. android:layout_height="400dp"
  5. app:layout_constraintLeft_toLeftOf="parent"
  6. app:layout_constraintRight_toRightOf="parent"
  7. app:layout_constraintTop_toTopOf="parent"
  8. app:layout_constraintBottom_toBottomOf="parent"
  9. android:background="@mipmap/ic_launcher"/>

第二步 创建GestureDetector实例并设置双击事件setOnDoubleTapListener

使用不复杂,直接上代码,通过代码注释和打印日志看一下:

  1. package com.example.testdemo.activity;
  2. import android.util.Log;
  3. import android.view.GestureDetector;
  4. import android.view.MotionEvent;
  5. import android.view.View;
  6. import android.widget.TextView;
  7. import com.example.testdemo.R;
  8. import com.example.testdemo.base.BaseActivity;
  9. public class GestureDetectorActivity extends BaseActivity implements View.OnTouchListener{
  10. private TextView contentTv;
  11. private GestureDetectorActivity mThis;
  12. private GestureDetector mGestureDetector;
  13. @Override
  14. public void initView() {
  15. super.initView();
  16. setContentView(R.layout.activity_gesture);
  17. mThis = this;
  18. mGestureDetector = new GestureDetector(this, new SingleGestureListener());
  19. contentTv = findViewById(R.id.gestureAct_content);
  20. contentTv.setOnTouchListener(this);
  21. contentTv.setFocusable(true);
  22. contentTv.setClickable(true);
  23. contentTv.setLongClickable(true);
  24. mGestureDetector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
  25. // 单击事件。用来判定该次点击是SingleTap而不是DoubleTap,如果连续点击两次就是DoubleTap手势,只有单击时才执行,是一个ACTION_DOWN事件
  26. @Override
  27. public boolean onSingleTapConfirmed(MotionEvent e) {
  28. Log.e("SingleGestureListener", "onSingleTapConfirmed ACTION= "+MotionEvent.actionToString(e.getAction()));
  29. return false;
  30. }
  31. //被系统认定为双击事件时执行,是个DOWN事件
  32. @Override
  33. public boolean onDoubleTap(MotionEvent e) {
  34. Log.e("SingleGestureListener", "onDoubleTap ACTION= "+MotionEvent.actionToString(e.getAction()));
  35. return false;
  36. }
  37. //第二次点击屏幕被系统认定时双击以后到双击操作完成,双指触发onDoubleTap以后,包含ACTION_DOWN、ACTION_MOVE、ACTION_UP事件
  38. @Override
  39. public boolean onDoubleTapEvent(MotionEvent e) {
  40. Log.e("SingleGestureListener", "onDoubleTapEvent ACTION= "+MotionEvent.actionToString(e.getAction()));
  41. return false;
  42. }
  43. });
  44. }
  45. @Override
  46. public boolean onTouch(View v, MotionEvent event) {
  47. return mGestureDetector.onTouchEvent(event);
  48. }
  49. private class SingleGestureListener implements GestureDetector.OnGestureListener{
  50. // 点击屏幕时ACTION_DOWN事件触发,只要点一定执行
  51. public boolean onDown(MotionEvent e) {
  52. Log.e("SingleGestureListener", "onDown ACTION= "+MotionEvent.actionToString(e.getAction()));
  53. return false;
  54. }
  55. /*
  56. * 一般来说点击屏幕onDown的那个ACTION_DOWN事件也会过来。但有两种情况例外,点了以后不松手和点了以后直接滑动不会执行该事件;点了不松手会执行onLongPress,点了以后进行滑动会执行onScroll。
  57. */
  58. public void onShowPress(MotionEvent e) {
  59. Log.e("SingleGestureListener", "onShowPress ACTION= "+MotionEvent.actionToString(e.getAction()));
  60. }
  61. // 用户点击屏幕并马上松开,由一个1个MotionEvent ACTION_UP触发,就是一个标准点击事件,同上,点了以后不松手和点了以后直接滑动不会执行该事件
  62. public boolean onSingleTapUp(MotionEvent e) {
  63. Log.e("SingleGestureListener", "onSingleTapUp ACTION= "+MotionEvent.actionToString(e.getAction()));
  64. return true;
  65. }
  66. // 点击屏幕后滑动,e1是 ACTION_DOWN触发, e2是ACTION_MOVE
  67. public boolean onScroll(MotionEvent e1, MotionEvent e2,
  68. float distanceX, float distanceY) {
  69. Log.e("SingleGestureListener", "onScroll:"+(e2.getX()-e1.getX()) +" "+distanceX+" e1_ACTION= "+MotionEvent.actionToString(e1.getAction())+" e2_ACTION= "+MotionEvent.actionToString(e2.getAction()));
  70. return true;
  71. }
  72. // 长按屏幕触发,ACTION_DOWN事件
  73. public void onLongPress(MotionEvent e) {
  74. Log.e("SingleGestureListener", "onLongPress ACTION= "+MotionEvent.actionToString(e.getAction()));
  75. }
  76. // 用户按下触摸屏、快速移动后松开,e1是 ACTION_DOWN, e2s是个ACTION_UP触发
  77. public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
  78. float velocityY) {
  79. Log.e("SingleGestureListener", "onFling"+" e1_ACTION= "+MotionEvent.actionToString(e1.getAction())+" e2_ACTION= "+MotionEvent.actionToString(e2.getAction()));
  80. return true;
  81. }
  82. };
  83. }

日志打印:

单击:点击屏幕后马上抬起
2024-07-13 21:23:09.356 19222-19222/com.example.testdemo E/SingleGestureListener: onDown     ACTION= ACTION_DOWN
2024-07-13 21:23:09.404 19222-19222/com.example.testdemo E/SingleGestureListener: onSingleTapUp     ACTION= ACTION_UP
2024-07-13 21:23:09.657 19222-19222/com.example.testdemo E/SingleGestureListener: onSingleTapConfirmed     ACTION= ACTION_DOWN


长按屏幕:长按以后执行,并且松开后也不会再执行其他的回调了,因为他是一个ACTION_UP事件
2024-07-13 21:23:17.696 19222-19222/com.example.testdemo E/SingleGestureListener: onDown     ACTION= ACTION_DOWN
2024-07-13 21:23:17.788 19222-19222/com.example.testdemo E/SingleGestureListener: onShowPress     ACTION= ACTION_DOWN
2024-07-13 21:23:18.089 19222-19222/com.example.testdemo E/SingleGestureListener: onLongPress     ACTION= ACTION_DOWN

滑动屏幕
 2024-07-13 21:35:08.821 21772-21772/com.example.testdemo E/SingleGestureListener: onDown     ACTION= ACTION_DOWN
 2024-07-13 21:35:08.866 21772-21772/com.example.testdemo E/SingleGestureListener: onScroll:7.7454224   -7.7454224       e1_ACTION= ACTION_DOWN       e2_ACTION= ACTION_MOVE
 2024-07-13 21:35:08.916 21772-21772/com.example.testdemo E/SingleGestureListener: onScroll:36.8945   -29.149078       e1_ACTION= ACTION_DOWN       e2_ACTION= ACTION_MOVE
 2024-07-13 21:35:08.966 21772-21772/com.example.testdemo E/SingleGestureListener: onScroll:60.22644   -23.33194       e1_ACTION= ACTION_DOWN       e2_ACTION= ACTION_MOVE
 2024-07-13 21:35:08.999 21772-21772/com.example.testdemo E/SingleGestureListener: onScroll:28.0   32.22644       e1_ACTION= ACTION_DOWN       e2_ACTION= ACTION_MOVE
 2024-07-13 21:35:09.027 21772-21772/com.example.testdemo E/SingleGestureListener: onFling       e1_ACTION= ACTION_DOWN       e2_ACTION= ACTION_UP
 双击屏幕:
 2024-07-13 21:35:53.532 21772-21772/com.example.testdemo E/SingleGestureListener: onDown     ACTION= ACTION_DOWN
 2024-07-13 21:35:53.601 21772-21772/com.example.testdemo E/SingleGestureListener: onSingleTapUp     ACTION= ACTION_UP
 2024-07-13 21:35:53.706 21772-21772/com.example.testdemo E/SingleGestureListener: onDoubleTap     ACTION= ACTION_DOWN
 2024-07-13 21:35:53.706 21772-21772/com.example.testdemo E/SingleGestureListener: onDoubleTapEvent     ACTION= ACTION_DOWN
 2024-07-13 21:35:53.706 21772-21772/com.example.testdemo E/SingleGestureListener: onDown     ACTION= ACTION_DOWN
 2024-07-13 21:35:53.770 21772-21772/com.example.testdemo E/SingleGestureListener: onDoubleTapEvent     ACTION= ACTION_MOVE
 2024-07-13 21:35:53.770 21772-21772/com.example.testdemo E/SingleGestureListener: onDoubleTapEvent     ACTION= ACTION_UP

6、ViewTreeObserver

如果我们想在onCreate生命周期获取一个View的宽高该怎么呢?

直接getWidth()getHeight()方法会有问题吗?没大问题,但是获取到的值都是0,为啥呢?因为测试还没完成measure绘制。因此需要注册一个监听器等view绘制完成以后再取获取宽高:

  1. ViewTreeObserver viewTreeObserver = contentTv.getViewTreeObserver();
  2. viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
  3. @Override
  4. public void onGlobalLayout() {
  5. Log.e("initView: -1", " width="+contentTv.getWidth()+" height= "+contentTv.getHeight());
  6. // 在这里注销监听器
  7. // viewTreeObserver.removeOnGlobalLayoutListener(this); //This ViewTreeObserver is not alive, call getViewTreeObserver() again
  8. contentTv.getViewTreeObserver().removeOnGlobalLayoutListener(this::onGlobalLayout);
  9. }
  10. });
  11. Log.e("initView: -2", " width="+contentTv.getWidth()+" height= "+contentTv.getHeight());

注意:

1、首先看到直接打印的initView: -2只想时间早于initView: -1,其次initView: -2执行了多次,因为没有调用removeOnGlobalLayoutListener注销监听器onGlobalLayout就会被回调多次
2024-07-13 22:37:53.641 4857-4857/com.example.testdemo E/initView: -1:   width=900     height= 1200
2024-07-13 22:37:53.705 4857-4857/com.example.testdemo E/initView: -1:   width=900     height= 1200
2024-07-13 22:37:55.430 5654-5654/com.example.testdemo E/initView: -2:   width=0     height= 0
2024-07-13 22:37:55.599 5654-5654/com.example.testdemo E/initView: -1:   width=900     height= 1200

2、其次如果viewTreeObserver.removeOnGlobalLayoutListener(this);  这样注销会引发crash闪退:This ViewTreeObserver is not alive, call getViewTreeObserver() again。所以,哟啊重新获取getViewTreeObserver对象,contentTv.getViewTreeObserver().removeOnGlobalLayoutListener(this::onGlobalLayout);就可以成功获取宽高并且不会执行多次,日志如下:

2024-07-13 22:48:05.698 9659-9659/com.example.testdemo E/initView: -2:   width=0     height= 0
 2024-07-13 22:48:05.868 9659-9659/com.example.testdemo E/initView: -1:   width=900     height= 1200

才疏学浅,如有错误,欢迎指正,多谢。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/824707
推荐阅读
相关标签
  

闽ICP备14008679号