Android自定义gridView仿头条频道拖动管理功能
程序员文章站
2022-07-05 19:35:47
项目中遇到这样个需求:app的功能导航需要可拖动排序,类似头条中的频道拖动管理。效果如下,gif不是很顺畅,真机会好很多。
虽然类似的文章网上搜一下有很多,但写的都不令人满意,注...
项目中遇到这样个需求:app的功能导航需要可拖动排序,类似头条中的频道拖动管理。效果如下,gif不是很顺畅,真机会好很多。
虽然类似的文章网上搜一下有很多,但写的都不令人满意,注释不清晰,而且动画还不够流畅。经本人整理优化后,拿出来供后续有需要的使用。
实现原理:
- gridview作为基本控件
- windowmanager.addview的方式实现可拖动的view
- translateanimation实现移动动画,动画完后更新adapter即可
主要的实现原理上面已经说明,源码中关键的地点也有注释,因此下面直接上源码。
package com.hai.draggrid; import android.content.context; import android.graphics.bitmap; import android.graphics.pixelformat; import android.util.attributeset; import android.view.gravity; import android.view.motionevent; import android.view.view; import android.view.windowmanager; import android.view.animation.animation; import android.view.animation.translateanimation; import android.widget.adapterview; import android.widget.baseadapter; import android.widget.gridview; import android.widget.imageview; /** * 长按拖动图标可以调整item位置的gridview * created by huanghp on 2019/10/15. * email h1132760021@sina.com */ public class draggridview extends gridview { private static final string tag = "draggridview"; private int downx, downy; private int rawx, rawy; private int lastposition = invalid_position; private int viewl, viewt; private int itemheight, itemwidth; private int itemcount; private double dragscale = 1.2d;//拖动view的放大比例 private imageview dragimageview; private windowmanager windowmanager = null; private windowmanager.layoutparams windowparams = null; private boolean ismoving = false; private animation lastanimation; private static final long time_animate = 300; public draggridview(context context, attributeset attrs) { this(context, attrs, 0); } public draggridview(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); setonitemlongclicklistener(new onitemlongclicklistener() { @override public boolean onitemlongclick(adapterview<?> parent, view view, int position, long id) { lastposition = position; view dragview = getchildat(lastposition - getfirstvisibleposition()); itemheight = dragview.getheight(); itemwidth = dragview.getwidth(); itemcount = getcount(); int rows = itemcount / getnumcolumns();// 算出行数 int left = (itemcount % getnumcolumns());// 算出最后一行多余的数量 if (lastposition != invalid_position) { viewl = downx - dragview.getleft(); viewt = downy - dragview.gettop(); dragview.destroydrawingcache(); dragview.setdrawingcacheenabled(true); bitmap bitmap = bitmap.createbitmap(dragview.getdrawingcache()); startdrag(bitmap); dragview.setvisibility(invisible); ismoving = false; ((adapter) getadapter()).setisdrag(true); requestdisallowintercepttouchevent(true); return true; } return false; } }); } private void startdrag(bitmap dragbitmap) { stopdrag(); windowparams = new windowmanager.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; imageview iv = new imageview(getcontext()); iv.setimagebitmap(dragbitmap); windowmanager = (windowmanager) getcontext().getsystemservice(context.window_service); windowmanager.addview(iv, windowparams); dragimageview = iv; } private void stopdrag() { if (dragimageview != null && windowmanager != null) { windowmanager.removeview(dragimageview); dragimageview = null; } } @override public boolean ontouchevent(motionevent ev) { int x = (int) ev.getx(); int y = (int) ev.gety(); switch (ev.getaction()) { case motionevent.action_down: downx = x; downy = y; rawx = (int) ev.getrawx(); rawy = (int) ev.getrawy(); break; case motionevent.action_move: if (dragimageview != null && lastposition != invalid_position) { updatedrag((int) ev.getrawx(), (int) ev.getrawy()); if (!ismoving) onmove(x, y, false); } break; case motionevent.action_up: // log.e(tag, "dragimageview is null=" + (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; } return super.ontouchevent(ev); } private void onmove(int movex, int movey, boolean ismoveup) { final int targetposition = pointtoposition(movex, movey); if (targetposition != invalid_position) { if (targetposition == lastposition) { //移动位置在还未到新item内 return; } //移需要移动的动item数量 int movecount = targetposition - lastposition; if (movecount != 0) { if (ismoveup) {//手指抬起时,不执行动画直接交换数据 adapter adapter = (adapter) getadapter(); adapter.exchange(lastposition, targetposition); lastposition = targetposition; ismoving = false; } else { int movecountabs = math.abs(movecount); float toxvalue = 0, toyvalue = 0; //movexp移动的距离百分比(相对于自己长度的百分比) float movexp = ((float) gethorizontalspacing() / (float) itemwidth) + 1.0f; float moveyp = ((float) getverticalspacing() / (float) itemheight) + 1.0f; int holdposition; // log.d(tag, "start annimation=" + movecountabs); for (int i = 0; i < movecountabs; i++) { //从左往右,或是从上往下 if (movecount > 0) { holdposition = lastposition + i + 1; //同一行 if (lastposition / getnumcolumns() == holdposition / getnumcolumns()) { toxvalue = -movexp; toyvalue = 0; } else if (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; } else if ((holdposition + 1) % getnumcolumns() == 0) { toxvalue = -(getnumcolumns() - 1) * movexp; toyvalue = moveyp; } else { toxvalue = movexp; toyvalue = 0; } } view holdview = getchildat(holdposition); animation moveanimation = createanimation(toxvalue, toyvalue); moveanimation.setanimationlistener(new animation.animationlistener() { @override public void onanimationstart(animation animation) { ismoving = true; } @override public void onanimationrepeat(animation animation) { } @override public void onanimationend(animation animation) { // 如果为最后个动画结束,那执行下面的方法 if (animation == lastanimation) { adapter adapter = (adapter) getadapter(); adapter.exchange(lastposition, targetposition); lastposition = targetposition; ismoving = false; } } }); holdview.startanimation(moveanimation); if (holdposition == targetposition) { lastanimation = moveanimation; } } } } } } public animation createanimation(float toxvalue, float toyvalue) { translateanimation mtranslateanimation = new translateanimation( 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); return mtranslateanimation; } private void updatedrag(int rawx, int rawy) { windowparams.alpha = 0.6f; windowparams.x = rawx - viewl; windowparams.y = rawy - viewt; windowmanager.updateviewlayout(dragimageview, windowparams); } static abstract class adapter extends baseadapter { protected boolean isdrag; protected int holdposition = -1; public void setisdrag(boolean isdrag) { this.isdrag = isdrag; } public void exchange(int startposition, int endpositon) { holdposition = endpositon; } } }
主要的代码就是draggridview,拿到此view实现起来就相当简单了。为了文章完整性,下面也贴上本效果图的主要使用代码。
string[] items = new string[]{"头条", "视频", "娱乐", "体育", "北京", "新时代" , "网易号", "段子", "冰雪运动", "科技", "汽车", "轻松一刻" , "时尚", "直播", "图片", "跟帖", "nba", "态度公开课" , "推荐", "热点", "社会", "趣图", "美女", "军事"}; gridview.setadapter(new draggridview.adapter() { @override public void exchange(int startposition, int endpositon) { super.exchange(startposition, endpositon); string item = list.get(startposition); if (startposition < endpositon) { list.add(endpositon + 1, item); list.remove(startposition); } else { list.add(endpositon, item); list.remove(startposition + 1); } for (int i = 0; i < list.size(); i++) { log.e(tag, "exchange: =" + list.get(i)); } notifydatasetchanged(); } ...省略部分代码 @override public view getview(int position, view convertview, viewgroup parent) { //todo,这里需要优化,没有复用views。也不能按传统方式服用view,否则会造成拖动的view空白 // if (convertview == null) { convertview = getlayoutinflater().inflate(r.layout.item, parent, false); // } ((textview) convertview.findviewbyid(r.id.tv)).settext(getitem(position)); if (isdrag && position == holdposition) { convertview.setvisibility(view.invisible); } else convertview.setvisibility(view.visible); return convertview; } });
本文到这就结束了,有需要的同学拿到*就可以直接使用了,谢谢!
不知道有没有眼尖的同学发现adapterd的getview方法中有个 todo需要优化。原因是这样:如果打开注释中的代码,复用convertview,会造成gridview释放后的新位置一片空白,不知道什么原因,因此折中的方法就是每次都是新生成一个convertview。
希
总结
以上所述是小编给大家介绍的android自定义gridview仿头条频道拖动管理功能,希望对大家有所帮助
上一篇: 深入理解c#多态