View的基础知识介绍

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

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


能够带着如下问题来看本文:android

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

目录

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

View与ViewGroup

Viewbash

public class Viewide

extends Object implements Drawable.Callback,KeyEvent.Callback,AccessibilityEventSource函数

java.lang.Object布局

android.view.Viewpost

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.Object

android.view.View

android.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坐标系

左上角为原点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

下面咱们来测试看看:

<!--  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>
复制代码
//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());
            }
        });
    }
}
复制代码

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

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 的模拟器上获得的值以下:

MainActivity: getScaledTouchSlop = 21
复制代码

VelocityTracker

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

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;
    }
});
复制代码
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:

velocityTracker.clear();
velocityTracker.recycle();
复制代码

GestureDetector

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

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

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 事件

运行程序,咱们执行一次单击,一次长按单击,而后双击一次,发下打印日志以下:

//第一次单击
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。 修改上述代码以下,只贴出修改的部分,

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;
    }
    ...
}
复制代码

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

//第一次单击
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 取决于滑动的距离和速度

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的滑动 的时候再具体分析。

public class TestScroller extends View {
    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();
        }
    }
    private void smoothScrollTo(int destX, int destY) {
        int scrollX = getScrollX();
        int scrollY = getScrollY();
        int deltaX = destX - scrollX;
        int deltaY = destY - scrollY;
        mScroller.startScroll(scrollX, 0, deltaX, 0, 1000);
        mScroller.startScroll(0, scrollY, 0, deltaY, 1000);
        invalidate();
    }
}
复制代码

若是以为不错的话,请帮忙点个赞呗。

以上


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

Android1024

`

相关文章
相关标签/搜索