目录
Android — View (一)
一、 View的基础知识
1.什么是View
在Android中, 控件大致被分为两类,即ViewGroup和View, ViewGroup作为父控件可以包含多个View, 并管理其包含的View控件, View是Android中所有控件的基类,是界面层的空间的一种抽象它代表了一个控件。
2.View的位置参数
View的位置参数主要有
参数名 | 含义 |
---|---|
top | View左上角到父容器顶部的像素距离 |
left | View左上角到父容器左边边缘像素距离 |
bottom | View右下角到父容器顶部的像素距离 |
right | View右下角到父容器左边边缘像素距离 |
x/y | View左上角坐标 |
translationX/translationY | View左上角相对于父容器的偏移量 |
坐标系以父布局左上角为基准的
3.MotionEvent
MotionEvent是指触摸事件的相关细节,在手指触摸时发生的一系列事件中经典的事件有以下几种:
- ACTION_DOWN 手指刚接触屏幕
- ACTION_MOVE 手指在屏幕上移动
- ACTION_UP 手指从屏幕松开的一瞬间
- ACTION_CANCEL 时间结束(非人为原因)
通过MotionEvent可以获取到触摸事件的x和y坐标
- getX/getY 相对于View左上角的x与y坐标
- getRawX/getRawY 相对于屏幕左上角的x与y坐标
正常情况下,一次手指触摸的行为会触发一系列点击事件,如:
- 点击屏幕后立刻松开,ACTION_DOWN -> ACTION_UP
- 点击屏幕后滑动手指后再松开,ACTION_DOWN -> ACTION_MOVE -> … -> ACTION_UP
事件序列:
4.TouchSlop
TouchSlop是系统所能识别的被认为是滑动的最小距离。这是一个常量和设备有关,不同的设备获取的值可能不同。如果滑动起始位置与结束位置的距离小于这个常量可以认为未达到滑动距离的临界值因此认定这个不是滑动提高用户体验。
可以通过以下代码获取:
ViewConfiguration.get(getContext()).getScaledTouchSlop();
5.VelocityTracker
VelocityTracker为速度追踪器,用于追踪手指在滑动过程中的速度,包括水平与竖直方向的速度。
使用方法:
//在onTouchEvent方法中追踪当前点击事件的速度
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
//获取在1000ms内中手指滑过的像素
//速度 =(终点位置 - 起点位置)/ 时间段
velocityTracker.computeCurrentVelocity(1000);
float xVelocity = velocityTracker.getXVelocity();
float yVelocity = velocityTracker.getYVelocity();
//在不需要使用时调用clear与recycle来重置并回收内存
velocityTracker.clear();
velocityTracker.recycle();
注意:和Android坐标轴相同方向结果为正,相反方向为负
6.GestureDetector
手势检测,用于辅助检测用户的单击、滑动、长按、双击等行为。
GestureDetector内部的Listener接口:
- OnGestureListener,这个Listener监听一些手势,如单击、滑动、长按等操作:
- OnDoubleTapListener,这个Listener监听双击和单击事件。
- OnContextClickListener,鼠标右键(加入外设)
- SimpleOnGestureListener,实现了上面三个接口的类,拥有上面三个的所有回调方法。
package com.skit.view;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
/**
* Author: shuike,
* Email: [email protected],
* Date: 2019/11/25.
* PS:
*/
public class MainActivity extends AppCompatActivity {
GestureDetector.SimpleOnGestureListener simpleOnGestureListener = new GestureDetector.SimpleOnGestureListener(){
//手指轻触并松开
@Override
public boolean onSingleTapUp(MotionEvent e) {
return super.onSingleTapUp(e);
}
//长按
@Override
public void onLongPress(MotionEvent e) {
super.onLongPress(e);
}
//按下并拖动
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return super.onScroll(e1, e2, distanceX, distanceY);
}
//按下触碰长按并松开
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return super.onFling(e1, e2, velocityX, velocityY);
}
//手指轻触屏幕的一瞬间,尚未松开
@Override
public void onShowPress(MotionEvent e) {
super.onShowPress(e);
}
//手指轻触屏幕的一瞬间
@Override
public boolean onDown(MotionEvent e) {
return super.onDown(e);
}
//双击
@Override
public boolean onDoubleTap(MotionEvent e) {
return super.onDoubleTap(e);
}
//发生双击并松开
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
return super.onDoubleTapEvent(e);
}
//严格的单击
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return super.onSingleTapConfirmed(e);
}
//鼠标右键点击时的回调
@Override
public boolean onContextClick(MotionEvent e) {
return super.onContextClick(e);
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final GestureDetector gestureDetector = new GestureDetector(this, simpleOnGestureListener);
View view = findViewById(R.id.view);
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
});
}
}
比较常用的:
- onSingleTapUp(单击)
- onFling(快速滑动)
- onScroll(拖动)
- onLongPress(长按)
- onDoubleTap(双击)
二、View的滑动
1.scrollTo/scrollBy
为了实现View的滑动,View提供了专门的方法来实现这个功能
scrollTo/scrollBy的区别:
- scrollTo是基于参数的绝对滑动。
- scrollBy是基于参数的相对滑动。
- 注意,两者都是移动View的内容,而不是VIew本身
scrollTo与scrollBy源码
/**
* 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) {
//可以看出scrollBy也是调用scrollBy实现的
scrollTo(mScrollX + x, mScrollY + y);
}
在滑动过程中:
-
mScrollX的值总是等于View的左边缘和View的内容左边缘在水平方向的距离。
-
mScrollY的值总是等于View的上边缘和View的内容上边缘在竖直方向的距离
mScrollX与mScrollY只能改变View的内容的位置而不能改变View在布局中的位置。mScrollX和mScrollY的值单位是像素,从左向右滑动mScrollX值为负,否则为正,从上往下滑动mScrollY值为负,否则为正。
2.使用动画
通过动画我们能让一个View进行平移,而平移就是一种滑动。使用动画来移动View主要是操作View的translationX与translationY,可以使用View补间动画或属性动画。
View动画
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<translate
android:duration="100"
android:fromXDelta="0"
android:fromYDelta="0"
android:interpolator="@android:anim/linear_interpolator"
android:toXDelta="100"
android:toYDelta="100" />
</set>
Animation animation = AnimationUtils.loadAnimation(this,R.anim.view_translate);
view.startAnimation(animation);
通过GIF可以看出来 View动画是对View的影像进行操作的,也就是说View动画并不能真正的改变View的位置。
属性动画
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view,"translationX",0,100).setDuration(1000);
objectAnimator.start();
通过GIF可以看出来属性动画是真正改变View的位置。
但它是从Android3.0开始推出的。
3.改变布局参数
改变布局参数,即会改变LayouParams的参数,如:要将一个View右移100像素点,我们可以把该View的LayoutParams的marginLeft参数增加100就可以实现
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
params.leftMargin += 100;
view.requestLayout();
//或者
//view.setLayoutParams(params);
4.滑动方式对比
上面分别使用三种方式实现了View的滑动,那么各个方式的区别是什么样的呢?
方式 | 优点 | 缺点 |
---|---|---|
scrollTo/scrollBy | 原生方法 | 只能滑动View内容,不能滑动View本身 |
动画 | 操作简单、适用于没有交互的View和复杂的动画效果 | Android3.0以下不兼容 |
改变布局参数 | 适用于有交互的View | 操作稍微复杂 |
5.跟随手指滑动的View编写
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getRawX();
float y = event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
float deltaX = x - mLastX;
float deltaY = y - mLastY;
float translationX = (getTranslationX() + deltaX);
float translationY = (getTranslationY() + deltaY);
setTranslationX(translationX);
setTranslationY(translationY);
break;
}
mLastX = x;
mLastY = y;
return true;
}
三.弹性滑动
1. Scroller
Scroller弹性滑动对象,用于实现View的弹性滑动,在使用View的scrollTo/scroolBy方法来滑动时过程是一瞬间完成的,没有过渡效果对用户体验不好,这时可以使用Scroller来实现过度滑动,Scroller本身无法让View弹性滑动,需要配合、View的computeScroll方法配合使用。
使用方法:
//第一步
private Scroller mScroller;
mScroller = new Scroller(context);
//第二步,调用startScroll()方法来初始化滚动数据并刷新界面
mScroller.startScroll(getScrollX(), 0, dx, 0);
//第三步,重写computeScroll方法,并在其内部完成平滑滚定的逻辑
@Override
public void computeScroll() {
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
Scroll相关源码:
//startX与startY为滑动的起点,dx与dy为要滑动的距离,duration为滑动的时间默认为250
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE;//模式在之后的computeScrollOffset做判断使用
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
mFinalX = startX + dx;
mFinalY = startY + dy;
mDeltaX = dx;
mDeltaY = dy;
mDurationReciprocal = 1.0f / (float) mDuration;
}
public boolean computeScrollOffset() {
if (mFinished) {
return false;
}
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
.....
}
}
else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
2.使用动画
动画本身就是一种渐进的过程,因此通过它来实现的滑动天然就具有弹性效果,比如将一个View在100ms内向右移动100像素。
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view,"translationX",0,100).setDuration(1000);
objectAnimator.start();
3.使用延时策略
private static final int MESSAGE_SCROLL_TO = 1;
private static final int FRAME_COUNT = -300;
private static final int DELAYED_TIME = 10;
Handler mHandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case MESSAGE_SCROLL_TO:
Toast.makeText(MainActivity.this, "shoudaole", Toast.LENGTH_SHORT).show();
mCount++;
if (mCount >= FRAME_COUNT) {
float fraction = mCount / FRAME_COUNT;
int scrollX = (int) (fraction * 100);
view.scrollTo(scrollX, 0 );
mHandler.sendEmptyMessageDelayed(MESSAGE_SCROLL_TO, DELAYED_TIME);
}
break;
}
}
};