public class XPagedView extends ViewGroup {
private static final String TAG = "XPagedView";
/**
* 阻尼系数,越小阻尼越大
*/
private static final float DAMPING_COEFFICIENT = 0.3f;
/**
* 页面自动切换延迟时间
*/
private static final int SCROLL_DURATION = 250;
private Context mContext;
private VelocityTracker mVelocityTracker;
/**
* 触发自动翻页的的最小速度
*/
private int mTriggerTrunPageVelocity;
/**
* 最大最小fling速度
*/
private int mMaxFlingVelocity, mMinFlingVelocity;
private int mLastDownX, mLastDownY;
/**
* 插入的间距
*/
private Rect mInsert = new Rect();
/**
* 当前是否处于左右拖拽滑动中
*/
private boolean mIsBeingDragging;
/**
* 是否允许阻尼效果
*/
private boolean mIsAllowDamping = true;
/**
* 上一次的方向 1表示向右 | -1表示向左
*/
private int mLastDirection = 0;
/**
* 代表了页数索引,[0, childCount), 暂时没用
*/
private int[] mPages;
/**
* 包含的子View集合
*/
private List<View> mChilds;
/**
* 当前页码
*/
private int mCurrentPage;
/**
* 第一次触摸的X坐标
*/
private int mStartX;
public XPagedView(Context context) {
super(context);
init(context);
}
public XPagedView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public XPagedView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mContext = context;
mChilds = new ArrayList<>();
// 默认初始化的时候是第一页
mCurrentPage = 0;
// 设置边距
mInsert.set(0, 0, 0, 0);
mTriggerTrunPageVelocity = (int) context.getResources().getDisplayMetrics().density * 500;
ViewConfiguration configuration = ViewConfiguration.get(mContext);
mMaxFlingVelocity = configuration.getScaledMaximumFlingVelocity();
mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity();
mScroller = new Scroller(mContext);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int childCount = getChildCount();
if (childCount == 0) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
if (widthSize < 0 || heightSize < 0) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
int myWidthSpec = MeasureSpec.makeMeasureSpec(widthSize - mInsert.left - mInsert.right,
MeasureSpec.EXACTLY);
int myHeightSpec = MeasureSpec.makeMeasureSpec(heightSize - mInsert.top - mInsert.bottom,
MeasureSpec.EXACTLY);
// 暂时用VG的大小
measureChildren(myWidthSpec, myHeightSpec);
setMeasuredDimension(widthSize, heightSize);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
if (childCount == 0) {
return;
}
if (mPages == null || childCount != mPages.length) {
mPages = new int[childCount];
}
int left = 0;
for (int index = 0; index < childCount; index++) {
View child = getChildAt(index);
if (child.getVisibility() != View.GONE) {
mChilds.add(child);
int childMeasuredWidth = child.getMeasuredWidth();
child.layout(left, t, left + childMeasuredWidth, b);
left += childMeasuredWidth;
}
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if (mIsBeingDragging && action == MotionEvent.ACTION_MOVE) return true;
mIsBeingDragging = Math.abs(ev.getX() - mLastDownX) > Math.abs(ev.getY() - mLastDownY);
boolean isScrollOnAxis_X =
Math.abs(ev.getX() - mLastDownX) > Math.abs(ev.getY() - mLastDownY);
if (action == MotionEvent.ACTION_MOVE && isScrollOnAxis_X) {
mIsBeingDragging = true;
}
acquireVelocityTrackerAndAddMovement(ev);
switch (action) {
case MotionEvent.ACTION_DOWN:
mLastDownX = (int) ev.getX();
mLastDownY = (int) ev.getY();
mStartX = mLastDownX;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
releaseVelocityTracker();
mIsBeingDragging = false;
break;
}
return mIsBeingDragging;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
acquireVelocityTrackerAndAddMovement(event);
switch (action) {
case MotionEvent.ACTION_MOVE:
int x = (int) event.getX();
int y = (int) event.getY();
int deltaX = x - mLastDownX;
int deltaY = y - mLastDownY;
if (mLastDirection == 0) {
mLastDirection = deltaX;
}
// 滑动过程中滑动的方向是否改变
boolean isChangedDirection = Math.signum(deltaX) != Math.signum(mLastDirection);
// 设置阻尼
if (Math.abs(deltaX) < getMeasuredWidth() / 2 && Math.abs(deltaX) != 1 && mIsAllowDamping) {
if ((mCurrentPage == 0 && deltaX > 0 ) || (mCurrentPage == getChildCount() - 1 && deltaX < 0)) {
deltaX = (int) (deltaX * DAMPING_COEFFICIENT);
} else {
mIsAllowDamping = false;
}
}
// 1:向右 -1:向左
mLastDirection = deltaX;
scrollBy((int) (-deltaX * 0.8f), 0);
mLastDownX = x;
mLastDownY = y;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
int endX = (int) event.getX();
// 是否翻页
boolean isOverMaxTurnPageDistance = Math.abs(endX - mStartX) > 0.5f * getMeasuredWidth();
// 1表示正数,-1表示负数,0表示0
int direction = (int) Math.signum(endX - mStartX);
if (direction > 0 && isOverMaxTurnPageDistance && mCurrentPage > 0) {
mCurrentPage--;
} else if (direction < 0 && isOverMaxTurnPageDistance
&& mCurrentPage < getChildCount() - 1) {
mCurrentPage++;
} else {
// 根据速度判断
mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
float velocity_X = mVelocityTracker.getXVelocity();
Log.d(TAG, "onTouchEvent: " + velocity_X);
if (Math.abs(velocity_X) > mMinFlingVelocity && velocity_X > 0) {
mCurrentPage--;
} else if (Math.abs(velocity_X) > mMinFlingVelocity && velocity_X < 0) {
mCurrentPage++;
}
}
if (mCurrentPage < 0) mCurrentPage = 0;
if (mCurrentPage > getChildCount() - 1) mCurrentPage = getChildCount() - 1;
startScroll();
mLastDownX = (int) event.getX();
mLastDownY = (int) event.getY();
mIsAllowDamping = true;
mIsBeingDragging = false;
releaseVelocityTracker();
break;
}
return true;
}
/**
* 开始滚动到目标位置
*/
private void startScroll() {
int currentPageSize = getCurrentPageView().getMeasuredWidth();
mScroller.startScroll(getScrollX(), 0,
mCurrentPage * currentPageSize - getScrollX(),
0, SCROLL_DURATION);
// 手动发起重新绘制请求
invalidate();
}
/**
* 切换到制定页面
*
* @param whichPage
* @return
*/
public boolean snapTo(int whichPage) {
// 验证 参数有效性
whichPage = verifyPage(whichPage);
mCurrentPage = whichPage;
startScroll();
return true;
}
public int verifyPage(int page) {
if (page > getChildCount() - 1) {
return mPages[getChildCount() - 1];
} else if (page < 0) {
return 0;
} else {
return page;
}
}
/**
* 获取速率
*
* @param ev
*/
private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
}
/**
* 释放VelocityTracker
*/
private void releaseVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.clear();
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
private Scroller mScroller;
/**
* 完成滚动时调用,调用一个动画去不断的滚动,而不是不可控的迅速完成滚动
*/
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
/**
* 获取当前页的子View
*
* @return
*/
public View getCurrentPageView() {
return mChilds.get(mCurrentPage);
}
}
左右滑动的分页View(模拟Launcher3的PagedView)
最新推荐文章于 2023-05-06 00:30:00 发布
这个博客介绍了一个自定义的滑动页面切换组件XPagedView的实现,包括阻尼效果、自动切换、触屏事件处理等功能。组件内部使用Scroller进行平滑滚动,并通过计算滑动速度和方向来判断是否触发页面切换。
摘要由CSDN通过智能技术生成