View

view,viewgroup,layoutparam介绍,
view的measure(),layout(),draw(),
添加到window显示,
自定义view,
事件处理motionevent,
gestureDetector,
坐标系,mScrollX/mScrollY
measureSpec,
滑动,
动画(分开写),

等会看看这个文章https://www.jianshu.com/p/a790982fd20e

是什么:

View是所有组件的基类,负责在屏幕占据一个矩形区域,在里面绘制和交互,用来创建交互式的UI组件,比如按钮、文本框等。
ViewGroup是View的子类,是一个隐形的负责在其中的view的布局摆放,要配合LayoutParam类使用

怎么使用:

一般不直接使用View或ViewGroup,而是使用上面提到的具体View子类,比如Button/TextView/RadioButton等,ViewGroup一般用线性布局LinearLayout,相对布局RelativeLayout,约束布局ConstraintLayout等
有两种使用方式

在xml文件中声明使用

1.现在xml文件中声明需要的控件,这些控件外一般会有一个viewgroup负责控制控件的摆放。一个控件对应的有哪些属性可以在xml里指定,比如textview直接点击他的text属性,就会跳转到他所有属性声明的attr.xml文件,就能看到所有textview的属性。

//layout_one_activity.xml(只看单个控件声明)
    <TextView
        android:id="@+id/tv1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        android:clickable="true"/>
    
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"/>

2.获取对应的xml文件并展示
xml对应的获取方式是他的ID ,例如layout_one_activity.xml,
就应该是Inflater.inflate(R.layout.layout_one_activity,rootView,null);或者activity直接在onCreate()中setContentView(R.layout.layout_one_activity);
3.根据id找到对应的控件
例如当前layout_one_activity.xml已经经过setContentView()设置给了activity这个textview就可以通过TextView tv1 = getViewById(R.id.tv1)获取。

在代码中初始化

1.使用new来初始化一个控件对象

TextView tv1 = new TextView(context);
//设置一些属性
tv1.setText("text_display");

2.添加到需要的地方,这个地方指activity、dialog、window、remoteview等,这些都是view可以依附展示的(系统展示view的过程)。
这里举例添加到activity里activity这篇文章里面说到contentview是一个viewgroup,如果用的是线性布局LinearLayout,则添加是需要配合LinearLayout.LayoutParam

LinearLayout.LayoutParam lp = new LinearLayout.LayoutParam(width,height);
addContentView(tv1,lp);

布局ViewGroup

这里布局,主要说的是ViewGroup怎么摆放内部的子view,系统是提供了一些容器可以直接使用,比如LinearLayout 在他的onLayout方法里去摆放子view,如果是竖直排列,它会获取所有的子view遍历,那子view的top坐标就会top +=之前子view高度。那么子view就是顺序向下摆放。
有两种使用方式

在xml文件中声明使用

在代码中初始化

LayoutParam

是保存子view的宽、高、边距、内边距等等信息的,告诉他的父容器,我想摆在哪个地方。每个对应的viewgroup都有自己的layoutparam子类比如LinearLayout.LayoutParam RelativeLayout.LayoutParam等使用时要对应使用,否则会抛异常。ViewGroup类中的LayoutParam是默认获取了layout_width,layout_height两个属性,所以他初始化的方法有这两个参数。这些都可以自定义时也可以添加其他属性。底下再展开。

使用

View measure() layout() draw()

上面说布局就是涉及到了layout这个步骤,自定义viewGroup指定要重写这个方法,这个自定义view官方文档说要自定义要重写onDraw(),onMeasure()。他们属于一个view的初始化过程,为什么是measure->layout->draw呢,可以点代码跳进去看,从view.onMeasure跳到一个performMeasure最终就到了一个八百多行的viewRootImpl.performTraversals里,里面反正就是按顺序调用了三个方法

performMeasure(){mView.measure()},
preformLayout(mView.layout()),
performDraw(){mView.draw()}

这三个方法都干啥了呢
measure()

  • 子view的measure就是在getDefaultSize()中根据MeasureSpec返回。如果是AT_MOST和EXACTLY,那返回的就是MeasureSpec中的size。UNSPECIFIED就返回系统内部测量的宽高,如果有drawable背景,那么返回的就是这个drawable的宽高。
  • viewgroup的measure是没有具体实现的,主要的逻辑就是完成自己的measure并且获取所有的子view完成他们的测量。所以看下系统自带的实现。
    measure之后,就可以通过getMeasureWidth()getMeasureHeight()获取宽高了

layout()方法是确定view本身的位置,onLayout()是确定子view的位置,主要通过setFrame()确定left、top、right、bottom四个顶点的坐标,viewGroup就是获取子view的宽高,根据自己的逻辑来计算坐标放置子view。
draw()
view.draw()里面也是一步一步的绘制,这个注释就写了

1.绘制背景 background.draw();
2.绘制自己 onDraw();
3.绘制子view
4.绘制装饰

View.draw()方法

一个小知识 在view中有个setWillNotDraw方法,设置为true,就标记这个view不具备绘制功能,系统就会自动对它进行优化。设置为false,就是view具备绘制功能。

一个小知识 如何在activity一启动就获取某个view的宽高

1.activity/view.onWindowFocusChanged()这个方法说明view已经初始化完成了,但是如果activity频繁的onResume/onPause()那么这个方法也会被频繁地调用。
2. view.post(runnable) post提交一个runnable到消息队列的尾部,要消费的时候view也就初始化完成了
3. viewTreeObserver 在view树的状态发生改变时,会调用onGlobalLayout,但这个方法会伴随view树状态改变调用多次,所以要及时移除监听。

Activity
void onStart() {
  ViewTreeObserver vto = view.addViewTreeObserver;
  vto.addOnGlobalLayoutListener(new ) {
            void onGlobalLayout() {
                  //要及时移除
                view.removeGlobalLayoutListener(this);
                int width = view.getMeasuredWidth();
            }
   }
}

自定义View

上面说了可以自定View,它主要有以下几种

1.继承View 重写onDraw(),要自己支持WARP_CONTENT, padding这些属性。
2. 继承ViewGroup,重写measure(),layout(),要处理好自己的测量还有子view的measure和layout
3. 继承现有的View 比如TextView 、Button等
4. 继承现有的ViewGroup 比如LinearLayout、FrameLayout等
一个小知识 尽量不要在view中使用handler,view有自己的post方法。
一个小知识 view中如果有线程、动画等,可以在view.onDetachedFromWindow()里处理,对应Activity退出或view被remove的时候。onAttachedFromWindow()是启动的时候会调用。这个就要跟window初始化放在一起讲讲

事件分发

就是当用户点击屏幕,会有一个MotionEvent生成,这个event在view中层级传递的过程就叫事件分发。从activity层级传递如下图。如果view的onTouchEvent返回false,说明view无法处理,会一层一层向上传递调用父类的onTouchEvent,最终会调用Activity的onTouchEvent直到被消费。


自上而下传递流程图

一个小知识 如果viewgroup把onInterceptTouchEvent方法重写并且返回true的话,这样viewgroup会在dispatchTouchEvent里当move、up事件来的时候,会把自己的intercept参数设置成true,这样再来事件的时候除了down事件每次都会走onInterceptTouchEvent方法,move、up就都不会再走onInterceptTouchEvent这个方法了。
三个方法主要逻辑伪代码如下《Android进阶之光》他俩写的都差不多,就记录一下

public void onDispatchTouchEvent(MotionEvent event) {
  boolean result = false;
  if(onInterceptTouchEvent(event)){
    result = super.onTouchEvent(event);
  }else {
    result = child.onTouchEvent(event);
  }
}

一个小知识 如果view设置了onTouchListener,那么这个优先级比onTouchEvent()方法高,有了触摸事件会先走listener的onTouch()方法,但如果这个onTouch()返回false,那他又会走onTouchEvent()方法。

滑动冲突

这个就是从上面的事件分发里面整出来的,关键一句话就是拦截需要的事件并处理。一般遇到的是比如父组件上下滑动和子view左右滑动冲突,要不就是两个嵌套的都是可以上下滑动的。ViewGroup拦截,重写onInterceptTouchEvent方法。比如父组件上下子view左右滑动冲突,就在父组件中只拦截上下滑动的事件(ΔY>ΔX的时候返回true),其他的传递给下层view。

GestureDetector

手势检测,辅助检测单击、双击、长按、滑动等行为。要用就初始化一个对象GestureDetector mGesture = GestureDetector(OnGestureListener的实现类对象),然后用在view的onTouchEvent里面result = mGesture.onTouchEvent(event)。return result;

一个小知识如果只是处理滑动,可以不用这个类,直接在onTouchEvent里面处理就行,如果要监听双击或者快速滑动啥的(就是你看看Listener里面都支持啥功能)还有一个类OnDoubleTapListener,也是双击啥的,可以了解了解

动画

分开写吧还是

一些重要概念

1.屏幕坐标系

屏幕左上角是原点,向右x轴正方向,向下y轴正方向。
系统提供api可以获取view的具体位置


《Android进阶之光》总结图

viewA 到ViewGroupB: getLeft(),getTop(),getRight(),getBottom()
view的宽度:width = getRight() - getLeft()
view的高度:height = getBottom() - getTop()
触摸点到屏幕 :getRawX(),getRawY()
触摸点到view: getX(),getY()

MeasureSpec

MeasureSpec是把布局需要的尺寸size和模式mood压缩成一个32位int值,前2位是mode,后30位是size。通过这个int值获取到mode,就是前面说的AT_MOST,EXACTLY,UNSPECIFIED这三种。通过Mode算出对应的view的宽高.

MeasureSpec和LayoutParam的关系: DecorView MeasureSpec是由窗口尺寸和自身的LayoutParam决定的,普通View通过父容器的MeasureSpec和自身的LayoutParam决定自己的MeasureSpec.我们给View设置LayoutParam系统会在父容器的约束下转换为对应的MeasureSpec,然后由这个确定View的宽高,调用ViewGroup.getChildMeasureSpec。
ViewGroup.getChildMeasureSpec总结表

父容器 AT_MOST EXACTLY UNSPECIFIED
子view 指定大小A A (EXACTLY) A (EXACTLY) A (EXACTLY)
子view MATCH_PARENT 父亲剩余空间(EXACTLY) 父亲剩余空间(AT_MOST) 0 (UNSPECIFIED)
子view WARP_CONTENT 父亲剩余空间(AT_MOST) 父亲剩余空间(AT_MOST) 0 (UNSPECIFIED)

滑动

一个View声明的时候,高度高度可以是指定的数值EXACTLY,也可以是AT_MOST指定取值为match_parent,但如果指定高度超过屏幕,那么也无法展示出来,这就需要我们的控件支持滑动。
实现滑动有以下几种方式

  • layout();
  • offsetLeftAndRight();/offsetTopAndBottom();
  • LayoutParams
  • 动画
  • scrollTo/scrollBy
  • Scroller

1.layout()方法
通过view的onTouchEvent方法,在ACTION_MOVE的时候调用layout()方法,将坐标设置为最新的左上右下(通过过去触摸滑动的距离(x,y),将view坐标加上这些偏移量进行移动)
2.offsetLeftAndRight();/offsetTopAndBottom();
调用的地方是相同的,不过是把MOVE的偏移量通过这两个方法设置即可x位移量通过offsetLeftAndRight();Y的偏移量通过offsetTopAndBottom();他俩是改变view的位置的,改变之后就是getLeft()/getTop()啥的获取值就变了。
3.LayoutParams
调用的地方是相同的,setLayoutParam()将偏移量设置给layoutParam.X =x+Δx;layoutParam.Y =y+Δy
4.动画
通过属性动画设置位移
5.scrollTo-->移动到指定x,y
6.scrollBy-->移动指定的偏移量Δx,Δy。这个偏移量是相对于view和view内容的位置来的,只会移动内容,不会移动view,如果是想作用到view上,应该获取他的parent来执行scrollBy。scrollBy内部其实也是调用了scrollTo实现的。

第一次调用肯定觉得设置正数就是跟着手指向右边滑动,设置成负数就会跟着手指香左边滑动。但是实际效果会奇怪,为啥要-(x)-(y)才会跟着手走,源码用到mScrollX/Y是在重绘的时候。

View.invalidate()

可以想象View的左上顶点A和它的Content的左上顶点B,scrollX= BA向量,与x轴同向时为正,scrollY= BA向量,与y轴同向时为正。可以看看下图。
《Android开发艺术探索》view的scrollX/Y图

7.Scroller
就这个可以实现平滑有过渡的移动,上面的方法就设置偏移量一步到位了,有些生硬
是一个滑动类,如果要让view配合scroller平滑有过度的移动,那么要和view的computeScroll()方法联合使用。第一步是初始化一个Scroller。第二步重写view的computeScroll()方法。第三步在需要的地方提供mScroller.startScroll(mScrollX, mScrollY, dx, dy,time); 这四个值都是像素值,并且正值往屏幕坐标系的负方向走的。time不传默认250毫秒
TextView重写的computeScroll()

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容