类ViewPager的Demo控件,涉及
- Measure过程与MeasureSpec
- Layout过程
- 滑动与滑动冲突
- 弹性滑动Scroller
package com.chavinchen.demo;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;
public class HorizontalPager extends ViewGroup {
private Scroller mScroller;
private VelocityTracker mVelocityTracker;
private float mDistanceThreshold = 0.5f;
private float mVelocityThreshold = 50f;
private int mLastInterceptX;
private int mLastInterceptY;
private int mLastX;
private int mPageWidth;
private int mCurrentIndex;
public HorizontalPager(Context context) {
super(context);
init(context, null);
}
public HorizontalPager(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public HorizontalPager(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
public HorizontalPager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int wMode = MeasureSpec.getMode(widthMeasureSpec);
int hMode = MeasureSpec.getMode(heightMeasureSpec);
int wSize = MeasureSpec.getSize(widthMeasureSpec);
int hSize = MeasureSpec.getSize(heightMeasureSpec);
// 如果宽度wrap_content, 则一页宽度为屏幕宽
if (MeasureSpec.AT_MOST == wMode) {
Resources resources = this.getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
mPageWidth = dm.widthPixels;
} else {
mPageWidth = wSize;
}
// 先测量子元素,剩余空间指定为一页大小
measureChildren(MeasureSpec.makeMeasureSpec(mPageWidth, wMode), heightMeasureSpec);
// 处理wrap_content, 高取子元素最大值
int maxH = 0;
for (int i = 0; i < getChildCount(); i++) {
if (getChildAt(i).getVisibility() == View.GONE) {
continue;
}
maxH = Math.max(maxH, getChildAt(i).getMeasuredHeight());
}
if (MeasureSpec.AT_MOST == wMode && MeasureSpec.AT_MOST == hMode) {
setMeasuredDimension(mPageWidth * getChildCount(), maxH);
} else if (MeasureSpec.AT_MOST == wMode) {
setMeasuredDimension(mPageWidth * getChildCount(), hSize);
} else if (MeasureSpec.AT_MOST == hMode) {
setMeasuredDimension(wSize * getChildCount(), maxH);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
View view;
int left = getPaddingLeft();
for (int i = 0; i < getChildCount(); i++) {
view = getChildAt(i);
if (view.getVisibility() == View.GONE) {
continue;
}
// 水平排列
view.layout(left,
getPaddingTop(),
left + mPageWidth,
getPaddingTop() + view.getMeasuredHeight());
left += mPageWidth;
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
mVelocityTracker.addMovement(event);
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
// 反向移动画布
scrollBy(-deltaX, 0);
break;
case MotionEvent.ACTION_UP:
int dis = getScrollX() - mPageWidth * mCurrentIndex;
if (Math.abs(dis) >= mPageWidth * mDistanceThreshold) {
// 超过距离阈值,则翻页
if (dis > 0) { // 正向距离,下一页
mCurrentIndex = mCurrentIndex + 1 == getChildCount()
? getChildCount() - 1 : mCurrentIndex + 1;
} else {
mCurrentIndex = mCurrentIndex - 1 < 0 ? 0 : mCurrentIndex - 1;
}
} else {
mVelocityTracker.computeCurrentVelocity(1000,
mVelocityThreshold + 1);
float xVelocity = mVelocityTracker.getXVelocity();
if (Math.abs(xVelocity) >= mVelocityThreshold) {
if (xVelocity > 0) { // 正向速度,上一页
mCurrentIndex = mCurrentIndex - 1 < 0 ? 0 : mCurrentIndex - 1;
} else {
mCurrentIndex = mCurrentIndex + 1 == getChildCount()
? getChildCount() - 1 : mCurrentIndex + 1;
}
}
}
smoothScrollTo(mCurrentIndex * mPageWidth, 0);
mVelocityTracker.clear();
break;
}
mLastX = x;
return true;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercept = false;
int x = (int) getX();
int y = (int) getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// 未滚动完成,再触碰终止滚动
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastInterceptX;
int deltaY = y - mLastInterceptY;
// 水平滑动则拦截
if (Math.abs(deltaX) - Math.abs(deltaY) > 0) {
intercept = true;
}
break;
case MotionEvent.ACTION_UP:
break;
}
// ACTION_DOWN不拦截,不走onTouchEvent先更新事件坐标
mLastX = x;
mLastInterceptX = x;
mLastInterceptY = y;
return intercept;
}
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate(); // 再刷新重绘
}
}
private void init(Context context, AttributeSet attrs) {
mScroller = new Scroller(getContext());
mVelocityTracker = VelocityTracker.obtain();
if (null == attrs) {
return;
}
// 取自定义属性
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.HorizontalPager);
mDistanceThreshold = array.getFloat(R.styleable.HorizontalPager_distance,
mDistanceThreshold);
mVelocityThreshold = array.getFloat(R.styleable.HorizontalPager_velocity,
mVelocityThreshold);
array.recycle();
}
private void smoothScrollTo(int destX, int destY) {
// 开始计算
mScroller.startScroll(getScrollX(), getScrollY(),
destX - getScrollX(), destY - getScrollY());
// 刷新重绘 -> computeScroll
invalidate();
}
}
自定义属性 res/values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="HorizontalPager">
<attr name="distance" format="float" />
<attr name="velocity" format="float" />
</declare-styleable>
</resources>
以上,备以查询。