Skip to content

View基础知识

  1. View的位置参数
  2. MotionEvent
  3. TouchSlop
  4. VelocityTracker
  5. GestureDetector
  6. Scroller

什么是View

View是所有控件的基类,它是界面层控件的一种抽象 android studio查看类的继承体系(快捷键CTRL+H) image.pngimage.png 可见ViewGroup也是继承自View,这让我想到了组合设计模式

View的位置参数

image.png 在onCreate()回调方法中去调用view.getLeft(), getRight()…getX()、getY() 等值为0 View的显示必须经历Measure(测量)、Layout(布局)和Draw(绘制)过程。而在Measure与Layout过程完成之后,View的width、height、top、left等属性才被正确赋值,此时我们才能获取到正确的值,这几个过程都晚于onCreate执行 android view.getLeft(), getRight()...等获取值为0_轻狂书生YT的博客-CSDN博客_view getleft 0 疑问:View是在什么时候绘制的?? Activity启动后View何时开始绘制(onCreate中还是onResume之后?) View在平移的过程中,top和left是原始左上角的位置信息,其值不会发生变化,此时发生改变的是x,y,translationX和translationY。 image.png

MotionEvent和TouchSlop

Scroller弹性滑动

View的滑动

三种实现View滑动的方式

  1. 通过View本身提供的scrollTo/scrollBy
  2. 通过动画给View施加平移效果
  3. 通过改变View的LayoutParams使得View重新布局

使用scrollTo/scrollBy

scrollBy也是调用了scrollTo方法 scrollBy:基于当前位置的相对滑动 scrollTo:基于所传递参数的绝对滑动

java
/**                                                                    
 * Set the scrolled position of your view. This will cause a call to   
 * {@link #onScrollChanged(int, int, int, int)} and the view will be   
 * invalidated.                                                        
 * @param x the x position to scroll to                                
 * @param y the y position to scroll to                                
 */                                                                    
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();                               
        }                                                              
    }                                                                  
}  

 /**                                                                       
  * Move the scrolled position of your view. This will cause a call to     
  * {@link #onScrollChanged(int, int, int, int)} and the view will be      
  * invalidated.                                                           
  * @param x the amount of pixels to scroll by horizontally                
  * @param y the amount of pixels to scroll by vertically                  
  */                                                                       
 public void scrollBy(int x, int y) {                                      
     scrollTo(mScrollX + x, mScrollY + y);                                 
 }
java
textView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //绝对滑动
        textView1.scrollTo(-100,0);
        Log.e(TAG, "onClick: textview1 mScrollX="+textView1.getScrollX() );
        //相对滑动
        textView.scrollBy(-100,0);
        Log.e(TAG, "onClick: textview mScrollX="+textView.getScrollX() );
    }
});

image.png 滑动过程中的几个要点:详看Demo chapter3_1

  1. View本身不会移动,移动的是View中的内容
  2. mScrollX的大小是View左边缘到View中内容的左边缘的水平距离
  3. mScrollY的大小是View上边缘到View中内容的上边缘的水平距离
  4. 从左往右滑动,mScrollX为正值(View左边缘在View中内容的左边缘的右边)

image.png

使用动画

在1000ms内从原点水平右移200px,并保持状态

xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true"
    android:zAdjustment="normal" >

    <translate
        android:duration="1000"
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:interpolator="@android:anim/linear_interpolator"
        android:toXDelta="200"
        android:toYDelta="0" />
</set>
java
      //使用原生动画实现滑动
        Animation animation= AnimationUtils.loadAnimation(this,R.anim.a1_translate);

        findViewById(R.id.textView1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                textView1.startAnimation(animation);
            }
        });
//        使用属性动画实现滑动
        textView2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ObjectAnimator.ofFloat(textView2,"translationX",0,100).setDuration(1000).start();
            }
        });

原生动画与属性动画的区别(在Android3.0的前提下,因为Android3.0之前没有属性动画) 原生动画并不能真正改变View的位置,如果控件有交互事件,那么控件内容不在原始位置了,但是View容器还在原始位置能响应交互事件。 可以新位置只是View的一个影像。 但是属性动画可以解决这个问题。 image.png

改变布局参数

实现把textView3控件向右平移100px

java
textView3.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ViewGroup.MarginLayoutParams params=(ViewGroup.MarginLayoutParams)textView3.getLayoutParams();
        params.leftMargin+=100;
        textView3.requestLayout();
    }
});

image.png

滑动方式的对比

  1. scrollTo/scrollBy:操作简单,适合对View内容的滑动
  2. 动画:操作简单。主要适用没有交互的View和实现复杂的动画效果(3.0以上用属性动画既简单也适用交互View)
  3. 改变布局参数:操作稍稍复杂,适用于有交互的View

弹性滑动

使用Scroller

使用动画

使用延时策略

Android事件分发机制详解_谁谁谁动了我的博客-CSDN博客_安卓事件分发

总结一下RecyclerView侧滑菜单的两种实现 - 掘金RecyclerView - 使用ItemTouchHelper实现侧滑删除效果_fjnu_se的博客-CSDN博客

事件分发机制(难点)

image.pngView的事件分发机制Android事件分发机制详解:史上最全面、最易懂 点击事件的传递顺序:Activity->Window->ViewGroup->具体的View 点击事件的三大方法:

  • dispatchTouchEvent 对一个事件进行分发,可能是分发给下一层处理,或者分发给自己。
  • onInterceptTouchEvent 这个方法只有 ViewGroup 有,用来判断对事件是否进行拦截,如果拦截就不会分发给下一层.
  • onTouchEvent 对事件进行处理,消耗或者不消耗,不消耗就会返回给上层。对于 ViewGroup 和 View 这个方法还受到 OnTouchListener 和 enable 属性 的影响,具体的后面会阐述。
java
    /**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //空方法、空实现
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
    	//
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

image.png Window是如何将事件传递给ViewGroup的? Window是一个抽象类,其中的superDispatchTouchEvent方法也是抽象方法。 它的实现类是PhoneWindow,这个类不能在AS中查看。 image.pngimage.png

image.png DecorView类也是在package com.android.internal.policy;这个包目录下。 image.png

滑动冲突

常见的滑动冲突场景

image.png 场景一:外部控件左右滑动,内部控件上下滑动:类似ViewPager嵌套Fragment(ListView) 场景二:类似ScrollView嵌套ListView

滑动冲突的处理规则

场景一:滑动的角度、距离差和速度差 场景二:业务的划分

滑动冲突的解决方式

  1. 外部拦截法

事件先经过父容器的拦截处理,重写父容器的onInterceptTouchEvent方法

  1. 内部拦截法

重写子元素的dispatchTouchEvent方法

外部拦截法

java
public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercepted = false;
        int x = (int) event.getX();
        int y = (int) event.getY();

        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            intercepted = false;
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
                intercepted = true;
            }
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            int deltaX = x - mLastXIntercept;
            int deltaY = y - mLastYIntercept;
            if (父容器需要拦截的事件规则) {
                intercepted = true;
            } else {
                intercepted = false;
            }
            break;
        }
        case MotionEvent.ACTION_UP: {
            intercepted = false;
            break;
        }
        default:
            break;
        }

        Log.d(TAG, "intercepted=" + intercepted);
        mLastX = x;
        mLastY = y;
        mLastXIntercept = x;
        mLastYIntercept = y;

        return intercepted;
    }

内部拦截法

父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理。

java
public boolean dispatchTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();

        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            parent.requestDisallowInterceptTouchEvent(true);
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            int deltaX = x - mLastX;
            int deltaY = y - mLastY;
            Log.d(TAG, "dx:" + deltaX + " dy:" + deltaY);
            if (父容器需要当前点击事件) {
                parent.requestDisallowInterceptTouchEvent(false);
            }
            break;
        }
        case MotionEvent.ACTION_UP: {
            break;
        }
        default:
            break;
        }

        mLastX = x;
        mLastY = y;
        return super.dispatchTouchEvent(event);
    }