ViewDragHelper

https://developer.android.com/reference/android/support/v4/widget/ViewDragHelper?hl=en

scrollBy与scrollTo的区别

scrollTo(x,y) 表示移动到一个具体的坐标点(x.y)

而scrollBy(dx,dy)表示移动的增量 为dx dy

获取偏移量后使用scrollBy来移动View,如下

int offsetX = x-lastX;

int offsetY = y - lastY;

scrollTo   scrollBy 方法移动的是View的content 即让View 的内容移动

如果在ViewGroup 中使用scrollTo scrollBy 方法,那么移动的将是所有子View

如果在View 中使用,那么移动的将是View的内容

例如 TextView content就是他的文本  ImageView content 就是它的drawable对象

如果要移动一个View可以通过它的父类,调用scrollBy方法 Parent.scrollBy(-dx,-dy)  增量要变成负值

 

package com.yifei.myapplication;

import android.content.Context;
import android.graphics.Color;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;

public class MyScrollByView extends View {
    private int lastX;
    private int lastY;


    public MyScrollByView(Context context) {
        super(context);
        initView();
    }

    public MyScrollByView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public MyScrollByView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public MyScrollByView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initView();
    }

    private void initView() {
        setBackgroundColor(Color.YELLOW);
    }

    @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;
                 ViewGroup parent = (ViewGroup) getParent();
                parent.scrollBy(-offsetX,-offsetY);
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                return false;
        }

        return true;
    }
}

 

 

Scroller  

Scroller 原理实现

它与前面使用的scrollTo 和scrollBy 方法来实现自view 跟随手指移动的原理基本类似 

虽然scrollBy方法是让子View瞬间从某点移动到另一个点,但是由于在ACTION_MOVE事件中

不断获取手指移动的微小的偏移量,这样就将一段距离分成了N个非常小的偏移量,这个原理与动画的实现原理也是基本

类似的,他们都是利用了人眼的视觉暂留特性

 

 

七 属性动画

实现一个滑动的效果

package com.yifei.myapplication;

import android.content.Context;
import android.graphics.Color;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

public class MyScrollerView extends View {
    private int lastX;
    private int lastY;
    private Scroller mScroller;

    public MyScrollerView(Context context) {
        super(context);
        initView(context);
    }

    public MyScrollerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    public MyScrollerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public MyScrollerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initView(context);
    }

    private void initView(Context context) {
        setBackgroundColor(Color.BLUE);
        //初始化Scroller
        mScroller = new Scroller(context);


    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = (int) event.getX();
                lastY = (int) event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                ((ViewGroup) getParent()).scrollBy(-offsetX, -offsetY);
                break;
            case MotionEvent.ACTION_UP://手指抬起操作
                //使用startScroll() 方法开启模拟过程
//                mScroller.startScroll();
                View viewGroup = (View) getParent();
                Log.d("MyScrollerView", "getScrollX--"+getScrollX()+"--getScrollY--"+getScrollY());
                mScroller.startScroll(
                        viewGroup.getScrollX(),
                        viewGroup.getScrollY(),
                        -viewGroup.getScrollX(),
                        -viewGroup.getScrollY());
                invalidate();//重绘
                break;
            default:
                return false;
        }
        return true;
    }

    //重写computeScroll() 实现重写模拟滑动
    @Override
    public void computeScroll() { //计算滚动的距离
        super.computeScroll();
        if (mScroller.computeScrollOffset()) {//如果scroll是在滑动 是否已经完成滑动
            //scrollTo(x,y) 表示具体移动到一个具体点的坐标点 (x,y)
            //scrollBy(dx,dy) 表示移动的增量,移动了多少
            //getCurrX(),getCurrY()通过这两个方法获得当前的滑动坐标
            ((View) getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); //获得当前点的坐标
            //通过重绘制,不断的调用computeScroll这个方法
            //执行流程,invalidate ()--->onDraw()----->invalidate()
            invalidate();

        }
    }
}

 

八 ViewDragHelper

理解ViewDragHelper

测量并且将View摆放按上去

package com.yifei.myapplication;

import android.content.Context;
import android.icu.util.Measure;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

public class MyDrawViewGroup extends ViewGroup {
    View redView;
    View blueView;

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

    public MyDrawViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyDrawViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public MyDrawViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    //初始化子View的引用 当ViewGroup 的xml结束标签被读取完成,会执行该方法
    //此时会知道自己有几个子控件
    //onFinishInflate() 方法一般用来初始化子控件
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        redView = getChildAt(0);
        blueView = getChildAt(1);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int measuerSize = MeasureSpec.makeMeasureSpec(redView.getLayoutParams().width, MeasureSpec.EXACTLY);//测量模式,为精准模式
        redView.measure(measuerSize,measuerSize);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
                redView.layout(0,0,redView.getMeasuredWidth(),redView.getMeasuredHeight());//把红色的View摆放上去
                blueView.layout(0,redView.getMeasuredHeight(),redView.getMeasuredWidth(),redView.getHeight()*2);
    }
}

把View 放在中间

package com.yifei.myapplication;

import android.content.Context;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

public class MyDrawViewGroup extends ViewGroup {
    View redView;
    View blueView;

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

    public MyDrawViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyDrawViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public MyDrawViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    //初始化子View的引用 当ViewGroup 的xml结束标签被读取完成,会执行该方法
    //此时会知道自己有几个子控件
    //onFinishInflate() 方法一般用来初始化子控件
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        redView = getChildAt(0);
        blueView = getChildAt(1);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int measuerSize = MeasureSpec.makeMeasureSpec(redView.getLayoutParams().width, MeasureSpec.EXACTLY);//测量模式,为精准模式
        redView.measure(measuerSize, measuerSize);
        blueView.measure(measuerSize, measuerSize);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int left =getWidth()/2-redView.getMeasuredWidth()/2;
        int top =0;
        redView.layout(left, top, left+redView.getMeasuredWidth(), redView.getMeasuredHeight());//把红色的View摆放上去
        blueView.layout(left, redView.getBottom(), left+redView.getMeasuredWidth(), redView.getBottom() +redView.getHeight());
    }
}

加上padding

package com.yifei.myapplication;

import android.content.Context;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

public class MyDrawViewGroup extends ViewGroup {
    View redView;
    View blueView;

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

    public MyDrawViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyDrawViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public MyDrawViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    //初始化子View的引用 当ViewGroup 的xml结束标签被读取完成,会执行该方法
    //此时会知道自己有几个子控件
    //onFinishInflate() 方法一般用来初始化子控件
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        redView = getChildAt(0);
        blueView = getChildAt(1);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int measuerSize = MeasureSpec.makeMeasureSpec(redView.getLayoutParams().width, MeasureSpec.EXACTLY);//测量模式,为精准模式
        redView.measure(measuerSize, measuerSize);
        blueView.measure(measuerSize, measuerSize);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int left =getPaddingLeft()+getWidth()/2-redView.getMeasuredWidth()/2;//把View放在中间 计算padding
        int top =0+redView.getPaddingTop();//加上padding
        redView.layout(left, top, left+redView.getMeasuredWidth(),top+ redView.getMeasuredHeight());//把红色的View摆放上去
        blueView.layout(left, redView.getBottom(), left+redView.getMeasuredWidth(), redView.getBottom() +redView.getHeight());
    }
}

另外一种测量方式

package com.yifei.myapplication;

import android.content.Context;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

public class MyDrawViewGroup extends ViewGroup {
    View redView;
    View blueView;

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

    public MyDrawViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyDrawViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public MyDrawViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    //初始化子View的引用 当ViewGroup 的xml结束标签被读取完成,会执行该方法
    //此时会知道自己有几个子控件
    //onFinishInflate() 方法一般用来初始化子控件
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        redView = getChildAt(0);
        blueView = getChildAt(1);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//没有特殊要求 可以这样做
        measureChild(redView,widthMeasureSpec,heightMeasureSpec);
        measureChild(blueView,widthMeasureSpec,heightMeasureSpec); //另外一种测量方式
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int left =getPaddingLeft()+getWidth()/2-redView.getMeasuredWidth()/2;
        int top =0+redView.getPaddingTop();
        redView.layout(left, top, left+redView.getMeasuredWidth(),top+ redView.getMeasuredHeight());//把红色的View摆放上去
        blueView.layout(left, redView.getBottom(), left+redView.getMeasuredWidth(), redView.getBottom() +redView.getHeight());
    }
}