您的位置:首页 > 移动开发 > Android开发

android-Viewflow开源项目学习

2015-11-18 12:43 351 查看
一直以来,没有写过技术博客。最近想整理一下自己学过的开源项目,通过动手加深对技术的理解

Viewflow项目介绍

Viewflow是github上很好用的横向滑动View的开源项目。他有跟android原生ListView一样的接口,在项目中能够快速运用。

ListView的启发

Viewflow的设计很自然联想到能够上下滑动的ListView,因此大部分人独立去实现这样的View时,比较容易出现的想法是改造ListView。我们通常想到:
将ListView的移动方向改变城左右滑动。
只显示一项数据。

因此Viewflow很自然的是继承AdapterView。

View宽高计算

android View在计算宽高布局时,有onMeasure和onLayout两个方法。

Viewflow中只显示一项ChildView,高度为ChildView的高度与上下padding之和,宽度为屏幕宽度;onLayout为每个childView申请布局。

滑动事件处理

android中不是单一view的滑动处理通常是onInterceptTouchEvent和onTouchEvent两个方法处理触摸事件的响应,Scroller处理视图滑动,并通过VelocityTracker处理滑动速度。Viewflow在滑动中显示什么位置的ChildView可以计算:
private void snapToDestination(){
final int screenWidth = getChildWdith();
final int whichScreen = (getScrollX() + (screenWidth / 2))
/ screenWidth;

snapToScreen(whichScreen);
}


android在绘制View前,都会调用computeScroll,因此在该方法中处理ChildView切换:

@Override
public void computeScroll() {
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}else if(mNextScreen != INVALID_SCREEN){
mCurrentScreen = Math.max(0,
Math.min(mNextScreen, getChildCount() - 1));
mNextScr
a0db
een = INVALID_SCREEN;
post(new Runnable() {
@Override
public void run() {
postViewSwitched(mLastScrollDirection);
}
});
}
}

android在Touch过程中,设置Scroller参数,获取滑动速率,用于ChildView切换计算

@Override
public boolean onTouchEvent(MotionEvent event) {
if(getChildCount() == 0)
return false;

if(mVelocityTracker == null){
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);

final int action = event.getAction();
final float x = event.getX();
switch(action){
case MotionEvent.ACTION_DOWN:
/**
* If being flinged and user touches, stop the fling. isFinished
* will be false if being flinged.
*/
if(mScroller.isFinished()){
mScroller.abortAnimation();
}

// Remember where the motion event started
mLastMotionX = x;

mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST
: TOUCH_STATE_SCROLLING;

break;
case MotionEvent.ACTION_MOVE:
final int deltaX = (int)(mLastMotionX - x);

boolean xMoved = Math.abs(deltaX) > mTouchSlop;
if(xMoved){

mTouchState = TOUCH_STATE_SCROLLING;

if(mViewInitializeListener != null)
initializeView(deltaX);
}

if(mTouchState == TOUCH_STATE_SCROLLING){
// Scroll to follow the motion event

mLastMotionX = x;

int scrollX = getScrollX();
if(deltaX < 0){
if(scrollX > 0){
scrollBy(Math.max(-scrollX, deltaX), 0);
}
}else if(deltaX > 0){
final int availableToScroll = getChildAt(
getChildCount() - 1).getRight()
- getPaddingRight() - getHorizontalFadingEdgeLength()
- scrollX - getChildWdith();
scrollBy(Math.min(availableToScroll, deltaX), 0);
}

return true;
}
break;
case MotionEvent.ACTION_UP:
if(mTouchState == TOUCH_STATE_SCROLLING){
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int veloctiyX = (int) velocityTracker.getXVelocity();

if(veloctiyX > SNAP_VELOCITY && mCurrentScreen > 0){
snapToScreen(mCurrentScreen - 1);
}else if(veloctiyX < - SNAP_VELOCITY &&
mCurrentScreen < getChildCount() - 1){
snapToScreen(mCurrentScreen + 1);
}else{
snapToDestination();
}

if(mVelocityTracker != null){
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}

mTouchState = TOUCH_STATE_REST;
break;
case MotionEvent.ACTION_CANCEL:
snapToDestination();
mTouchState = TOUCH_STATE_REST;
break;
}
return true;
}

ChildView缓存

Viewflow中使用链表处理ChildView的缓存。

private LinkedList<View> mLoadedViews;
private LinkedList<View> mRecycledViews;


Viewflow中加载的ChildView保存到mLoadViews链表中,不再需要加载的ChildView放在mRecycledViews中。在需要生成新的ChildView加载数据时,可以重新利用mRecycleViews中的没在加载的ChildView。
private View obtainView(int position){
View convertView = getRecycledView();
View view = mAdapter.getView(position, convertView, this);

if(view != convertView && convertView != null){
mRecycledViews.add(convertView);
}
mLastObtainedViewWasRecycled = (view == convertView);
LayoutParams p = view.getLayoutParams();
if(p == null){
p = new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
view.setLayoutParams(p);
}
return view;
}


在加载时,
mRecycleViews中恢复的ChildView,调用attachViewToParent
新创建的ChildView,调用addViewInLayout

在切换显示View时,处理缓存view:
private void postViewSwitched(int direction){
if(direction == 0)
return;

if(direction > 0){ // to the right
mCurrentAdapterIndex ++;
mCurrentBufferIndex ++;
mLazyInit.remove(LazyInit.LEFT);
mLazyInit.add(LazyInit.RIGHT);

// Recycle view outside buffer range
if(mCurrentAdapterIndex > mSideBuffer){
recycleView(mLoadedViews.removeFirst());
mCurrentBufferIndex --;
}

int newBufferIndex = mCurrentAdapterIndex + mSideBuffer;
if(newBufferIndex < mAdapter.getCount())
mLoadedViews.addLast(makeAndAddView(newBufferIndex, true));

}else { //to the left
mCurrentAdapterIndex --;
mCurrentBufferIndex --;
mLazyInit.add(LazyInit.LEFT);
mLazyInit.remove(LazyInit.RIGHT);

if((mAdapter.getCount() - 1 - mCurrentAdapterIndex) > mSideBuffer){
recycleView(mLoadedViews.removeLast());
}

int newBufferIndex = mCurrentAdapterIndex - mSideBuffer;
if(newBufferIndex > -1){
mLoadedViews.addFirst(makeAndAddView(newBufferIndex, false));
mCurrentBufferIndex ++;
}

}

requestLayout();
setVisibleView(mCurrentBufferIndex, true);
if(mIndicator != null){
mIndicator.onSwitched(mLoadedViews.get(mCurrentBufferIndex),
mCurrentAdapterIndex);
}

if(mViewSwitchListener != null){
mViewSwitchListener.onSwitched(mLoadedViews.get(mCurrentBufferIndex),
mCurrentAdapterIndex);
}
}
 

将切换不需要加载的View加入mRecycleViews中:

protected void recycleView(View v){
if(v == null)
return;
mRecycledViews.addFirst(v);
detachViewFromParent(v);
}

结束语

Viewflow的github链接,本人认识有限,希望大家指正。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息