初探Android Scroll——scrollTo()与scrollBy()

来源:互联网 发布:淘宝企业店铺开店资料 编辑:程序博客网 时间:2024/05/01 11:24

Scroll是android中很常见的交互方式,使用得好可以使我们的应用更具有体验性。本着学习与探究的精神,我尝试来解析一下我所知道的android scroll,这篇文章是我对scrollTo()和scrollBy()的一些理解。

1. View边界问题

在学习ScrollTo()和ScrollBy()之前,我们有必要来了解一下android的坐标系,如下图所示:
android坐标系
通常我们所看到的就是这个坐标系,也就是手机的左上角为原点(0,0)水平方向为x轴,垂直方向为y轴,整个屏幕就是个在x和y轴正方向的有限矩形区域。我们再来看下android的ui结构,如下图:
android ui 结构
可以看到,我们所能看到的手机屏幕视图就是一个ViewGroup,而ViewGroup也是继承自View。回顾一下view的绘制,可以知道,其实我们所看到的视图内容是画在画布Canvas上的,当然canvas也拥有一个坐标系,我们在通过drawXXX()方法画图时就需要知道图形的具体坐标。
view与canvas的坐标系
因为我们一般都是以我们屏幕(或者说view)为参考系的,view本身的变化,我们肉眼是看不到的,只能看到相对view的canvas的变化。所以我们通过移动canvas可以造成view内容移动的视觉。
我们来定义一个view,重写onDraw()画出一个200*200的蓝色矩形,以下是java代码:

publicclass MyViewextends View {    public MyView(Context context) {        super(context);    }    public MyView(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        Paint paint = new Paint();        paint.setColor(Color.BLUE);        paint.setStyle(Paint.Style.FILL);        canvas.drawRect(0,0,200,200,paint);    }}

呈现出来的如下(由于布局代码很简单,就不贴上来了):
自定义view1
从代码中我们可以看到这样一个过程:我们拿起笔Paint,在画布Canvas上画了一个我们想要的图形,蓝色的正方形,然后通过View看到了它。就好像一个画家画了一幅画,然后把它镶嵌到画框里,我们通过画框,看到了画里有一个人。但如果画家本来是画了一群人,只是把显示一个人的画的部分放到画框里让我们看到呢?再来看,View就是一个画框,Canvas就是画布,Paint是笔,那么我们所看到是不是视图的部分呢?接下来我们来证实这个想法。DrawRect()前4个参数是X,Y轴的起始和结束位置,我们尝试把它设为负数看看有什么效果。

canvas.drawRect(-100,0,200,200,paint);

自定义view2
显示的视图并没有什么不一样,显然“画框”View的左上角是和(0,0)重合了。Canvas中有一个translate(x,y),它起到平移画布的效果,我们将画布右移100,然后再将之前的矩形画到上面。

@Overrideprotected void onDraw(Canvas canvas) {    super.onDraw(canvas);    Paint paint = new Paint();    paint.setColor(Color.BLUE);    paint.setStyle(Paint.Style.FILL);    canvas.save();    canvas.translate(100,0);    canvas.drawRect(-100,0,200,200,paint);    canvas.restore();}

这时视图变成以下这样了:
自定义view3
显然我们画在x轴负轴的部分也被显示出来了,这就说明,画布的内容并不只是我们看到的这些,可能有很多内容是被View这个块中空的板遮住了。X,Y的二维平面式没有边界的,所以我们在画布上画的画理论上是无限大的,View只是截取了一部分让我们看到。
我看了一些有关view绘制的文章,有些作者喜欢把View说成是无界的,我觉得这种说法不准确,应该说view是有界的,而Canvas是无界的。我更喜欢View是一个中空的不透明板的说法,我们透过view无限绘图世界的一小部分。

2. 终于到ScrollTo()和ScrollBy()了

或许上面讲了一堆废话,但是尝试了解一些原理,并自己把自己的理解写下来并不是一件容易的事。 ScrollTo()和ScrollBy()是View类通过api,可以通过他们滑动view(其实是滑动view的内容)。通过字词就可以理解,ScrollTo()是滑动到目标位置,ScrollBy()是滑动所给偏移量。我们看下ScrollBy()的实现:

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

其实对ScrollTo()再次包装,而mScrollX和mScrollY是view相对canvas的偏移量,可以通过view实例方法getScrollX()和getScollY()获取他们的值。我们来测试一下ScrollTo(),布局就只添加一个TextView,部分java代码如下:

@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_test);    TextView tv = (TextView) findViewById(R.id.tv);    Log.i("TestActivity",tv.getScrollX()+","+tv.getScrollY());    tv.scrollTo(100, 0);    Log.i("TestActivity", tv.getScrollX() + "," + tv.getScrollY());}

先输出tv的初始ScrollX和ScrollY,然后滑动到(100,0)再输出当前的结果如下:
输出log
我们自定义一个view,来看使用scrollTo()的效果。MyView的java代码:

public class MyView extends View {    public MyView(Context context) {        super(context);    }    public MyView(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        canvas.drawColor(Color.YELLOW);        Paint paint = new Paint();        paint.setColor(Color.BLUE);        paint.setStyle(Paint.Style.FILL);        canvas.drawRect(-100,0,200,200,paint);    }}

布局中加入MyView控件:

<com.forzero.moveview.MyView    android:layout_width="200dp"    android:layout_height="200dp"    android:id="@+id/view2" />

Activity的java代码:

@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_test);    MyView view2 = (MyView) findViewById(R.id.view2);    view2.scrollTo(-100, 0);}

运行前的MyView视图如下:
运行前
运行后如下:
运行后
通过scrollTo(),可以把之前隐藏的蓝色部分显示出来了,这就证明scrollTo()和scrollBy()只是移动View的内容,我们想要移动view本身可以对其父容器操作,代码如下:

((View)myView.getParent()).scrollBy(-100, 0);

值得注意的是,由于偏移量是view相对canvas的,所以要达到想要的效果则需取数值的相反数。

3. 一个Demo

布局:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context=".MainActivity">    <RelativeLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_centerInParent="true">        <ImageView            android:id="@+id/view"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_centerInParent="true"            android:src="@mipmap/ic_launcher" />    </RelativeLayout>    <Button        android:id="@+id/btn1"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="x:100" />    <Button        android:id="@+id/btn2"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_toRightOf="@id/btn1"        android:text="x:-100" />    <Button        android:id="@+id/btn3"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_toRightOf="@id/btn2"        android:text="y:100" />    <Button        android:id="@+id/btn4"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_toRightOf="@id/btn3"        android:text="y:-100" /></RelativeLayout>

java代码:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{    private ImageView myView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        myView  = (ImageView) findViewById(R.id.view);        findViewById(R.id.btn1).setOnClickListener(this);        findViewById(R.id.btn2).setOnClickListener(this);        findViewById(R.id.btn3).setOnClickListener(this);        findViewById(R.id.btn4).setOnClickListener(this);    }    @Override    public void onClick(View v) {        switch (v.getId()){            case R.id.btn1:                ((View)myView.getParent()).scrollBy(-100, 0);                break;            case R.id.btn2:                ((View)myView.getParent()).scrollBy(100, 0);                break;            case R.id.btn3:                ((View)myView.getParent()).scrollBy(0, -100);                break;            case R.id.btn4:                ((View)myView.getParent()).scrollBy(0, 100);                break;        }    }}

效果截图:
效果截图

0 0
原创粉丝点击