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(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。