2.要想实现滚动,毫无疑问,我们需要借助Scroller
当然一切看起来很简单,其实不然,除此之外,你还需要对于滑动冲突进行处理等等,下面我开始介绍啦。
这就是我们这次项目的大致
[](()3.实现
因为我们是要打造一个容器类,所以肯定得继承自 ViewGroup
按照一般的思路,我们肯定是先要进行一些变量的申明,onMeasure,onLayout *** 作
private void init(Context context) {
mCamera = new Camera();
mMatrix = new Matrix();
if (mScroller == null) {
mScroller = new Scroller(context);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
//滑动到设置的StartScreen位置
scrollTo(0, mStartScreen * mHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childTop = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
child.layout(0, childTop,
child.getMeasuredWidth(), childTop + child.getMeasuredHeight());
childTop = childTop + child.getMeasuredHeight();
}
}
}
完成这些 *** 作后,我们需要在onTouchEvent中进行滑动事件的处理
3.1 完成无限循环滑动滚动
我们的item数量是有限的,如何实现无限循 《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》无偿开源 徽信搜索公众号【编程进阶路】 环滚动呢?很简单,以3个item为例子(分别为1,2,3),我们让屏幕显示的是2
如此反复,屏幕所在的位置始终是第2个item所在的位置,这样就实现了我们的无限循环滚动,向下滚动也是如此
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (!mScroller.isFinished()) {
//当上一次滑动没有结束时,再次点击,强制滑动在点击位置结束
mScroller.setFinalY(mScroller.getCurrY());
mScroller.abortAnimation();
scrollTo(0, getScrollY());
}
mDownY = y;
break;
case MotionEvent.ACTION_MOVE:
int realDelta = (int) (mDownY - y);
mDownY = y;
if (mScroller.isFinished()) {
//因为要循环滚动
recycleMove(realDelta);
}
break;
case MotionEvent.ACTION_UP:
mVelocityTracker.computeCurrentVelocity(1000);
float yVelocity = mVelocityTracker.getYVelocity();
//滑动的速度大于规定的速度,或者向上滑动时,上一页页面展现出的高度超过1/2。则设定状态为State.ToPre
if (yVelocity > standerSpeed || ((getScrollY() + mHeight / 2) / mHeight < mStartScreen)) {
mState = State.ToPre;
} else if (yVelocity < -standerSpeed || ((getScrollY() + mHeight / 2) / mHeight > mStartScreen)) {
//滑动的速度大于规定的速度,或者向下滑动时,下一页页面展现出的高度超过1/2。则设定状态为State.ToNext
mState = State.ToNext;
} else {
mState = State.Normal;
}
//根据mState进行相应的变化
changeByState(yVelocity);
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break;
}
//返回true,消耗点击事件
return true;
}
当手从屏幕上移开时,我们来看下这个方法changeByState(yVelocity);
我们以mState = State.ToPre 为例子来说明
/**
-
mState = State.ToPre 时进行的动作
-
@param yVelocity 竖直方向的速度
*/
private void toPreAction(float yVelocity) {
int startY;
int delta;
int duration;
mState = State.ToPre;
addPre();//增加新的页面
//计算松手后滑动的item个数
int flingSpeedCount= (yVelocity - standerSpeed) > 0 ? (int) (yVelocity - standerSpeed) : 0;
addCount = flingSpeedCount/ flingSpeed + 1;
//mScroller开始的坐标
startY = getScrollY() + mHeight;
setScrollY(startY);
//mScroller 移动的距离
delta = -(startY - mStartScreen * mHeight) - (addCount - 1) * mHeight;
duration = (Math.abs(delta)) * 3;
mScroller.startScroll(0, startY, 0, delta, duration);
addCount–;
}
然后会进入addPre方法中
/**
- 把最后一个item移动到第一个item位置
*/
private void addPre() {
mCurScreen = ((mCurScreen - 1) + getChildCount()) % getChildCount();
int childCount = getChildCount();
View view = getChildAt(childCount - 1);
removeViewAt(childCount - 1);
addView(view, 0);
if (iStereoListener != null) {
iStereoListener.toPre(mCurScreen);
}
}
最后mScroller.startScroll(0, startY, 0, delta, duration); 开始执行。
执行的过程中会回调这个函数方法computeScroll
完成到这一步,我们的无限滑动滚动就算是完成了
3.2 实现3D切换效果。
正常情况下,我们自定义ViewGroup并不需要重写dispatchDraw 方法。
而这里我们则需要重写
@Override
protected void dispatchDraw(Canvas canvas) {
if (!isAdding && isCan3D) {
//当开启3D效果并且当前状态不属于 computeScroll中 addPre() 或者addNext()
//如果不做这个判断,addPre() 或者addNext()时页面会进行闪动一下
//我当时写的时候就被这个坑了,后来通过log判断,原来是computeScroll中的onlayout,和子Child的draw触发的顺序导致的。
//知道原理的朋友希望可以告知下
for (int i = 0; i < getChildCount(); i++) {
drawScreen(canvas, i, getDrawingTime());
}
} else {
isAdding = false;
super.dispatchDraw(canvas);
}
}
好,我们来drawScreen这个方法
private void drawScreen(Canvas canvas, int i, long drawingTime) {
int curScreenY = mHeight * i;
//屏幕中不显示的部分不进行绘制
if (getScrollY() + mHeight < curScreenY) {
return;
}
if (curScreenY < getScrollY() - mHeight) {
return;
}
float centerX = mWidth / 2;
float centerY = (getScrollY() > curScreenY) ? curScreenY + mHeight : curScreenY;
float degree = mAngle * (getScrollY() - curScreenY) / mHeight;
if (degree > 90 || degree < -90) {
return;
}
canvas.save();
mCamera.save();
mCamera.rotateX(degree);
mCamera.getMatrix(mMatrix);
mCamera.restore();
mMatrix.preTranslate(-centerX, -centerY);
mMatrix.postTranslate(centerX, centerY);
canvas.concat(mMatrix);
drawChild(canvas, getChildAt(i), drawingTime);
canvas.restore();
}
这里面的关键就在于
mCamera.rotateX(degree);
mMatrix.preTranslate(-centerX, -centerY);
mMatrix.postTranslate(centerX, centerY);
对于Camera我们知道我们整个布局都是平铺的,为什么会产生3D的效果呢?原因就是这个Camera类,人如其名,它就相当于一个相机,它对物体进行拍照。我们把相机正对物体拍摄,拍摄出的效果就是平面的,当我们把相机旋转了90度再来拍摄原来物体,物体就相当于旋转了90度。
Camera拍摄完毕后,然后把拍摄的参数值传到Matrix中,Matrix再和Canvas绑定,由Canvas进行绘制。最终显示在屏幕中。
那么preTranslate,postTranslate又是怎么一回事呢?
很简单,我们知道坐标系是以(0,0)作为参照点的。现在我们对拍摄的对象进行的缩放变形 *** 作是在物体的中心。我们需要把物体的中心先移动到(0,0)位置,最后再移动到物体原来中心位置即可。
具体的大家可以参考下这篇文章
[http://blog.csdn.net/rav009/article/details/7763223](() ( Android postTranslate和preTranslate的理解)
不过对于Camera的坐标系我还有一点点疑问,我准备有机会写一篇关于Camera和Matrix文章。
3.3 滑动事件冲突的处理(请先查看更新说明)
完成上面两个步骤,那么我们就算Over了吗?
不!还有很重要的一点,就是事件冲突的处理。 举个例子:我们把手放到我们的容器上,系统怎么知道我们这个滑动事件是给容器还是要给容器的子类的呢?
(给容器自己,则进行滑动的 *** 作,给容器的子类,则容器的子类可以进行点击事件的判断处理)
对于这种情况,我就很大度啦,全部交给容器子类处理!子类不要,OK,那容器你自己拿来玩吧。
————之所以不走寻常路:交给容器处理,容器不需要再交给子类
原因在于:容器拿到滑动事件只需要做滑动 *** 作,而子类则不同,它有点击事件需要判断,一个容器有很多子类,而很多子类只有一个共同的容器,如果把控制权交给容器,那么容器怎么可能能够判断得出不同的子类到底需不需要这个滑动事件呢?所以,既然这么麻烦,那么统统交给子类处理。
交给子类处理,则容器中onInterceptTouchEvent需要做如下 *** 作
@Override
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)