在android开发中,很多地方都会遇到滑屏事件,如果不能很好的掌握android的滑屏事件,那么自己的开发能力将会很难进行提高。说实话,每次遇到滑屏事件里面的类对会很头疼,因为他们都很陌生~~~~所以自己更要迎难而上。
那么,为什么会出现这么多滑屏的事件呢?我们要明白在android view视图是没有边界的,canvas是无边界的。想想我们常用的ListView和GridView就知道了。我们平常可见的只是通过Layout布局来控制显示区域,超过了这个显示区域将不会进行显示。
又有问题了,我们怎么样把不显示的区域移动到显示的区域呢?而该布局只能显示一段特定的视图区域。从View.java类里的源码,我们会发现View类里面的scrollTo和scrollBy方法,这样就可以把不显示的区域的内容(注意不是View)坐标“滚动”到显示区域了。
既然有滚动,那么我们怎样获取它们的滚动值呢?
在View.java类源码中:
/**
* The offset, in pixels, by which the content of this view is scrolled
* horizontally.
* {@hide}
*/
@ViewDebug.ExportedProperty(category = "scrolling")
protected int mScrollX;
/**
* The offset, in pixels, by which the content of this view is scrolled
* vertically.
* {@hide}
*/
@ViewDebug.ExportedProperty(category = "scrolling")
protected int mScrollY;
/**
* Return the scrolled left position of this view. This is the left edge of
* the displayed part of your view. You do not need to draw any pixels
* farther left, since those are outside of the frame of your view on
* screen.
*
* @return The left edge of the displayed part of your view, in pixels.
*/
public final int getScrollX() {
return mScrollX;
}
/**
* Return the scrolled top position of this view. This is the top edge of
* the displayed part of your view. You do not need to draw any pixels above
* it, since those are outside of the frame of your view on screen.
*
* @return The top edge of the displayed part of your view, in pixels.
*/
public final int getScrollY() {
return mScrollY;
}
首先,我们要认识mScrollX和mScrollY两个变量:
mScrollX: 该视图内容相当于视图起始坐标的偏移量, X轴方向
mScrollY: 该视图内容相当于视图起始坐标的偏移量, Y轴方向
它们的值分别通过getScrollX() 和getScrollY()方法获得。这样我们就可以 获得它们的滚动值了。
我们又怎么移动到指定地点呢?
在View.java中,
/**
* Set the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the x position to scroll to
* @param y the y position to scroll to
*/
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();
}
}
}
从代码中,我们看出scrollTo()方法就是移动到指定的点。它首先判断这里移动的点是否和上次移动的偏移量一样,如果这次移动和上次移动的坐标是同一个,那么就没有必要进行移动了。
我们又怎么控制移动偏移量?
/**
* Move the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the amount of pixels to scroll by horizontally
* @param y the amount of pixels to scroll by vertically
*/
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
我们可以看出它其实就是调用scrollTo方法,它只是将当前视图内容偏移偏移(x , y)个单位。
注意:
了解了这些机制后,我们再来看看移动问题。我们调用scrollTo(0,10)是不是就是像下偏移呢?不是的,它其实是向上偏移。这是为什么呢?
在scrollTo(x,y)中,x和y分别被赋值给了mScrollX和mScrollY,最后调用postInvalidateOnAnimation()方法。之后这个方法就会通知View进行重绘。在onDraw方法中我们可以看到第六步源码;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
// we're done...
}
它调用了onDrawScrollBars(canvas)方法。而scrollbars就是由于scroll引起的。而onDrawScrollBara(canvas)这个方法是绘制水平和垂直方向的ScrollBar,最后都会调用invalidate(left, top, right, bottom)方法。在invalidate(left, top, right, bottom)这个方法的最后,可以看到 tmpr.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY),所以它就是反方法了。
那么我们怎么使用它们呢?
下面我们来写一个Demo来认识它(参考)。
我们先自定义一个ViewGroup的CusScrollView .java。
public class CusScrollView extends ViewGroup {
private int lastX = 0; //最后坐标
private int currX = 0; //当前坐标
private int offX = 0; //相对偏移量
public CusScrollView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
/**
* @param context
* @param attrs
*/
public CusScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
// TODO Auto-generated constructor stub
}
/**
* @param context
* @param attrs
* @param defStyle
*/
public CusScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
for(int i = 0 ; i < getChildCount();i++){
View v = getChildAt(i);
//横向布局
v.layout(0+i*getWidth(), 0, getWidth()+i*getWidth(), getHeight());
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 只考虑水平方向
lastX = (int) event.getX();
return true;
case MotionEvent.ACTION_MOVE:
// 只考虑水平方向
currX = (int) event.getX();
offX = currX - lastX;
scrollBy(-offX, 0); //相对向右滑动
break;
case MotionEvent.ACTION_UP:
scrollTo(0, 0); //回到起点
break;
}
invalidate();
// TODO Auto-generated method stub
return super.onTouchEvent(event);
}
}
然后我们在布局中使用自定义ViewGroup
activity_main.xml
<LinearLayout 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"
android:orientation="vertical" >
<com.example.studyscrollto.CusScrollView
android:id="@+id/CusScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</com.example.studyscrollto.CusScrollView>
</LinearLayout>
我们再来看MainActivity.java
public class MainActivity extends Activity {
private int[] images = { R.drawable.guide_img1, R.drawable.guide_img2, R.drawable.guide_img3,
R.drawable.guide_img4};
private CusScrollView mCusScrollView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mCusScrollView = (CusScrollView) this.findViewById(R.id.CusScrollView);
for (int i = 0; i < images.length; i++) {
ImageView mImageView = new ImageView(this);
mImageView.setScaleType(ScaleType.FIT_XY);
mImageView.setBackgroundResource(images[i]);
mImageView.setLayoutParams(new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mCusScrollView.addView(mImageView);
}
}
}
在上面的例子里,我们知道Layout的子控件就是ImageView,所以当我们滑动的时候,图片通过scrollBy会向右滑动,但是我们发现有一个问题,当我们滑动一下,非常快就回到了原点。有时候我们想他慢点滑动,那么我们怎么办呢?后面继续学习Scroller类。将会解决这个问题。
最后感谢这位博主给我学习这两个方法提供了平台。