Skip to content

Instantly share code, notes, and snippets.

@pythoncat1024
Last active August 12, 2019 05:20
Show Gist options
  • Save pythoncat1024/be98b72fd041171efb7e42e6c16fe10f to your computer and use it in GitHub Desktop.
Save pythoncat1024/be98b72fd041171efb7e42e6c16fe10f to your computer and use it in GitHub Desktop.
横向滑动view , 实现 HorizontalScrollView 的效果
...
<HorizontalScrollLayout
android:background="@drawable/border_shape"
android:layout_width="300dp"
android:layout_height="300dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="3000dp"
android:layout_height="match_parent"
android:background="@drawable/long_picture">
</LinearLayout>
</HorizontalScrollLayout>
...
package com.python.cat.needwork.widgets.measure;
import android.content.Context;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.OverScroller;
import com.apkfuns.logutils.LogUtils;
import com.python.cat.needwork.widgets.ViewUtils;
public class HorizontalScrollLayout extends LinearLayout {
private int touchSlop;
private OverScroller mOverScroller;
private int tapTimeout;
private float downX;
private float downY;
private long downTime;
private boolean isMoving;
private int leftBorder;
private int rightBorder;
private int selfWidth;
private VelocityTracker velocityTracker;
private ViewConfiguration configuration;
private int minimumFlingVelocity;
private int maximumFlingVelocity;
public HorizontalScrollLayout(Context context) {
super(context);
init();
}
public HorizontalScrollLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public HorizontalScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public HorizontalScrollLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
LogUtils.i(ViewUtils.logMeasureSpec(widthMeasureSpec, "LogLayout.width"));
LogUtils.i(ViewUtils.logMeasureSpec(heightMeasureSpec, "LogLayout.height"));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
LogUtils.e("real width=%s,height=%s", w, h);
View childAt = getChildAt(0);
ViewGroup.MarginLayoutParams params = (MarginLayoutParams) childAt.getLayoutParams();
leftBorder = params.leftMargin;
rightBorder = params.width - params.rightMargin;
selfWidth = getWidth() - getPaddingLeft() - getPaddingRight();
LogUtils.e("leftBorder=%s,rightBorder=%s,selfWidth=%s",
leftBorder, rightBorder, selfWidth);
}
private void init() {
configuration = ViewConfiguration.get(getContext());
touchSlop = configuration.getScaledTouchSlop();
tapTimeout = ViewConfiguration.getTapTimeout();
minimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
maximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
mOverScroller = new OverScroller(getContext());
LogUtils.e("touchSlop=%s,tapTimeout=%s,minFlingV=%s,maxiFlingV=%s",
touchSlop, tapTimeout, minimumFlingVelocity, maximumFlingVelocity);
}
@Override
public boolean performClick() {
return super.performClick();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain();
}
velocityTracker.addMovement(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
downTime = SystemClock.elapsedRealtime();
downX = event.getX();
downY = event.getY();
LogUtils.d("down--- %s,%s", downX, downY);
}
break;
case MotionEvent.ACTION_MOVE: {
float moveX = event.getX();
float moveY = event.getY();
float diffX = moveX - downX;
float diffY = moveY - downY;
LogUtils.i("touchSlop=%s, diffX=%s,diffY=%s", touchSlop, diffX, diffY);
if (!isMoving && Math.abs(diffX) > Math.abs(diffY)
&& Math.abs(diffX) > touchSlop) {
isMoving = true;
LogUtils.w("can move dx= %s, scrollX=%s", diffX, getScrollX());
if (diffX > 0 && getScrollX() <= leftBorder) {
// don't move, want to move to left
LogUtils.e("不要滑动了。。。left " + getScrollX());
} else if (diffX < 0 && getScrollX() + selfWidth >= rightBorder) {
// don/t move, want to move to right
LogUtils.e("不要滑动了。。。right " + (getScrollX() + selfWidth));
} else if (diffX < 0
&& getScrollX() + Math.abs(diffX) + selfWidth > rightBorder) {
scrollTo(rightBorder - selfWidth, 0);
} else if (diffX > 0 && getScrollX() - Math.abs(diffX) < leftBorder) {
scrollTo(leftBorder, 0);
} else {
scrollBy((int) -diffX, 0);
}
} else if (isMoving) {
// 这个逻辑的目的是,在连续滑动的过程中,每次滑动距离可以非常小
LogUtils.w("can move dx= %s, scrollX=%s", diffX, getScrollX());
if (diffX > 0 && getScrollX() <= leftBorder) {
// don't move, want to move to left
LogUtils.e("不要滑动了。。。left " + getScrollX());
} else if (diffX < 0 && getScrollX() + selfWidth >= rightBorder) {
// don/t move, want to move to right
LogUtils.e("不要滑动了。。。right " + (getScrollX() + selfWidth));
} else if (diffX < 0
&& getScrollX() + Math.abs(diffX) + selfWidth > rightBorder) {
scrollTo(rightBorder - selfWidth, 0);
} else if (diffX > 0 && getScrollX() - Math.abs(diffX) < leftBorder) {
scrollTo(leftBorder, 0);
} else {
scrollBy((int) -diffX, 0);
}
}
downX = moveX;
downY = moveY;
}
break;
case MotionEvent.ACTION_UP: {
isMoving = false;
velocityTracker.computeCurrentVelocity(1000, maximumFlingVelocity);
int xVelocity = Math.round(velocityTracker.getXVelocity());
float upX = event.getX();
float upY = event.getY();
float diffX = upX - downX;
float diffY = upY - downY;
if (Math.abs(diffX) < touchSlop
&& Math.abs(diffY) < touchSlop
&& SystemClock.elapsedRealtime() - downTime < tapTimeout) {
performClick();
LogUtils.e("is a click event");
} else {
LogUtils.e("up----l %s, %s ", getScrollX(), rightBorder - selfWidth - getScrollX());
// 强制结束 mScroller 未完成的动画
mOverScroller.forceFinished(true);
final boolean canFling = (getScaleX() > 0 || xVelocity > 0) &&
(getScrollX() < getScrollRange() || xVelocity < 0);
if (canFling && Math.abs(xVelocity) > minimumFlingVelocity) {
int width = getWidth() - getPaddingLeft() - getPaddingRight();
int right = getChildAt(0).getWidth();
// 参考:android.widget.ScrollView.fling() 方法完成
mOverScroller.fling(getScrollX(), getScrollY(), -xVelocity, 0,
0, Math.max(0, right - width),
0, 0, 0, width / 2);
}
// 滚动到最左边 ok
// mOverScroller.startScroll(scrollX, getScrollY(), leftBorder - scrollX, getScrollY());
// 滚动到最右边 ok
// mOverScroller.startScroll(scrollX, getScrollY(), rightBorder - selfWidth - scrollX, getScrollY());
// scrollBy(rightBorder - selfWidth - scrollX, 0); // ok
// 让 View 重绘
invalidate(); // 一定要调用,否则有时候不触发,如果是使用 Scroller 的话!
}
velocityTracker.clear();
velocityTracker.recycle();
velocityTracker = null;
}
break;
case MotionEvent.ACTION_CANCEL: {
isMoving = false;
LogUtils.e("action--- cancel");
velocityTracker.recycle();
}
break;
default:
LogUtils.e("action--- " + event.getAction());
break;
}
return true; // 不true 的话,只有 down 能接收到
}
/**
* 参考:android.widget.ScrollView#getScrollRange
*/
private int getScrollRange() {
int scrollRange = 0;
if (getChildCount() > 0) {
View child = getChildAt(0);
scrollRange = Math.max(0,
child.getWidth() - (getWidth() - getPaddingLeft() - getPaddingRight()));
}
return scrollRange;
}
@Override
public void computeScroll() {
// 第三步,重写computeScroll()方法,并在其内部完成平滑滚动的逻辑
if (mOverScroller.computeScrollOffset()) {
scrollTo(mOverScroller.getCurrX(), mOverScroller.getCurrY());
postInvalidate();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment