View基本知识
在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.jpggetLocationInWindow 和 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有着重要的意义。
图片来自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