自定义View-平滑滚动

来源:互联网 发布:php 制作扇形统计图 编辑:程序博客网 时间:2024/04/29 20:59

TouchSlidingScreen:

public class TouchSlidingScreen extends ViewGroup {    //平滑滚动中要用到Scroller    private Scroller scroller;    //最小滑动距离,超过了才认为开始滑动    private int touchSlop = 0;    //停止状态    private static final int TOUCH_STATE_STOP = 0;    //滑动状态    private static final int TOUCH_STATE_FLING = 1;    private int touchState = TOUCH_STATE_STOP;    ///上次触摸屏的 x 位置    private float lastX = 0;    //当前位置    private int curScreen;    //VelocityTracker 主要用于跟踪触摸屏事件(flinging 事件和其他 gestures 手势事件)的速率    private VelocityTracker velocityTracker;    public static int SNAP_VELOCITY = 600;  //最小的滑动速率    public TouchSlidingScreen(Context context) {        this(context, null);    }    public TouchSlidingScreen(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public TouchSlidingScreen(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init(context);    }    public void init(Context context) {        scroller = new Scroller(context);        //获取到当前手机上默认的最小滑动距离        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        //测量子View        measureChildren(widthMeasureSpec, heightMeasureSpec);        int width = measureWidth(widthMeasureSpec);        int height = measureHeight(heightMeasureSpec);        //保存测量自己的宽高        setMeasuredDimension(width, height);    }    //子View宽必须是match_parent,容器总的宽是一屏*子View的个数    private int measureWidth(int widthMeasureSpec) {        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int width = 0;        //ViewGroup不能是包裹        if (widthMode == MeasureSpec.AT_MOST) {            throw new IllegalStateException("the width must is match_parent!");        } else {            width = widthSize;        }        return width * getChildCount();    }    //容易的高必须是match_parent    private int measureHeight(int heightMeasureSpec) {        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        int height = 0;        if (heightMode == MeasureSpec.AT_MOST) {            throw new IllegalStateException("the height must is match_parent!");        } else {            height = heightSize;        }        return height;    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        int count = getChildCount();        int childWidth = (r - l) / count;//一屏的宽度        int childHeight = b - t;//一屏的高度        for (int i = 0; i < count; i++) {            int left = i * childWidth;            int top = 0;            int right = (i + 1) * childWidth;            int bottom = childHeight;            View childView = getChildAt(i);            //定位子View            childView.layout(left, top, right, bottom);        }    }    //事件拦截    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        int action = ev.getAction();        int x = (int) ev.getX();        int y = (int) ev.getY();        if (action == MotionEvent.ACTION_MOVE && touchState == TOUCH_STATE_STOP) {//滑动并且屏幕中的状态是停止时            return true;        }        switch (action) {            case MotionEvent.ACTION_DOWN:                lastX = x;                touchState = scroller.isFinished() ? TOUCH_STATE_STOP : TOUCH_STATE_FLING;                break;            case MotionEvent.ACTION_HOVER_MOVE:                //滑动距离过小不算滑动                int dx = (int) Math.abs(x - lastX);                if (dx > touchSlop) {                    touchState = TOUCH_STATE_FLING;                }                break;            case MotionEvent.ACTION_CANCEL:            case MotionEvent.ACTION_UP:                touchState = TOUCH_STATE_STOP;                break;        }        return touchState != TOUCH_STATE_STOP;//滑动时返回true拦截,不交给子View    }    /**     * @param event     * @return     */    public boolean onTouchEvent(MotionEvent event) {        if (velocityTracker == null) {            //使用 obtain()方法得到这个类的实例            velocityTracker = VelocityTracker.obtain();        }        //addMovement(MotionEvent)函数将你接受到的 motionevent 加入到 VelocityTracker 类实例中        velocityTracker.addMovement(event);        super.onTouchEvent(event);        int action = event.getAction();        final int x = (int) event.getX();        switch (action) {            case MotionEvent.ACTION_DOWN:                //手指按下时,如果正在滚动,则立刻停止                if (scroller != null && !scroller.isFinished()) {                    scroller.abortAnimation();                }                lastX = x;                break;            case MotionEvent.ACTION_MOVE:                //随手指滚动                int dx = (int) (lastX - x);                scrollBy(dx, 0);                lastX = x;                break;            case MotionEvent.ACTION_UP:                final VelocityTracker velocityTracker = this.velocityTracker;                //使用 computeCurrentVelocity(int)初始化速率的单位                velocityTracker.computeCurrentVelocity(1000);                //获得x方向的速率                int velocityX = (int) velocityTracker.getXVelocity();                //通过 velocityX 的正负值可以判断滑动方向                if (velocityX > SNAP_VELOCITY && curScreen > 0) {                    moveToPrevious();                } else if (velocityX < -SNAP_VELOCITY && curScreen < (getChildCount() - 1)) {                    moveToNext();                } else {                    moveToDestination();                }                if (velocityTracker != null) {//释放并回收资源                    this.velocityTracker.clear();                    this.velocityTracker.recycle();                    this.velocityTracker = null;                }                touchState = TOUCH_STATE_STOP;                break;            case MotionEvent.ACTION_CANCEL:                touchState = TOUCH_STATE_STOP;                break;        }        return true;    }    public void moveToScreen(int whichScreen) {        curScreen = whichScreen;        int scrollX = getScrollX();        int splitWidth = getWidth() / getChildCount();        int dx = curScreen * splitWidth - scrollX;        scroller.startScroll(scrollX, 0, dx, 0, Math.abs(dx));        invalidate();    }    @Override    public void computeScroll() {        super.computeScroll();        if (scroller.computeScrollOffset()) {            scrollTo(scroller.getCurrX(), scroller.getCurrY());            postInvalidate();        }    }        public void moveToDestination() {        //每一屏的宽度        int splitWidth = getWidth() / getChildCount();        //判断是回滚还是进入下一分屏        int toScreen = (getScrollX() + splitWidth / 2) / splitWidth;        //移动到目标分屏        moveToScreen(toScreen);    }    /**     * 滚动到下一屏     */    public void moveToNext() {        if (curScreen >= getChildCount() - 1) {            Toast.makeText(getContext(), "当前已经是最后一页", Toast.LENGTH_SHORT).show();            return;        }        moveToScreen(curScreen + 1);    }    /**     * 滚动到上一屏     */    public void moveToPrevious() {        if (curScreen <= 0) {            Toast.makeText(getContext(), "当前已经是第一页", Toast.LENGTH_SHORT).show();            return;        }        moveToScreen(curScreen - 1);    }}

activity_main:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <com.example.user.myapplication4.TouchSlidingScreen        android:id="@+id/ml"        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="1">        <LinearLayout            android:layout_width="0dp"            android:layout_height="0dp"            android:background="#FFFF00"            android:orientation="vertical"></LinearLayout>        <LinearLayout            android:layout_width="0dp"            android:layout_height="0dp"            android:background="#00FF00"            android:orientation="vertical"></LinearLayout>        <LinearLayout            android:layout_width="0dp"            android:layout_height="0dp"            android:background="#0000FF"            android:orientation="vertical"></LinearLayout>        <LinearLayout            android:layout_width="0dp"            android:layout_height="0dp"            android:background="#00FFFF"            android:orientation="vertical"></LinearLayout>    </com.example.user.myapplication4.TouchSlidingScreen>    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:orientation="horizontal">        <Button            android:id="@+id/pre"            android:layout_width="0dp"            android:layout_height="wrap_content"            android:layout_weight="1"            android:text="上一屏" />        <Button            android:id="@+id/next"            android:layout_width="0dp"            android:layout_height="wrap_content"            android:layout_weight="1"            android:background="@android:color/holo_green_light"            android:text="下一屏" />    </LinearLayout></LinearLayout>

MainActivity:

public class MainActivity extends Activity {    private TouchSlidingScreen ml;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        ml = (TouchSlidingScreen) findViewById(R.id.ml);        Button pre = (Button) findViewById(R.id.pre);        Button next = (Button) findViewById(R.id.next);        pre.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                ml.moveToPrevious();            }        });        next.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                ml.moveToNext();            }        });        Timer timer = new Timer();        timer.schedule(new TimerTask() {            @Override            public void run() {               handler.sendEmptyMessage(0);            }        },2000,3000);    }    Handler handler = new Handler(){        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);            switch (msg.what){                case 0:                    ml.moveToNext();                    break;            }        }    };}


第一步:初始化。平滑滚动需要使用 Scroller 对象,另外还需要给定一个最小滑动距离,通过 ViewConfiguration.get(context).getScaledTouchSlop()可以获取到当前手机上默认的最小滑动距离。

第二步:测量容器宽度与高度。不允许使用 MeasureSpec.AT_MOST,每个子组件与容器相同,容器的 layout_width 值虽然为 MeasureSpec.EXACTLY,但容器大小 =父容器的宽度 *子组件的个数,高度与父容器相同。

第三步:定位子组件。默认情况下,屏幕出现第一个子组件,子组件占满容器的可见区域,其他子组件以相同大小依次排列在后面。

第四步:判断滚动状态,状态为分两种:停止状态和滑动状态。容器根据状态决定是否截拦事件。

第五步:惯性滚屏:

手指滑动距离如果超过容器一半或者滑动速度足够快,则进入下一屏(或者上一屏)。如果没有超过一半或速度很慢则回滚到初始位置。定义 moveToDestination()方法如下,最关键的语句是 inttoScreen=(getScrollX()+splitWidth/2 ) / splitWidth,getScrollX()是容器滚动过的距离,splitWidth 是每一屏的宽度。比如每一屏的宽度为 10,当前屏为第 2 屏,容器已滚过 23,则 toScreen=(23 +10/ 2) / 10=(23 + 5) / 10 = 28/10 = 2.8= 2,也就是说要回滚到第 2 屏;如果容器已滚动28,则 toScreen=(28 + 10 /2)/ 10 = 32/10 = 3.2 =3,表示要滚动到第 3 屏。

第六步:响应用户手指的按下、移动和松开事件,这是整个滑动的关键,特别是松开后,要判断滚屏还是回滚。为了支持上一屏和下一屏,需要辨别手指滑动的方向,VelocityTracker 类可以获取 x 方向的速率,其正值代表向左滑动,负值代表向右滑动。如果 x 方向的速率在[-SNAP_VELOCITY,SNAP_VELOCITY]之间,则要根据用户滑动的距离(滑动距离是否超过一屏的1/2)决定是要继续滚屏还是回滚到初始状态。

VelocityTracker :
addMovement(MotionEvent)函数将你接受到的 motion event 加入到 VelocityTracker 类实例中。当我们需要使用到速率时,使用 computeCurrentVelocity(int)初始化速率的单位,并获得当前的事件的速率,然后使用 getXVelocity()或 getXVelocity()获得横向和竖向的速率。另外,通过VelocityTracker 还可以知道手指的滑动方向。

VelocityTracker 的基本使用如下:
手指按下时(ACTION_DOWN),获取 VelocityTracker 对象
if(velocityTracker==null){
//创建 velocityTracker 对象
velocityTracker=VelocityTracker.obtain();
}
//关联事件对象
velocityTracker.addMovement(ev);
手指移动过程中(ACTION_MOVE),计算速率
velocityTracker.computeCurrentVelocity(1000);
获取 x、y 两个方向的速率:
int velocityX= velocityTracker.getXVelocity();
int velocityY =velocityTracker.getYVelocity();
手指松开后(ACTION_UP),释放并回收资源
//释放 VelocityTracker 资源
if(velocityTracker !=null){
velocityTracker.clear();
velocityTracker.recycle();
velocityTracker=null;
}

0 0
原创粉丝点击