1. 什么是View
View 是Android中所有控件的基类,ViewGroup继承了View,其内部包含一组View(或ViewGroup)
2. View的位置参数
先来了解一下 top, bottom, left, right,具体如下图所示。Android的坐标系是以屏幕的左上角为原点,x 轴向右为正,y 轴向下为正。而这四个参数是以其 父容器 (注意是父容器,不一定是整个屏幕哦)为参照物的相对坐标。
由此,可得出View的宽,高:
width = right - left;
height = bottom - top;
如果想获取这四个参数的值,可以通过view.getTop(), view.getBottom(), view.getLeft(), view.getRight()来获得。这四个参数分别对应View源码里的mTop, mBottom, mLeft, mRight.
从 Android 3.0 开始,View 又增加了几个参数:x, y, translationX, translationY. 其中x,y 是View相对于父容器左上角坐标,translationX 是View左上角相对于父容器在x轴的偏移量,translationY 是View左上角相对于父容器在y轴的偏移量。
这四个新增的参数也可通过相应的getXXX来获取, translationX, translationY的默认值是0.0(float类型);其中,
x = left + translationX;
y = top + translationY;
View 自身在做平移过程中,只有新增的这四个参数x, y, translationX, translationY会发生改变,而top, bottom, left, right这四个值则不会改变。
3. MotionEvent
手指点击屏幕滑动后抬起,看似简单,实际上包括一系列事件:ACTION_DOWN->ACTION_MOVE->...->ACTION_MOVE->ACTION_UP.
提到滑动事件,一般都会用到 MotionEvent 。通过这个对象我们可以获取事件发生时的 x,y 坐标,其中:
getX()/getY() 是相对于当前 View 左上角的 x, y 坐标;
getRawX()/getRawY()是相对于手机屏幕左上角的 x, y 坐标。
4. 直接滑动
- scrollTo/scrollBy
// 使用scrollBy()方法直接滑动View里的内容(滑动瞬间完成)
private void scrollByTest() {
Log.e(TAG,
"before left = " + textView.getLeft() + ",right = " + textView.getRight() + ",top = "
+ textView.getTop() + ",bottom = " + textView.getBottom() + ",x=" + textView.getX() + ",y = "
+ textView.getY() + ",translationX=" + textView.getTranslationX() + ",translationY = "
+ textView.getTranslationY());
// mScrollX 表示(View左边缘-view内容左边缘)
// 从左向右滑动时mScrollX是负的,反之是正的;从上向下滑动时,mScrollY是负的,反之是正的,此处是View的内容向左滑动100px
textView.scrollBy(100, 0);
Log.e(TAG,
"after left = " + textView.getLeft() + ",right = " + textView.getRight() + ",top = " + textView.getTop()
+ ",bottom = " + textView.getBottom() + ",x=" + textView.getX() + ",y = " + textView.getY()
+ ",translationX=" + textView.getTranslationX() + ",translationY = "
+ textView.getTranslationY());
}
测试的结果如下图,可以发现,scrollBy(实际上还是调用的scrollTo)只是移动了View的内容,View本身位置并没有改变。
- 改变布局参数
比如,宽100多屏,高50dp的TextView位于左上角,父容器是LinearLayout,现在,想把TextView向右移动100px。
private void scrollByUpdateLayoutParams() {
Log.e(TAG,
"before left = " + textView.getLeft() + ",right = " + textView.getRight() + ",top = "
+ textView.getTop() + ",bottom = " + textView.getBottom() + ",x=" + textView.getX() + ",y = "
+ textView.getY() + ",translationX=" + textView.getTranslationX() + ",translationY = "
+ textView.getTranslationY());
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) textView.getLayoutParams();
Log.e(TAG,"before leftMargin = " + params.leftMargin);
// 向右移动100,就相当于左边距增加100
params.leftMargin += 100;
textView.setLayoutParams(params);
Log.e(TAG,"after leftMargin = " + params.leftMargin);
Log.e(TAG,
"after left = " + textView.getLeft() + ",right = " + textView.getRight() + ",top = " + textView.getTop()
+ ",bottom = " + textView.getBottom() + ",x=" + textView.getX() + ",y = " + textView.getY()
+ ",translationX=" + textView.getTranslationX() + ",translationY = "
+ textView.getTranslationY());
}
测试结果如下图, TextView向右移动了100px:
此处测试需要注意的是,如果父容器是RelativeLayout,TextView不要设置layout_centerInParent = “true”,android:layout_centerHorizontal = "true等属性,不然的话,看不到滑动效果。
直接滑动很简单也很生硬,嘎嘣一下就滑过去了,用户体验着实不好。怎么办呢?办法还是有的,下面看一下怎样实现弹性滑动。
5. 弹性滑动
- 使用Scroller
Scroller类是一个实现弹性滑动的工具。那我们怎么使用它?它是怎么实现弹性滑动的呢?
使用Scroller主要有三步:
首先,new 一个Scroller对象;
其次,调用其startScroll方法,然后再调用invalidate();
最后,重写View的computeScroll()方法(注意这个方法是View的方法,不是Scroller的)
public class CustomView extends LinearLayout
{
private Scroller mScroller;
public CustomView(Context context)
{
this(context, null);
}
public CustomView(Context context, @Nullable AttributeSet attrs)
{
this(context, attrs, 0);
}
public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
//首先,new 一个Scroller对象
mScroller = new Scroller(getContext());
}
public void startSmoothScroll(int targetX, int targetY)
{
int diffX = targetX - getScrollX();
int diffY = targetY - getScrollY();
// 其次,调用其startScroll方法,然后再调用invalidate();
mScroller.startScroll(getScrollX(), getScrollY(), diffX, diffY, 3000);
invalidate();
}
@Override
public void computeScroll()
{
// 重写computeScroll()方法,根据mScroller.computeScrollOffset()来判断是否继续滚动(true继续滚),然后调用scrollTo实现滚动,并再次调用invalidate(),以继续进行下一轮的滚动
if (mScroller.computeScrollOffset())
{
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
}
布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<july.com.scrolldemo.CustomView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/customView"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="july.com.scrolldemo.MainActivity">
<TextView
android:id="@+id/tv"
android:layout_width="100dp"
android:layout_height="50dp"
android:background="@color/colorAccent"
android:gravity="center"
android:text="Hello World!"
/>
</july.com.scrolldemo.CustomView>
点击TextView,将其向屏幕右下方平滑滚动,目标位置为(-400,-600)
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
customView.startSmoothScroll(-400,-600);
}
});
为什么这样使用Scoller,就能实现弹性滚动呢?原理大致如下:
- 属性动画
属性动画实现弹性动画简直不要太简单,比如想实现两秒内TextView向右平滑滚动100px,一行代码就可以搞定:
ObjectAnimator.ofFloat(textView,"translationX",0,100).setDuration(2000).start();
这里只介绍思路,具体的属性动画的使用可以自己去学习哈。
- 使用延时策略
使用延迟策略也可以实现弹性滑动,其思想是通过循环发送一系列延迟消息,按百分比每一次滚动一点距离从而达到平滑滚动的效果。可以使用Handler或View的postDelayed方法,或者线程的sleep方法来实现。
下面以Handler为例,控制TextView通过10次滑动实现向右平滑滚动200px。
private static final int MSG_AUTO_SCROLL = 0;
private static final int INTERVAL = 200;
private static final int DISTANCE = -200;
/**
* 1O次滚动
*/
private static final int MAX_COUNT = 10;
private int mCount = 0;
private Handler mHandler = new AutoScrollHandler();
private class AutoScrollHandler extends Handler
{
@Override
public void handleMessage(Message msg)
{
if (msg.what == MSG_AUTO_SCROLL)
{
doScroll();
}
}
}
private void sendAutoMessage()
{
mHandler.removeMessages(MSG_AUTO_SCROLL);
mHandler.sendEmptyMessageDelayed(MSG_AUTO_SCROLL, INTERVAL);
}
private void doScroll()
{
Log.e(TAG, "doScroll run,mCount = " + mCount);
mCount++;
if (mCount <= MAX_COUNT)
{
float fraction = (float) (mCount * 1.0 / MAX_COUNT);
// 注意scrollBy是View的内容滚动,所以想要实现textview本身的滚动,就要使用textview的父容器来调用scrollBy方法,这样父容器的内容就是textview,就能实现textview本身的滚动啦
customView.scrollBy((int) (DISTANCE * fraction), 0);
sendAutoMessage();
}
}