自定义控件(29)---onTouchEvent与Scroller

转自 http://blog.csdn.net/yanzhenjie1003/article/details/53046027

我们定义一个ScrollLayout,然后继承自LinearLayout,在xml中引用,然后在ScrollLayout中放一个TextView,并让内容居中:

<?xml version="1.0" encoding="utf-8"?>
<com.yanzhenjie.defineview.widget.ScrollLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="按住我拖动试试" />
</com.yanzhenjie.defineview.widget.ScrollLayout>

布局就是这样的,根据上面的分析我们实现ScrollLayout的具体代码,请看:

// 手指最后在View中的坐标。
private int mLastX;
private int mLastY;

// 手指按下时View的相对坐标。
private int mDownViewX;
private int mDownViewY;

@Override
public boolean onTouchEvent(MotionEvent event) {
    // 第一步,记录手指在view的坐标。
    int x = (int) event.getRawX();
    int y = (int) event.getRawY();
    int action = event.getAction();
    switch (action) {
        case MotionEvent.ACTION_DOWN: {
            // 记录View相对于初始位置的滚动坐标。
            mDownViewX = getScrollX();
            mDownViewY = getScrollY();

            // 更新手指此时的坐标。
            mLastX = x;
            mLastY = y;
            return true;
        }
        case MotionEvent.ACTION_MOVE: {
            // 计算手指此时的坐标和上次的坐标滑动的距离。
            int dy = y - mLastY;
            int dx = x - mLastX;

            // 更新手指此时的坐标。
            mLastX = x;
            mLastY = y;

            // 滑动相对距离。
            scrollBy(-dx, -dy);
            return true;
        }
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL: {
            scrollTo(mDownViewX, mDownViewY);
            return true;
        }
    }
    return super.onTouchEvent(event);
}

这里写图片描述

Scroller
Scroller是手指滑动中比较重要的一个辅助类,可以辅助我们完成一些动画参数的计算等,下面把它的几个重要的方法做个简单解释。

Scroller#startScroll(int startX, int startY, int dx, int dy)

Scroller#startScroll(int startX, int startY, int dx, int dy, int duration)
这俩方法几乎是一样的,用来标记一个View想要从哪里移动到哪里。
startX,x方向从哪里开始移动。
startY,y方向从哪里开始移动。
dx,x方向移动多远。
dy,y方向移动多远。
duration,这个移动操作需要多少时间执行完,默认是250毫秒。
private Scroller mScroller;
private int mLastX;
private int mLastY;

 public ScrollLayout(Context context) {
    this(context, null, 0);
}

public ScrollLayout(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public ScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    mScroller = new Scroller(context);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    int x = (int) event.getRawX();
    int y = (int) event.getRawY();

    int action = event.getAction();
    switch (action) {
        case MotionEvent.ACTION_DOWN: {
            if (!mScroller.isFinished()) { // 如果上次的调用没有执行完就取消。
                mScroller.abortAnimation();
            }
            mLastX = x;
            mLastY = y;
            return true;
        }
        case MotionEvent.ACTION_MOVE: {
            int dy = y - mLastY;
            int dx = x - mLastX;

            mLastX = x;
            mLastY = y;

            scrollBy(-dx, -dy);
            return true;
        }
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL: {
            // XY都从滑动的距离回去,最后一个参数是多少毫秒内执行完这个动作。
            mScroller.startScroll(getScrollX(), getScrollY(), -getScrollX(), -getScrollY(), 1000);
            invalidate();
            return true;
        }
    }
    return super.onTouchEvent(event);
}

/**
 * 这个方法在调用了invalidate()后被回调。
 */
@Override
public void computeScroll() {
    if (mScroller.computeScrollOffset()) { // 计算新位置,并判断上一个滚动是否完成。
        scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
        invalidate();// 再次调用computeScroll。
    }
}

这里写图片描述


类似ViewPager的翻页效果
content_scroll_pager.xml

<?xml version="1.0" encoding="utf-8"?>
<com.safly.ui.ScrollPager xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/content_scroll_pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_blue_light"
        android:gravity="center"
        android:minHeight="200dp"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="第一页" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_green_dark"
        android:gravity="center"
        android:minHeight="200dp"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="第二页" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_orange_dark"
        android:gravity="center"
        android:minHeight="200dp"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="第三页" />

    </LinearLayout>
</com.safly.ui.ScrollPager>
/*
 * Copyright © Yan Zhenjie. All Rights Reserved
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.safly.ui;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

/**
 * Created by Yan Zhenjie on 2016/11/5.
 */
public class ScrollPager extends ViewGroup {

    private Scroller mScroller;

    // 手指每次移动时需要更新xy,记录上次手指所处的坐标。
    private float mLastX;

    public ScrollPager(Context context) {
        this(context, null, 0);
    }

    public ScrollPager(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ScrollPager(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mScroller = new Scroller(context);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getRawX();

        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                if (!mScroller.isFinished()) { // 如果上次的调用没有执行完就取消。
                    mScroller.abortAnimation();
                }
                mLastX = x;
                return true;
            case MotionEvent.ACTION_MOVE:
                int dxMove = (int) (mLastX - x);
                scrollBy(dxMove, 0);
                mLastX = x;
                return true;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL: {
                // 当手指抬起时,第几屏占的比例大就去第几屏。(这里在除的时候+view一半宽是因为滑动到0.6的时候,不到1,结果就是0
                // 其实按照惯性应该是1,所以我们给它补上一般的屏,这样相当于4设5入。)
                int sonIndex = (getScrollX() + getWidth() / 2) / getWidth();

                // 如果滑动页面超过当前页面数,那么把屏index定为最大页面数的index。
                int childCount = getChildCount();
                if (sonIndex >= childCount)
                    sonIndex = childCount - 1;

                // 现在滑动的相对距离。
                int dx = sonIndex * getWidth() - getScrollX();
                // Y方向不变,X方向到目的地。
                mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
                invalidate();
                break;
            }
        }
        return super.onTouchEvent(event);
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int childCount = getChildCount();
        // 在Layout 子view之前测量子view大小,在onLayout的时候才能调用getMeasuredWidth()和getMeasuredHeight()。
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (changed) {
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                int childW = childView.getMeasuredWidth();

                // 把所有子view放在水平方向,依次排开。
                // left:  0, w, 2w, 3w..
                // top:   0...
                // right: w, 2w, 3w...
                // topL   h...
                childView.layout(i * childW, 0, childW * i + childW, childView.getMeasuredHeight());
            }
        }
    }
}

这里写图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值