本篇文章已授权微信公众号 顾林海 独家发布

Android中的坐标系

在Android中,屏幕左上角是Android坐标系的原点,向右是x轴正方向,向下是y轴正方向,通过getRawX()和getRawY()方法可以获取屏幕的坐标系,通过getX()和getY()方法可以获取手指在某个View的坐标系。

通过如下方法可以获得View到其父控件的距离:

  • getTop():获取View自身顶边到其父布局顶边的距离。

  • getLeft():获取View自身左边到其父布局左边的距离。

  • getRight():获取View自身右边到其父布局左边的距离。

  • getBottom():获取View自身底边到其父布局顶边的距离。

总结如图:

Scroller

scrollTo(x,y)表示移动到一个具体的坐标点,而scrollBy(dx,dy)表示移动的增量为dx、dy,scrollBy最终还是调用scrollTo方法。

使用scrollTo/scrollBy方法进行滑动,整个滑动效果是瞬间完成的,可以使用Scroller来实现过渡效果的滑动。Scroller本身不能实现View的滑动,需要与View的computeScroll方法配合使用。

private Scroller mScroller;private Context mContext;private void init(){    mScroller=new Scroller(mContext);}@Overridepublic void computeScroll() {    super.computeScroll();    if(mScroller.computeScrollOffset()){        //内容在移动        scrollTo(mScroller.getCurrX(),mScroller.getCurrY());        //重绘        invalidate();    }}public void smoothScrollTo(int destX,int desY){    int scrollX=getScrollX();    int scrollY=getScrollY();    int deltaX=destX-scrollX;    int deltaY=desY-scrollY;    mScroller.startScroll(scrollX,scrollY,deltaX,deltaY,2000);    invalidate();}复制代码

通过调用invalidate()方法不断地进行重绘,重绘就会调用computeScroll()方法,就这样通过不断的移动来实现滑动效果。

Scroller构造方法:

public Scroller(Context context) {    this(context, null);}public Scroller(Context context, Interpolator interpolator) {    this(context, interpolator,            context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);}public Scroller(Context context, Interpolator interpolator, boolean flywheel) {    mFinished = true;    if (interpolator == null) {        mInterpolator = new ViscousFluidInterpolator();    } else {        mInterpolator = interpolator;    }    mPpi = context.getResources().getDisplayMetrics().density * 160.0f;    mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());    mFlywheel = flywheel;    mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning}复制代码

Scroller提供了三个构造方法,平时使用最多的就是第一个,第二个传入一个差值器Interpolator,默认使用ViscousFluidInterpolator。

startScroll()方法:

public void startScroll(int startX, int startY, int dx, int dy, int duration) {    mMode = SCROLL_MODE;    mFinished = false;    mDuration = duration;    mStartTime = AnimationUtils.currentAnimationTimeMillis();    mStartX = startX;    mStartY = startY;    mFinalX = startX + dx;    mFinalY = startY + dy;    mDeltaX = dx;    mDeltaY = dy;    mDurationReciprocal = 1.0f / (float) mDuration;}复制代码

startScroll方法中并没有执行滑动代码,而是保存了各种参数,startX和startY表示滑动开始的起点,dx和dy表示滑动的距离,duration表示滑动持续的时间。这个startScroll方法为进行滑动做准备,在startScroll方法后,调用invalidate()方法进行重绘,重绘调用draw()方法,而draw()方法又会调用View的computeScroll()方法,重写computeScroll()方法。

@Overridepublic void computeScroll() {super.computeScroll();    if(mScroller.computeScrollOffset()){        //内容在移动        scrollTo(mScroller.getCurrX(),mScroller.getCurrY());        //重绘        invalidate();    }}复制代码

在computeScroll()方法中通过Scroller获取当前的ScrollX和ScrollY,然后调用scrollTo()方法进行View的滑动,接着调用invalidate()方法进行重绘,重绘又会调用draw()方法,draw()方法调用computeScroll()方法,就这样不停的重绘不停的执行scrollTo方法,当调用Scroller对象的computeScrollOffset()方法,该方法返回false时滑动停止。

computeScrollOffset方法:

public boolean computeScrollOffset() {    if (mFinished) {        return false;    }    int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);    if (timePassed < mDuration) {        switch (mMode) {        case SCROLL_MODE:            final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);            mCurrX = mStartX + Math.round(x * mDeltaX);            mCurrY = mStartY + Math.round(x * mDeltaY);            break;        case FLING_MODE:            final float t = (float) timePassed / mDuration;            final int index = (int) (NB_SAMPLES * t);            float distanceCoef = 1.f;            float velocityCoef = 0.f;            if (index < NB_SAMPLES) {                final float t_inf = (float) index / NB_SAMPLES;                final float t_sup = (float) (index + 1) / NB_SAMPLES;                final float d_inf = SPLINE_POSITION[index];                final float d_sup = SPLINE_POSITION[index + 1];                velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);                distanceCoef = d_inf + (t - t_inf) * velocityCoef;            }            mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;            mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));            // Pin to mMinX <= mCurrX <= mMaxX            mCurrX = Math.min(mCurrX, mMaxX);            mCurrX = Math.max(mCurrX, mMinX);            mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));            // Pin to mMinY <= mCurrY <= mMaxY            mCurrY = Math.min(mCurrY, mMaxY);            mCurrY = Math.max(mCurrY, mMinY);            if (mCurrX == mFinalX && mCurrY == mFinalY) {                mFinished = true;            }            break;        }    }    else {        mCurrX = mFinalX;        mCurrY = mFinalY;        mFinished = true;    }    return true;}复制代码

一开始计算动画的持续时间timePassed,如果动画持续时间小于我们设置的滑动时间mDuration,执行switch语句,在上述startScroll方法中mMode被设置为SCROLL_MODE,所以执行分支语句SCROLL_MODE,根据差值器来计算出在该时间段内移动的距离,赋值给mCurrX和mCurrY。

getCurrX和getCurrY方法:

public final int getCurrX() {    return mCurrX;}public final int getCurrY() {    return mCurrY;}复制代码

这两个方法就拿到了computeScrollOffset方法中计算出来的某个时间段内应该移动的距离。

MeasureSpec

MeasureSpec是View的内部类,系统将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后在onMeasure方法中根据这个MeasureSpec来确定View的宽和高。

MeasureSpec代表32位的int值,高2位代表SpecMode,低30位代表SpecSize。SpecMode是指测量模式,SpecSize是指测量大小。

SpecMode提供3中模式:

  1. UNSPECIFIED:表示未指定模式,View想多大就多大,父容器不做限制,一般用于系统内部的测量。

  2. AT_MOST:表示最大模式,对应于wrap_content属性,子View的最终大小是父View指定的SpecSize值,并且子View的大小不能大于这个值。

  3. EXACTLY:表示精确模式,对应于match_parent属性和具体的数值,父容器测量出View所需要的大小,也就是SpecSize值。

View测量过程中,通过makeMeasureSpec来保存宽高,通过getMode获取指定模式,通过getSize获取宽和高。MeasureSpec是受自身LayoutParams和父容器的MeasureSpec共同影响的。

View的工作流程

View的工作流程指的是measure、layout和draw。measure用来测量View的宽高,layout用来确定View的位置,draw用来绘制View。

Activity构建过程中,创建DecoreView后,它的内容还无法显示,因为它还没有被加载到Window中。

当调用Activity的startActivity方法时,最终调用ActivityThread的handleLaunchActivity方法来创建Activity。handleLaunchActivity方法中先通过performLaunchActivity方法来创建Activity,再执行handleResumeActivity方法。

Activity的startActivity局部过程如下:

WindowManager的addView方法传入DecorView,WindowManager的实现类是WindowManagerImpl。

WindowManagerImpl的addView相关过程如下:

ViewRootImpl是View的根View,控制View的测量和绘制,同时持有WindowSession通过Binder与WMS通信,最终将DecorView加载到Window中。

开始View的工作流程是在ViewRootImpl的performTraversals()方法中,performTraversals方法中重要的三个方法是:performMeasure、performLayout和performDraw,分别对应测量、布局和绘制。

View进行测量时,根据SpecMode来返回不同的值,在AT_MOST和EXACTLY模式下,都返回SpecSize这个值,也就是说它的wrap_content和match_parent属性的效果都一样,因此在自定义View时需要重写onMeasure方法,对wrap_content属性进行处理。对于ViewGroup来说,它会遍历子元素的measure方法,根据父容器的MeasureSpec模式再结合子元素的LayoutParams属性来得出子元素的MeasureSpec属性。

View进行布局时,通过layout方法确定自身的位置,在layout方法中调用setFrame方法确定mLeft、mTop、mRight、mBottom这4个值,通过这4个值就可以确定自身在父容器中的位置,在调用setFrame方法后,调用onLayout方法,这是一个空方法,由它们的子类来确定;对于ViewGroup来说,遍历layout方法用来确定子元素的位置,onLayout也是一个空方法,交由它的子类实现。

最后进行绘制时,会按照一定步骤来进行绘制:绘制背景、保存当前canvas层、绘制View的内容、绘制子View、绘制子View的边缘、绘制装饰。在第三步绘制View的内容时,调用onDraw方法,这是一个空方法,需要子类实现。


更多相关文章

  1. android View移动的四种方式
  2. 关于Android(安卓)SDK包里没有/docs文件夹帮助文档的处理方法
  3. android 显示消息框的方法
  4. android中对apk文件反编译的方法(详细)
  5. Java、Android的异常处理原理&Android(安卓)Crash捕获、分发及处
  6. 百度 腾讯 阿里UC 迅雷 部分Android笔试 面试题
  7. Android(安卓)源码分析之旅3.4--onConfigurationChanged
  8. 一文全面了解Android单元测试
  9. 好的android程序该这样编写

随机推荐

  1. Android线程优先级设置方法
  2. 急需人才
  3. Android(安卓)studio 新建项目后报错:Coul
  4. Android(安卓)AsyncTask
  5. 在Android程序中使用全局变量
  6. Android中Bundle的使用示例
  7. Android核心分析(21)----Android应用框架之
  8. Android入门教程(四)之------Android工程
  9. android 截取头像
  10. 浅入浅出 Android(安卓)安全:第二章 Andro