Android 从 0 开始自定义控件之 View 的滑动(二)

2,555 阅读12分钟

转载请标明出处: blog.csdn.net/airsaid/art…
本文出自:周游的博客

前言

由于Android的屏幕局限性,为了展示更多的东西,实现一个可以滑动的View还是非常重要的。所以这篇文章主要记下View的几种常见滑动方式:

  • 通过View的ScrollBy和ScrollTo方法实现滑动。
  • 通过动画给View施加位移效果来实现滑动。
  • 通过改变View的LayoutParams使View重新布局从而实现滑动。

scrollBy()&scrollTo()

Android专门提供了scrollBy()和scrollTo()方法来实现View的滑动,而我们通过查看scrollBy()方法的源码可以发现,其方法内部其实同样调用的scrolllTo()方法:

public void scrollBy(int x, int y) {
    scrollTo(mScrollX + x, mScrollY + y);
}

那么两个方法之间又有什么区别和联系呢?一句话可以理解为:

  • scrollBy()方法是基于当前位置的相对滑动。
  • scrollTo()方法是基于所传递参数的绝对滑动。

通过查看源码我们知道,scrollBy()方法内部其实就是调用了scrollTo()方法,只不过参数中多加了mScrollX和mScrollY这两个变量,那么这2个变量究竟是什么呢?
mScrollX和mScrollY我们可以理解为View的偏移量,初始值都为0。当View发生移动时,比如说,View往左横向移动了100px,那么这时,mScrollX的值则为100。相反,如果是往右移动100px,那么此时值为-100。mScrollY则是如果往上垂直移动100px,mScrollY的值则为100,否则为-100。具体如下图所示:

这里写图片描述
(图片摘自:github.com/Idtk/Blog/b…

下面来写个Demo,演示下两者的区别和使用:

  • 第一步:布局中定义好Layout,并且包裹好两个button


    

    

  • 第二步:代码中设置点击事件
public class MainActivity extends AppCompatActivity {

    private LinearLayout mLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mLayout = (LinearLayout) findViewById(R.id.layout);
        findViewById(R.id.btn_scrollto).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("test", "点击前getScrollX值:" + mLayout.getScrollX());
                Log.e("test", "点击前getScrollY值:" + mLayout.getScrollY());
                mLayout.scrollTo(-50, -50);
                Log.e("test", "点击后getScrollX值:" + mLayout.getScrollX());
                Log.e("test", "点击后getScrollY值:" + mLayout.getScrollY());
            }
        });

        findViewById(R.id.btn_scrollby).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mLayout.scrollBy(-50, -50);
            }
        });
    }
}

运行结果:
这里写图片描述
打印结果(只取了第一次点击的结果):

11-10 14:15:37.447 23408-23408/com.airsaid.viewdemo E/test: 点击前getScrollX值:0
11-10 14:15:37.447 23408-23408/com.airsaid.viewdemo E/test: 点击前getScrollY值:0
11-10 14:15:37.447 23408-23408/com.airsaid.viewdemo E/test: 点击后getScrollX值:-50
11-10 14:15:37.447 23408-23408/com.airsaid.viewdemo E/test: 点击后getScrollY值:-50

通过上面的实践可以得知:
当我们执行scrollTo()方法,Layout中的View,只会移动到scrollTo()方法参数指定的位置上。而scrollBy()方法则是加上了scrollX和scrollY的值,所以每点击一次,都会动态根据当前位置移动。
scrollX和scrollY的值,通过上面的打印结果也能理解的很清楚,这里就不废话了,具体自己操作一遍就明白了。

需要注意的是:
通过上面的代码,我们可以看到,使用scrollBy()和scrollTo()方法能方便的滑动效果并且不影响内部元素的点击事件,但是只能滑动View的内容,而不能滑动View的本身
所以调用scrollBy()和scrollTo()方法时,是用Layout去调用的,这一点需要尤其理解清楚。

通过scrollTo() 、scrollBy()实现滑动的方法到说到这里了。下面来说说另一种滑动实现:动画。

使用动画

通过动画来实现View的滑动,主要是操作View的translationX和translationY这两个属性。我们既可以使用补间动画也可以使用属性动画。下面分别实现下用补间和属性两种动画实现将Button往右移动100px:

  • 第一种:补间动画
TranslateAnimation anim = new TranslateAnimation(0, 100, 0, 0);
anim.setDuration(300);
anim.start();
btn.setAnimation(anim);
  • 第二种:属性动画
ObjectAnimator anim = ObjectAnimator.ofFloat(btn, "translationX", 0, 100);
anim.setDuration(300);
anim.start();

使用动画移动View时,需要注意的是View动画是对View的影像做操作的。移动后其实并不能真正改变View的位置参数,包括宽/高。并且使用补间动画时,View并不能响应移动后位置的事件,这一点在以前关于动画的博客上有说明,这里就不详细细说了,有兴趣的可以看看以前关于动画的文章:补间动画属性动画

改变布局参数

通过改变View的布局的参数实现View的滑动,其用到的类是:LayoutParams。这个类相当于一个Layout的信息包,里面封装了关于Layout的宽、高等信息。如果我们想将一个View往右移动,那么就可以不断的增加View的leftMargin:

ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
lp.leftMargin += 100;
view.setLayoutParams(lp);

通过这种方式实现的View的移动,View移动后的位置就是其真实的位置。

实例

随手指滑动的ImageView

下面这个实例,自定义了一个View继承自ImageView,该ImageView可以跟随手指的触摸移动。同时在布局中还定义了一个Button,点击该Button修改ImageView的背景。
View:


/**
 * 作者:周游
 * 时间:2016/11/12
 * 博客:http://blog.csdn.net/airsaid
 */
public class MoveView extends ImageView{

    public MoveView(Context context, AttributeSet attrs) {
        super(context, attrs);

    }

    int lastX = 0;
    int lastY = 0;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        int action = event.getAction();
        switch (action){
            case MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                  layout(getLeft() + offsetX, getTop() + offsetY,
                        getRight() + offsetX , getBottom() + offsetY);
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return true;
    }
}

布局:



点击事件:

public void set(View v) {
    mMoveView.setBackgroundColor(Color.RED);
}

运行结果:
这里写图片描述
通过运行结果我们可以看到,该ImageView可以跟随手指的移动,但是当我们重新设置ImageView的背景后,该ImageView立马又回到原来位置了?
出现这个情况,其实就是因为View虽然移动了,但是其位置属性依然还在原来的,这时候如果想要在更改ImageView背景后,View依然在原地,那么就可以使用上面第三种方法实现View的移动了。
我们将ACTION_MOVE中移动View的代码修改为:

case MotionEvent.ACTION_MOVE:
    int offsetX = x - lastX;
    int offsetY = y - lastY;
 /*  
    layout(getLeft() + offsetX, getTop() + offsetY,
            getRight() + offsetX , getBottom() + offsetY);*/

    ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams();
    lp.leftMargin = getLeft() + offsetX;
    lp.topMargin = getTop() + offsetY;
    setLayoutParams(lp);
    break;

运行结果:
这里写图片描述

总结

上面介绍了三种常见的View移动方式,这三种都可以实现View的移动。其中:

  • scrollTo()/scrollBy():操作简单,但是只能够对View内容进行移动。
  • 动画:使用简单,主要用于没有交互的View和实现复杂的动画效果。
  • 改变布局参数:操作稍复杂,适用于有交互的View。
    在具体的使用中,可以根据具体使用情况选择适合的方式进行View的移动。

参考

1,《Android开发艺术探索》
2, github.com/Idtk/Blog/b…

转载请标明出处: blog.csdn.net/airsaid/art…
本文出自:周游的博客

前言

由于Android的屏幕局限性,为了展示更多的东西,实现一个可以滑动的View还是非常重要的。所以这篇文章主要记下View的几种常见滑动方式:

  • 通过View的ScrollBy和ScrollTo方法实现滑动。
  • 通过动画给View施加位移效果来实现滑动。
  • 通过改变View的LayoutParams使View重新布局从而实现滑动。

scrollBy()&scrollTo()

Android专门提供了scrollBy()和scrollTo()方法来实现View的滑动,而我们通过查看scrollBy()方法的源码可以发现,其方法内部其实同样调用的scrolllTo()方法:

public void scrollBy(int x, int y) {
    scrollTo(mScrollX + x, mScrollY + y);
}

那么两个方法之间又有什么区别和联系呢?一句话可以理解为:

  • scrollBy()方法是基于当前位置的相对滑动。
  • scrollTo()方法是基于所传递参数的绝对滑动。

通过查看源码我们知道,scrollBy()方法内部其实就是调用了scrollTo()方法,只不过参数中多加了mScrollX和mScrollY这两个变量,那么这2个变量究竟是什么呢?
mScrollX和mScrollY我们可以理解为View的偏移量,初始值都为0。当View发生移动时,比如说,View往左横向移动了100px,那么这时,mScrollX的值则为100。相反,如果是往右移动100px,那么此时值为-100。mScrollY则是如果往上垂直移动100px,mScrollY的值则为100,否则为-100。具体如下图所示:

这里写图片描述
(图片摘自:github.com/Idtk/Blog/b…

下面来写个Demo,演示下两者的区别和使用:

  • 第一步:布局中定义好Layout,并且包裹好两个button


    

    

  • 第二步:代码中设置点击事件
public class MainActivity extends AppCompatActivity {

    private LinearLayout mLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mLayout = (LinearLayout) findViewById(R.id.layout);
        findViewById(R.id.btn_scrollto).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("test", "点击前getScrollX值:" + mLayout.getScrollX());
                Log.e("test", "点击前getScrollY值:" + mLayout.getScrollY());
                mLayout.scrollTo(-50, -50);
                Log.e("test", "点击后getScrollX值:" + mLayout.getScrollX());
                Log.e("test", "点击后getScrollY值:" + mLayout.getScrollY());
            }
        });

        findViewById(R.id.btn_scrollby).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mLayout.scrollBy(-50, -50);
            }
        });
    }
}

运行结果:
这里写图片描述
打印结果(只取了第一次点击的结果):

11-10 14:15:37.447 23408-23408/com.airsaid.viewdemo E/test: 点击前getScrollX值:0
11-10 14:15:37.447 23408-23408/com.airsaid.viewdemo E/test: 点击前getScrollY值:0
11-10 14:15:37.447 23408-23408/com.airsaid.viewdemo E/test: 点击后getScrollX值:-50
11-10 14:15:37.447 23408-23408/com.airsaid.viewdemo E/test: 点击后getScrollY值:-50

通过上面的实践可以得知:
当我们执行scrollTo()方法,Layout中的View,只会移动到scrollTo()方法参数指定的位置上。而scrollBy()方法则是加上了scrollX和scrollY的值,所以每点击一次,都会动态根据当前位置移动。
scrollX和scrollY的值,通过上面的打印结果也能理解的很清楚,这里就不废话了,具体自己操作一遍就明白了。

需要注意的是:
通过上面的代码,我们可以看到,使用scrollBy()和scrollTo()方法能方便的滑动效果并且不影响内部元素的点击事件,但是只能滑动View的内容,而不能滑动View的本身
所以调用scrollBy()和scrollTo()方法时,是用Layout去调用的,这一点需要尤其理解清楚。

通过scrollTo() 、scrollBy()实现滑动的方法到说到这里了。下面来说说另一种滑动实现:动画。

使用动画

通过动画来实现View的滑动,主要是操作View的translationX和translationY这两个属性。我们既可以使用补间动画也可以使用属性动画。下面分别实现下用补间和属性两种动画实现将Button往右移动100px:

  • 第一种:补间动画
TranslateAnimation anim = new TranslateAnimation(0, 100, 0, 0);
anim.setDuration(300);
anim.start();
btn.setAnimation(anim);
  • 第二种:属性动画
ObjectAnimator anim = ObjectAnimator.ofFloat(btn, "translationX", 0, 100);
anim.setDuration(300);
anim.start();

使用动画移动View时,需要注意的是View动画是对View的影像做操作的。移动后其实并不能真正改变View的位置参数,包括宽/高。并且使用补间动画时,View并不能响应移动后位置的事件,这一点在以前关于动画的博客上有说明,这里就不详细细说了,有兴趣的可以看看以前关于动画的文章:补间动画属性动画

改变布局参数

通过改变View的布局的参数实现View的滑动,其用到的类是:LayoutParams。这个类相当于一个Layout的信息包,里面封装了关于Layout的宽、高等信息。如果我们想将一个View往右移动,那么就可以不断的增加View的leftMargin:

ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
lp.leftMargin += 100;
view.setLayoutParams(lp);

通过这种方式实现的View的移动,View移动后的位置就是其真实的位置。

实例

随手指滑动的ImageView

下面这个实例,自定义了一个View继承自ImageView,该ImageView可以跟随手指的触摸移动。同时在布局中还定义了一个Button,点击该Button修改ImageView的背景。
View:


/**
 * 作者:周游
 * 时间:2016/11/12
 * 博客:http://blog.csdn.net/airsaid
 */
public class MoveView extends ImageView{

    public MoveView(Context context, AttributeSet attrs) {
        super(context, attrs);

    }

    int lastX = 0;
    int lastY = 0;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        int action = event.getAction();
        switch (action){
            case MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                  layout(getLeft() + offsetX, getTop() + offsetY,
                        getRight() + offsetX , getBottom() + offsetY);
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return true;
    }
}

布局:



点击事件:

public void set(View v) {
    mMoveView.setBackgroundColor(Color.RED);
}

运行结果:
这里写图片描述
通过运行结果我们可以看到,该ImageView可以跟随手指的移动,但是当我们重新设置ImageView的背景后,该ImageView立马又回到原来位置了?
出现这个情况,其实就是因为View虽然移动了,但是其位置属性依然还在原来的,这时候如果想要在更改ImageView背景后,View依然在原地,那么就可以使用上面第三种方法实现View的移动了。
我们将ACTION_MOVE中移动View的代码修改为:

case MotionEvent.ACTION_MOVE:
    int offsetX = x - lastX;
    int offsetY = y - lastY;
 /*  
    layout(getLeft() + offsetX, getTop() + offsetY,
            getRight() + offsetX , getBottom() + offsetY);*/

    ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams();
    lp.leftMargin = getLeft() + offsetX;
    lp.topMargin = getTop() + offsetY;
    setLayoutParams(lp);
    break;

运行结果:
这里写图片描述

总结

上面介绍了三种常见的View移动方式,这三种都可以实现View的移动。其中:

  • scrollTo()/scrollBy():操作简单,但是只能够对View内容进行移动。
  • 动画:使用简单,主要用于没有交互的View和实现复杂的动画效果。
  • 改变布局参数:操作稍复杂,适用于有交互的View。
    在具体的使用中,可以根据具体使用情况选择适合的方式进行View的移动。

参考

1,《Android开发艺术探索》
2, github.com/Idtk/Blog/b…