在生活中我们使用 Android 的 App 经常会看到一些炫丽的动态界面,出现这种效果很多是因为 View 的滑动,比如 RecyclerView 的滑动,掌握 View 的滑动方式,我们也能做出这样的效果;实现 View 的滑动常见的有4种方法,它们分别是 使用 scrollTo/scrollBy、使用动画、改变布局参数和使用 layout,下面将对它们一一进行介绍。
1、使用 scrollTo/scrollBy
View提供了 scrollTo(int x,int y) 和 scrollBy(int x,int y) 这两个方法实现了它的滑动,具体实现代码(基于 Android Api 27)如下所示:
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();
}
}
}
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
从scrollTo(int x,int y) 和 scrollBy(int x,int y) 这两个方法总结得出,scrollTo(int x,int y) 实现了所传递参数的绝对滑动,而 scrollBy(int x,int y) 实现了当前位置的相对滑动,因为它调用了 scrollTo(int x,int y) 方法。在这2个方法中,出现了 mScrollX 和 mScrollY 这两个成员变量,其中 mScrollX 表示 View 的左边缘和 View 内容左边缘在水平方向的距离,mScrollY 表示 View 上边缘和 View 内容上边缘在垂直方向上的距离。下面写一个例子来使用 scrollBy(int x,int y) 方法;首先新建一个布局文件 activity_view_general_slide.xml,它的代码如下所示:
然后再新建一个Java文件,名字为 ViewGeneralSlideActivity,代码如下所示:
private TextView mTv;
private MyHandler mHandler = null;
private int mSlideY = 20;
private int mSlideX = 20;
private MyThread thread = null;
private boolean isRunThread = true;
class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
mTv.scrollBy(0,mSlideY);
mSlideY += 10;
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_general_slide);
mTv = findViewById(R.id.tv_scroll);
mHandler = new MyHandler();
thread = new MyThread();
}
public void onClickView(View view) {
thread.start();
}
class MyThread extends Thread {
@Override
public void run() {
super.run();
while (isRunThread) {
try {
Thread.sleep(500);
sendMessage();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
isRunThread = false;
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
}
private void sendMessage() {
Message message = Message.obtain();
mHandler.sendMessage(message);
}
没点击“scrollBy滑动”控件之前的效果如下所示:
点击“scrollBy滑动”控件之后的效果如下所示:
注意:不管是 scrollBy(int x,int y) 还是 scrollTo(int x,int y) 方法,滑动的并非是 View 本身,而是 View 的内容。
2、使用动画
使用动画来移动 View ,是通过 View 的 translationX 和 translationY 这两个属性来实现的,它即可以采用传统的 View 动画,也可以采用属性动画;在使用属性动画方面,如果要在 Android 3.0以下版本使用,那么就要使用到开源动画库 nineoldandroids,它的官方地址为:http://nineoldandroids.com/ 。下面举个例子用代码实现使用 View 动画来移动。
2、1 采用传统的 View 动画
(1) 在 res 目录新建 anim 文件夹并在 anim 文件夹下创建 view_translate.xml:
(2) 新建一个 Java 文件,名字叫 AnimationActivity,并实现它的代码,如下所示:
private TextView mTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_animation);
mTv = findViewById(R.id.tv);
}
public void onClick(View view) {
Animation animation = AnimationUtils.loadAnimation(this, R.anim.view_translate);
mTv.startAnimation(animation);
}
(3) 在 layout 文件夹下新建一个 activity_animation.xml 文件,代码如下所示:
没点击 “点击该按钮可移动view” 这个按钮之前,它运行的效果图如下所示:
点击 “点击该按钮可移动view” 这个按钮之后,它运行时移动的过程中,某一瞬间的效果图如下所示:
2、2 使用属性动画移动
(1) 为了兼容 Android 3.0 以下版本的手机,在项目 app 目录下的 build.gradle文件中添加 nineoldandroids 的依赖:
implementation 'com.nineoldandroids:library:2.4.0'
(2) 新建一个 Java 文件,名字叫 Animation2Activity,它的代码如下所示:
private TextView mTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_animation2);
mTv = findViewById(R.id.tv);
}
public void onClick(View view) {
ObjectAnimator.ofFloat(mTv,"translationY",0,300).setDuration(1000).start();
}
public void onClick2(View view) {
Toast.makeText(this, "点击了“可移动的属性view”", Toast.LENGTH_SHORT).show();
}
(3) 在 layout 文件夹下新建一个 activity_animation2.xml 文件,代码如下所示:
没点击 “点击该按钮可属性动画移动view” 之前,它的运行效果图如下所示:
点击 “点击该按钮可属性动画移动view” 这个按钮之后,它运行时移动的过程中,某一瞬间的效果图如下所示:
对以上使用动画的例子进行总结得出:(1) 传统的 View 动画只是对影像做移动,并不是真正的改变 View 的位置,它的 fillAfter 属性默认值为 false,也就等同于做完动画之后会回到原位置;当 fillAfter 属性设置为 true 的时候,View 做完动画后,影像不会回到原始的位置,但如果在该 View 做一个事件监听,点击做完动画后的 View 是不会响应事件的,点击 View 的原始位置就会响应事件,因为在系统眼里 View 做完动画之后它的位置没有发生改变。(2) 属性动画在 Android 3.0版本以下的手机不可以用,要想使用,必须添加 nineoldandroids 这个依赖酷;属性动画不是对影像做移动,而是真正的改变了 View 的位置,如果在该 View 做一个事件监听,点击 View 的原始位置是不会有事件响应的,而点击做完动画之后的位置就会有事件响应。
3、改变布局参数
通过改变布局参数来实现 View 的滑动的思路有2个:(1) 向右移动一个View,只需要把它的 marginLeft 参数增大,向其它方向移动同理,只需改变相应的 margin 参数;(2) 要移动的 View 的旁边预先放一个 View,它的初始化宽度为0,然后要想右移动View,只需把预先放置的那个View的宽度增大,这样就把要移动的 View “推”到右边了。示例如下所示:
(1) 新建一个布局文件,名字叫 activity_change_layout_parameter.xml
新建一个 Java 文件,名字叫 ChangeLayoutParameterActivity,它的代码如下所示:
TextView mTv;
View mView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_change_layout_parameter);
mTv = findViewById(R.id.tv);
mView = findViewById(R.id.view);
}
public void onClick(View view) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mTv.getLayoutParams();
params.leftMargin += 200;
params.width += 200;
mTv.requestLayout();
}
public void onClick2(View view) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mView.getLayoutParams();
params.width += 200;
mView.requestLayout();
}
public void onClick3(View view) {
Toast.makeText(this,"点击了“改变布局参数可移动的第一个View” ",Toast.LENGTH_SHORT).show();
}
public void onClick4(View view) {
Toast.makeText(this,"点击了“改变布局参数可移动的第二个View” ",Toast.LENGTH_SHORT).show();
}
没点击 “点击我可移动第一个View” 按钮之前,效果图如下所示:
点击 “点击我可移动第一个View” 按钮之后,效果图如下所示:
对以上改变布局参数移动 View 进行总结:不管是对 View 的 marginLeft 参数进行增大;还是要移动的 View 的旁边预先放一个 View,然后要向右移动View;他们两种方法都实现了 View 真正位置的移动,而不是 View 影像的移动;如果在 移动的 View 做事件监听,点击原始位置不会事件响应,点击移动后的位置会有事件响应。
4、使用 layout
使用 layout 移动 View 的思路是这样的:在View的onTouchEvent方法中对MotionEvent中的坐标进行记录,记录按下的时候记录,在移动的时候计算他们的偏移量,调用layout()对view的位置进行重绘制;下面举个例子:
(1) 新建一个布局文件,名叫 activity_use_layout.xml :
(2) 新建一个 Java 文件,名叫 UseLayoutActivity,它的实现代码如下所示:
TextView mTv;
private int lastX,lastY;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_use_layout);
mTv = findViewById(R.id.tv);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getRawX();
int y = (int) event.getRawY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
int offsetX = x - lastX;
int offsetY = y - lastY;
mTv.layout(mTv.getLeft()+offsetX,
mTv.getTop()+offsetY,
mTv.getRight()+offsetX,
mTv.getBottom()+offsetY);
lastX = x;
lastY = y;
break;
}
return true;
}
public void onClick(View v){
Toast.makeText(this," 点击了 “使用 Layout移动View” 按钮 ",Toast.LENGTH_SHORT).show();
}
没滑动屏幕之前的效果图如下所示:
滑动屏幕某一瞬间的效果图如下所示:
对以上使用 layout 移动 View 进行总结:使用 layout 移动 View,改变的不是 View 的影像,而是 View 真正的位置,所以如果要设置 View 的事件监听,点击移动后的 View 才会响应事件;使用 layout 移动 View 是和 Activity 的触摸事件结合使用的,触摸的时候获取相对于屏幕触摸的x 和 y 坐标,然后手指移动的过程中计算出移动的距离再加上 View 相对于父视图的位置,得到 View 移动后的位置,最后 View 使用 layout 方法 重新布局再进行重新绘画。
好了,本篇文章写到这里就结束了,由于本人技术水平有限,难免会有出错的地方,欢迎批评指正,谢谢大家的阅读,另外附上Android的View滑动demo
关注微信公众号,阅读更多有趣的技术文章