当前位置: 首页 > news >正文

展开说说: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和高度为例看源码:

/*** Return the height of your view.** @return The height of your view, in pixels.*/
@ViewDebug.ExportedProperty(category = "layout")
public final int getHeight() {return mBottom - mTop;
}/*** Top position of this view relative to its parent.** @return The top of this view, in pixels.*/
@ViewDebug.CapturedViewProperty
public final int getTop() {return mTop;
}

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

3、View移动scrollTo和ScrollBy

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

/*** Set the scrolled position of your view. This will cause a call to* {@link #onScrollChanged(int, int, int, int)} and the view will be* invalidated.* @param x the x position to scroll to* @param y the y position to scroll to*/
public void scrollTo(int x, int y) {if (mScrollX != x || mScrollY != y) {int oldX = mScrollX;int oldY = mScrollY;mScrollX = x;mScrollY = y;invalidateParentCaches();onScrollChanged(mScrollX, mScrollY, oldX, oldY);if (!awakenScrollBars()) {postInvalidateOnAnimation();}}
}/*** Move the scrolled position of your view. This will cause a call to* {@link #onScrollChanged(int, int, int, int)} and the view will be* invalidated.* @param x the amount of pixels to scroll by horizontally* @param y the amount of pixels to scroll by vertically*/
public void scrollBy(int x, int y) {scrollTo(mScrollX + x, mScrollY + y);
}

总结:

两个方法都是传入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参数。看一下源码:

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

四种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方法

<TextViewandroid:id="@+id/gestureAct_content"android:layout_width="300dp"android:layout_height="400dp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintBottom_toBottomOf="parent"android:background="@mipmap/ic_launcher"/>

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

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

package com.example.testdemo.activity;import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;import com.example.testdemo.R;
import com.example.testdemo.base.BaseActivity;public class GestureDetectorActivity extends BaseActivity  implements View.OnTouchListener{private TextView contentTv;private GestureDetectorActivity mThis;private GestureDetector mGestureDetector;@Overridepublic void initView() {super.initView();setContentView(R.layout.activity_gesture);mThis = this;mGestureDetector = new GestureDetector(this, new SingleGestureListener());contentTv = findViewById(R.id.gestureAct_content);contentTv.setOnTouchListener(this);contentTv.setFocusable(true);contentTv.setClickable(true);contentTv.setLongClickable(true);mGestureDetector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
//            单击事件。用来判定该次点击是SingleTap而不是DoubleTap,如果连续点击两次就是DoubleTap手势,只有单击时才执行,是一个ACTION_DOWN事件@Overridepublic boolean onSingleTapConfirmed(MotionEvent e) {Log.e("SingleGestureListener", "onSingleTapConfirmed     ACTION= "+MotionEvent.actionToString(e.getAction()));return false;}
//被系统认定为双击事件时执行,是个DOWN事件@Overridepublic boolean onDoubleTap(MotionEvent e) {Log.e("SingleGestureListener", "onDoubleTap     ACTION= "+MotionEvent.actionToString(e.getAction()));return false;}
//第二次点击屏幕被系统认定时双击以后到双击操作完成,双指触发onDoubleTap以后,包含ACTION_DOWN、ACTION_MOVE、ACTION_UP事件@Overridepublic boolean onDoubleTapEvent(MotionEvent e) {Log.e("SingleGestureListener", "onDoubleTapEvent     ACTION= "+MotionEvent.actionToString(e.getAction()));return false;}});}@Overridepublic boolean onTouch(View v, MotionEvent event) {return mGestureDetector.onTouchEvent(event);}private class SingleGestureListener implements GestureDetector.OnGestureListener{// 点击屏幕时ACTION_DOWN事件触发,只要点一定执行public boolean onDown(MotionEvent e) {Log.e("SingleGestureListener", "onDown     ACTION= "+MotionEvent.actionToString(e.getAction()));return false;}/** 一般来说点击屏幕onDown的那个ACTION_DOWN事件也会过来。但有两种情况例外,点了以后不松手和点了以后直接滑动不会执行该事件;点了不松手会执行onLongPress,点了以后进行滑动会执行onScroll。*/public void onShowPress(MotionEvent e) {Log.e("SingleGestureListener", "onShowPress     ACTION= "+MotionEvent.actionToString(e.getAction()));}// 用户点击屏幕并马上松开,由一个1个MotionEvent ACTION_UP触发,就是一个标准点击事件,同上,点了以后不松手和点了以后直接滑动不会执行该事件public boolean onSingleTapUp(MotionEvent e) {Log.e("SingleGestureListener", "onSingleTapUp     ACTION= "+MotionEvent.actionToString(e.getAction()));return true;}// 点击屏幕后滑动,e1是 ACTION_DOWN触发, e2是ACTION_MOVEpublic boolean onScroll(MotionEvent e1, MotionEvent e2,float distanceX, float distanceY) {Log.e("SingleGestureListener", "onScroll:"+(e2.getX()-e1.getX()) +"   "+distanceX+"       e1_ACTION= "+MotionEvent.actionToString(e1.getAction())+"       e2_ACTION= "+MotionEvent.actionToString(e2.getAction()));return true;}// 长按屏幕触发,ACTION_DOWN事件public void onLongPress(MotionEvent e) {Log.e("SingleGestureListener", "onLongPress     ACTION= "+MotionEvent.actionToString(e.getAction()));}// 用户按下触摸屏、快速移动后松开,e1是 ACTION_DOWN, e2s是个ACTION_UP触发public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY) {Log.e("SingleGestureListener", "onFling"+"       e1_ACTION= "+MotionEvent.actionToString(e1.getAction())+"       e2_ACTION= "+MotionEvent.actionToString(e2.getAction()));return true;}};}

日志打印:

单击:点击屏幕后马上抬起
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绘制完成以后再取获取宽高:

  ViewTreeObserver viewTreeObserver = contentTv.getViewTreeObserver();viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {@Overridepublic void onGlobalLayout() {Log.e("initView: -1", "  width="+contentTv.getWidth()+"     height= "+contentTv.getHeight());// 在这里注销监听器
//                    viewTreeObserver.removeOnGlobalLayoutListener(this);  //This ViewTreeObserver is not alive, call getViewTreeObserver() againcontentTv.getViewTreeObserver().removeOnGlobalLayoutListener(this::onGlobalLayout);}});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

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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【Qt 基础】绘图
  • 如何判断服务器是否被攻击
  • 微信小程序如何实现登陆和注册功能?
  • ShardingSphere-JDBC —— 整合 mybatis-plus,调用批量方法执行更新操作扫所有分表问题
  • 【cocos creator】2.4.x实现简单3d功能,点击选中,旋转,材质修改,透明材质
  • c++课后作业
  • Oracle左连接过滤条件注意事项
  • 【Linux杂货铺】3.程序地址空间
  • UART编程
  • 基于复旦微JFMQL100TAI的全国产化FPGA+AI人工智能异构计算平台,兼容XC7Z045-2FFG900I
  • 全面揭秘:ChatGPT-4o带来的下一代AI能力
  • 环境管理开发实战
  • 卸载docker
  • Python input NameError: name ‘xxx‘ is not defined.
  • 智充科技营收增速放缓:经营成本飙升,应收账款大幅增长
  • Angular 响应式表单之下拉框
  • CEF与代理
  • DataBase in Android
  • gcc介绍及安装
  • Leetcode 27 Remove Element
  • leetcode388. Longest Absolute File Path
  • Python代码面试必读 - Data Structures and Algorithms in Python
  • Redis提升并发能力 | 从0开始构建SpringCloud微服务(2)
  • Redis学习笔记 - pipline(流水线、管道)
  • SpringBoot 实战 (三) | 配置文件详解
  • Windows Containers 大冒险: 容器网络
  • windows下使用nginx调试简介
  • 对象管理器(defineProperty)学习笔记
  • 前端设计模式
  • 如何使用 JavaScript 解析 URL
  • 如何用Ubuntu和Xen来设置Kubernetes?
  • 三分钟教你同步 Visual Studio Code 设置
  • 使用SAX解析XML
  • 算法之不定期更新(一)(2018-04-12)
  • 推荐一款sublime text 3 支持JSX和es201x 代码格式化的插件
  • 微服务框架lagom
  • 新年再起“裁员潮”,“钢铁侠”马斯克要一举裁掉SpaceX 600余名员工 ...
  • ​Spring Boot 分片上传文件
  • #Spring-boot高级
  • (C语言)球球大作战
  • (阿里云在线播放)基于SpringBoot+Vue前后端分离的在线教育平台项目
  • (安全基本功)磁盘MBR,分区表,活动分区,引导扇区。。。详解与区别
  • (补充)IDEA项目结构
  • (附源码)spring boot基于小程序酒店疫情系统 毕业设计 091931
  • (解决办法)ASP.NET导出Excel,打开时提示“您尝试打开文件'XXX.xls'的格式与文件扩展名指定文件不一致
  • (七)Appdesigner-初步入门及常用组件的使用方法说明
  • (算法设计与分析)第一章算法概述-习题
  • (源码版)2024美国大学生数学建模E题财产保险的可持续模型详解思路+具体代码季节性时序预测SARIMA天气预测建模
  • .libPaths()设置包加载目录
  • .net framwork4.6操作MySQL报错Character set ‘utf8mb3‘ is not supported 解决方法
  • .NET Reactor简单使用教程
  • .NET 应用架构指导 V2 学习笔记(一) 软件架构的关键原则
  • .NetCore部署微服务(二)
  • .Net实现SCrypt Hash加密
  • .NET应用UI框架DevExpress XAF v24.1 - 可用性进一步增强