public class View {
....
protected int mScrollX; //该视图内容相当于视图起始坐标的偏移量,X轴方向
protected int mScrollY; //该视图内容相当于视图起始坐标的偏移量,Y轴方向
//返回值
public final int getScrollX() {
return mScrollX;
}
public final int getScrollY() {
return mScrollY;
}
public void scrollTo(int x, int y) {
//偏移位置发生了改变
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x; //赋新值,保存当前便宜量
mScrollY = y;
//回调onScrollChanged方法
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
invalidate(); //一般都引起重绘
}
}
}
// 看出区别了吧 。 mScrollX 与 mScrollY 代表我们当前偏移的位置 , 在当前位置继续偏移(x ,y)个单位
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
//...
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/scroll_to_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="scrollTo"/>
<Button
android:id="@+id/scroll_by_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="scrollBy"/>
</LinearLayout>
public class MainActivity extends AppCompatActivity {
private LinearLayout layout;
private Button scrollToBtn;
private Button scrollByBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
layout = (LinearLayout) findViewById(R.id.layout);
scrollToBtn = (Button) findViewById(R.id.scroll_to_btn);
scrollByBtn = (Button) findViewById(R.id.scroll_by_btn);
scrollToBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
layout.scrollTo(getResources().getDimensionPixelOffset(R.dimen.horizontal_scroll),
getResources().getDimensionPixelOffset(R.dimen.horizontal_scroll));
}
});
scrollByBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
layout.scrollBy(getResources().getDimensionPixelOffset(R.dimen.horizontal_scroll),
getResources().getDimensionPixelOffset(R.dimen.horizontal_scroll));
}
});
}
}
<resources>
<dimen name="horizontal_scroll">-20dp</dimen>
<dimen name="vertical_scroll">-30dp</dimen>
</resources>
public class Scroller {
private int mStartX; //起始坐标点 , X轴方向
private int mStartY; //起始坐标点 , Y轴方向
private int mCurrX; //当前坐标点 X轴, 即调用startScroll函数后,经过一定时间所达到的值
private int mCurrY; //当前坐标点 Y轴, 即调用startScroll函数后,经过一定时间所达到的值
private float mDeltaX; //应该继续滑动的距离, X轴方向
private float mDeltaY; //应该继续滑动的距离, Y轴方向
private boolean mFinished; //是否已经完成本次滑动操作, 如果完成则为 true
//构造函数
public Scroller(Context context) {
this(context, null);
}
public final boolean isFinished() {
return mFinished;
}
//强制结束本次滑屏操作
public final void forceFinished(boolean finished) {
mFinished = finished;
}
public final int getCurrX() {
return mCurrX;
}
/* Call this when you want to know the new location. If it returns true,
* the animation is not yet finished. loc will be altered to provide the
* new location. */
//根据当前已经消逝的时间计算当前的坐标点,保存在mCurrX和mCurrY值中
public boolean computeScrollOffset() {
if (mFinished) { //已经完成了本次动画控制,直接返回为false
return false;
}
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
float x = (float)timePassed * mDurationReciprocal;
...
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
...
}
else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
//开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,即到达坐标为(startX+dx , startY+dy)出
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX; mStartY = startY;
mFinalX = startX + dx; mFinalY = startY + dy;
mDeltaX = dx; mDeltaY = dy;
...
}
}
/**
* Called by a parent to request that a child update its values for mScrollX and mScrollY if necessary. This will typically be done if the child is animating a scroll using a {@link android.widget.Scroller Scroller}
* object.
* 由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制 */
public void computeScroll() { //空方法 ,自定义ViewGroup必须实现方法体
}
@Override
protected void dispatchDraw(Canvas canvas){
...
for (int i = 0; i < count; i++) {
final View child = children[getChildDrawingOrder(count, i)];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
...
child.computeScroll();
...
}
public class ScrollerLayout extends ViewGroup {
private Scroller mScroller; //用于完成滚动操作的实例
private VelocityTracker mVelocityTracker = null ; //处理触摸的速率
public static int SNAP_VELOCITY = 600 ; //最小的滑动速率
private int mTouchSlop = 0 ; //最小滑动距离,超过了,才认为开始滑动
private float mLastionMotionX = 0 ; //上次触发ACTION_MOVE事件时的屏幕坐标
private int curScreen = 0 ; //当前屏幕
private int leftBorder; //界面可滚动的左边界
private int rightBorder; //界面可滚动的右边界
//两种状态: 是否处于滑屏状态
private static final int TOUCH_STATE_REST = 0; //什么都没做的状态
private static final int TOUCH_STATE_SCROLLING = 1; //开始滑屏的状态
private int mTouchState = TOUCH_STATE_REST; //默认是什么都没做的状态
public ScrollerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// 创建Scroller的实例
mScroller = new Scroller(context);
//初始化一个最小滑动距离
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
@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();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
// 为ScrollerLayout中的每一个子控件在水平方向上进行布局
childView.layout(i * childView.getMeasuredWidth(), 0, (i + 1) * childView.getMeasuredWidth(), childView.getMeasuredHeight());
}
}
// 初始化左右边界值
leftBorder = getChildAt(0).getLeft();
rightBorder = getChildAt(getChildCount() - 1).getRight();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
//表示已经开始滑动了,不需要走该Action_MOVE方法了(第一次时可能调用)。
//该方法主要用于用户快速松开手指,又快速按下的行为。此时认为是处于滑屏状态的。
if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
return true;
}
final float x = ev.getX();
switch (action) {
case MotionEvent.ACTION_MOVE:
final int xDiff = (int) Math.abs(mLastionMotionX - x);
//超过了最小滑动距离,就可以认为开始滑动了
if (xDiff > mTouchSlop) {
mTouchState = TOUCH_STATE_SCROLLING;
}
break;
case MotionEvent.ACTION_DOWN:
mLastionMotionX = x;
mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mTouchState = TOUCH_STATE_REST;
break;
}
return mTouchState != TOUCH_STATE_REST;
}
public boolean onTouchEvent(MotionEvent event){
super.onTouchEvent(event);
//获得VelocityTracker对象,并且添加滑动对象
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
//触摸点
float x = event.getX();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
//如果屏幕的动画还没结束,你就按下了,我们就结束上一次动画,即开始这次新ACTION_DOWN的动画
if(mScroller != null){
if(!mScroller.isFinished()){
mScroller.abortAnimation();
}
}
mLastionMotionX = x ; //记住开始落下的屏幕点
break ;
case MotionEvent.ACTION_MOVE:
int detaX = (int)(mLastionMotionX - x ); //每次滑动屏幕,屏幕应该移动的距离
if (getScrollX() + detaX < leftBorder) { //防止用户拖出边界这里还专门做了边界保护,当拖出边界时就调用scrollTo()方法来回到边界位置
scrollTo(leftBorder, 0);
return true;
} else if (getScrollX() + getWidth() + detaX > rightBorder) {
scrollTo(rightBorder - getWidth(), 0);
return true;
}
scrollBy(detaX, 0);//开始缓慢滑屏咯。 detaX > 0 向右滑动 , detaX < 0 向左滑动
mLastionMotionX = x ;
break ;
case MotionEvent.ACTION_UP:
final VelocityTracker velocityTracker = mVelocityTracker ;
velocityTracker.computeCurrentVelocity(1000);
//计算速率
int velocityX = (int) velocityTracker.getXVelocity() ;
//滑动速率达到了一个标准(快速向右滑屏,返回上一个屏幕) 马上进行切屏处理
if (velocityX > SNAP_VELOCITY && curScreen > 0) {
// Fling enough to move left
snapToScreen(curScreen - 1);
}
//快速向左滑屏,返回下一个屏幕
else if(velocityX < -SNAP_VELOCITY && curScreen < (getChildCount()-1)){
snapToScreen(curScreen + 1);
}
//以上为快速移动的 ,强制切换屏幕
else{
//我们是缓慢移动的,因此先判断是保留在本屏幕还是到下一屏幕
snapToDestination();
}
//回收VelocityTracker对象
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
//修正mTouchState值
mTouchState = TOUCH_STATE_REST ;
break;
case MotionEvent.ACTION_CANCEL:
mTouchState = TOUCH_STATE_REST ;
break;
}
return true ;
}
//我们是缓慢移动的,因此需要根据偏移值判断目标屏是哪个
private void snapToDestination(){
//判断是否超过下一屏的中间位置,如果达到就抵达下一屏,否则保持在原屏幕
//公式意思是:假设当前滑屏偏移值即 scrollCurX 加上每个屏幕一半的宽度,除以每个屏幕的宽度就是我们目标屏所在位置了。
int destScreen = (getScrollX() + getWidth() / 2 ) / getWidth() ;
snapToScreen(destScreen);
}
//真正的实现跳转屏幕的方法
private void snapToScreen(int whichScreen){
//简单的移到目标屏幕,可能是当前屏或者下一屏幕,直接跳转过去,不太友好,为了友好性,我们在增加一个动画效果
curScreen = whichScreen ;
//防止屏幕越界,即超过屏幕数
if(curScreen > getChildCount() - 1)
curScreen = getChildCount() - 1 ;
//为了达到下一屏幕或者当前屏幕,我们需要继续滑动的距离.根据dx值,可能向左滑动,也可能向右滑动
int dx = curScreen * getWidth() - getScrollX() ;
mScroller.startScroll(getScrollX(), 0, dx, 0, Math.abs(dx) * 2);
//由于触摸事件不会重新绘制View,所以此时需要手动刷新View 否则没效果
invalidate();
}
@Override
public void computeScroll() {
//重写computeScroll()方法,并在其内部完成平滑滚动的逻辑
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<com.hx.scroller.ScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="@drawable/crazy_1" />
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="@drawable/crazy_2" />
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="@drawable/crazy_3" />
</com.hx.scroller.ScrollerLayout>
机械节能产品生产企业官网模板...
大气智能家居家具装修装饰类企业通用网站模板...
礼品公司网站模板
宽屏简约大气婚纱摄影影楼模板...
蓝白WAP手机综合医院类整站源码(独立后台)...苏ICP备2024110244号-2 苏公网安备32050702011978号 增值电信业务经营许可证编号:苏B2-20251499 | Copyright 2018 - 2025 源码网商城 (www.ymwmall.com) 版权所有