Android View 滑动基础知识

1. 什么是View

View 是Android中所有控件的基类,ViewGroup继承了View,其内部包含一组View(或ViewGroup)

2. View的位置参数

先来了解一下 top, bottom, left, right,具体如下图所示。Android的坐标系是以屏幕的左上角为原点,x 轴向右为正,y 轴向下为正。而这四个参数是以其 父容器 (注意是父容器,不一定是整个屏幕哦)为参照物的相对坐标。

View 的位置参数与父容器的关系.png

由此,可得出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本身位置并没有改变。


scrollBy(100, 0)结果.png
  • 改变布局参数

比如,宽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:


scrollByUpdateLayoutParams.png

此处测试需要注意的是,如果父容器是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,就能实现弹性滚动呢?原理大致如下:


Scoller实现弹性滑动的原理.png
  • 属性动画

属性动画实现弹性动画简直不要太简单,比如想实现两秒内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();
        }
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,635评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,628评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,971评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,986评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,006评论 6 394
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,784评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,475评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,364评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,860评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,008评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,152评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,829评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,490评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,035评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,428评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,127评论 2 356

推荐阅读更多精彩内容