自定义View(一) View的基础知识

自定义View系列

自定义View(一) View的基础知识

自定义View(二) View的工作原理

1.View是什么

View是Android所有控件的父类,其子类包括ViewGroup和View

2.View的基础知识

2.1 View的位置参数

Button button = new Button(this);
        float x = button.getX(); // 相对父容器的左上角坐标 x = left + granslationX
        float y = button.getY(); // 相对父容器的左上角坐标 y = left + granslationY
        int left = button.getLeft(); //相对父容器的左上角横坐标
        int top = button.getTop();  //相对父容器的左上角纵坐标
        int right = button.getRight();//相对父容器的右下角横坐标
        int bottom = button.getBottom();//相对父容器的右下角纵坐标
        float translationX = button.getTranslationX();//相对父容器的偏移量横坐标
        float translationY = button.getTranslationY();//相对父容器的偏移量纵坐标
        int height = button.getHeight(); // 高度,height = bottom - top
        int width = button.getWidth(); // 宽度,width = right - left

2.2MotionEvent

 @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN: // 点击那瞬间
                float rawX = ev.getRawX(); // 相对屏幕的横坐标
                float rawY = ev.getRawY();// 相对屏幕的纵坐标
                float x = ev.getX();// 相对父容器的横坐标
                float y = ev.getY();// 相对父容器的纵坐标
                break;
            case MotionEvent.ACTION_MOVE: // 移动
                break;
            case MotionEvent.ACTION_UP: //松开
                break;
        }
        return true;
    }

2.3 TouchSlop

                int scaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();

touchSlop为判断系统最小的滑动距离,大于它意味着滑动,不大于它意味着不滑动

2.4 VelocityTracker

 @Override
    public boolean onTouchEvent(MotionEvent ev) {
        VelocityTracker veloctiyTracker = VelocityTracker.obtain();
        veloctiyTracker.addMovement(ev);
        veloctiyTracker.computeCurrentVelocity(1000); // 单位时间内测试速度,单位是ms
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                float xVelocity = veloctiyTracker.getXVelocity(); // 滑动在单位时间内的速度,可以是负数
                float yVelocity = veloctiyTracker.getYVelocity();
                break;
            case MotionEvent.ACTION_UP:
                veloctiyTracker.clear();
                veloctiyTracker.recycle(); //不用时候重置回收
                break;
        }
        return true;
    }

2.5 GestureDetector

  @Override
    public boolean onTouchEvent(MotionEvent ev) {
        GestureDetector gestureDetector = new GestureDetector(getContext(),new MyGestureDetector());
        gestureDetector.setOnDoubleTapListener(new MyDoubleTapListener());
        gestureDetector.onTouchEvent(ev);
        return true;
    }

    public class MyGestureDetector implements 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; // 由Action Down 和连续的Action Move完成
        }

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

        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            return false; // 快速滑动
        }

    }


    class MyDoubleTapListener implements  GestureDetector.OnDoubleTapListener{

        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) { //严格的单击行为,和onSingleTapUp不同
            return false;
        }

        @Override
        public boolean onDoubleTap(MotionEvent e) { // 双击
            return false;
        }

        @Override
        public boolean onDoubleTapEvent(MotionEvent e) { // 双击行为,Action Down,Move,Up都会触发该回调
            return false;
        }
    }

3.View的滑动

3.1 使用scrollTo/scrollBy

scrollTo和scrolBy只能改变View内容的位置,不能改变其本身的位置,看源码

  public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    }
public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }

scrollBy本质是调用了scrollTo,mScrollX为View的左边缘和内容的左边缘的距离,mScrollY为View的下边缘和内容的下边缘的距离,如果scrollTo(x,y),x为正,则View内容向负坐标偏移,为负,则Viw内容向正坐标偏移,和我们一般偏移常识刚好相反

3.2 使用动画

传统的View动画只能改变内容位置,不能改变真正的View位置,属性动画才能改变View真正的位置,动画说明见动画方面文章

3.3 改变布局参数

使用View的LayoutParams属性动态改变

3.4 使用Scroller(平滑)

Scroller的模版使用

public  void startScroll() {
        scroller = new Scroller(context);
        scroller.startScroll(100,100,-300,-300,2000);
        invalidate();
    }

    @Override
    public void computeScroll() {
        if (scroller!=null && scroller.computeScrollOffset()) {
            scrollTo(scroller.getCurrX(),scroller.getCurrY());
            postInvalidate();
        }
    }

Scroller调用startScroll开始滑动,看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;
    }

没有滑动开始的逻辑,这是因为什么呢?其实现实质是调用了invalidate重绘,在draw方法中,调用了computeScroll方法,在draw方法中的computeScroll是空实现,所以我们需要重写方法,在computeScroll重写,调用scrollTo获取位置后滑动,而为什么Scroller能够实现顺滑,而不像scrollTo只能瞬滑,看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;
    }

通过时间百分比的变化,实时的算出mCurrX和mCurrY的位置,然后用scrollTo方法变更位置,后再调用了postInvalidate继续重绘,最后实现了平滑的操作,不过要注意,它只能对View的内容操作!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值