package com.example.myapplication;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.LinearLayout;
import android.widget.Scroller;
import androidx.annotation.Nullable;
import androidx.core.view.ViewConfigurationCompat;
/**
* Created by ZhouWengong on 2020/5/21.
*/
public class ScrollerLinearLayout extends LinearLayout implements View.OnClickListener {
public static final String TAG = ScrollerLinearLayout.class.getSimpleName();
/**
* 用于完成滚动操作的实例
*/
private Scroller mScroller;
/**
* 判定为拖动的最小移动像素数
*/
private int mTouchSlop;
/**
* 手机按下时的屏幕坐标
*/
private float mXDown;
/**
* 手机当时所处的屏幕坐标
*/
private float mXMove;
/**
* 上次触发ACTION_MOVE事件时的屏幕坐标
*/
private float mXLastMove;
/**
* 界面可滚动的左边界
*/
private int leftBorder;
/**
* 界面可滚动的右边界
*/
private int rightBorder;
private int lastPosition;
private int currentPosition;
private VelocityTracker mVelocityTracker;
private int maxX;
private int itemWidth;
private boolean stopScroll = true;
private OnPositionChangedListener positionChangedListener;
public ScrollerLinearLayout(Context context) {
this(context, null);
}
public ScrollerLinearLayout(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ScrollerLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
// 第一步,创建Scroller的实例
mScroller = new Scroller(getContext());
ViewConfiguration configuration = ViewConfiguration.get(getContext());
// 获取TouchSlop值
mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
Log.e(TAG, "zwg----[init]: ");
}
public void setPositionChangedListener(OnPositionChangedListener positionChangedListener) {
this.positionChangedListener = positionChangedListener;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
// 为ScrollerLayout中的每一个子控件测量大小
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed) {
int childCount = getChildCount();
int centerX = getWidth() / 2;
View childAt0 = getChildAt(0);
if (childAt0 == null) {
Log.e(TAG, "zwg----[onLayout]: has no child ");
return;
}
final int margin = 30;
int childWidth = childAt0.getMeasuredWidth();
int measuredHeight = childAt0.getMeasuredHeight();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
// 为ScrollerLayout中的每一个子控件在水平方向上进行布局
int left = -childWidth / 2 + i * (childWidth + margin) + centerX;
childView.layout(left, 0,
left + childWidth, measuredHeight);
childView.setTag(i);
childView.setOnClickListener(this);
}
// 初始化左右边界值
leftBorder = getChildAt(0).getLeft();
rightBorder = getChildAt(getChildCount() - 1).getRight();
//滑动边界, 左边0, 右边maxX
maxX = -(getWidth() + childWidth) / 2 + rightBorder;
itemWidth = childWidth + margin;
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mXDown = ev.getRawX();
mXLastMove = mXDown;
break;
case MotionEvent.ACTION_MOVE:
mXMove = ev.getRawX();
float diff = Math.abs(mXMove - mXDown);
mXLastMove = mXMove;
// 当手指拖动值大于TouchSlop值时,认为应该进行滚动,拦截子控件的事件
if (diff > mTouchSlop) {
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
mXMove = event.getRawX();
int scrolledX = (int) (mXLastMove - mXMove);
// if (getScrollX() + scrolledX < leftBorder) {
// scrollTo(leftBorder, 0);
// return true;
// } else if (getScrollX() + getWidth() + scrolledX > rightBorder) {
// scrollTo(rightBorder - getWidth(), 0);
// return true;
// }
// scrollBy(scrolledX, 0);
int position = (getScrollX() + itemWidth / 2) / itemWidth;
changePosition(position);
if (currentPosition == 0 && scrolledX < 0) {
scrollBy((int) (scrolledX / 3), 0);
} else if (currentPosition == getChildCount() - 1 && getScrollX() > maxX) {
scrollBy(scrolledX / 3, 0);
} else {
scrollBy(scrolledX, 0);
}
mXLastMove = mXMove;
break;
case MotionEvent.ACTION_UP:
// 第二步,调用startScroll()方法来初始化滚动数据并刷新界面
mVelocityTracker.computeCurrentVelocity(1000, 5000.0f);
int xVelocity = (int) mVelocityTracker.getXVelocity();
if (Math.abs(xVelocity) > 2000) {
mScroller.fling(getScrollX(), getScrollY(), -xVelocity, 0,
0, maxX, 0, 0);
stopScroll = true;
} else {
int targetIndex = (getScrollX() + itemWidth / 2) / itemWidth;
int dx = targetIndex * itemWidth - getScrollX();
mScroller.startScroll(getScrollX(), 0, dx, 0);
//没有fling时走这个回调, 有fling时走computeScroll()里的回调,点击走点击的回调
if (positionChangedListener != null) {
int positionUp = (getScrollX() + itemWidth / 2) / itemWidth;
if (positionUp != lastPosition) {
lastPosition = positionUp;
Log.e(TAG, "zwg----[onTouchEvent] onSelect: " + positionUp);
positionChangedListener.onSelect(positionUp);
}
}
}
invalidate();
break;
}
return super.onTouchEvent(event);
}
@Override
public void computeScroll() {
// 第三步,重写computeScroll()方法,并在其内部完成平滑滚动的逻辑
if (mScroller.computeScrollOffset()) {
int currX = mScroller.getCurrX();
if (currX < 0) {
currX = 0;
} else {
currX = currX > maxX ? maxX : currX;
}
scrollTo(currX, 0);
postInvalidate();
int position = (getScrollX() + itemWidth / 2) / itemWidth;
changePosition(position);
if (mScroller.getCurrX() == mScroller.getFinalX() && stopScroll) {
stopScroll = false;
currX = itemWidth * position;
mScroller.startScroll(getScrollX(), 0, -getScrollX() + currX, 0, 500);
postInvalidate();
//有fling时走这个回调
if (positionChangedListener != null) {
lastPosition = position;
Log.e(TAG, "zwg----[computeScroll] onSelect: " + position);
positionChangedListener.onSelect(position);
}
}
}
}
@Override
public void onClick(View v) {
select((int) v.getTag());
}
public void select(int position) {
Log.e(TAG, "zwg----[select] click position: " + position);
int i = (-currentPosition + position) * itemWidth;
mScroller.startScroll(getScrollX(), 0, i, 0, 1000);
postInvalidate();
if (positionChangedListener != null) {
lastPosition = position;
Log.e(TAG, "zwg----[select] onSelect: " + position);
positionChangedListener.onSelect(position);
}
}
private void changePosition(int position) {
if (currentPosition == position) {
return;
}
if (position >= getChildCount() || position < 0) {
Log.e(TAG, "zwg----[changePosition]: invalidate position ");
return;
}
currentPosition = position;
Log.e(TAG, "zwg----[changePosition] currentPosition: " + currentPosition);
if (positionChangedListener != null) {
positionChangedListener.onPositionChanged(currentPosition);
}
}
public interface OnPositionChangedListener {
//滑动时回调, 实时改变
void onPositionChanged(int position);
//最终选择时回调, 只回调一次
void onSelect(int position);
}
}