View基本知识

2018-10-10  本文已影响11人  30cf443c3643

在Android的视图控件中,有简单的文本TextView和按钮Button,还有复杂的RelativeLayout和ListView。但是不管是简单的View还是复杂的ViewGroup控件,他们的共同基类都是View。View和ViewGroup是一个非常经典的组合模式的关系,类似文件和文件夹的关系。那为什么ViewGroup有容器的功能呢?


2018-10-08_153947.png

可以看到ViewGroup实现了ViewManager接口,而ViewManager定义了addView,removeView等操作视图的方法,ViewParent则定义了刷新容器等方法。另外ViewGroup的onLayout是一个空实现,所以它的子类要自己去实现,例如LinearLayout

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            layoutVertical(l, t, r, b);
        } else {
            layoutHorizontal(l, t, r, b);
        }
    }

此外onMeasure和onDraw方法,ViewGroup都没有重写,只是增加了计算子View的方法,dispatchDraw方法中调用子view的onDraw方法。由此构成了一个控件树。


687.jpg

屏幕坐标系

屏幕坐标系 屏幕左上角为原点,x轴向右为正,y轴向下为负
view 的getTop()、getBottom(),getLeft(),getRight()指的的是相对于父容器的数值。


005Xtdi2gw1f1qzqwvkkbj308c0dwgm9.jpg

还有getWidth,getHeight。有一点要特别注意view的这几个值在Activity的onCreate和onStart里返回的都是0。因为view的测量measure过程跟activity的生命周期并不是同步的。如果View的Visible初始状态是gone,getWidth跟getHeight得到的数值为0;

MotionEvent

在view的事件体系里,我们可以通过MotionEvent得到点击事件发生的相对当前view的xy坐标以及相对屏幕的绝对坐标xy

005Xtdi2jw1f1r2bdlqhbj308c0dwwew.jpg

getLocationInWindow 和 getLocationOnScreen

2018-09-07_160917.png

还特意去验证了下,控件C在屏幕A所在的窗口里 getLocationInWindow和getLocationOnScreen数值一样。例如,c在dialog里就有差别了。但是在我把控件C改为gone状态时候,它的x为0,y的值却为210?

    public void getLocationOnScreen(@Size(2) int[] outLocation) {
        getLocationInWindow(outLocation);

        final AttachInfo info = mAttachInfo;
        if (info != null) {
            outLocation[0] += info.mWindowLeft;
            outLocation[1] += info.mWindowTop;
        }
    }

ViewConfiguration

这个类主要定义了UI中所使用到的标准常量,像超时、尺寸、距离,通过ViewConfiguration.get(context)获得实例。常用的方法有

vc.getScaledTouchSlop() 系统能识别的被认为是滑动的最小距离

其他还有

// 获得允许执行fling (抛)的最大速度值
int maximumVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
需要配合VelocityTracker速度追踪来使用

在onTouchEvent中追踪当前点击事件的速度

    private void acquireVelocityTracker(final MotionEvent event) {
        if (null == mVelocityTracker) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);
    }

当我们想知道当前的滑动速度时

mVelocityTracker.computeCurrentVelocity(1000); 单位是毫秒
然后得到
float y = mVelocityTracker.getYVelocity();//getYVelocity前必须调用computeCurrentVelocity
表示1000毫秒内划过的像素数 比如10个像素,那速度就是10像素/1000毫秒

最后重置回收 clear() recycle()
ViewConfiguration api

static float getScrollFriction()
一个代表了摩擦系数的标量。它应用在flings 或 scrolls 状态。mScroll.setFriction

static int getDoubleTapTimeout()
返回一个 双击的毫秒超时时间。
它在第一次点击的up事件开始记录,在第二次点击的down事件停止。
两次点击之间的时间值小于等于getDoubleTapTimeout(),就表示是一个双击操作

View的生命周期

它的生命周期会随着Activity的生命周期进行变化,掌握View的生命周期对我们自定义View有着重要的意义。

view_lifecycle.png
图片来自beesAndroid下的视图框架view

LayoutParams

LayoutParams是什么?

Every single ViewGroup (e.g. LinearLayout , RelativeLayout , CoordinatorLayout , etc.) needs to store information
about its children's properties. About the way its children are being laid out in the ViewGroup . This information is
stored in objects of a wrapper class ViewGroup.LayoutParams .

LayoutParams继承于Android.View.ViewGroup.LayoutParams相当于一个Layout的信息包,它封装了Layout的位置、高、宽等信息。假设在屏幕上一块区域是由一个Layout占领的,如果将一个View添加到一个Layout中,最好告诉Layout用户期望的布局方式,也就是将一个认可的layoutParams传递进去。

Most of ViewGroups reutilize the ability to set margins for their children, so they do not subclass
ViewGroup.LayoutParams directly, but they subclass ViewGroup.MarginLayoutParams instead (which itself is a
subclass of ViewGroup.LayoutParams ).

大多数ViewGroup具有为children设置外边距的能力,所以他们不是ViewGroup.LayoutParams的直接子类,而是MarginLayoutParams的子类
可以查看LinearLayout.LayoutParams的源代码

public static class LayoutParams extends ViewGroup.MarginLayoutParams { ...}

getLayoutParams

Because the LayoutParams object is directly related to the enclosing ViewGroup , this method will return a non-null
value only when View is attached to the ViewGroup . You need to bare in mind that this object might not be present
at all times. Especially you should not depend on having it inside View's constructor.

因为LayoutParams跟封闭的ViewGroup直接相关,getLayoutParams可能返回一个null值。必须牢记,只有当View依附到ViewGroup时才能使用。尤其不能在View的构造函数中使用

错误代码

public class ExampleView extends View {
   public ExampleView(Context context) {
      super(context);
      setupView(context);
}
   public ExampleView(Context context, AttributeSet attrs) {
       super(context, attrs);
       setupView(context);
}
   public ExampleView(Context context, AttributeSet attrs, int defStyle) {
       super(context, attrs, defStyle);
       setupView(context);
  } 
   private void setupView(Context context) {
       if (getLayoutParams().height == 50){ // DO NOT DO THIS!
      // This might produce NullPointerException
            doSomething();
      }
   }
//...
}

而是在onAttachedToWindow中,跟得到view的宽高是一样的

@Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    if (getLayoutParams().height == 50) { // getLayoutParams() will NOT return null here
          doSomething();
   }
}

getLayoutParams的正确示范

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/outer_layout"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="vertical">
      <FrameLayout
           android:id="@+id/inner_layout"
          android:layout_width="match_parent"
          android:layout_height="50dp"
          android:layout_gravity="right"/>
</LinearLayout>

正确

FrameLayout innerLayout = (FrameLayout)findViewById(R.id.inner_layout);
LinearLayout.LayoutParams par = (LinearLayout.LayoutParams) innerLayout.getLayoutParams();
// CORRECT! the enclosing layout is a LinearLayout

错误

FrameLayout innerLayout = (FrameLayout)findViewById(R.id.inner_layout);
FrameLayout.LayoutParams par = (FrameLayout.LayoutParams) innerLayout.getLayoutParams();
// INCORRECT! This will produce ClassCastException
上一篇下一篇

猜你喜欢

热点阅读