View 的各种知识

来源:互联网 发布:数据库自动生成代码 编辑:程序博客网 时间:2024/04/30 07:25

多次调用onMeasure onLayout

先看代码

public class MyView extends View {private static final String TAG = "MyView";public MyView(Context context) {    super(context);    init();}public MyView(Context context, AttributeSet attrs) {    super(context, attrs);    init();}public MyView(Context context, AttributeSet attrs, int defStyleAttr) {    super(context, attrs, defStyleAttr);    init();}private void init() {}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    super.onMeasure(widthMeasureSpec, heightMeasureSpec);    Log.d(TAG, "View onMeasure");}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {    super.onLayout(changed, left, top, right, bottom);    Log.d(TAG, "View onLayout");}@Overrideprotected void onDraw(Canvas canvas) {    super.onDraw(canvas);    Log.d(TAG, "View onDraw");}@Overridepublic void computeScroll() {    super.computeScroll();    Log.d(TAG, "view computeScroll");}@Overridepublic void draw(Canvas canvas) {    super.draw(canvas);}}  

布局文件

<?xml version="1.0" encoding="utf-8"?><com.example.jinxiong.blog.MyView    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:id="@+id/activity_main"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="com.example.jinxiong.blog.MainActivity"></com.example.jinxiong.blog.MyView>  

Log打印

09-30 10:01:29.454 27838-27838/com.example.jinxiong.blog D/MyView: View onMeasure09-30 10:01:29.538 27838-27838/com.example.jinxiong.blog D/MyView: View onLayout09-30 10:01:29.754 27838-27838/com.example.jinxiong.blog D/MyView: View onMeasure09-30 10:01:29.754 27838-27838/com.example.jinxiong.blog D/MyView: View onLayout09-30 10:01:29.754 27838-27838/com.example.jinxiong.blog D/MyView: view computeScroll09-30 10:01:29.754 27838-27838/com.example.jinxiong.blog D/MyView: View onDraw

为什么会调用了两次的onMeasure onLayout方法尼?
官方文档:
A parent View may call measure() more than once on its children. For example, the parent may measure each child once with unspecified dimensions to find out how big they want to be, then call measure() on them again with actual numbers if the sum of all the children’s unconstrained sizes is too big or too small (that is, if the children don’t agree among themselves as to how much space they each get, the parent will intervene and set the rules on the second pass).
一个父View或许会多次调用子View的measure()方法。举个例子,父View会使用不明确的尺寸去丈量看看子View到底需要多大,当子View总的尺寸太大或者太小的时候会再次使用实际的尺寸去调用onmeasure()
网上也有很多这个问题的答案想法
http://stackoverflow.com/questions/21178959/why-android-onmeasure-be-called-twice-in-my-custom-view

https://github.com/android-cn/android-discuss/issues/386

关于自定义ViewGroup 没有调用onDraw

public class MyViewGroup extends LinearLayout {private static final String TAG = "MyView";public MyViewGroup(Context context) {    super(context);}public MyViewGroup(Context context, AttributeSet attrs) {    super(context, attrs);}public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {    super(context, attrs, defStyleAttr);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    super.onMeasure(widthMeasureSpec, heightMeasureSpec);    Log.d(TAG, "ViewGroup onMeasure");}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {    super.onLayout(changed, left, top, right, bottom);    Log.d(TAG, "ViewGroup onLayout");}@Overrideprotected void onDraw(Canvas canvas) {    super.onDraw(canvas);    Log.d(TAG, "ViewGroup onDraw");}@Overrideprotected void dispatchDraw(Canvas canvas) {    super.dispatchDraw(canvas);    Log.d(TAG, "viewGroup dispatchDraw");}}  

布局

<?xml version="1.0" encoding="utf-8"?><com.example.jinxiong.blog.MyViewGroup    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:id="@+id/activity_main"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="com.example.jinxiong.blog.MainActivity"></com.example.jinxiong.blog.MyViewGroup>  

Log

09-30 10:16:45.474 9522-9522/com.example.jinxiong.blog D/MyView: ViewGroup onMeasure09-30 10:16:45.538 9522-9522/com.example.jinxiong.blog D/MyView: ViewGroup onLayout09-30 10:16:45.554 9522-9522/com.example.jinxiong.blog D/MyView: ViewGroup onMeasure09-30 10:16:45.554 9522-9522/com.example.jinxiong.blog D/MyView: ViewGroup onLayout09-30 10:16:45.554 9522-9522/com.example.jinxiong.blog D/MyView: viewGroup dispatchDraw  

但是当我布局文件中使用①中的MyViewGroup的时候他就会调用onDraw,为什么?
其实在View中有这么一个方法

 /**     * If this view doesn't do any drawing on its own, set this flag to     * allow further optimizations. By default, this flag is not set on     * View, but could be set on some View subclasses such as ViewGroup.     *     * Typically, if you override {@link #onDraw(android.graphics.Canvas)}     * you should clear this flag.     *     * @param willNotDraw whether or not this View draw on its own     */    public void setWillNotDraw(boolean willNotDraw) {    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);}

当我们继承自ViewGroup(这里LinearLayout)的时候,这个标志位一直是打开的,而对于View默认是false的,所以当我们在MyViewGroup中重新设置:

setWillNotDraw(false);  

那么它的onDraw就会调用。至于为什么这样,是因为android优化,既然你这个ViewGroup没有什么东西draw,那么我为什么要调用这个方法尼,(我的布局文件没有设置background,也设置其他需要draw)。当然我们也可以在我们的MyView中手动设置setWillNotDraw(true); 那么当我们没有放置任何东西给这个view draw ,那么它的onDraw也不会被调用

View视图是没有边界的


可以看到我们可以在这个坐标系中画一条跨越234象限的直线,但是在手机的屏幕上我们只能看到第4象限的某一部分,手机屏幕的大小我们这里是A*B ,对于我们想要看到其余的地方,那么我们需要通过scrollTo/By方法来实现

<?xml version="1.0" encoding="utf-8"?><com.example.jinxiong.blog.MyView    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:id="@+id/activity_main"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="com.example.jinxiong.blog.MainActivity"></com.example.jinxiong.blog.MyView>  

MyView中onDraw方法

@Overridepublic void onDraw(Canvas canvas) {    Paint paint = new Paint();    paint.setColor(Color.GREEN);    canvas.drawLine(-200, -200, 0, 0, paint);    paint.setColor(Color.RED);    canvas.drawLine(0, 0, 200, 200, paint);    paint.setColor(Color.BLUE);    canvas.drawLine(200, 200, 400, 400, paint);    super.draw(canvas);}

Activity

@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    final View view = this.findViewById(R.id.activity_main);    view.setOnClickListener(new View.OnClickListener() {        @Override        public void onClick(View v) {            view.scrollBy(-200,-200);        }    });}


点击之后

相当于这个样子:

    /**方法注释     * 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     */  

其实这个方法就是使这个调用scrollBy/To的View 的内容滑动,其实怎么解释它的正负比较好理解,我的想法就是:

假设我们想看到“我想看到你”这个字符串,我们怎么滑动,我们把手指放在屏幕上,然后慢慢向上移动,就可以看到,那么我们可以记录下你down事件时候的坐标,然后在记录你move事件的坐标,用最后down坐标减去move坐标,那么是不是得到Y坐标是一个正数,就是view.scrollBy(0,Y)。然后我们想看到“手机的宽度为A”假设我们画了这个文字在canvas上,那么我们也是可以像上面那样,用上面的方法可以得知Y是个负数。你可以想象成这个view是可以上下滑动的,想listView那样。

手机硬件加速问题

MyViewGroup

public class MyViewGroup extends LinearLayout {private static final String TAG = "MyView";public MyViewGroup(Context context) {    super(context);    init();}public MyViewGroup(Context context, AttributeSet attrs) {    super(context, attrs);    init();}public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {    super(context, attrs, defStyleAttr);    init();}private void init() {    setWillNotDraw(false);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    super.onMeasure(widthMeasureSpec, heightMeasureSpec);    Log.d(TAG, "ViewGroup onMeasure");}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {    super.onLayout(changed, left, top, right, bottom);    Log.d(TAG, "ViewGroup onLayout");}@Overrideprotected void onDraw(Canvas canvas) {    super.onDraw(canvas);    Log.d(TAG, "ViewGroup onDraw");}@Overrideprotected void dispatchDraw(Canvas canvas) {    super.dispatchDraw(canvas);    Log.d(TAG, "viewGroup dispatchDraw");}}

MyView

public class MyView extends View{private static final String TAG = "MyView";public MyView(Context context) {    super(context);    init();}public MyView(Context context, AttributeSet attrs) {    super(context, attrs);    init();}public MyView(Context context, AttributeSet attrs, int defStyleAttr) {    super(context, attrs, defStyleAttr);    init();}private void init() {//        setWillNotDraw(true);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    super.onMeasure(widthMeasureSpec, heightMeasureSpec);    Log.d(TAG, "View onMeasure");}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {    super.onLayout(changed, left, top, right, bottom);    Log.d(TAG, "View onLayout");}@Overrideprotected void onDraw(Canvas canvas) {    super.onDraw(canvas);    Log.d(TAG, "View onDraw");}@Overridepublic void computeScroll() {    super.computeScroll();    Log.d(TAG, "view computeScroll");}}  

布局:

<?xml version="1.0" encoding="utf-8"?><com.example.jinxiong.blog.MyViewGroupxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/activity_main"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@android:color/holo_blue_bright"tools:context="com.example.jinxiong.blog.MainActivity"><com.example.jinxiong.blog.MyView    android:layout_width="match_parent"    android:layout_height="match_parent"    android:id="@+id/myView"    android:background="@android:color/black"    /></com.example.jinxiong.blog.MyViewGroup>  

Activity

public class MainActivity extends AppCompatActivity {private static final String TAG = "MyView";@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    final View viewGroup = this.findViewById(R.id.activity_main);    final View view = this.findViewById(R.id.myView);    viewGroup.setOnClickListener(new View.OnClickListener() {        @Override        public void onClick(View v) {            viewGroup.scrollBy(0,100);            if (viewGroup.getScrollY() > 1200) {                viewGroup.scrollTo(0, 0);            }        }    });}}  

原始样子:

点击之后:

09-30 23:50:07.390 1465-1465/com.example.jinxiong.blog D/MyView: ViewGroup onDraw09-30 23:50:07.390 1465-1465/com.example.jinxiong.blog D/MyView: viewGroup dispatchDraw09-30 23:50:07.854 1465-1465/com.example.jinxiong.blog D/MyView: ViewGroup onDraw09-30 23:50:07.854 1465-1465/com.example.jinxiong.blog D/MyView: viewGroup dispatchDraw09-30 23:50:16.822 1465-1465/com.example.jinxiong.blog D/MyView: ViewGroup onDraw09-30 23:50:16.822 1465-1465/com.example.jinxiong.blog D/MyView: viewGroup dispatchDraw  

为什么我的MyView没有重绘尼?当我点击多次之后viewGroup.scrollTo(0,0) myViewde onDraw方法还是没有调用,者就有点不科学了,为什么?后来查了很久才发现原来有个硬件加速的东西,当我在我的manifest中添加了

android:hardwareAccelerated="false"  

再次运行,每一次的点击

09-30 23:57:26.078 7783-7783/com.example.jinxiong.blog D/MyView: ViewGroup onDraw09-30 23:57:26.078 7783-7783/com.example.jinxiong.blog D/MyView: view computeScroll09-30 23:57:26.078 7783-7783/com.example.jinxiong.blog D/MyView: View onDraw09-30 23:57:26.078 7783-7783/com.example.jinxiong.blog D/MyView: viewGroup dispatchDraw

ok了这样就,摘录自一篇文章:

Android的绘制模型
开启硬件加速后,Android框架将采用新的绘制模型。基于软件的绘制模型和基于硬件的绘制模型有和不同呢?
基于软件的绘制模型 在软件绘制模型下,视图按照如下两个步骤绘制:
1. Invalidate the hierarchy(注:hierarchy怎么翻译?)
2. Draw the hierarchy
应用程序调用invalidate()更新UI的某一部分,失效(invalidation)消息将会在整个视图层中传递,计算每个需要重绘的区域(即脏区域)。然后Android系统将会重绘所有和脏区域有交集的view。很明显,这种绘图模式存在缺点:
1. 每个绘制操作中会执行不必要的代码。比如如果应用程序调用invalidate()重绘button,而button又位于另一个view之上,即使该view没有变化,也会进行重绘。
2. 可能会掩盖一些应用程序的bug。因为android系统会重绘与脏区域有交集的view,所以view的内容可能会在没有调用invalidate()的情况下重绘。这可能会导致一个view依赖于其它view的失效才得到正确的行为。
基于硬件的绘制模型
Android系统仍然使用invalidate()和draw()来绘制view,但在处理绘制上有所不同。Android系统记录绘制命令到显示列表,而不是立即执行绘制命令。另一个优化就是Android系统只需记录和更新标记为脏(通过invalidate())的view。新的绘制模型包含三个步骤:
1. Invalidate the hierarchy
2. 记录和更新显示列表
3. 绘制显示列表

就是说当我们开启了硬件加速(默认是开的3.0开始),那么当我们改变某一个view的时候只会更新该view的脏区,举个例子吧
开了硬件加速:

public class MainActivity extends AppCompatActivity {private static final String TAG = "MyView";private int color = 20;@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    final View viewGroup = this.findViewById(R.id.activity_main);//        final View view = this.findViewById(R.id.myView);    viewGroup.setOnClickListener(new View.OnClickListener() {        @Override        public void onClick(View v) {            viewGroup.setBackgroundColor(Color.rgb(color + 20, 20, 20));            color += 20;        }    });}}

其余代码不变

10-01 00:36:02.686 10350-10350/com.example.jinxiong.blog D/MyView: ViewGroup onDraw10-01 00:36:02.686 10350-10350/com.example.jinxiong.blog D/MyView: viewGroup dispatchDraw10-01 00:36:57.554 10350-10350/com.example.jinxiong.blog D/MyView: ViewGroup onDraw10-01 00:36:57.554 10350-10350/com.example.jinxiong.blog D/MyView: viewGroup dispatchDraw

关闭硬件加速

10-01 00:38:06.606 12367-12367/com.example.jinxiong.blog D/MyView: ViewGroup onDraw10-01 00:38:06.606 12367-12367/com.example.jinxiong.blog D/MyView: view computeScroll10-01 00:38:06.610 12367-12367/com.example.jinxiong.blog D/MyView: View onDraw10-01 00:38:06.610 12367-12367/com.example.jinxiong.blog D/MyView: viewGroup dispatchDraw

硬件加速相关链接
http://blog.sina.com.cn/s/blog_821e2bb10102vdij.html

关于scrollTo/By 和 setTranslation

其实一开始对这两个方法不是很好的理解,但是慢慢的还是明白了,但是后来很奇怪为什么么他两不会引起onMeasure 和 onLayout这个两个方法

无论对于MyView 还是MyViewGroup 他们之间的位置都是没有改变的,那么为什么还需要measure 和 layout 尼

/**     * Sets the horizontal location of this view relative to its {@link #getLeft() left} position.     * This effectively positions the object post-layout, in addition to wherever the object's     * layout placed it.     *     * @param translationX The horizontal position of this view relative to its left position,     * in pixels.     *     * @attr ref android.R.styleable#View_translationX     */

translationX/Y是view中的一个属性,只是在layout 之后再次移动的位置,并不会改变view的left ,top,right,bottom的值,也不会引起重新measure 和layout ,只有改变某个view 的padding 和margin属性值才会引起重新measure 和 layout(我暂时知道的/微笑)

0 0