转自 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());
}
}
}
}