Android进阶之光笔记一:View体系
一、坐标系
Android系统中有两种坐标系,分别为Android坐标系和View坐标系。
1、Android坐标系
在Android中,将屏幕左上角的顶点作为Android坐标系的原点,这个原点向右是X轴正方向,向下是Y轴正方向,如图:
2、View坐标系
除了Android坐标系,还有一个坐标系:View坐标系,它与Android坐标系并不冲突,两者是共同存在的,它们一起来帮助开发者更好地控制View。
1)View自身的坐标
通过如下方法可以获得View到其父控件(ViewGroup)的距离。
- getTop():获取View自身顶边到其父布局顶边的距离。
- getLeft():获取View自身左边到其父布局左边的距离。
- getRight():获取View自身右边到其父布局左边的距离。
- getBottom():获取View自身底边到其父布局顶边的距离。
2)MotionEvent提供的方法
上图中间的那个圆点,假设就是我们触摸的点。我们知道无论是View还是ViewGroup,最终的点击事件都会由onTouchEvent(MotionEvent event)方法来处理。MotionEvent在用户交互中作用重大,其内部提供了很多事件常量,比如我们常用的ACTION_DOWN、ACTION_UP和ACTION_MOVE。此外,MotionEvent也提供了获取焦点坐标的各种方法。
- getX():获取点击事件距离控件左边的距离,即视图坐标。
- getY():获取点击事件距离控件顶边的距离,即视图坐标。
- getRawX():获取点击事件距离整个屏幕左边的距离,即绝对坐标。
- getRawY():获取点击事件距离整个屏幕顶边的距离,即绝对坐标。
二、View的滑动
View的滑动是Android实现自定义控件的基础,同时在开发中我们也难免会遇到View的滑动处理。其实不管是哪种滑动方式,其基本思想都是类似的:当点击事件传到Vi e w时,系统记下触摸点的坐标,手指移动时系统记下移动后触摸的坐标并算出偏移量,并通过偏移量来修改View的坐标。
实现View滑动有很多种方法,在这里主要讲解6种滑动方法,分别是layout()、offsetLeftAndRight() 与 offsetTopAndBottom()、LayoutParams、动画、scollTo 与 scollBy、Scroller。
1、layout()
View进行绘制的时候会调用layout()方法来设置显示的位置,因此我们同样也可以通过修改View的left、top、right、bottom这4种属性来控制View的坐标。代码如下所示:
package com.example.customview.view;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
public class LayoutView extends View {
private int lastX;
private int lastY;
public LayoutView(Context context) {
super(context);
}
public LayoutView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public LayoutView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//步骤一:
//获取手指触摸点的横纵坐标
int X = (int) event.getX();
int Y = (int) event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
lastX = X;
lastY = Y;
break;
//步骤二:
//接下来我们在ACTION_MOVE事件中计算偏移量
case MotionEvent.ACTION_MOVE:
int offsetX = X - lastX;
int offsetY = Y - lastY;
//步骤三:
//在每次移动时都会调用layout()方法对屏幕重新布局,从而达到移动View的效果。自定义View,
layout(getLeft() + offsetX,getTop() + offsetY,
getRight() + offsetX,getBottom() + offsetY);
break;
}
return true;
}
}
<?xml version="1.0" encoding="utf-8"?>
<!--步骤四:我们在布局中引用自定义View就可以了。-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".LayoutActivity"
android:orientation="vertical">
<com.example.customview.view.LayoutView
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_margin="50dp"
android:background="@color/colorPrimary"
tools:ignore="MissingConstraints" />
</LinearLayout>
2、offsetLeftAndRight() 和 offsetTopAndBottom()
这两种方法和layout()方法的效果差不多,其使用方式也差不多。
将ACTION_MOVE中的代码替换成如下代码:
case MotionEvent.ACTION_MOVE:
int offsetX = X - lastX;
int offsetY = Y - lastY;
//对left 和 right进偏移
offsetLeftAndRight(offsetX);
//对top 和 button进行偏移
offsetTopAndBottom(offsetY);
break;
3、LayoutParams(改变布局参数)
LayoutParams主要保存了一个View的布局参数,因此我们可以通过LayoutParams来改变View的布局参数从而达到改变View位置的效果。
同样,我们将 ACTION_MOVE中的代码替换成如下代码:
case MotionEvent.ACTION_MOVE:
int offsetX = X - lastX;
int offsetY = Y - lastY;
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
break;
三、View的事件分发机制
- Activity或View类的onTouchEvent()回调函数会接收到touch事件。
- 一个完整的手势是从ACTION_DOWN开始,到ACTION_UP结束。
- 简单的情况下,我们只需要在onTouchEvent()中写个switch case语句,处理各种事件(Touch Down、 Touch
Move、 Touch Up等),但是比较复杂的动作就需要更多的处理了。 - ViewGroup作为一个parent是可以截获传向它的child的touch事件的。
- 如果一个ViewGroup的onInterceptTouchEvent()方法返回true,说明Touch事件被截获,子View不再接收到Touch事件,而是转向本ViewGroup的
onTouchEvent()方法处理。从Down开始,之后的Move,Up都会在相应的onTouchEvent()方法中处理。 - 如果onInterceptTouchEvent()返回false,则事件会交给child view处理。
- Android中提供了ViewGroup、View、Activity三个层次的Touch事件处理。
- 处理过程是按照Touch事件从上到下传递,再按照是否消费的返回值,从下到上返回,即如果View的onTouchEvent返回false,将会向上传给它的parent的ViewGroup,如果ViewGroup不处理,将会一直向上返回到Activity。即隧道式向下分发,然后冒泡式向上处理。
1、事件分发三个方法的用法
1)dispatchTouchEvent()
用来分派事件。
Touch 事件发生时 Activity 的 dispatchTouchEvent(MotionEvent ev) 方法会以隧道方式(从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递)将事件传递给最外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法,并由该 View 的 dispatchTouchEvent(MotionEvent ev) 方法对事件进行分发。
一般不重写该方法
2)onInterceptTouchEvent()
用来拦截事件。
- 如果 onInterceptTouchEvent 返回 true,则表示将事件进行拦截,并将拦截到的事件交由当前 View 的
onTouchEvent 进行处理; - 如果 onInterceptTouchEvent 返回 false,则表示将事件放行,当前 View 上的事件会被传递到子 View
上,再由子 View 的dispatchTouchEvent 来开始这个事件的分发; - 如果 onInterceptTouchEvent 返回super.onInterceptTouchEvent(ev),事件默认不会被拦截,
ViewGroup类中的源码实现就是{return false;}
表示不拦截该事件,事件将向下传递(传递给其子View)
3)onTouchEvent()
用来处理事件。
- 如果事件传递到当前 View 的 onTouchEvent 方法,而该方法返回了 false,那么这个事件会从当前 View向上传递,并且都是由上层 View 的 onTouchEvent 来接收,如果传递到上面的 onTouchEvent 也返回false,这个事件就会“消失”,而且接收不到下一次事件。
- 返回true则表示该View能处理该事件,事件将终止向上传递(传递给其父View)
- 如果返回 super.onTouchEvent(ev) 默认处理事件的逻辑和返回 false 时相同。
本文地址:https://blog.csdn.net/m0_46211029/article/details/107353166
推荐阅读