前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >View的基础知识介绍

View的基础知识介绍

作者头像
103style
发布2022-12-19 13:48:52
3570
发布2022-12-19 13:48:52
举报

转载请以链接形式标明出处: 本文出自:103style的博客

《Android开发艺术探索》 学习记录


可以带着以下问题来看本文:

  • View的坐标系和坐标,平移等动画改变的是什么属性?
  • View有哪些事件?
  • 如果获取系统可识别的最短滑动距离?
  • 如果计算滑动的速度?
  • 单击、双击、长按等事件的监听?
  • 弹性滑动的实现?

目录

  • View 与 ViewGroup
  • View 的位置参数
  • MotionEvent 和 TouchSlop
  • VelocityTracker
  • GestureDetector
  • Scroller

View与ViewGroup

View

public class View extends Object implements Drawable.Callback,KeyEvent.Callback,AccessibilityEventSource java.lang.Object    ↳ android.view.View Known direct subclasses AnalogClock , ImageView,KeyboardViewMediaRouteButtonProgressBar , Space , SurfaceView , TextViewTextureViewViewGroup,ViewStub. Known indirect subclasses AbsListViewAbsSeekBarAbsSpinner , AbsoluteLayoutAutoCompleteTextViewButtonCalendarViewCheckBoxCheckedTextViewChronometer, and 57 others..

ViewGroup

public abstract class ViewGroup extends View implements ViewParent, ViewManager java.lang.Objectandroid.view.Viewandroid.view.ViewGroup Known direct subclasses AbsoluteLayout, AdapterView<T extends Adapter>, FragmentBreadCrumbs, FrameLayout, GridLayout, LinearLayout, RelativeLayout, SlidingDrawer, Toolbar, TvView. Known indirect subclasses AbsListView, AbsSpinner, CalendarView, DatePicker, ExpandableListView, Gallery, GridView, HorizontalScrollView,ImageSwitcher, and 26 others.

通过上面的官方介绍,我们可以看到,View 是我们平常看到的视图上所有元素的父类,按钮Button、文本TextView、图片ImageView 等。 ViewGroup 也是 View 的子类,ViewGroup 相当与 View 的容器,可以包含很多的 View.


View的位置参数

View的坐标系如下图:

View坐标系
View坐标系

左上角为原点O(0,0),X、Y轴分别向右向下递增。 图中 View 和 ViewGroup 的位置由其四个顶点决定,以View为例,分别对应四个属性:LeftTopRightBottom. 所以 Width = Right - Left, Height = Bottom - Top.

Android 3.0 开始,View又增加了 xytranslationXtranslationY 四个参数。 xy 即为上图中的A点,分别对应A点在View坐标系中的X、Y轴上的坐标。 translationXtranslationY则为相对于父容器ViewGroup的偏移量,默认为 0。 他们的关系为: x = left + tranlastionXy = top + tranlastionY.

需要注意的是:在平移过程中,top 和 left 表示的是原始左上角的位置信息,是不变的,发生改变的是 x、y、translationX、translationY

下面我们来测试看看:

代码语言:javascript
复制
<!--  activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="8dp"
        android:padding="8dp"
        android:text="Hello World!" />
</LinearLayout>
代码语言:javascript
复制
//MainActivity.java
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final TextView tv = findViewById(R.id.tv);
        tv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e(TAG, "tv.getLeft() = " + tv.getLeft());
                Log.e(TAG, "tv.getTop() = " + tv.getTop());
                Log.e(TAG, "tv.getRight() = " + tv.getRight());
                Log.e(TAG, "tv.getBottom() = " + tv.getBottom());
                Log.e(TAG, "tv.getWidth() = " + tv.getWidth());
                Log.e(TAG, "tv.getHeight() = " + tv.getHeight());
                Log.e(TAG, "tv.getX() = " + tv.getX());
                Log.e(TAG, "tv.getY() = " + tv.getY());
                Log.e(TAG, "tv.getTranslationX() = " + tv.getTranslationX());
                Log.e(TAG, "tv.getTranslationY() = " + tv.getTranslationY());
            }
        });
    }
}

点击按钮,打印日志如下:

代码语言:javascript
复制
MainActivity: tv.getLeft() = 21
MainActivity: tv.getTop() = 21
MainActivity: tv.getRight() = 263
MainActivity: tv.getBottom() = 114
MainActivity: tv.getWidth() = 242
MainActivity: tv.getHeight() = 93
MainActivity: tv.getX() = 21.0
MainActivity: tv.getY() = 21.0
MainActivity: tv.getTranslationX() = 0.0
MainActivity: tv.getTranslationY() = 0.0

我们可以看到 left、top、right、bottom 是整形的, 而 x、y、translationX、translationY 是浮点型的


MotionEvent 和 TouchSlop

MotionEvent 即为我们点击屏幕所产生的一些列事件,主要有以下几个:

  • ACTION_DOWN:手指刚接触屏幕。
  • ACTION_MOVE:手指在屏幕上滑动。
  • ACTION_UP:手指离开屏幕的一瞬间。
  • ACTION_CANCEL:消耗了DOWN事件却没有消耗UP事件,再次触发DOWN时,会先触发CANCEL事件。

一般依次点击屏幕操作,会产生一些列事件:DOWN → 0个或多个 MOVE → UP。 通过MotionEvent 我们可以知道事件发生的 x , y 坐标, 可以通过系统提供的 getX()/getY()getRawX()/getRawY()获取。 getX()/getY()是对于当前View左上角的坐标. getRawX()/getRawY()则是对于屏幕左上点的坐标.

TouchSlop 则是系统所能识别的最短的滑动距离, 这个距离可以通过 ViewConfiguration.get(getContext()).getScaledTouchSlop() 获得。 在 Genymotion上的 Google pixel 9.0系统 420dpi 的模拟器上得到的值如下:

代码语言:javascript
复制
MainActivity: getScaledTouchSlop = 21

VelocityTracker

VelocityTracker 是用来记录手指滑动过程中的速度的,包括水平方向和数值方向。 可以通过如下方式来获取当前事件的滑动速度:

代码语言:javascript
复制
tv.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                VelocityTracker velocityTracker = VelocityTracker.obtain();
                velocityTracker.addMovement(event);
                velocityTracker.computeCurrentVelocity(1000);
                float vX = velocityTracker.getXVelocity();
                float vY = velocityTracker.getYVelocity();
                Log.e(TAG, "vX = " + vX + ", vY = " + vY);
                velocityTracker.clear();
                velocityTracker.recycle();
                break;
        }
        return true;
    }
});
代码语言:javascript
复制
MainActivity: vX = 542.164, vY = 271.18683
MainActivity: vX = 2257.9578, vY = 291.47467
MainActivity: vX = 2237.9333, vY = 379.69537
MainActivity: vX = 1676.5919, vY = 697.79443
MainActivity: vX = 1672.0844, vY = 288.5999
MainActivity: vX = 645.7418, vY = 322.51065
MainActivity: vX = 810.2783, vY = 270.19778

当然最后,在不用的时候记得调用以下代码重置并回收掉 VelocityTracker:

代码语言:javascript
复制
velocityTracker.clear();
velocityTracker.recycle();

GestureDetector

GestureDetector 即手势检测,用于辅助我们捕获用户的 单击、双击、滑动、长按等行为。

使用也很简单,只需要创建一个下面来看个示例。 在构造函数中创建 通过 gestureDetector = new GestureDetector(context, this) 创建 GestureDetector, 然后实现 GestureDetector.OnGestureListenerGestureDetector.OnDoubleTapListener 接口, 然后在 onTouchEvent 中 返回 gestureDetector.onTouchEvent(event)

代码语言:javascript
复制
public class TestGestureDetector extends View implements GestureDetector.OnGestureListener,
        GestureDetector.OnDoubleTapListener {
    private static final String TAG = "TestGestureDetector";
    GestureDetector gestureDetector;
    public TestGestureDetector(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        gestureDetector = new GestureDetector(context, this);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return gestureDetector.onTouchEvent(event);
    }
    @Override
    public boolean onDown(MotionEvent e) {
        Log.e(TAG, "onDown: action = " + e.getAction());
        return false;
    }
    @Override
    public void onShowPress(MotionEvent e) {
        Log.e(TAG, "onShowPress:");
    }
    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        Log.e(TAG, "onSingleTapUp: " + e.getAction());
        return false;
    }
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        Log.e(TAG, "onScroll: e1.action = " + e1.getAction() + ", e2.action = " + e2.getAction());
        return false;
    }
    @Override
    public void onLongPress(MotionEvent e) {
        Log.e(TAG, "onLongPress: action = " + e.getAction());
    }
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        Log.e(TAG, "onFling: e1.action = " + e1.getAction() + ", e2.action = " + e2.getAction());
        return false;
    }
    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        Log.e(TAG, "onSingleTapConfirmed: action = " + e.getAction());
        return false;
    }
    @Override
    public boolean onDoubleTap(MotionEvent e) {
        Log.e(TAG, "onDoubleTap: action = " + e.getAction());
        return false;
    }
    @Override
    public boolean onDoubleTapEvent(MotionEvent e) {
        Log.e(TAG, "onDoubleTapEvent: action = " + e.getAction());
        return false;
    }
}

然后在布局中让它占满屏幕。

tips: action = 0DOWN 事件 action = 1UP 事件 action = 2MOVE 事件

运行程序,我们执行一次单击,一次长按单击,然后双击一次,发下打印日志如下:

代码语言:javascript
复制
//第一次单击
TestGestureDetector: onDown: action = 0
TestGestureDetector: onShowPress:
TestGestureDetector: onLongPress: action = 0
//第一次长按单击
TestGestureDetector: onDown: action = 0
TestGestureDetector: onShowPress:
TestGestureDetector: onLongPress: action = 0
//第一次双击
TestGestureDetector: onDown: action = 0
TestGestureDetector: onShowPress:
TestGestureDetector: onDown: action = 0
TestGestureDetector: onShowPress:
TestGestureDetector: onLongPress: action = 0

通过上面的日志信息我们可以知道 : 一次 单击长按单击 操作会触发 onDownonShowPressonLongPress三个回调。 双击 操作则会依次触发 onDownonShowPressonDownonShowPressonLongPress 五次回调。

显示单击出现 onLongPress 是不合理的,我们可以通过 gestureDetector.setIsLongpressEnabled(false) 禁用掉,而且我们也没有监听到 单机和双击等其他回调,这是为什么呢?

这是因为我们 没有消耗掉 DOWN 事件,这涉及到事件分发相关的知识了,这里先不说,后面会写文章单独讲解。那怎么消耗掉 DOWN 事件呢?很简单,只要在 onDown 中返回 true。 修改上述代码如下,只贴出修改的部分,

代码语言:javascript
复制
public class TestGestureDetector extends View implements GestureDetector.OnGestureListener,
        GestureDetector.OnDoubleTapListener {
    ...
    public TestGestureDetector(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        gestureDetector = new GestureDetector(context, this);
        gestureDetector.setIsLongpressEnabled(false);
    }
    @Override
    public boolean onDown(MotionEvent e) {
        Log.e(TAG, "onDown: action = " + e.getAction());
        return true;
    }
    ...
}

运行程序,在执行一次单击,一次长按单击和一次双击,日志如下:

代码语言:javascript
复制
//第一次单击
TestGestureDetector: onDown: action = 0
TestGestureDetector: onSingleTapUp: 1
TestGestureDetector: onSingleTapConfirmed: action = 0
//第一次长按单击
TestGestureDetector: onDown: action = 0
TestGestureDetector: onShowPress:
TestGestureDetector: onSingleTapUp: 1
TestGestureDetector: onSingleTapConfirmed: action = 1
//第一次双击
TestGestureDetector: onDown: action = 0
TestGestureDetector: onSingleTapUp: 1
TestGestureDetector: onDoubleTap: action = 0
TestGestureDetector: onDoubleTapEvent: action = 0
TestGestureDetector: onDown: action = 0
TestGestureDetector: onDoubleTapEvent: action = 1

我们可以看到现在一次单击则会触发onDownonSingleTapUponSingleTapConfirmed 这三个回调。 一次长按单击则会触发onDownonShowPressonSingleTapUponSingleTapConfirmed 这四个回调。 一次双击则会一次触发onDownonSingleTapUponDoubleTaponDoubleTapEventonDownonDoubleTapEvent 这六个回调。

而我们在屏幕上快速滑动时,则会触发 onDownonShowPressonScrollonScrollonFling这五个回调,onShowPress 取决于你在按下和开始滑动之前的时间间隔,短的话就不会有, 是否有 onFling 取决于滑动的距离和速度

代码语言:javascript
复制
TestGestureDetector: onDown: action = 0
TestGestureDetector: onShowPress:
TestGestureDetector: onScroll: e1.action = 0, e2.action = 2
TestGestureDetector: onScroll: e1.action = 0, e2.action = 2
TestGestureDetector: onFling: e1.action = 0, e2.action = 1

下面我们来统一介绍下这些回调具体的含义把:

方法名

描述

所属接口

onDown

触摸View的瞬间,由一个 DOWN 触发

OnGestureListener

onShowPress

触摸View未松开或者滑动时触发

OnGestureListener

onSingleTapUp

触摸后松开,在onDown的基础上加了个 UP 事件,属于单击行为

OnGestureListener

onScroll

按下并拖动,由一个 DOWN 和 多个 MOVE 组成,属于拖动行为

OnGestureListener

onLongPress

长按事件

OnGestureListener

onFling

快速滑动后松开,需要滑动一定的距离

OnGestureListener

onSingleTapConfirmed

严格的单击行为,onSingleTapUp之后只能是onSingleTapConfirmed 或 onDoubleTap 中 的一个

OnDoubleTapListener

onDoubleTap

双击行为,和 onSingleTapConfirmed 不共存

OnDoubleTapListener

onDoubleTapEvent

表示双击行为的发生,一次双击行为会触发多次onDoubleTapEvent

OnDoubleTapListener


Scroller

Scroller 用于实现View的弹性滑动,当我们使用View的 scrollToscrollBy 方法进行滑动时,滑动时瞬间完成的,没有过渡效果使得用户体验不好,这个时候就可以使用 Scroler 来解决这一用户体验差的问题。 Scroller本身无法让View弹性滑动,需要配合View的 computeScroll 方法。

那如果使用Scroller呢? 它的典型代码是固定的,如下所示。 至于为什么能够实现,我们下篇文章介绍 View的滑动 的时候再具体分析。

代码语言:javascript
复制
public class TestScroller extends TextView {
    private static final String TAG = "TestScroller";
    Scroller mScroller;
    public TestScroller(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mScroller = new Scroller(context);
    }
    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }
    public void smoothScrollTo(int destX, int destY) {
        int scrollX = getScrollX();
        int scrollY = getScrollY();
        int deltaX = destX - scrollX;
        int deltaY = destY - scrollY;
        mScroller.startScroll(scrollX, scrollY, deltaX, deltaY, 1000);
        invalidate();
    }
}
代码语言:javascript
复制
//activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <com.lxk.viewdemo.TestScroller
        android:id="@+id/tv"
        android:layout_width="320dp"
        android:layout_height="320dp"
        android:layout_margin="8dp"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:padding="8dp"
        android:text="Hello World!" />
</LinearLayout>
代码语言:javascript
复制
//MainActivity.java
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final TestScroller scroller = findViewById(R.id.tv);
        scroller.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                scroller.smoothScrollTo(200, 200);
            }
        });
}

运行看看,可以看到点击之后,内容在 1s 内往左上方各平移了 200px

Scroll
Scroll

如果觉得不错的话,请帮忙点个赞呗。

以上


扫描下面的二维码,关注我的公众号 Android1024, 点关注,不迷路。

`

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-11-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录
  • View与ViewGroup
  • View的位置参数
  • MotionEvent 和 TouchSlop
  • VelocityTracker
  • GestureDetector
  • Scroller
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档