Android View基础知识总结

《Android开发者艺术》

View的位置参数
  • View初始位置主要由左上角与右下角初始坐标决定(mLeft,mRight,mTop,mBottom),单位是像素.该坐标系的坐标原点为View父容器的左上角.
  • getLeft():这类函数是用来获取View控件左边相对于父容器左边最开始的距离.
  • getX():这类函数是用来获取View控件移动后左边相对于父容器左边的距离(不是初始距离).
// View.java
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {
    // 这些都是View控件在父容器中的初始坐标值,单位是像素.
    protected int mLeft;// 初始左上角X坐标
    protected int mRight;// 初始右下角X坐标
    protected int mTop;// 初始左上角Y坐标
    protected int mBottom;// 初始右下角Y坐标
    // 获取View控件相对与父容器控件的初始左上角Y坐标
    public final int getTop() {
        return mTop;
    }
    // 设置View控件相对与父容器控件的的初始左上角Y坐标
    public final void setTop(int top) {
        ...
    }
    public final int getBottom() {
        return mBottom;
    }
    public final void setBottom(int bottom) {
        ...
    }
    public final int getLeft() {
        return mLeft;
    }
    public final void setLeft(int left) {
        ...
    }
    public final int getRight() {
        return mRight;
    }
    public final void setRight(int right) {
        ...
    }
    // 获取View控件左边距离父控件左边的距离.
    public float getX() {
        return mLeft + getTranslationX();
    }
    public void setX(float x) {
        setTranslationX(x - mLeft);
    }
    // 获取View控件上边距离父控件上边的距离.
    public float getY() {
        return mTop + getTranslationY();
    }
    public void setY(float y) {
        setTranslationY(y - mTop);
    }
    
    // 获取View控件相对于父容器左上角X轴偏移量
    public float getTranslationX() {
        return mRenderNode.getTranslationX();
    }
    // 设置X轴偏移量
    public void setTranslationX(float translationX) {
        if (translationX != getTranslationX()) {
            invalidateViewProperty(true, false);
            mRenderNode.setTranslationX(translationX);
            invalidateViewProperty(false, true);
    
            invalidateParentIfNeededAndWasQuickRejected();
            notifySubtreeAccessibilityStateChangedIfNeeded();
        }
    }

    // 获取View控件相对于父容器左上角Y轴偏移量
    public float getTranslationY() {
        return mRenderNode.getTranslationY();
    }
    // 设置Y轴偏移量
    public void setTranslationY(float translationY) {
        if (translationY != getTranslationY()) {
            invalidateViewProperty(true, false);
            mRenderNode.setTranslationY(translationY);
            invalidateViewProperty(false, true);
    
            invalidateParentIfNeededAndWasQuickRejected();
            notifySubtreeAccessibilityStateChangedIfNeeded();
        }
    }
}
弹性滑动
  • scrollTo():实现了基于所传递参数,对View内容得绝对滑动(无法改变View在布局中的初始坐标).
  • scrollBy():最终调用scrollTo(),实现了基于View当前内容位置的相对滑动(无法改变View在布局中的初始坐标).
// View.java
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {
    // 单位像素,代表View内容左边与View控件左边之间的距离.
    protected int mScrollX;
    // 单位像素,代表View内容上边与View控件上边之间的距离.
    protected int mScrollY;
    // 基于View内容初始位置的绝对滑动
    // 参数x: x为+,则View内容向屏幕左侧移动.x为-,则View内容向屏幕右侧移动.单位为像素
    // 参数y: y为+,则View内容向屏幕上方移动.y为-,则View内容向屏幕下方移动.单位为像素
    public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            // 传入的像素参数最后赋值给了mScrollX和mScrollY.
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    }
    // 基于View内容当前位置的相对滑动.
    public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }
    // 获取View内容左边与View控件左边间的距离.
    public final int getScrollX() {
        return mScrollX;
    }
    // 获取View内容上边与View控件上边间的距离
    public final int getScrollY() {
        return mScrollY;
    }
}
  • 弹性滑动使用
public class MyTextView extends android.support.v7.widget.AppCompatTextView {
    /**
     * 缓慢滑动到指定位置
     *
     * @param destX View内容左边距离View控件左边距离
     * @param destY View内容上边距离View控件上边距离
     */
    private Scroller mScroller;
    private void smoothScrollTo(int destX, int destY) {
        mScroller = new Scroller(this.getContext());
        // 内容距离控件边的距离
        int scrollX = getScrollX();
        int scrollY = getScrollY();
        // 计算需要滑动的距离
        int delteX = destX - scrollX;
        int delteY = destY - scrollY;
        // 为Scroller设置初始值
        mScroller.startScroll(scrollX, scrollY, delteX, delteY, 1000);
        // 重新绘制
        invalidate();
    }
    @Override
    public void computeScroll() {
        // 每次View控件重绘都会调用到这个地方
        if (mScroller != null) {
            // 先计算
            // 可以得出目前时间还需不需要重绘
            // 如果需要重绘,可以算出View内容边距离View控件边当前的距离.
            if (mScroller.computeScrollOffset()){
                // 从mScroller中取出当前计算的值,使用scrollTo()让内容移动
                scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
                // 移动完成之后重新绘制,这样就能知道下一次是否停止移动了.
                postInvalidate();
            }
        }
    }
}
  • Scroller是一个工具类, 用来计算一定时间之后View内容左边距离View控件左边距离,还有View内容上边距离View控件上边距离.
public class Scroller  {
    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
        mMode = SCROLL_MODE;//默认是SCROLL_MODE
        mFinished = false;
        mDuration = duration;// 滑动消耗时长
        mStartTime = AnimationUtils.currentAnimationTimeMillis();// 记录移动开始时间
        // View内容左边距离View控件左边距离,也就是滑动起点距离.
        mStartX = startX;
        mStartY = startY;
        // 起点距离加上需要滑动距离,最终得到View内容左边距离View控件左边距离,也就是滑动终点距离.
        mFinalX = startX + dx;
        mFinalY = startY + dy;
        // 滑动距离
        mDeltaX = dx;
        mDeltaY = dy;
        // 持续时间倒数
        mDurationReciprocal = 1.0f / (float) mDuration;
    }
    public boolean computeScrollOffset() {
        if (mFinished) {
            // 滑动结束返回
            return false;
        }
        // 从开始滑动到执行到此处流失的时间
        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
        // 如果流失的时间小于滑动总时长
        if (timePassed < mDuration) {
            switch (mMode) {
            case SCROLL_MODE:
                // mMode默认为SCROLL_MODE
                // 流失的时间占总时间的百分比,经过插值器运算得到一个值.
                final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
                // 滑动的起点加上滑动距离乘以时间流逝百分比,得到当前View内容左边距离View控件左边的距离.
                mCurrX = mStartX + Math.round(x * mDeltaX);
                mCurrY = mStartY + Math.round(x * mDeltaY);
                break;
            case FLING_MODE:
                ...
                break;
            }
        }
        else {
            mCurrX = mFinalX;
            mCurrY = mFinalY;
            // 滑动结束
            mFinished = true;
        }
        return true;
    }
}
利用动画特性来实现移动
/**
 * 缓慢滑动到指定位置
 * @param destX View内容左边距离View控件左边距离
 * @param destY View内容上边距离View控件上边距离
 */
private void smoothScrollTo(int destX, int destY) {
    // 内容距离控件边的距离
    final int scrollX = mTextView.getScrollX();
    final int scrollY = mTextView.getScrollY();
    // 计算需要滑动的距离
    final int delteX = destX - scrollX;
    final int delteY = destY - scrollY;
    final ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1).setDuration(1000);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // 动画进度百分比
            float animatedFraction = valueAnimator.getAnimatedFraction();
            int x = (int) (scrollX + (delteX * animatedFraction));
            int y = (int) (scrollY + (delteY * animatedFraction));
            mTextView.scrollTo(x,y);
        }
    });
    valueAnimator.start();
}
常见辅助类
  • MotionEvent中的方法
  1. getX()/getY(): 获取手指触碰屏幕点的坐标,该坐标系原点为触碰到的当前View的左上角.
  2. getRawX()/getRawY():获取手指触碰屏幕点的坐标,该坐标系原点为手机屏幕左上角.
  • TouchSlop为滑动最小距离,滑动时小于该值将忽略滑动.
// 获取方法
ViewConfiguration.get(context).getScaledTouchSlop()
  • VelocityTracker:它是用来追踪手指在滑动过程中的速度,水平和竖直速度都能获取到.下面是使用方法.
mTextView.setOnTouchListener(new View.OnTouchListener() {
    int pointerId;
    // 获取VelocityTracker对象
    VelocityTracker mVelocityTracker = VelocityTracker.obtain();
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        mVelocityTracker.addMovement(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 获取第一个触摸点Id
                pointerId = event.getPointerId(0);
                break;
            case MotionEvent.ACTION_MOVE:
                // 计算1s内的平均速度
                mVelocityTracker.computeCurrentVelocity(1000);
                // 获取第一个触摸点在界面滑动的速度。
                // 当从右向左,水平方向的速度为-.
                float velocityX = mVelocityTracker.getXVelocity(pointerId);
                // 当从下向上,垂直方向的速度为-.
                float velocityY = mVelocityTracker.getYVelocity(pointerId);
                break;
            case MotionEvent.ACTION_UP:
                // 回收
                mVelocityTracker.clear();
                mVelocityTracker.recycle();
                break;
        }
        return true;
    }
});
  • GestureDetector:手势检测,用于检测用户单击,滑动,长按,双击等行为.下面是使用方法.
  1. 实际开发中可以不用它的, 完全在OnTouchEvent中自己实现监听滑动也可以.另外还有一个OnDoubleTapListener,是用来监听双击这些的,如果实际中要监听双击倒是可以用到GestureDetector.
mTv.setOnTouchListener(new View.OnTouchListener() {
    GestureDetector mGestureDetector = new GestureDetector(getApplicationContext(), new GestureDetector.OnGestureListener() {
        @Override
        public boolean onDown(MotionEvent e) {
            // 手指触碰屏幕瞬间调用
            return false;
        }

        @Override
        public void onShowPress(MotionEvent e) {
            // 手指触碰屏幕瞬间后没有松开或者拖动
        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            // 手指触碰屏幕后松开,单机事件
            return false;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            // 手指按下并拖动
            return false;
        }

        @Override
        public void onLongPress(MotionEvent e) {
            // 长按屏幕
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            // 按下屏幕快速滑动后松开
            return false;
        }
    });

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // 解决长按屏幕无法拖动现象
        mGestureDetector.setIsLongpressEnabled(false);
        mGestureDetector.onTouchEvent(event);
        return true;
    }
});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值