开源项目学习与分析系列——DynamicGridView
2014-04-07 18:04
381 查看
废话不多说先上图,只有一张静态图,实现的是可拖拽的GridView。有很什么方便的GIF制作软件,推荐一下,另外我从windowsLivewriter上传blog中的代码样式变得好丑了:
说到这里再推荐一篇同样是写可拖拽GridView的blogAndroid可拖拽的GridView效果实现,长按可拖拽和item实时交换
这里很好的讲解了拖拽的原理,也实现了item交换的效果。下面是他的步骤,和我要分析的这个开源项目有一些不同,我将比较来讲。
先说一下推荐blog的思路:
根据手指按下的X,Y坐标来获取我们在GridView上面点击的item
手指按下的时候使用Handler和Runnable来实现一个定时器,假如定时时间为1000毫秒,在1000毫秒内,如果手指抬起了移除定时器,没有抬起并且手指点击在GridView的item所在的区域,则表示我们长按了GridView的item
如果我们长按了item则隐藏item,然后使用WindowManager来添加一个item的镜像在屏幕用来代替刚刚隐藏的item
当我们手指在屏幕移动的时候,更新item镜像的位置,然后在根据我们移动的X,Y的坐标来获取移动到GridView的哪一个位置
到GridView的item过多的时候,可能一屏幕显示不完,我们手指拖动item镜像到屏幕下方,要触发GridView想上滚动,同理,当我们手指拖动item镜像到屏幕上面,触发GridView向下滚动
GridView交换数据,刷新界面,移除item的镜像
DynamicGridView的思路:
根据手指按下的X,Y坐标来获取我们在GridView上面点击的item
在itemOnlongClickListener开启拖拽模式。隐藏item。
获取有拖拽item的内容的BitmapDrawable,并在重写dispatchDraw函数,将其显示出来,替换刚刚隐藏的item
当我们手指在屏幕移动的时候,更新itemBitmapDrawable的位置,然后在根据我们移动的X,Y的坐标来获取移动到GridView的哪一个位置
到GridView的item过多的时候,可能一屏幕显示不完,我们手指拖动item镜像到屏幕下方,要触发GridView想上滚动,同理,当我们手指拖动item镜像到屏幕上面,触发GridView向下滚动
GridView交换数据,改变隐藏Item的位置,刷新界面
当手指抬起后,将BitmapDrawable置为NULL,然后将隐藏的item显示
看起来两者的区别不大,但是上面只是一个思路,在多细节上的有很大的差异。这里交换数据是指将某一项数据从就得位置插入到新的位置,而不是前面blog中的将两个位置的数据交换。
作者在判断是否符合交换条件和找到item上面花费了比较多的代码。代码比较多,再看代码之前,有几个关键点。
1.GridView中的item视图从没有真正移动过,添加动画也不会改变item的真正位置
2.假设没有BitmapDrawable即镜像不存在,也不考虑动画效果,我们就可以跟清楚的知道真正发生改变的是什么。我们的手指移到哪里,哪里的item视图便隐藏,原先的隐藏的视图可见。在这个过程中改变数据的位置。
3.然后才是item交换的动画发生,此时data数据已经交换完成,并且调用了adapter.notifyDatasetchange()函数。
画一个草图来表示一下。
图片上显示的是adapter提供的数据,虚线表示为不可见。
第一张图,表示手指从5划入数据是9的item范围内,在数据层就是将5插入数据9之后,在视图上就是将镜像移动到数据为9的item范围内,并位置为4的视图设置为可见,位置为8的视图设置为不可见,等待下轮绘制。
第二张图,在下一轮draw环节,若不加人动画效果,GridView就会这样显示,位置为8的视图不可见,当然这个时候你会看到有着一个随着手指移动的显示数据为5的镜像。
第三张图,表示在显示第二张图的时候给Item视图添加的动画,比如会给显示数据7的位置为5的视图(绿色)设置这样的动画,从位置6(黄色)移过来。这样整体的效果就好像镜像让出一个位置,然后后面的视图一个接一个的补上。
我根据这个开源项目完成了一个demo,觉得作者实现的有些地方很巧妙,比如动画,有些地方有些冗余,比如根据Id找到数据位置在找到的视图位置。
packageorg.askerov.dynamicgid;
importandroid.animation.*;
importandroid.annotation.TargetApi;
importandroid.content.Context;
importandroid.graphics.Bitmap;
importandroid.graphics.Canvas;
importandroid.graphics.Point;
importandroid.graphics.Rect;
importandroid.graphics.drawable.BitmapDrawable;
importandroid.os.Build;
importandroid.util.AttributeSet;
importandroid.util.DisplayMetrics;
importandroid.view.MotionEvent;
importandroid.view.View;
importandroid.view.ViewTreeObserver;
importandroid.view.animation.AccelerateDecelerateInterpolator;
importandroid.widget.AbsListView;
importandroid.widget.AdapterView;
importandroid.widget.GridView;
importandroid.widget.ListAdapter;
importjava.util.ArrayList;
importjava.util.LinkedList;
importjava.util.List;
/**
*Author:alexaskerov
*Date:9/6/13
*Time:12:31PM
*/
publicclassDynamicGridViewextendsGridView{
privatestaticfinalintINVALID_ID=AbstractDynamicGridAdapter.INVALID_ID;
/*
*动画持续时间
*/
privatestaticfinalintMOVE_DURATION=300;
privatestaticfinalintSMOOTH_SCROLL_AMOUNT_AT_EDGE=8;
/*
*Item镜像
*/
privateBitmapDrawablemHoverCell;
/*
*BitmapDrawable当前边界
*/
privateRectmHoverCellCurrentBounds;
/*
*BitmapDrawable最初边界
*/
privateRectmHoverCellOriginalBounds;
/*
*这个,不过多解释,当前边界与最初边界的差值
*/
privateintmTotalOffsetY=0;
privateintmTotalOffsetX=0;
/*
*mDownX,mDown记录手指按下的位置
*mLastEventY,mLastEventX最后手指移动的位置
*每次更新完mHoverCellCurrentBounds位置后,会将mLastEventY,mLastEventX的值赋给mDownX,mDown
*个人感觉作者在这一点上定义的变量有些重复
*/
privateintmDownX=-1;
privateintmDownY=-1;
privateintmLastEventY=-1;
privateintmLastEventX=-1;
/*
*作者自定义的adapter中为每一个data设置了一个Long型Id,在我自己写的demo中没有用到这个。
*作者的思路是将每一项数据和一个Id绑定,通过Id可以找到这个数据现在的位置position,这个位置就是该项数据在视图中的位置
*/
privateList<Long>idList=newArrayList<Long>();
/*
*记录按下的item对应数据的Id,这个值在一次完整的拖拽中不会改变
*/
privatelongmMobileItemId=INVALID_ID;
/*
*
*/
privatebooleanmCellIsMobile=false;
privateintmActivePointerId=INVALID_ID;
privatebooleanmIsMobileScrolling;
privateintmSmoothScrollAmountAtEdge=0;
privatebooleanmIsWaitingForScrollFinish=false;
privateintmScrollState=OnScrollListener.SCROLL_STATE_IDLE;
privatebooleanmIsEditMode=false;
privateList<ObjectAnimator>mWobbleAnimators=newLinkedList<ObjectAnimator>();
privateOnDropListenermDropListener;
privatebooleanmHoverAnimation;
/*
*当数据交换时item的动画
*/
privatebooleanmReorderAnimation;
/*
*当进入拖拽模式后,没有被选中的item会左右摇晃,mWobbleInEditMode表示是否要开启摇晃的动画模式
*/
privatebooleanmWobbleInEditMode=true;
privateOnItemLongClickListenermUserLongClickListener;
/*
*拖拽模式的入口
*/
privateOnItemLongClickListenermLocalLongClickListener=newOnItemLongClickListener(){
publicbooleanonItemLongClick(AdapterView
arg0,Viewarg1,intpos,longid){
if(!isEnabled()||isEditMode())
returnfalse;
mTotalOffsetY=0;
mTotalOffsetX=0;
intposition=pointToPosition(mDownX,mDownY);
intitemNum=position-getFirstVisiblePosition();
ViewselectedView=getChildAt(itemNum);
//获取与该项数据绑定的Id
mMobileItemId=getAdapter().getItemId(position);
//获取镜像bitmapdrawable
mHoverCell=getAndAddHoverView(selectedView);
if(isPostHoneycomb()&&selectedView!=null)
selectedView.setVisibility(View.INVISIBLE);
mCellIsMobile=true;
//将当前Gridview可见的item所对应的数据的Id记为邻居
updateNeighborViewsForId(mMobileItemId);
if(isPostHoneycomb()&&mWobbleInEditMode)
startWobbleAnimation();
if(mUserLongClickListener!=null)
mUserLongClickListener.onItemLongClick(arg0,arg1,pos,id);
mIsEditMode=true;
returntrue;
}
};
privateOnItemClickListenermUserItemClickListener;
privateOnItemClickListenermLocalItemClickListener=newOnItemClickListener(){
@Override
publicvoidonItemClick(AdapterView
parent,Viewview,intposition,longid){
if(!isEditMode()&&isEnabled()&&mUserItemClickListener!=null){
mUserItemClickListener.onItemClick(parent,view,position,id);
}
}
};
publicDynamicGridView(Contextcontext){
super(context);
init(context);
}
publicDynamicGridView(Contextcontext,AttributeSetattrs){
super(context,attrs);
init(context);
}
publicDynamicGridView(Contextcontext,AttributeSetattrs,intdefStyle){
super(context,attrs,defStyle);
init(context);
}
publicvoidsetOnDropListener(OnDropListenerdropListener){
this.mDropListener=dropListener;
}
publicvoidstartEditMode(){
mIsEditMode=true;
if(isPostHoneycomb()&&mWobbleInEditMode)
startWobbleAnimation();
}
publicvoidstopEditMode(){
mIsEditMode=false;
if(isPostHoneycomb()&&mWobbleInEditMode)
stopWobble(true);
}
publicbooleanisEditMode(){
returnmIsEditMode;
}
publicbooleanisWobbleInEditMode(){
returnmWobbleInEditMode;
}
publicvoidsetWobbleInEditMode(booleanwobbleInEditMode){
this.mWobbleInEditMode=wobbleInEditMode;
}
@Override
publicvoidsetOnItemLongClickListener(finalOnItemLongClickListenerlistener){
mUserLongClickListener=listener;
super.setOnItemLongClickListener(mLocalLongClickListener);
}
@Override
publicvoidsetOnItemClickListener(OnItemClickListenerlistener){
this.mUserItemClickListener=listener;
super.setOnItemClickListener(mLocalItemClickListener);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
privatevoidstartWobbleAnimation(){
for(inti=0;i<getChildCount();i++){
Viewv=getChildAt(i);
if(v!=null&&Boolean.TRUE!=v.getTag(R.id.dynamic_grid_wobble_tag)){
if(i%2==0)
animateWobble(v);
else
animateWobbleInverse(v);
v.setTag(R.id.dynamic_grid_wobble_tag,true);
}
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
privatevoidstopWobble(booleanresetRotation){
for(AnimatorwobbleAnimator:mWobbleAnimators){
wobbleAnimator.cancel();
}
mWobbleAnimators.clear();
for(inti=0;i<getChildCount();i++){
Viewv=getChildAt(i);
if(v!=null){
if(resetRotation)v.setRotation(0);
v.setTag(R.id.dynamic_grid_wobble_tag,false);
}
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
privatevoidrestartWobble(){
stopWobble(false);
startWobbleAnimation();
}
publicvoidinit(Contextcontext){
//设置ScrollListener,当滚动后继续判断switchItem
setOnScrollListener(mScrollListener);
DisplayMetricsmetrics=context.getResources().getDisplayMetrics();
mSmoothScrollAmountAtEdge=(int)(SMOOTH_SCROLL_AMOUNT_AT_EDGE*metrics.density+0.5f);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
privatevoidanimateWobble(Viewv){
ObjectAnimatoranimator=createBaseWobble(v);
animator.setFloatValues(-2,2);
animator.start();
mWobbleAnimators.add(animator);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
privatevoidanimateWobbleInverse(Viewv){
ObjectAnimatoranimator=createBaseWobble(v);
animator.setFloatValues(2,-2);
animator.start();
mWobbleAnimators.add(animator);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
privateObjectAnimatorcreateBaseWobble(Viewv){
ObjectAnimatoranimator=newObjectAnimator();
animator.setDuration(180);
animator.setRepeatMode(ValueAnimator.REVERSE);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setPropertyName("rotation");
animator.setTarget(v);
returnanimator;
}
privatevoidreorderElements(intoriginalPosition,inttargetPosition){
getAdapterInterface().reorderItems(originalPosition,targetPosition);
}
privateintgetColumnCount(){
returngetAdapterInterface().getColumnCount();
}
privateAbstractDynamicGridAdaptergetAdapterInterface(){
return((AbstractDynamicGridAdapter)getAdapter());
}
/**
*Createsthehovercellwiththeappropriatebitmapandofappropriate
*size.Thehovercell'sBitmapDrawableisdrawnontopofthebitmapevery
*singletimeaninvalidatecallismade.
*/
privateBitmapDrawablegetAndAddHoverView(Viewv){
intw=v.getWidth();
inth=v.getHeight();
inttop=v.getTop();
intleft=v.getLeft();
Bitmapb=getBitmapFromView(v);
BitmapDrawabledrawable=newBitmapDrawable(getResources(),b);
mHoverCellOriginalBounds=newRect(left,top,left+w,top+h);
mHoverCellCurrentBounds=newRect(mHoverCellOriginalBounds);
drawable.setBounds(mHoverCellCurrentBounds);
returndrawable;
}
/**
*Returnsabitmapshowingascreenshotoftheviewpassedin.
*/
privateBitmapgetBitmapFromView(Viewv){
Bitmapbitmap=Bitmap.createBitmap(v.getWidth(),v.getHeight(),Bitmap.Config.ARGB_8888);
Canvascanvas=newCanvas(bitmap);
v.draw(canvas);
returnbitmap;
}
privatevoidupdateNeighborViewsForId(longitemId){
intdraggedPos=getPositionForID(itemId);
for(intpos=getFirstVisiblePosition();pos<=getLastVisiblePosition();pos++){
if(draggedPos!=pos){
idList.add(getId(pos));
}
}
}
/**
*Retrievesthepositioninthegridcorrespondingto<code>itemId
code>
*/
publicintgetPositionForID(longitemId){
Viewv=getViewForId(itemId);
if(v==null){
return-1;
}else{
returngetPositionForView(v);
}
}
publicViewgetViewForId(longitemId){
intfirstVisiblePosition=getFirstVisiblePosition();
AbstractDynamicGridAdapteradapter=((AbstractDynamicGridAdapter)getAdapter());
for(inti=0;i<getChildCount();i++){
Viewv=getChildAt(i);
intposition=firstVisiblePosition+i;
longid=adapter.getItemId(position);
if(id==itemId){
returnv;
}
}
returnnull;
}
@Override
publicbooleanonTouchEvent(MotionEventevent){
switch(event.getAction()&MotionEvent.ACTION_MASK){
/*
*记下按下的位置和按下的触点Id,若mIsEditMode已开启,则更新选中的
*mMobileItemId和mCellIsMobile,以及邻居Id,这种情况只出现在长按开
*启拖拽模式后,在结束的时候没有调用StopEditMode()
*/
caseMotionEvent.ACTION_DOWN:
mDownX=(int)event.getX();
mDownY=(int)event.getY();
mActivePointerId=event.getPointerId(0);
if(mIsEditMode&&isEnabled()){
layoutChildren();
mTotalOffsetY=0;
mTotalOffsetX=0;
intposition=pointToPosition(mDownX,mDownY);
intitemNum=position-getFirstVisiblePosition();
ViewselectedView=getChildAt(itemNum);
if(selectedView==null){
returnfalse;
}else{
mMobileItemId=getAdapter().getItemId(position);
mHoverCell=getAndAddHoverView(selectedView);
if(isPostHoneycomb())
selectedView.setVisibility(View.INVISIBLE);
mCellIsMobile=true;
updateNeighborViewsForId(mMobileItemId);
}
}elseif(!isEnabled()){
returnfalse;
}
break;
/*
*核心功能在这里了,
*event.findPointerIndex()关于多点触摸的知识,将在下面做出讲解
*更新镜像位置mHoverCell.setBounds(mHoverCellCurrentBounds);invalidate();
*判断是否满足交换数据的条件handleCellSwitch();
*判断是否Scroll的条件handleMobileCellScroll();
*/
caseMotionEvent.ACTION_MOVE:
if(mActivePointerId==INVALID_ID){
break;
}
intpointerIndex=event.findPointerIndex(mActivePointerId);
mLastEventY=(int)event.getY(pointerIndex);
mLastEventX=(int)event.getX(pointerIndex);
intdeltaY=mLastEventY-mDownY;
intdeltaX=mLastEventX-mDownX;
if(mCellIsMobile){
mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left+deltaX+mTotalOffsetX,
mHoverCellOriginalBounds.top+deltaY+mTotalOffsetY);
mHoverCell.setBounds(mHoverCellCurrentBounds);
invalidate();
handleCellSwitch();
mIsMobileScrolling=false;
handleMobileCellScroll();
returnfalse;
}
break;
/*
*下面都是结束拖拽,应对不同的情况
*/
caseMotionEvent.ACTION_UP:
touchEventsEnded();
if(mDropListener!=null){
mDropListener.onActionDrop();
}
break;
caseMotionEvent.ACTION_CANCEL:
touchEventsCancelled();
if(mDropListener!=null){
mDropListener.onActionDrop();
}
break;
caseMotionEvent.ACTION_POINTER_UP:
/*Ifamultitoucheventtookplaceandtheoriginaltouchdictating
*themovementofthehovercellhasended,thenthedraggingevent
*endsandthehovercellisanimatedtoitscorrespondingposition
*inthelistview.*/
pointerIndex=(event.getAction()&MotionEvent.ACTION_POINTER_INDEX_MASK)>>
MotionEvent.ACTION_POINTER_INDEX_SHIFT;
finalintpointerId=event.getPointerId(pointerIndex);
if(pointerId==mActivePointerId){
touchEventsEnded();
}
break;
default:
break;
}
returnsuper.onTouchEvent(event);
}
privatevoidhandleMobileCellScroll(){
mIsMobileScrolling=handleMobileCellScroll(mHoverCellCurrentBounds);
}
/*
*computeVerticalScrollOffset计算滑动thumb已滑动的距离
*computeVerticalScrollExtent计算thumb的高度
*computeVerticalScrollRange计算thumb所表示的高度,一般为视图高度
*/
publicbooleanhandleMobileCellScroll(Rectr){
intoffset=computeVerticalScrollOffset();
intheight=getHeight();
intextent=computeVerticalScrollExtent();
intrange=computeVerticalScrollRange();
inthoverViewTop=r.top;
inthoverHeight=r.height();
if(hoverViewTop<=0&&offset>0){
smoothScrollBy(-mSmoothScrollAmountAtEdge,0);
returntrue;
}
说到这里再推荐一篇同样是写可拖拽GridView的blog
这里很好的讲解了拖拽的原理,也实现了item交换的效果。下面是他的步骤,和我要分析的这个开源项目有一些不同,我将比较来讲。
先说一下推荐blog的思路:
根据手指按下的X,Y坐标来获取我们在GridView上面点击的item
手指按下的时候使用Handler和Runnable来实现一个定时器,假如定时时间为1000毫秒,在1000毫秒内,如果手指抬起了移除定时器,没有抬起并且手指点击在GridView的item所在的区域,则表示我们长按了GridView的item
如果我们长按了item则隐藏item,然后使用WindowManager来添加一个item的镜像在屏幕用来代替刚刚隐藏的item
当我们手指在屏幕移动的时候,更新item镜像的位置,然后在根据我们移动的X,Y的坐标来获取移动到GridView的哪一个位置
到GridView的item过多的时候,可能一屏幕显示不完,我们手指拖动item镜像到屏幕下方,要触发GridView想上滚动,同理,当我们手指拖动item镜像到屏幕上面,触发GridView向下滚动
GridView交换数据,刷新界面,移除item的镜像
DynamicGridView的思路:
根据手指按下的X,Y坐标来获取我们在GridView上面点击的item
在itemOnlongClickListener开启拖拽模式。隐藏item。
获取有拖拽item的内容的BitmapDrawable,并在重写dispatchDraw函数,将其显示出来,替换刚刚隐藏的item
当我们手指在屏幕移动的时候,更新itemBitmapDrawable的位置,然后在根据我们移动的X,Y的坐标来获取移动到GridView的哪一个位置
到GridView的item过多的时候,可能一屏幕显示不完,我们手指拖动item镜像到屏幕下方,要触发GridView想上滚动,同理,当我们手指拖动item镜像到屏幕上面,触发GridView向下滚动
GridView交换数据,改变隐藏Item的位置,刷新界面
当手指抬起后,将BitmapDrawable置为NULL,然后将隐藏的item显示
看起来两者的区别不大,但是上面只是一个思路,在多细节上的有很大的差异。这里交换数据是指将某一项数据从就得位置插入到新的位置,而不是前面blog中的将两个位置的数据交换。
作者在判断是否符合交换条件和找到item上面花费了比较多的代码。代码比较多,再看代码之前,有几个关键点。
1.GridView中的item视图从没有真正移动过,添加动画也不会改变item的真正位置
2.假设没有BitmapDrawable即镜像不存在,也不考虑动画效果,我们就可以跟清楚的知道真正发生改变的是什么。我们的手指移到哪里,哪里的item视图便隐藏,原先的隐藏的视图可见。在这个过程中改变数据的位置。
3.然后才是item交换的动画发生,此时data数据已经交换完成,并且调用了adapter.notifyDatasetchange()函数。
画一个草图来表示一下。
图片上显示的是adapter提供的数据,虚线表示为不可见。
第一张图,表示手指从5划入数据是9的item范围内,在数据层就是将5插入数据9之后,在视图上就是将镜像移动到数据为9的item范围内,并位置为4的视图设置为可见,位置为8的视图设置为不可见,等待下轮绘制。
第二张图,在下一轮draw环节,若不加人动画效果,GridView就会这样显示,位置为8的视图不可见,当然这个时候你会看到有着一个随着手指移动的显示数据为5的镜像。
第三张图,表示在显示第二张图的时候给Item视图添加的动画,比如会给显示数据7的位置为5的视图(绿色)设置这样的动画,从位置6(黄色)移过来。这样整体的效果就好像镜像让出一个位置,然后后面的视图一个接一个的补上。
我根据这个开源项目完成了一个demo,觉得作者实现的有些地方很巧妙,比如动画,有些地方有些冗余,比如根据Id找到数据位置在找到的视图位置。
packageorg.askerov.dynamicgid;
importandroid.animation.*;
importandroid.content.Context;
importandroid.graphics.Canvas;
importandroid.graphics.Rect;
importandroid.os.Build;
importandroid.util.DisplayMetrics;
importandroid.view.View;
importandroid.view.animation.AccelerateDecelerateInterpolator;
importandroid.widget.AdapterView;
importandroid.widget.ListAdapter;
importjava.util.ArrayList;
importjava.util.List;
/**
*Date:9/6/13
*/
privatestaticfinalintINVALID_ID=AbstractDynamicGridAdapter.INVALID_ID;
*动画持续时间
privatestaticfinalintMOVE_DURATION=300;
/*
*/
/*
*/
/*
*/
/*
*/
privateintmTotalOffsetX=0;
*mDownX,mDown记录手指按下的位置
*每次更新完mHoverCellCurrentBounds位置后,会将mLastEventY,mLastEventX的值赋给mDownX,mDown
*/
privateintmDownY=-1;
privateintmLastEventX=-1;
*作者自定义的adapter中为每一个data设置了一个Long型Id,在我自己写的demo中没有用到这个。
*/
*记录按下的item对应数据的Id,这个值在一次完整的拖拽中不会改变
privatelongmMobileItemId=INVALID_ID;
*
privatebooleanmCellIsMobile=false;
privateintmSmoothScrollAmountAtEdge=0;
privateintmScrollState=OnScrollListener.SCROLL_STATE_IDLE;
privatebooleanmIsEditMode=false;
privateOnDropListenermDropListener;
/*
*/
/*
*/
/*
*/
publicbooleanonItemLongClick(AdapterView
arg0,Viewarg1,intpos,longid){
returnfalse;
mTotalOffsetX=0;
intposition=pointToPosition(mDownX,mDownY);
//获取与该项数据绑定的Id
//获取镜像bitmapdrawable
if(isPostHoneycomb()&&selectedView!=null)
//将当前Gridview可见的item所对应的数据的Id记为邻居
startWobbleAnimation();
if(mUserLongClickListener!=null)
}
privateOnItemClickListenermLocalItemClickListener=newOnItemClickListener(){
publicvoidonItemClick(AdapterView
parent,Viewview,intposition,longid){
mUserItemClickListener.onItemClick(parent,view,position,id);
}
super(context);
}
publicDynamicGridView(Contextcontext,AttributeSetattrs){
init(context);
super(context,attrs,defStyle);
}
publicvoidsetOnDropListener(OnDropListenerdropListener){
}
publicvoidstartEditMode(){
if(isPostHoneycomb()&&mWobbleInEditMode)
}
publicvoidstopEditMode(){
if(isPostHoneycomb()&&mWobbleInEditMode)
}
publicbooleanisEditMode(){
}
publicbooleanisWobbleInEditMode(){
}
publicvoidsetWobbleInEditMode(booleanwobbleInEditMode){
}
@Override
mUserLongClickListener=listener;
}
@Override
this.mUserItemClickListener=listener;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
for(inti=0;i<getChildCount();i++){
if(v!=null&&Boolean.TRUE!=v.getTag(R.id.dynamic_grid_wobble_tag)){
animateWobble(v);
animateWobbleInverse(v);
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
for(AnimatorwobbleAnimator:mWobbleAnimators){
}
for(inti=0;i<getChildCount();i++){
if(v!=null){
v.setTag(R.id.dynamic_grid_wobble_tag,false);
}
privatevoidrestartWobble(){
startWobbleAnimation();
//设置ScrollListener,当滚动后继续判断switchItem
DisplayMetricsmetrics=context.getResources().getDisplayMetrics();
privatevoidanimateWobble(Viewv){
animator.setFloatValues(-2,2);
mWobbleAnimators.add(animator);
privatevoidanimateWobbleInverse(Viewv){
animator.setFloatValues(2,-2);
mWobbleAnimators.add(animator);
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
ObjectAnimatoranimator=newObjectAnimator();
animator.setRepeatMode(ValueAnimator.REVERSE);
animator.setPropertyName("rotation");
returnanimator;
privatevoidreorderElements(intoriginalPosition,inttargetPosition){
}
privateintgetColumnCount(){
}
privateAbstractDynamicGridAdaptergetAdapterInterface(){
}
/**
*size.Thehovercell'sBitmapDrawableisdrawnontopofthebitmapevery
*/
inth=v.getHeight();
intleft=v.getLeft();
Bitmapb=getBitmapFromView(v);
BitmapDrawabledrawable=newBitmapDrawable(getResources(),b);
mHoverCellOriginalBounds=newRect(left,top,left+w,top+h);
}
/**
*/
Bitmapbitmap=Bitmap.createBitmap(v.getWidth(),v.getHeight(),Bitmap.Config.ARGB_8888);
v.draw(canvas);
}
intdraggedPos=getPositionForID(itemId);
if(draggedPos!=pos){
}
}
/**
code>
*/
Viewv=getViewForId(itemId);
return-1;
returngetPositionForView(v);
}
publicViewgetViewForId(longitemId){
AbstractDynamicGridAdapteradapter=((AbstractDynamicGridAdapter)getAdapter());
Viewv=getChildAt(i);
longid=adapter.getItemId(position);
returnv;
}
}
@Override
switch(event.getAction()&MotionEvent.ACTION_MASK){
*记下按下的位置和按下的触点Id,若mIsEditMode已开启,则更新选中的
*启拖拽模式后,在结束的时候没有调用StopEditMode()
caseMotionEvent.ACTION_DOWN:
mDownY=(int)event.getY();
layoutChildren();
mTotalOffsetY=0;
intitemNum=position-getFirstVisiblePosition();
if(selectedView==null){
}else{
mHoverCell=getAndAddHoverView(selectedView);
selectedView.setVisibility(View.INVISIBLE);
updateNeighborViewsForId(mMobileItemId);
}elseif(!isEnabled()){
}
break;
*核心功能在这里了,
*更新镜像位置mHoverCell.setBounds(mHoverCellCurrentBounds);invalidate();
*判断是否Scroll的条件handleMobileCellScroll();
caseMotionEvent.ACTION_MOVE:
break;
mLastEventX=(int)event.getX(pointerIndex);
intdeltaX=mLastEventX-mDownX;
if(mCellIsMobile){
mHoverCellOriginalBounds.top+deltaY+mTotalOffsetY);
invalidate();
mIsMobileScrolling=false;
}
break;
*下面都是结束拖拽,应对不同的情况
caseMotionEvent.ACTION_UP:
if(mDropListener!=null){
}
caseMotionEvent.ACTION_CANCEL:
if(mDropListener!=null){
}
caseMotionEvent.ACTION_POINTER_UP:
*themovementofthehovercellhasended,thenthedraggingevent
*inthelistview.*/
MotionEvent.ACTION_POINTER_INDEX_SHIFT;
if(pointerId==mActivePointerId){
}
default:
}
returnsuper.onTouchEvent(event);
mIsMobileScrolling=handleMobileCellScroll(mHoverCellCurrentBounds);
*computeVerticalScrollOffset计算滑动thumb已滑动的距离
*computeVerticalScrollRange计算thumb所表示的高度,一般为视图高度
publicbooleanhandleMobileCellScroll(Rectr){
intheight=getHeight();
intrange=computeVerticalScrollRange();
inthoverHeight=r.height();
if(hoverViewTop<=0&&offset>0){
returntrue;