view的理解

view视图的了解(重点)

VlES3R.png
View获取自身宽高
  • getHeight():获取View自身高度
  • getWidth():获取View自身宽度

View自身坐标
通过以下方法可以获得View到其父控件(ViewGroup)的距离:

  • getTop():获取View自身顶边到其父布局顶边的距离
  • getLeft():获取View自身左边到其父布局左边的距离
  • getRight():获取View自身右边到其父布局右边的距离
  • getBottom():获取View自身底边到其父布局顶边的距离

MotionEvent提供的方法取得的坐标

看到图上那个深蓝色的点,假设就是我们触摸的点,我们知道无论是View还是ViewGroup,最终的点击事件都会由onTouchEvent(MotionEvent event)方法来处理,MotionEvent也提供了各种获取 点坐标的方法:

  • getX():获取点击事件距离控件左边的距离,即视图坐标
  • getY():获取点击事件距离控件顶边的距离,即视图坐标
  • getRawX():获取点击事件距离整个屏幕左边距离,即绝对坐标
  • getRawY():获取点击事件距离整个屏幕顶边的距离,即绝对坐标

实现滑动的六种方法

我的个人理解:layout(),offsetLeftAndroidRight()与offsetTopAndBottom(),LayoutParams,动画,scollTo与scollBy,与scoller;
拿一个layout()来讲解一下,剩下的4种是类似的,然后再讲解一下scoller.

layout()
onTouchEvent.PNG

接下来我们在ACTION_MOVE事件中计算偏移量,再调用layout()方法重新放置这个自定义view的位置就好了:


ACTION_MOVE.PNG

接着在布局中自定义这个view就好了


image.png
scroller

scroller本身是不能实现view的滑动的,它需要配合view的computeScroll()方法才能实现滑动效果

  • 首先我们要初始化Scroller:


    image.png
  • 接下来重写computeScroll()方法,系统会在绘制View的时候在draw()方法中调用该方法,这个方法中我们调用父类的scrollTo()方法并通过Scroller来不断获取当前的滚动值,每滑动一小段距离我们就调用invalidate()方法不断的进行重绘,重绘就会调用computeScroll()方法,这样我们就通过不断的移动一个小的距离并连贯起来就实现了平滑移动的效果:


    image.png
  • 调用Scroller.startScroll()方法。
    • 在startScroll()方法中并没有调用类似开启滑动的方法,而是保存了传进来的各种参数。startX,startY表示滑动开始的起点,dx,dy表示滑动的距离,duration则表示滑动持续的时间,startScroll()方法只是用来做前期准备的,并不能使view进行滑动。关键是我们在startScroll()方法后调用了invalidate()方法,这个方法会导致view的重绘,而View的重绘会调用view的draw()方法,draw()方法又会调用View的computeScroller()方法,我们重写computeScroll()方法;

      我们在CustomView中写一个smoothScrollTo()方法,调用Scroller.startScroll()方法,在2000毫秒内沿X轴平移delta像素:
      image.png
  • 最后我将在主函数中调用此方法
    image.png

android的视图动画和属性动画

视图动画:AlphaAnimation(透明),RotateAnimation(旋转),TranslateAnimation(平移),ScaleAnimation(缩放),这些视图动画都非常简单
  • 在res/anim里建立
    image.png

    +后在函数中调用(旋转需要加插值器)
属性动画之valueAnimation
  • res/animator
  • 主函数(valueAnimation.java中一些属性的值)

View的分发机制

点击事件有三个重要的方法它们分别是:

  • dispatchTouchEvent(MotionEvent ev);用来进行下事件的分发
  • onInterceptTouchEvent(MotionEvent ev);用来进行事件的拦截,在dispatchTouchEvent()中调用,需要注意的是View没有提供该方法
  • onTouchEvent(MotionEvent ev):用来处理点击事件,在dispatchTouchEvent()方法中进行调用
    我想给大家看看onTouchEvent()的源码



    上面可以看到只要View的CLICKABLE和LONG_CLICKABLE一个为true,那么onTouchEvent就会返回true消耗这个事件。CLICKABLE和LONG_CLICKABLE代表View可以被点击和长按点击,可以通过View的setClickable和setLongClickable方法来设置,也可以通过View的setOnClickListenter和setOnLongClickListener来设置,他们会自动将View的设置为CLICKABLE和LONG_CLICKABLE。
    接着在ACTION_UP事件会调用performClick()方法:


2.点击事件分发的传递规则(由上而下)

事件由上而下传递返回值规则为:true,拦截,不继续向下传递;false,不拦截,继续向下传递。

2.点击事件分发的传递规则(由下而上)

事件由下而上传递返回值规则为:true,处理了,不继续向上传递;false,不处理,继续向上传递。

自定义View

1.继承系统控件的自定义View(继承TextView)

一般情况下我们在onDraw()方法中进行处理



这是在onDraw()方法中画了一条红色的横线,自定义的View,下面就是在布局中引用这个View.java

2.继承View的自定义View(继承View)

这里就是画一个正方形


对padding属性进行处理

修改布局文件加入padding属性


自定义View

实现onLayout

onLayout是ViewGroup唯一一个抽象方法,需要我们自己去实现



遍历所有的子元素,如果子元素不是GONE,则调用子元素的layout方法将其放置到合适的位置上,相当于默认第一个子元素占满了屏幕,后面的子元素就是在第一个屏幕后面紧挨着和屏幕一样大小的后续元素,所以left是一直累加的,top保持0,bottom保持第一个元素的高度,right就是left+元素的宽度,同样这里没有处理自身的pading以及子元素的margin。

处理滑动冲突

这个自定义ViewGroup是水平滑动,如果里面是ListView,则ListView是垂直滑动,如果我们检测到的滑动方向是水平的话,就让父View拦截用来进行View的滑动切换 :

快速滑动到其他页面

我们不只滑动超过一半才切换到上/下一个页面,如果滑动速度很快的话,我们也可以判定为用户想要滑动到其他页面,这样的体验也是好的。 这部分也是在onTouchEvent中的ACTION_UP部分:

这里又需要用到VelocityTracker,它用来测试滑动速度的。使用方法也很简单,首先在构造函数中进行初始化,也就是前面的init方法中增加一条语句:

接着改写onTouchEvent部分:

再次触摸屏幕阻止页面继续滑动

要实现在弹性滑动过程中再次触摸拦截,肯定要在onInterceptTouchEvent中的ACTION_DOWN中去判断,如果在ACTION_DOWN的时候,scroller还没有完成,说明上一次的滑动还正在进行中,则直接中断scroller:

最后我们在主布局中引用主View.java,作为父容器,里面有两个ListView:

接着在代码中为ListView填加数据

最后的源码:

public class HorizontalView extends ViewGroup {
    private int lastX;
    private int lastY;
    private int currentIndex = 0; //当前子元素
    private int childWidth = 0;
    private Scroller scroller;
    private VelocityTracker tracker;    //增加速度检测,如果速度比较快的话,就算没有滑动超过一半的屏幕也可以
    private int lastInterceptX=0;
    private int lastInterceptY=0;
    public HorizontalView(Context context) {
        super(context);
        init();
    }

    public HorizontalView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public HorizontalView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public void init() {
        scroller = new Scroller(getContext());
        tracker = VelocityTracker.obtain();
    }

    //todo intercept的拦截逻辑
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercept = false;
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                intercept = false;
                //如果动画还没有执行完成,则打断
                if (!scroller.isFinished()) {
                    scroller.abortAnimation();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - lastInterceptX;
                int deltaY = y - lastInterceptY;
                //水平方向距离长  MOVE中返回true一次,后续的MOVE和UP都不会收到此请求
                if (Math.abs(deltaX) - Math.abs(deltaY) > 0) {
                    intercept = true;
                    Log.i("wangshu","intercept = true");
                } else {
                    intercept = false;
                    Log.i("wangshu","intercept = false");
                }
                break;
            case MotionEvent.ACTION_UP:
                intercept = false;
                break;
        }
        //因为DOWN返回false,所以onTouchEvent中无法获取DOWN事件,这里要负责设置lastX,lastY
        lastX = x;
        lastY = y;
        lastInterceptX = x;
        lastInterceptY = y;
        return intercept;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        tracker.addMovement(event);
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (!scroller.isFinished()) {
                    scroller.abortAnimation();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                //跟随手指滑动
                int deltaX = x - lastX;
                scrollBy(-deltaX, 0);
                break;
            //释放手指以后开始自动滑动到目标位置
            case MotionEvent.ACTION_UP:
                //相对于当前View滑动的距离,正为向左,负为向右
                int distance = getScrollX() - currentIndex * childWidth;

                //必须滑动的距离要大于1/2个宽度,否则不会切换到其他页面
                if (Math.abs(distance) > childWidth / 2) {
                    if (distance > 0) {
                        currentIndex++;
                    } else {
                        currentIndex--;
                    }
                } else {
                    tracker.computeCurrentVelocity(1000);
                    float xV = tracker.getXVelocity();
                    if (Math.abs(xV) > 50) {
                        if (xV > 0) {
                            currentIndex--;
                        } else {
                            currentIndex++;
                        }
                    }
                }
                currentIndex = currentIndex < 0 ? 0 : currentIndex > getChildCount() - 1 ? 
                getChildCount() - 1 : currentIndex;
                smoothScrollTo(currentIndex * childWidth, 0);
                tracker.clear();
                break;
            default:
                break;
        }
        lastX = x;
        lastY = y;
        return true;
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        //测量所有子元素
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        //处理wrap_content的情况
        if (getChildCount() == 0) {
            setMeasuredDimension(0, 0);
        } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            View childOne = getChildAt(0);
            int childWidth = childOne.getMeasuredWidth();
            int childHeight = childOne.getMeasuredHeight();
            setMeasuredDimension(childWidth * getChildCount(), childHeight);
        } else if (widthMode == MeasureSpec.AT_MOST) {
            View childOne = getChildAt(0);
            int childWidth = childOne.getMeasuredWidth();
            setMeasuredDimension(childWidth * getChildCount(), heightSize);
        } else if (heightMode == MeasureSpec.AT_MOST) {
            int childHeight = getChildAt(0).getMeasuredHeight();
            setMeasuredDimension(widthSize, childHeight);
        }
    }
    @Override
    public void computeScroll() {
        super.computeScroll();
        if (scroller.computeScrollOffset()) {
            scrollTo(scroller.getCurrX(), scroller.getCurrY());
            postInvalidate();
        }
    }
    public void smoothScrollTo(int destX, int destY) {
        scroller.startScroll(getScrollX(), getScrollY(), destX - getScrollX(),
        destY - getScrollY(), 1000);
        invalidate();
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        int left = 0; //左边的距离
        View child;
        //遍历布局子元素
        for (int i = 0; i < childCount; i++) {
            child = getChildAt(i);
            if (child.getVisibility() != View.GONE) {
                int width = child.getMeasuredWidth();
                childWidth = width; //赋值给子元素宽度变量
                child.layout(left, 0, left + width, child.getMeasuredHeight());
                left += width;
            }
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容