根据我参考的所有资料 都是需要先了解 ScrollBy ScrollTo mScrollX mScrollY的这些概念了。
看别人讲解就和看API没什么区别 ,很多东西不是很理解,然后我就做了个例子。
imageView.scrollTo(0,0);
btnScrollTo_1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
imageView.scrollTo(100,20);
Log.i(TAG,"mScrollX:"+imageView.getScrollX()+" mScrollY:"+imageView.getScrollY());
}
});
btnScrollTo_2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
imageView.scrollTo(20,100);
Log.i(TAG, "mScrollX:" + imageView.getScrollX() + " mScrollY:" + imageView.getScrollY());
}
});
btnLeft.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
imageView.scrollBy(10,0);
Log.i(TAG, "mScrollX:" + imageView.getScrollX() + " mScrollY:" + imageView.getScrollY());
}
});
btnRight.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
imageView.scrollBy(-10, 0);
Log.i(TAG, "mScrollX:" + imageView.getScrollX() + " mScrollY:" + imageView.getScrollY());
}
});
btnDown.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
imageView.scrollBy(0, -10);
Log.i(TAG, "mScrollX:" + imageView.getScrollX() + " mScrollY:" + imageView.getScrollY());
}
});
btnUp.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
imageView.scrollBy(0, 10);
Log.i(TAG, "mScrollX:" + imageView.getScrollX() + " mScrollY:" + imageView.getScrollY());
}
});
下边的Log分别是 btnUp btnDown btnLeft btnrRight btnScrollTo_1 btnScrollTo_2 按下时的日志:
记住这张图 记住张图 记住这张图
scrollBy
简单来说 ScrollBy 就是使View 或者控件进行移动的时候调用,两个参数 x,y ,比如你想从家往学校走,这些两个参数就是你设定的你走路的步伐长度,比如你把参数设置为(1m,0m);的话,那么就是你去学校的步长为每步一米。 每调用一次,那么你的腿就往前迈一步。y就是换一个方向啦。当然这些值也可以为负,比如你到学校后每调用一次(-1m,0)的话你就是从学校往家的路程中迈进一步,这里是以像素为单位的。叫做偏移量。
/** * Move the scrolled position of your view. This will cause a call to * {@link #onScrollChanged(int, int, int, int)} and the view will be * invalidated. * @param x the amount of pixels to scroll by horizontally * @param y the amount of pixels to scroll by vertically */ public void scrollBy(int x, int y) { scrollTo(mScrollX + x, mScrollY + y); }
scrollTo
这个方法的调用就用一个词形容就是,一步到位,填充坐标后就一步迈向这个点。在你的View上设置滚动位置时调用。
/** * Set the scrolled position of your view. This will cause a call to * {@link #onScrollChanged(int, int, int, int)} and the view will be * invalidated. * @param x the x position to scroll to * @param y the y position to scroll to */ public void scrollTo(int x, int y) { if (mScrollX != x || mScrollY != y) { int oldX = mScrollX; int oldY = mScrollY; mScrollX = x; mScrollY = y; invalidateParentCaches(); onScrollChanged(mScrollX, mScrollY, oldX, oldY); if (!awakenScrollBars()) { postInvalidateOnAnimation(); } } }
getScrollX & getScrollY
这两个方法就是获取 你的步伐长度的,在scrollBy方法中是指定步伐长度,而这两个方法就是获得在X轴和Y轴上的步伐长度 在这里叫做 偏移量
/** * Return the scrolled left position of this view. This is the left edge of * the displayed part of your view. You do not need to draw any pixels * farther left, since those are outside of the frame of your view on * screen. * * @return The left edge of the displayed part of your view, in pixels. */ public final int getScrollX() { return mScrollX; }
相对位置:
通过API可以看出一个问题 ,它进行移动或者其他获取scrollX时,都是以左上角这个位置进行计算的,或者是进行相对位移的。比如一个ImageView控件进行滚动,他是按照这个控件进行移动的,还有View都是按照这个规则定点计算获取的。并不是屏幕的这个View。
说完基础说正事:
看看目录结构:
代码还是相当麻烦的。只要是贴一下SlidingMenu这个文件。我自己仿写的也是一头雾水。大部分理解了。还不算全懂,
package com.example.hejingzhou.sliddemo;
import android.app.Activity;
import android.content.Context;
import android.graphics.Point;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.RelativeLayout;
import android.widget.Scroller;
/**
* Created by Hejingzhou on 2016/3/5.
*/
public class SlidingMenu extends RelativeLayout {
private String TAG = getClass().getSimpleName();
private View mSlidingView;//中View
private View mMenuView;//左View
private View mDetailView;//右View
private RelativeLayout bgShade;
private int screenWidth, screenHeight;//屏幕宽高
private Context mContext;
private Scroller mScroller;
private VelocityTracker mVelocityTracker;//跟踪速度
private int mTouchSlop;//触摸最短距离
private float mLastMotionX, mLastMotionY;
private static final int VELOCITY = 50;
private boolean mIsBeingDragged = true;//拖动是存在的
private boolean tCanSlideLeft = true, tCanSlideRight = false;//左划true右划false
private boolean hasClickLeft = false, hasClickRight = false;
public SlidingMenu(Context context) {
super(context);
Log.i("SlidingMenu", "参数个数 " + 1);
Init(context);
}
public SlidingMenu(Context context, AttributeSet attrs) {
super(context, attrs);
Log.i("SlidingMenu", "参数个数 " + 2);
Init(context);
}
public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Log.i("SlidingMenu", "参数个数 " + 3);
Init(context);
}
private void Init(Context context) {
mContext = context;
bgShade = new RelativeLayout(context);//在这个继承的类中实例化创建一个相对布局
mScroller = new Scroller(getContext());//实例化滑动类
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
/**
* 主要是构建一个相对布局宽高是手机的宽高像素
*/
WindowManager windowManager = ((Activity) context).getWindow().getWindowManager();// 创建一个WindowsManager对象
Display display = windowManager.getDefaultDisplay();//实例化一个显示屏操作的类
Point point = new Point();
display.getSize(point);
screenWidth = point.x;
Log.i(TAG, "像素宽" + screenWidth);
screenHeight = point.y;
Log.i(TAG, "像素高" + screenHeight);
LayoutParams bgParams = new LayoutParams(screenWidth, screenHeight);/*****************************/
bgParams.addRule(RelativeLayout.CENTER_IN_PARENT);//设置位于父布局的中间
bgShade.setLayoutParams(bgParams);
Log.i("SlidingMenu","初始化完成");
}
/* public void addViews(View left, View center, View right) {
setLeftView(left);
setRightView(right);
setCenterView(center);
}*/
/**
* 设置左MenuView的宽高参数 宽是WRAP_CONTENT 高是MATCH_ARENT
*
* @param view
*/
public void setLeftView(View view) {
RelativeLayout.LayoutParams behindParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.MATCH_PARENT);
addView(view, behindParams);
mMenuView = view;
}
/**
* 设置右MenuView的宽高参数 宽是WRAP_CONTENT 高是MATCH_ARENT
*
* @param right
*/
public void setRightView(View right) {
RelativeLayout.LayoutParams behindParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.MATCH_PARENT);
behindParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
addView(right, behindParams);
mDetailView = right;
}
/**
* 设置中间主View的宽高参数 宽是MATCH_ARENT 高是MATCH_ARENT
*
* @param center
*/
public void setCenterView(View center) {
RelativeLayout.LayoutParams aboveParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
RelativeLayout.LayoutParams bgParams = new RelativeLayout.LayoutParams(screenWidth, screenHeight);
bgParams.addRule(RelativeLayout.CENTER_IN_PARENT);
View bgShadeContent = new View(mContext);
Log.i(TAG,"程序获取资源没出错_前");
//bgShadeContent.setBackgroundResource(R.drawable.shade_bg);
Log.i(TAG, "程序获取资源没出错_后");
bgShade.addView(bgShadeContent, bgParams);
addView(bgShade, bgParams);
addView(center, aboveParams);
mSlidingView = center;
mSlidingView.bringToFront();
}
/**
* public void scrollBy (int x, int y)
* Added in API level 1
* Move the scrolled position of your view.移动滚动视图的位置
* This will cause a call to onScrollChanged(int, int, int, int) and the view will be invalidated.视图将失效。
* Parameters
* x int: the amount of pixels to scroll by horizontally水平滚动的像素的数量
* y int: the amount of pixels to scroll by vertically
*/
@Override
public void scrollTo(int x, int y) {
super.scrollTo(x, y);
postInvalidate();//刷新View,导致失效发生在随后通过事件循环周期
}
/**
* 计算卷轴的滑动 进行相应的左右移动
*/
public void computeScroll() {
if (!mScroller.isFinished()) {//如果滚动还没有完成
if (mScroller.computeScrollOffset()) {//返回最新的滚动位置,返回true表示已经完成滚动
int oldX = mSlidingView.getScrollX();
int oldY = mSlidingView.getScrollY();//得到一个View到另一个View的偏移量
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();//当前在XY轴上的抵消值
if (oldX != x || oldY != y) { //手指触电进行了移动
if (mSlidingView != null) {
mSlidingView.scrollTo(x, y);//直接就把这个View移动到(X,Y)这个位置
if (x < 0) bgShade.scrollTo(x + 20, y);//如果当前布局在相对原点的右边,那么这个相对布局往左移动20单位
else bgShade.scrollTo(x - 20, y);//向右移动20单位
}
}
invalidate();//
}
}
}
private boolean canSlideLeft = true;
private boolean canSlideRight = false;//左右滑动标志
/**
* 传进来的 ture OR flase
*
* @param left
* @param right
*/
public void setCanSliding(boolean left, boolean right) {
canSlideLeft = left;
canSlideRight = right;
}
/**
* 触摸监听拦截
*
* @param event
* @return
*/
public boolean onInterceptTouchEvent(MotionEvent event) {
final int action = event.getAction();//返回正在执行的操作
final float x = event.getX();
final float y = event.getY();//gatXY(),得到的是自身控件的左上角坐标
switch (action) {
case MotionEvent.ACTION_DOWN: //按下
mLastMotionX = x;
mLastMotionY = y;
mIsBeingDragged = false;//拖动是否存在
if (canSlideLeft) {
mMenuView.setVisibility(View.VISIBLE);//设置启动这个可见View
mDetailView.setVisibility(View.INVISIBLE);//这种View是无形的,但它仍然占用空间布局的目的。
}
if (canSlideRight) {
mMenuView.setVisibility(View.INVISIBLE);
mDetailView.setVisibility(View.VISIBLE);
}
break;
case MotionEvent.ACTION_MOVE:
final float dx = x - mLastMotionX;//dx = 移动后当前的控件坐标 - 移动前按下时的坐标
final float xDiff = Math.abs(dx);//绝对值
final float dy = y - mLastMotionY;
final float yDiff = Math.abs(dy);
/**
* 通过手滑动的焦点坐标和View的缩放比例进行判断
* 主要是证明是否进行了滑动 左划 Or 右划
*/
if (xDiff > mTouchSlop && xDiff > yDiff) {//如果差值大于最小距离并且x轴差值大于y轴差值
if (canSlideLeft) {
/**
* float oldScrollX = mSlidingView.getScrollX(); 写错一步 写成getScaleX()
* 这一步导致在从中间的Activity滑向左菜单后,再次划回主Actyvity时
* 还需要划到主Activity 右View后才能带动左菜单
*/
float oldScrollX = mSlidingView.getScrollX();//获取一个View的X(横向)缩放比例搜索,如果没有调用 setScaleX,那么返回1。
if (oldScrollX < 0) {
mIsBeingDragged = true;//证明是拖动的
mLastMotionX = x;
} else {
if (dx > 0) {
mIsBeingDragged = true;
mLastMotionX = x;
}
}
} else if (canSlideRight) {
float oldScrollX = mSlidingView.getScrollX();
if (oldScrollX > 0) {
mIsBeingDragged = true;
mLastMotionX = x;
} else {
if (dx < 0) {
mIsBeingDragged = true;
mLastMotionX = x;
}
}
}
}
break;
}
return mIsBeingDragged;
}
/**
* 触摸键听 和 滑动的算法 的详细操作
*
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();//如果滑动速度为空,就返回滑动速度类,继续监听。
}
mVelocityTracker.addMovement(event);//将触摸事件添加到速度实例化的类中
final int action = event.getAction();//返回当前的事件
final float x = event.getX();
final float y = event.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
if (!mScroller.isFinished()) {//如果滑动还没有完成,那么就停止滑动
mScroller.abortAnimation();
}
mLastMotionX = x;
mLastMotionY = y;
/**
* 如果mSlidingView的滑动偏移量和负的菜单View宽 并且 最后焦点的X小于菜单的宽 返回false
*/
if (mSlidingView.getScrollX() == -getMenuViewWidth() && mLastMotionX < getMenuViewWidth()) {
return false;
}
if (mSlidingView.getScrollX() == getMenuViewWidth() && mLastMotionX > getMenuViewWidth()) {
/**
* 第二处错误 第一遍写的时候反悔了ture ,但是改为flase后程序呈现效果并未发生变化
*/
return false;
}
break;
case MotionEvent.ACTION_MOVE:
if (mIsBeingDragged) {
final float deltaX = mLastMotionX - x;//按下时的焦点X坐标 - 现在的X坐标
mLastMotionX = x;
float oldScrollX = mSlidingView.getScrollX();//获取这个中间Activity的这个mScrollX值
float scrollX = oldScrollX + deltaX;
if (canSlideLeft) {
if (scrollX > 0) {
scrollX = 0;
}
}
if (canSlideRight) {
if (scrollX < 0)
scrollX = 0;
}
if (deltaX < 0 && oldScrollX < 0) {
final float leftBound = 0;
final float rightBound = -getMenuViewWidth();
if (scrollX > leftBound) {
scrollX = leftBound;
} else if (scrollX < rightBound) {
scrollX = rightBound;
}
} else if (deltaX > 0 && oldScrollX > 0) {
final float rightBound = getDetailViewWidth();
final float leftBound = 0;
if (scrollX < leftBound) {
scrollX = leftBound;
} else if (scrollX > rightBound) {
scrollX = rightBound;
}
}
if (mSlidingView != null) {
mSlidingView.scrollTo((int) scrollX, mSlidingView.getScrollY());//固定中间这个Activity的位置
if (scrollX < 0)
bgShade.scrollTo((int) scrollX + 20, mSlidingView.getScrollY());//左移20固定
else
bgShade.scrollTo((int) scrollX - 20, mSlidingView.getScrollY());//右移20固定
}
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(100);//最大速度
float xVelocity = velocityTracker.getXVelocity();//得到计算的速度
int oldScrollX = mSlidingView.getScrollX();//将mSlidingView的ScrollX
int dx = 0;
if (oldScrollX <= 0 && canSlideLeft) {
/**
* 第六处错误,多写一句错误的 导致在左菜单中向主View中划的过程中 不会慢慢滑动过渡过去
* 会跟着手的触点 进行平移
*/
// if (xVelocity <= 0 && canSlideLeft) {
if (xVelocity > VELOCITY) {
dx = -getMenuViewWidth() - oldScrollX;
} else if (xVelocity < -VELOCITY) {
dx = -oldScrollX;
if (hasClickLeft) {
/**
* 第七处错误 没有写hasClickLeft = false; 点击按键时进行实时停止
*/
hasClickLeft = false;
setCanSliding(tCanSlideLeft, tCanSlideRight);
}
} else if (oldScrollX < -getMenuViewWidth() / 2) {
dx = -getMenuViewWidth() - oldScrollX;
} else if (oldScrollX >= -getMenuViewWidth() / 2) {
dx = -oldScrollX;
if (hasClickLeft) {
hasClickLeft = false;
setCanSliding(tCanSlideLeft, tCanSlideRight);
}
// }
}
if (oldScrollX >= 0 && canSlideRight) {
if (xVelocity < -VELOCITY) {
dx = getDetailViewWidth() - oldScrollX;
} else if (xVelocity > VELOCITY) {
dx = -oldScrollX;
if (hasClickRight) {
hasClickRight = false;
setCanSliding(tCanSlideLeft, tCanSlideRight);
}
} else if (oldScrollX > getDetailViewWidth() / 2) {
dx = getDetailViewWidth() - oldScrollX;
} else if (oldScrollX <= getDetailViewWidth() / 2) {
dx = -oldScrollX;
if (hasClickRight) {
hasClickRight = false;
setCanSliding(tCanSlideLeft, tCanSlideRight);
}
}
}
smoothScrollTo(dx);
}
/**
* 第三处错误 没有写 break 添加后没有什么变化
*/
break;
}
}
/**
* 第四处错误 返回的super.onTouchEvent(event)
*/
return true;
}
/**
* 最左边的Menu菜单的View
*/
public void showLeftView() {
int menuWidth = mMenuView.getWidth();//这个菜单的View值
int oldScrollX = mSlidingView.getScrollX();//SlidingView的mScrollX值
if (oldScrollX == 0) {//如果SlidingView就没动
mMenuView.setVisibility(View.VISIBLE);
mDetailView.setVisibility(View.INVISIBLE);
smoothScrollTo(-menuWidth);//开始平滑 在X轴上的 -menuWidth像素
tCanSlideLeft = canSlideLeft;//左划置为ture
tCanSlideRight = canSlideRight;//右划置为flase
hasClickLeft = true;//左菜单Button按下
setCanSliding(true, false);
} else if (oldScrollX == -menuWidth) {//中间主视图SlidingView在右划视图的状态下
smoothScrollTo(menuWidth);//向左移动menuWidth
if (hasClickLeft) {//左Button置为false
hasClickLeft = false;
setCanSliding(tCanSlideLeft, tCanSlideRight);//设置左右划动状态标记
}
}
}
/**
* 最右边的DetailView视图
*/
public void showRightView() {
int menuWidth = mDetailView.getWidth();
int oldScrollx = mSlidingView.getScrollX();
if (oldScrollx == 0) {
mMenuView.setVisibility(View.INVISIBLE);
mDetailView.setVisibility(View.VISIBLE);
smoothScrollTo(menuWidth);
tCanSlideLeft = canSlideLeft;
tCanSlideRight = canSlideRight;
hasClickRight = true;
setCanSliding(false, true);
} else if (oldScrollx == menuWidth) {
smoothScrollTo(-menuWidth);
if (hasClickRight) {
hasClickRight = false;
setCanSliding(tCanSlideLeft, tCanSlideRight);
}
}
}
/**
* 平滑开始
*
* @param dx 平滑X轴的距离参数
*/
private void smoothScrollTo(int dx) {
/**
* 第五处错误 写成 -500 这个值直接影响着 从主View 划回到左菜单的时间
*/
int duration = 500;
int oldScrollX = mSlidingView.getScrollX();
/**
* startX 水平滑动的偏移量
* startY 垂直滑动的偏移量
* dx dx水平滑动距离,+向左
* dy dy竖直滑动距离,+向上
*duration 水平滑动的时间
*/
mScroller.startScroll(oldScrollX, mSlidingView.getScrollY(), dx
, mSlidingView.getScrollY(), duration);
invalidate();
}
/**
* 获取最右边的DetailView的宽
*
* @return
*/
private int getDetailViewWidth() {
if (mDetailView == null) {
return 0;
}
return mDetailView.getWidth();
}
/**
* 获得菜单的宽
* @return
*/
private int getMenuViewWidth() {
if (mMenuView == null) {
return 0;
}
return mMenuView.getWidth();//返回你的View的宽
}
}
很晕菜,
效果: