Android自定义gridView仿头条频道拖动管理功能
项目中遇到这样个需求:app的功能导航需要可拖动排序,类似头条中的频道拖动管理。效果如下,gif不是很顺畅,真机会好很多。
虽然类似的文章网上搜一下有很多,但写的都不令人满意,注释不清晰,而且动画还不够流畅。经本人整理优化后,拿出来供后续有需要的使用。
实现原理:
- gridView作为基本控件
- WindowManager.addView的方式实现可拖动的view
- TranslateAnimation实现移动动画,动画完后更新adapter即可
主要的实现原理上面已经说明,源码中关键的地点也有注释,因此下面直接上源码。
packagecom.hai.draggrid;
importandroid.content.Context;
importandroid.graphics.Bitmap;
importandroid.graphics.PixelFormat;
importandroid.util.AttributeSet;
importandroid.view.Gravity;
importandroid.view.MotionEvent;
importandroid.view.View;
importandroid.view.WindowManager;
importandroid.view.animation.Animation;
importandroid.view.animation.TranslateAnimation;
importandroid.widget.AdapterView;
importandroid.widget.BaseAdapter;
importandroid.widget.GridView;
importandroid.widget.ImageView;
/**
*长按拖动图标可以调整item位置的Gridview
*Createdbyhuanghpon2019/10/15.
*Emailh1132760021@sina.com
*/
publicclassDragGridViewextendsGridView{
privatestaticfinalStringTAG="DragGridView";
privateintdownX,downY;
privateintrawX,rawY;
privateintlastPosition=INVALID_POSITION;
privateintviewL,viewT;
privateintitemHeight,itemWidth;
privateintitemCount;
privatedoubledragScale=1.2D;//拖动view的放大比例
privateImageViewdragImageView;
privateWindowManagerwindowManager=null;
privateWindowManager.LayoutParamswindowParams=null;
privatebooleanisMoving=false;
privateAnimationlastAnimation;
privatestaticfinallongTIME_ANIMATE=300;
publicDragGridView(Contextcontext,AttributeSetattrs){
this(context,attrs,0);
}
publicDragGridView(Contextcontext,AttributeSetattrs,intdefStyleAttr){
super(context,attrs,defStyleAttr);
setOnItemLongClickListener(newOnItemLongClickListener(){
@Override
publicbooleanonItemLongClick(AdapterView>parent,Viewview,intposition,longid){
lastPosition=position;
ViewdragView=getChildAt(lastPosition-getFirstVisiblePosition());
itemHeight=dragView.getHeight();
itemWidth=dragView.getWidth();
itemCount=getCount();
introws=itemCount/getNumColumns();//算出行数
intleft=(itemCount%getNumColumns());//算出最后一行多余的数量
if(lastPosition!=INVALID_POSITION){
viewL=downX-dragView.getLeft();
viewT=downY-dragView.getTop();
dragView.destroyDrawingCache();
dragView.setDrawingCacheEnabled(true);
Bitmapbitmap=Bitmap.createBitmap(dragView.getDrawingCache());
startDrag(bitmap);
dragView.setVisibility(INVISIBLE);
isMoving=false;
((Adapter)getAdapter()).setIsDrag(true);
requestDisallowInterceptTouchEvent(true);
returntrue;
}
returnfalse;
}
});
}
privatevoidstartDrag(BitmapdragBitmap){
stopDrag();
windowParams=newWindowManager.LayoutParams();
windowParams.gravity=Gravity.TOP|Gravity.LEFT;
//得到preview左上角相对于屏幕的坐标
windowParams.x=rawX-viewL;
windowParams.y=rawY-viewT;
//设置拖拽item的宽和高
windowParams.width=(int)(dragScale*dragBitmap.getWidth());//放大dragScale倍,可以设置拖动后的倍数
windowParams.height=(int)(dragScale*dragBitmap.getHeight());//放大dragScale倍,可以设置拖动后的倍数
this.windowParams.flags=WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
|WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
this.windowParams.format=PixelFormat.TRANSLUCENT;
this.windowParams.windowAnimations=0;
ImageViewiv=newImageView(getContext());
iv.setImageBitmap(dragBitmap);
windowManager=(WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
windowManager.addView(iv,windowParams);
dragImageView=iv;
}
privatevoidstopDrag(){
if(dragImageView!=null&&windowManager!=null){
windowManager.removeView(dragImageView);
dragImageView=null;
}
}
@Override
publicbooleanonTouchEvent(MotionEventev){
intx=(int)ev.getX();
inty=(int)ev.getY();
switch(ev.getAction()){
caseMotionEvent.ACTION_DOWN:
downX=x;
downY=y;
rawX=(int)ev.getRawX();
rawY=(int)ev.getRawY();
break;
caseMotionEvent.ACTION_MOVE:
if(dragImageView!=null&&lastPosition!=INVALID_POSITION){
updateDrag((int)ev.getRawX(),(int)ev.getRawY());
if(!isMoving)onMove(x,y,false);
}
break;
caseMotionEvent.ACTION_UP:
//Log.e(TAG,"dragImageViewisnull="+(dragImageView==null)+",lastposition="+lastPosition
//+",pointToPosition="+pointToPosition(x,y)+",ismove="+isMoving);
if(dragImageView!=null&&lastPosition!=INVALID_POSITION){
//if(isMoving)onMove(x,y,true);//动画还未执行完的情况下,重设动画会清除之前设置的动画。
stopDrag();
((Adapter)getAdapter()).setIsDrag(false);
((BaseAdapter)getAdapter()).notifyDataSetChanged();
requestDisallowInterceptTouchEvent(false);
}
break;
}
returnsuper.onTouchEvent(ev);
}
privatevoidonMove(intmoveX,intmoveY,booleanisMoveUp){
finalinttargetPosition=pointToPosition(moveX,moveY);
if(targetPosition!=INVALID_POSITION){
if(targetPosition==lastPosition){
//移动位置在还未到新item内
return;
}
//移需要移动的动ITEM数量
intmoveCount=targetPosition-lastPosition;
if(moveCount!=0){
if(isMoveUp){//手指抬起时,不执行动画直接交换数据
Adapteradapter=(Adapter)getAdapter();
adapter.exchange(lastPosition,targetPosition);
lastPosition=targetPosition;
isMoving=false;
}else{
intmoveCountAbs=Math.abs(moveCount);
floattoXvalue=0,toYvalue=0;
//moveXP移动的距离百分比(相对于自己长度的百分比)
floatmoveXP=((float)getHorizontalSpacing()/(float)itemWidth)+1.0f;
floatmoveYP=((float)getVerticalSpacing()/(float)itemHeight)+1.0f;
intholdPosition;
//Log.d(TAG,"startannimation="+moveCountAbs);
for(inti=0;i0){
holdPosition=lastPosition+i+1;
//同一行
if(lastPosition/getNumColumns()==holdPosition/getNumColumns()){
toXvalue=-moveXP;
toYvalue=0;
}elseif(holdPosition%getNumColumns()==0){
toXvalue=(getNumColumns()-1)*moveXP;
toYvalue=-moveYP;
}else{
toXvalue=-moveXP;
toYvalue=0;
}
}else{
//从右往左,或是从下往上
holdPosition=lastPosition-i-1;
if(lastPosition/getNumColumns()==holdPosition/getNumColumns()){
toXvalue=moveXP;
toYvalue=0;
}elseif((holdPosition+1)%getNumColumns()==0){
toXvalue=-(getNumColumns()-1)*moveXP;
toYvalue=moveYP;
}else{
toXvalue=moveXP;
toYvalue=0;
}
}
ViewholdView=getChildAt(holdPosition);
AnimationmoveAnimation=createAnimation(toXvalue,toYvalue);
moveAnimation.setAnimationListener(newAnimation.AnimationListener(){
@Override
publicvoidonAnimationStart(Animationanimation){
isMoving=true;
}
@Override
publicvoidonAnimationRepeat(Animationanimation){
}
@Override
publicvoidonAnimationEnd(Animationanimation){
//如果为最后个动画结束,那执行下面的方法
if(animation==lastAnimation){
Adapteradapter=(Adapter)getAdapter();
adapter.exchange(lastPosition,targetPosition);
lastPosition=targetPosition;
isMoving=false;
}
}
});
holdView.startAnimation(moveAnimation);
if(holdPosition==targetPosition){
lastAnimation=moveAnimation;
}
}
}
}
}
}
publicAnimationcreateAnimation(floattoXValue,floattoYValue){
TranslateAnimationmTranslateAnimation=newTranslateAnimation(
Animation.RELATIVE_TO_SELF,0.0F,Animation.RELATIVE_TO_SELF,toXValue,
Animation.RELATIVE_TO_SELF,0.0F,Animation.RELATIVE_TO_SELF,toYValue);
mTranslateAnimation.setFillAfter(true);//设置一个动画效果执行完毕后,View对象保留在终止的位置。
mTranslateAnimation.setDuration(TIME_ANIMATE);
returnmTranslateAnimation;
}
privatevoidupdateDrag(intrawX,intrawY){
windowParams.alpha=0.6f;
windowParams.x=rawX-viewL;
windowParams.y=rawY-viewT;
windowManager.updateViewLayout(dragImageView,windowParams);
}
staticabstractclassAdapterextendsBaseAdapter{
protectedbooleanisDrag;
protectedintholdPosition=-1;
publicvoidsetIsDrag(booleanisDrag){
this.isDrag=isDrag;
}
publicvoidexchange(intstartPosition,intendPositon){
holdPosition=endPositon;
}
}
}
主要的代码就是DragGridView,拿到此view实现起来就相当简单了。为了文章完整性,下面也贴上本效果图的主要使用代码。
String[]items=newString[]{"头条","视频","娱乐","体育","北京","新时代"
,"网易号","段子","冰雪运动","科技","汽车","轻松一刻"
,"时尚","直播","图片","跟帖","NBA","态度公开课"
,"推荐","热点","社会","趣图","美女","军事"};
gridView.setAdapter(newDragGridView.Adapter(){
@Override
publicvoidexchange(intstartPosition,intendPositon){
super.exchange(startPosition,endPositon);
Stringitem=list.get(startPosition);
if(startPosition
本文到这就结束了,有需要的同学拿到轮子就可以直接使用了,谢谢!
不知道有没有眼尖的同学发现Adapterd的getView方法中有个todo需要优化。原因是这样:如果打开注释中的代码,复用convertView,会造成gridView释放后的新位置一片空白,不知道什么原因,因此折中的方法就是每次都是新生成一个convertView。
希
总结
以上所述是小编给大家介绍的Android自定义gridView仿头条频道拖动管理功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。