一文学懂 Android View



前言

View是所有可视化空间的父类,系统提供了很多基础控件,比如Button,TextView等,但是仅仅使用他们是完全不能满足需求的,因此我们就需要自定义控件,而自定义控件,就需要对Android的View体系有充分的了解。有一个经典场景就是屏幕的滑动,当处于不同层级的View都响应用户滑动,就会导致滑动冲突。为了正常的响应滑动事件,我们就要对View的事件分发机制有充分的了解。

View基础知识

我们先从实践出发,了解View的一些功能。再深入去了解他在复杂场景的下的使用方法。

View的子类

View是Android中所有控件的父类,除了View以外,还有一个View的子类叫做ViewGroup,从名字上看,表示控件组,这意味着View不单单指一个,他也可以是一组控件。

所以Button是一个View,不是ViewGroup,而LinearLayout既是View又是ViewGroup。并且ViewGroup的内部,既可以有View,也可以有ViewGroup。

View的位置

View的位置由四个顶点决定,分别是bottom,top,left,right。这些坐标都是相对于View的父容器而言,因此它是相对坐标。

我们可以使用getLeft(),getRight(),getTop(),getBottom()来获得View的位置。

Android3额外增加了几个参数:x、y、translationX、translationY。换算关系为 x = left + translationX。(需要实践)

View 在平移的过程中,top 和 left 表示的是原始左上角的位置信息,其值并不会发生改变,此时发生改变的是x、y、translationX和 translationY这四个参数。

View的触碰事件

1、MotionEvent

ACTION_DOWN–手指刚解除屏幕

ACTION_MOVE–手指在屏幕上移动

ACTIONUP — 手指松开一瞬间

一般有手机点击和手机滑动松开的两种情况。对应的为down-up , down-move-up。

视图类中重写onTouchEvent方法

查看代码
@Override
	public boolean onTouchEvent(MotionEvent event) {
		Log.d("Action",""+event.getAction());
		float x = event.getX();//带event才是触摸点坐标,不带为view坐标,并且X是相对view的坐标,view的左上角为0,0。只有rawX才是整个屏幕的
		float y = event.getY();
		Log.d("public button",x+" "+y);
		return true;
	}

2、 TouchSlop

TouchSlop是系统所能识别为滑动的最小距离,不同设备不同。是一个常量

ViewConfiguration.get(getContextO).getScaledTouchSlop()

3、VelocityTracker

速度追踪,能够识别在一段时间内,手指滑动的速度

视图类中重写onTouchEvent方法

查看代码
@Override
public boolean onTouchEvent(MotionEvent event) {
   VelocityTracker velocityTracker= VelocityTracker.obtain();
   velocityTracker.addMovement (event);
   velocityTracker.computeCurrentVelocity (1000);
   int xVelocity =(int) velocityTracker.getXVelocity();
   int yVelocity =(int) velocityTracker.getYVelocity();
   Log.d("speed",xVelocity+" "+yVelocity);
   return true;
}

4、GestureDetector

手势检测。可以检测长按,轻触,单击,双击等行为。需要先创建一个监听器对象GestureDetector.OnGestureListener,然后创建GestureDetector时传入监听器,再为视图设置TouchEvent监听器,在onTouch事件中使用GestureDetector接管即可

实例
GestureDetector.OnGestureListener listener = new GestureDetector.OnGestureListener() {
   @Override
   public boolean onDown(MotionEvent e) {
      Log.d("down","down");
      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;
   }
};
LinearLayout ll = findViewById(R.id.MA_ll);
GestureDetector gestureDetector= new GestureDetector(this,listener);
ll.setOnTouchListener(new View.OnTouchListener() {
   @Override
   public boolean onTouch(View v, MotionEvent event) {
      return gestureDetector.onTouchEvent(event);
   }
});

5、Scroller

弹性滑动,实现组件的缓慢移动

View的滑动

视图手指滑动

查看代码

@Override
	public boolean onTouchEvent(MotionEvent event) {
		//每次回调onTouchEvent的时候,我们获取触摸点的代码
		int x = (int) event.getX();
		int y = (int) event.getY();
		switch (event.getAction()) {
			case MotionEvent.ACTION_DOWN:
				// 记录触摸点坐标
				lastX = x;
				lastY = y;
				break;
			case MotionEvent.ACTION_MOVE:
				// 计算偏移量
				int offsetX = x - lastX;
				int offsetY = y - lastY;
				// 在当前left、top、right、bottom的基础上加上偏移量
				layout(getLeft() + offsetX,
						getTop() + offsetY,
						getRight() + offsetX,
						getBottom() + offsetY);
				break;
		}
		return true;
	}

Scroll滑动

被谁捕获就移动谁

查看代码

	public PublicButton1(Context context, AttributeSet attrs) {
		super(context, attrs);
		LayoutInflater.from(context).inflate(R.layout.button1,this);//加载某个布局,并为他添加父布局
		Button qwq = findViewById(R.id.publicButton);
		LinearLayout linearLayout = findViewById(R.id.linearlayout);
		linearLayout.setBackgroundColor(getResources().getColor(R.color.design_default_color_secondary));
		qwq.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				scrollBy(-10,-10);
			}
		});
		scrollBy(-100,-100);
	}

动画滑动

待定

弹性滑动

Scroller实现,需要重写computeScroll方法

查看代码

	private void smoothScrollTo(int destX,int destY) {
		int scrollX = getScrollX();
		int deltaX = destX - scrollX;
		scroller.startScroll(scrollX, 0, deltaX, 0, 1000);
		invalidate();
	}

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

View的事件分发机制

点击事件的传递规则

分析的对象是MotionEvent,所谓对点击事件的分发,就是对MotionEvent的分发,系统需要把这个事件发给具体的一个View。

分发过程由三个方法共同完成:dispatchTouchEvent、onlnterceptTouchEvent和 onTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev)

用来进行事件的分发。如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View 的 onTouchEvent 和下级 View 的 dispatchTouchEvent 方法的影响,表示是否消耗当前事件。

public boolean onInterceptTouchEvent(MotionEvent event)

在上述方法内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。

public boolean onTouchEvent(MotionEvent event)

在 dispatchTouchEvent 方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View 无法再次接收到事件。

他们的关系可以用以下伪代码表示

查看代码
public boolean dispatchTouchEvent(MotionEvent ev) {
		boolean consume = false;
		if (onInterceptTouchEvent(ev)) {
			consume = onTouchEvent(ev);
		} else {
			consume = child.dispatchTouchEvent(ev);
		}
		return consume;
	}

 对于一个根ViewGroup来说,点击事件产生后,首先会传递给它,这时它的 dispatchTouchEvent就会被调用,

如果这个ViewGroup 的 onInterceptTouchEvent方法返回 true 就表示它要拦截当前事件,接着事件就会交给这个ViewGroup处理,即它的onTouchEvent方法就会被调用;

如果这个ViewGroup 的onInterceptTouchEvent方法返回 false就表示它不拦截当前事件,这时当前事件就会继续传递给它的子元素,

接着子元素的dispatchTouchEvent 方法就会被调用,如此反复直到事件被最终处理。

当一个View 需要处理事件时,

如果它设置了 OnTouchListener,那么OnTouchListener中的 onTouch方法会被回调。

这时事件如何处理还要看 onTouch 的返回值,

如果返回 false,则当前View的 onTouchEvent 方法会被调用;

如果返回 true,那么 onTouchEvent 方法将不会被调用。

可以把OnTouchListener理解为外部监听器,而onTouchEvent理解为内部监听器。onTouchListener在外部用set绑定,而onTouchEvent是重写组件方法实现的。

在 onTouchEvent 方法中,如果当前设置的有OnClickListener,那么它的 onClick 方法会被调用。可以看出,平时我们常用的 OnClickListener,其优先级最低,即处于事件传递的尾端。

当一个点击事件产生后,它的传递过程遵循如下顺序∶Activity->Window->View,顶级 View 接收到事件后,就会按照事件分发机制去分发事件。

从上到下(点击事件),再从下到上(onTouchEvent)

原创文章,作者:dweifng,如若转载,请注明出处:https://blog.ytso.com/273296.html

(0)
上一篇 2022年7月10日
下一篇 2022年7月10日

相关推荐

发表回复

登录后才能评论