Android知识进阶

View之View树View坐标系View滑动

2017-11-19  本文已影响93人  付凯强

1. View树

View树.png

2. 坐标系

Android系统有两种坐标系:Android坐标系和View坐标系。

2.1 Android坐标系

Android坐标系.png

2.2 View坐标系

View坐标系.png
width = getRight()- getLeft();
height = getBottom() - getTop();

说明:系统为我们提供了获取View的宽和高的方法。

public final int getWidth() {
        return mRight - mLeft;
    }
public final int getHeight() {
        return mBottom - mTop;
    }

通过如下方法可以获得View到父控件(ViewGroup)的距离。

  1. getTop():获取View自身顶边到父布局顶边的距离。
  2. getLeft():获取View自身左边到其父布局左边的距离。
  3. getRight():获取View自身右边到其父布局左边的距离。
  4. getBottom():获取View自身底边到其父布局顶边的距离。

View坐标系中的圆点就是我们假设就是我们触摸的点。

不管是View还是ViewGroup,最终的点击事件都会由onTouchEvent(MotionEvent event)方法来处理。

MotionEvent在用户交互中作用巨大。其内部提供了许多常量,比如ACTION_DOWN ACTION_UP ACTION_MOVE .此外,MotionEvent也提供了获取焦点坐标的各种方法。

  1. getX:获取点击事件距离控件左边的距离,即视图坐标。
  2. getY:获取点击事件距离控件顶边的距离,即视图坐标。
  3. getRawX:获取点击事件距离整个屏幕左边的距离,即绝对坐标。
  4. getRawY:获取点击事件距离整个屏幕顶边的距离,即绝对坐标。

3. View的滑动

3.1 layout()方法

/**
 * Created by FuKaiqiang on 2017-11-13.
 */

public class CustomView extends View {

    private int lastX;
    private int lastY;

    public CustomView(Context context) {
        super(context);
    }

    public CustomView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //获取手机触摸点的横坐标和纵坐标
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //记录手机按下的坐标
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                //计算移动的距离
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                //调用layout方法来重新放置它的位置
                layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);
                break;
        }
        //处理点击事件返回true
        return true;
    }
}
   <com.best.testview.CustomView
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_margin="50dp"
        android:background="@android:color/holo_red_light"/>

说明:

  1. 首先自定义CustomView继承自View,在onTouchEvent方法中获取触摸点的坐标。
  2. 然后在ACTION_MOVE事件中计算偏移量,再调用layout方法重新设置这个自定义View的位置即可。
  3. 每次移动时都会调用layout方法对屏幕重新布局,从而达到移动View的效果。
  4. 把自定义View引用到布局中即可。

3.2 offsetLeftAndRight与offsetTopAndBottom

    //计算移动的距离
    int offsetX = x - lastX;
    int offsetY = y - lastY;
    //对left和right进行偏移
    offsetLeftAndRight(offsetX);
    //对top和bottom进行偏移
    offsetTopAndBottom(offsetY);
    break;

3.3 LayoutParams(改变布局参数)

    //计算移动的距离
    int offsetX = x - lastX;
    int offsetY = y - lastY;
    LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
    layoutParams.leftMargin = getLeft() + offsetX;
    layoutParams.topMargin = getTop()+offsetY;
    setLayoutParams(layoutParams);

说明:如果父控件是LinearLayout,我们就用LinearLayout.LayoutParams。如果父控件是RelativieLayout,则要使用RelativeLayout.LayoutParams.除了使用布局的LayoutParams外,我们还可以使用ViewGroup.MarginLayoutParams来实现:

    //计算移动的距离
    int offsetX = x - lastX;
    int offsetY = y - lastY;
    ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
    layoutParams.leftMargin = getLeft() + offsetX;
    layoutParams.topMargin = getTop() + offsetY;
    setLayoutParams(layoutParams);

注意:如果父布局是ConstraintLayout,测试结果是ConstraintLayout.LayoutParams无效, ViewGroup.MarginLayoutParams无效。

3.4 动画

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android">
        <translate
            android:duration="1000"
            android:fromXDelta="0"
            android:toXDelta="300">
        </translate>
    </set>
    mCustomView.setAnimation(AnimationUtils.loadAnimation(this, R.anim.translate));

说明:我们会发现方块向右平移了300像素,然后又回到原来的位置,为了解决这个问题,我们在translate.xml中加上fillAfter = "true",方块平移后就停留在当前位置了。

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
         android:fillAfter="true">
        <translate
            android:duration="1000"
            android:fromXDelta="0"
            android:toXDelta="300">
        </translate>
    </set>

注意:当然View动画并不会改变View的位置参数,也就是说点击事件的响应还在原始位置,对系统来说,方块位置并没有改变。Android 3.0出现的属性动画就解决了上述问题,代码如下:

    ObjectAnimator.ofFloat(mCustomView, "translationX", 0, 300).setDuration(1000).start();

3.5 scrollTo与scrollBy

    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();
            }
        }
    }
    public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }

注意:scrollTo、scrollBy移动的是View的内容,如果再ViewGroup中使用,则是移动其所有的子View,我们将ACTION_MOVE代码替换为以下代码:

((View) getParent()).scrollBy(-offsetX, -offsetY);

说明:可能你会问为何设置为负值,是因为scrollBy这个方法移动的是手机的屏幕而不是View。而上面的其他方法移动的都是View。

3.6 Scroller

    public CustomView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mScroller = new Scroller(context);
    }
    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mScroller.computeScrollOffset()) {
            ((View) getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }
    public void smoothScrollTo(int destX,int destY){
        int scrollX = getScrollX();
        int delta = destX - scrollX;
        mScroller.startScroll(scrollX,0,delta,0,2000);
        invalidate();
    }
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                mCustomView.smoothScrollTo(-300, 0);
            }
        }, 3000);
上一篇 下一篇

猜你喜欢

热点阅读