近期在做项目中需要浏览电影海报,图片浏览使用Android Gallery控件可以很方便实现的。但Android 原生Gallery是不支持纵向翻动。网络上也没找到好控件来实现这样的效果,只好自己把Gallery 的代码拔出来读一编,搞懂实现原理后重写了一个 Vertical Gallery 控件。本文将分享Android Gallery 实现原理,并给出重写纵向gallery的方法。
要重写出纵向Gallery,需要搞清楚两个方面:1 .Gallery是如何初始Layout的 ; 2. 按键或触屏操作是如何驱动图片滑动的。本文也是从这两个方面来介绍Gallery。
1. 原生Gallery Layout 实现。
图1,Gallery 显示效果
第二步:将第一步绘制的图片移动至gallery 中心位置
第三步:绘制右边图片
第四步:绘制左边图片
第五步:设置选择焦点项,此处选中图片放大不是有gallery来实现的,而是有使用gallery时监听选中回调来实现的放大
代码分析:
图1,Gallery 显示效果
图1 为假定要gallery 要显示的效果,gallery 的每个item为一张图片,其他Image 1放大图片表示选中焦点项。下面通过序列图来标示其layout的过程。
第一步:绘制第一张图
第二步:将第一步绘制的图片移动至gallery 中心位置
第三步:绘制右边图片
第四步:绘制左边图片
第五步:设置选择焦点项,此处选中图片放大不是有gallery来实现的,而是有使用gallery时监听选中回调来实现的放大
代码分析:
void layout(int delta, boolean animate) {
... ...
// All views go in recycler while we are in layout
recycleAllViews();
// Clear out old views
//removeAllViewsInLayout();
detachAllViewsFromParent();
/*
* These will be used to give initial positions to views entering the
* gallery as we scroll
*/
mRightMost = 0;
mLeftMost = 0;
// Make selected view and center it
/*
* mFirstPosition will be decreased as we add views to the left later
* on. The 0 for x will be offset in a couple lines down.
*/
//在设置Adapter 时 初始 selectposition 为0
mFirstPosition = mSelectedPosition;
// 绘制第一张图
View sel = makeAndAddView(mSelectedPosition, 0, 0, true);
// Put the selected child in the center
// 通过gallery 中心计算 将选中图片移动只gallery中心需要偏移的位置
int selectedOffset = childrenLeft + (childrenWidth / 2) - (sel.getWidth() / 2) +
mSelectedCenterOffset;
sel.offsetLeftAndRight(selectedOffset);
// 绘制中心右边的图片
fillToGalleryRight();
// 绘制中心左边的图片
fillToGalleryLeft();
// Flush any cached views that did not get reused above
mRecycler.clear();
invalidate();
// 设置通知选择选中焦点
checkSelectionChanged();
mDataChanged = false;
mNeedSync = false;
setNextSelectedPositionInt(mSelectedPosition);
updateSelectedItemMetadata();
}
这个函数定义在文件frameworks/base/core/java/android/widget/Gallery.java中。
makeAndAddView 函数实现获取一个view 并将其设置到指定位置上去,获取View方式有两种,要么从已存在的recycler里获取或者从adapter里获取一个新的View。
结合以上描述,要实现纵向Gallery,只要根据上面的流程将左换成上,将右换下来执行相关流程便可以完成纵向Gallery的layout。但需要注意的是makeAndAddView的时候会调用setUpChild方法需要计算子view的大小和位置,在计算同样需要将左换上,右换下来计算纵向Gallery 子View 的大小及位置。
2. Gallery 子View的移动
有两种方式触发Gallery 图片的移动。1,是通过keydown事件来触发;2,是通过触屏手势来移动图片。Gallery 子View的移动借助于Scroller,Scroller查看Android 动画章节中的介绍(
Android 动画原理)。由于keydown事件触发的移动与手势触发的Gallery子View移动中的移动原理相似,这里只介绍按键事件触发的移动,如下图所示。
图2: Gallery 按键移动流程
图2: Gallery 按键移动流程
第1步:Gallery 接收到左右键按键,往前或往后移动一个子View
第3步:计算移动一个子View 需要移动的距离
第4步:设置FlingRunnable 来移动指定距离
第5步:FlingRunnable通过Scroller的startScroll来触发移动,这里只是设置移动的距离,设置移动时间,并计算出移动的终点位置。
第6、7步就是移动过程,trackMotionScroll一次移动一定的距离,该距离有Scroller来计算。通过不停的查看是否还要移动,最终移动到目的地。more就是通过scroller来计算是否还需要继续移动,Scroller会根据指定的移动时间及移动距离来计算,这是动画的一个原理,如果性能越好移动的越平滑。如果more为false 表示不再移动,通过endFling来结束,在结束移动的过程中还需要保证移动的目标子View移动到Gallery的中心位置。
从中可以看出,Gallery 通过trackMotionScroll来不停的移动子view于达到目的地。trackMotionScroll代码如下:
void trackMotionScroll(int deltaX) {
if (getChildCount() == 0) {
return;
}
// 判断移动方向
boolean toLeft = deltaX < 0;
int limitedDeltaX = getLimitedMotionScrollAmount(toLeft, deltaX);
if (limitedDeltaX != deltaX) {
// The above call returned a limited amount, so stop any scrolls/flings
mFlingRunnable.endFling(false);
onFinishedMovement();
}
// 将所有子View 都移动 limiterDeltaX 的距离
offsetChildrenLeftAndRight(limitedDeltaX);
// 去掉移出屏幕的子View
detachOffScreenChildren(toLeft);
// 补充移动后空余位置的视图
if (toLeft) {
// If moved left, there will be empty space on the right
fillToGalleryRight();
} else {
// Similarly, empty space on the left
fillToGalleryLeft();
}
// Clear unused views
mRecycler.clear();
// 移动过程中会重新修改选中项
setSelectionToCenterChild();
final View selChild = mSelectedChild;
if (selChild != null) {
final int childLeft = selChild.getLeft();
final int childCenter = selChild.getWidth() / 2;
final int galleryCenter = getWidth() / 2;
mSelectedCenterOffset = childLeft + childCenter - galleryCenter;
}
onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
invalidate();
}
这个函数定义在文件frameworks/base/core/java/android/widget/Gallery.java中。
通过以上分析,我们已知Gallery 子View移动的实现原理,触屏滑动效果原理相似。要实现纵向Gallery也就不难了,还是那句话,移动及计算位置时左换上,右换下即可。