View的基础与滑动
1.View的基础知识
1.1View是什么
View是一种界面层的控件的一种抽象
,我们知道的大多数控件都是直接或间接继承自View
如EditText,TextView等等
注意:
EditText在compose中并没有相关控件,而是通过TextField来进行组合
Android中View本身就可以是单个控件也可以是由多个控件组合成的一组控件
如下面
1.2View的位置参数
这个分为两种情况,一种是相对于ViewGroup的,一种相对于Screen的
先说相对于ViewGroup的
1.2.1ViewGroup中的位置参数
getLeft | view的最左边相对于viewGroup最左边的距离 |
---|---|
getRight | view的最右边相对于ViewGroup最左边的距离 |
getTop | view的最上面相对于ViewGroup最上面的距离 |
getBottom | view的最下面相对于ViewGroup最上面的距离 |
getX | 你触碰的那个点相对于view的X距离 |
getY | 你触碰的那个点相对于view的Y距离 |
1.2.2Screen中的位置参数
getRawX | 你触碰的那个点相对于Screen的x距离 |
---|---|
getRawY | 你触碰的那个点相对于Screen的y的距离 |
1.2.3图解
说一说getX与getLeft的关系
我们要先将一个概念叫做偏移量:
你在屏幕按动的那个到你父容器(即view)的距离,这个图里面我是用touch表示的,
就是偏移量,translation
getX = getLeft+translationX,正值表示向右平移,负值表示向左平移。
getY = getTop+translationY,正值表示向下平移,负值表示向上平移。
1.3MotionEvent
当你手指接触到屏幕的时候,MotionEvent有4个典型事件如下:
- ACTION_DOWN ,这个是手指刚接触到屏幕的时候
- ACTION_MOVE,这个是手指在屏幕上移动
- ACTION_UP,这个是手指从屏幕上松开
- ACTION_CANCEL,这个是触摸事件被取消或中断的动作类型。
前面的还好理解,那么后面两个怎么区分呢?
chatPGT中说遇到如下事件,会执行后者
- 手指移出了视图范围:当用户在触摸一个视图时,如果手指移动到视图范围之外,系统会生成一个
ACTION_CANCEL
的触摸事件,表示触摸事件被取消。这种情况下,ACTION_CANCEL
会被执行,以便你可以在代码中相应地处理触摸事件的取消。- 系统事件中断触摸:当触摸事件正在进行时,如果系统发生某些中断事件,例如电话呼入、弹出对话框等,系统会生成一个
ACTION_CANCEL
的触摸事件,表示触摸事件被中断。这样可以确保用户在触摸过程中不会发生意外的操作。在这种情况下,你可以通过处理ACTION_CANCEL
事件来恢复到触摸事件之前的状态或进行必要的清理工作。
虽然我没实现出来
ACTION_UP这个就是指头一离开,就会触发
1.4TouchSlop
这个是系统能识别出来的滑动的最小距离,如果两次滑动的距离小于它便不会被识别出来
用
ViewConfiguration.get(getContext().getScaledTouchSlop());
获得
2.View的滑动
《Android开发艺术探索》中只有3种滑动,分别是
1.动画
2.scrollTo/scrollBy
3.LayoutParams
艺术开发探索把后面的这种称为改变布局参数
而《进阶之光》在以上的基础上还说了
1.layout
2.offsetLeftAndRight/offsetTopAndBottom
而这两种其实也就属于改变布局参数
所以就分成3个部分讲
2.1动画
《进阶之光》讲了2种动画,
一种是属性动画,一种是View动画
先讲视图动画
2.1.1View动画
我们在res中创建anim文件,在这里面创建translate.xml文件
注意是set类型哦
以下是里面的一些属性
set不用说了,translate是进行移动的,可以进行x,y轴的移动。alpha是控件的透明度,rotate是实现旋转的,scale是扩大多少倍或者缩小至几分1
比如我用translate
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="0"
android:duration="1000"
android:toXDelta="200"/>
</set>
ima = findViewById(R.id.what);
ima.setAnimation(AnimationUtils.loadAnimation(this,R.anim.translate));
这时候我的ima会在1000ms内从x = 0移动到x = 200,但是执行完后又会退回去
我们只需要在set里面加一句
android:fillAfter = "true"
就行了
但是还是有一个问题,比如你移动的是一个button,你让它移动到x = 200的位置后,你再点击它,它并不会有相应,你必须点击x = 0的时候它才会有响应
View动画并不能改变View的位置参数
ima = findViewById(R.id.what);
Log.d("TAG",""+ima.getLeft());
ima.setAnimation(AnimationUtils.loadAnimation(this,R.anim.translate));
ima.getX();
Log.d("TAG",""+ima.getLeft());
打印出来
D/TAG: 0.0
D/TAG: 0.0
另一种讲的是
2.1.2属性动画
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mButton,"translationX",200);
objectAnimator.setDuration(2000);
objectAnimator.start();
这样之后mButton便会在x轴上移动移动2000ms移动200处
并且运行完之后没有返回
这时候,我们点击button发现它可以跳转到第二个Activity中,成功。
这时候我们打印以下getLeft()
发现也全是
TAG: 0
TAG: 0
和View动画最后打印出来的一模一样
这是因为start();为异步方法
在start方法后写上
Handler handler = new Handler();
Runnable runnable = new Runnable() {
@Override
public void run() {
Log.d("TAG",""+ima.getLeft());
}
};
handler.postDelayed(runnable, 2000);
打印出来结果了
TAG: 492
或者
objectAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
Log.d("TAG",""+mButton.getTranslationX());
Log.d("TAG",""+mButton.getLeft());
}
});
也会发现getLeft改变了
但是你延迟2s打印view动画的getLeft它不会改变
2.2scrollTo/scrollBy
这个主要就是滑动,其中scrollTo是移动到某个坐标
而scrollBy是移动了多少坐标
scrollBy内部调用的就是scrollTo
看看二者的源码
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();
}
}
}
scrollBy就是调用了个scrollTo就没了
所以看源码就直接就看scrollTo的源码
if语句判断的是:滑动的位置是否和原位置相同,不相同才会滑动
用两个新变量保存原来的位置,再把新位置赋值给mScrollX 与 mScrollY
通过
invalidateParentCaches();
来清除之前父布局的缓存
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
用于监听与回调
最后再第二个if语句中
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
这段代码的目的是在滚动条没有被唤醒(显示)时,请求视图在下一帧进行刷新。这样做的目的是在需要显示滚动条时,确保滚动条能够正确地显示在视图上。
如果你要使用scrollTo/scrollBy有点需要
注意
在ACTION_MOVE
中,你想让它移动到(x,y)
正常情况下是
((View)getParent()),scrollTo(x,y)
但是你会发现它移动的位置不太对劲,移动到了(-x,-y)了
把代码改成
((View)getParent()),scrollTo(-x,-y)
便成功移动到(x,y)
因为scrollTo(-x, -y)
方法中的参数是负值。scrollTo()
方法中的参数表示要滚动到的目标位置相对于视图左上角的偏移量。通过传入负值,实际上是在将视图向相反的方向滚动,从而实现了将视图移动到目标位置的效果。
2.3 改变布局参数
2.3.1layout
case MotionEvent.ACTION_MOVE:
Log.d("TAG","MOVE");
int x1 = x - lastX;
int y1 = y - lastY;
// 处理移动事件
layout(getLeft() + x1,getTop() + y1,getRight() + x1,getBottom() + y1);
break;
2.3.2setLeftAndRight
case MotionEvent.ACTION_MOVE:
Log.d("TAG", "MOVE");
int x1 = x - lastX; // 处理移动事件
int left = view.getLeft() + x1;
int right = view.getRight() + x1;
view.setLeftAndRight(left, right);
break;
setTopAndBottom差不多
2.3.3LayoutParams
case MotionEvent.ACTION_MOVE:
Log.d("TAG", "MOVE");
int x1 = x - lastX;
int y1 = y - lastY;
// 处理移动事件
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
layoutParams.leftMargin = layoutParams.leftMargin + x1;
layoutParams.topMargin = layoutParams.topMargin + y1;
view.setLayoutParams(layoutParams);
break;
case MotionEvent.ACTION_MOVE:
Log.d("TAG", "MOVE");
int x1 = x - lastX;
int y1 = y - lastY;
// 处理移动事件
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
layoutParams.leftMargin = layoutParams.leftMargin + x1;
layoutParams.topMargin = layoutParams.topMargin + y1;
view.setLayoutParams(layoutParams);
break;