欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android自定义gridView仿头条频道拖动管理功能

程序员文章站 2022-03-31 14:08:56
项目中遇到这样个需求:app的功能导航需要可拖动排序,类似头条中的频道拖动管理。效果如下,gif不是很顺畅,真机会好很多。 虽然类似的文章网上搜一下有很多,但写的都不令人满意,注...

项目中遇到这样个需求:app的功能导航需要可拖动排序,类似头条中的频道拖动管理。效果如下,gif不是很顺畅,真机会好很多。

Android自定义gridView仿头条频道拖动管理功能

虽然类似的文章网上搜一下有很多,但写的都不令人满意,注释不清晰,而且动画还不够流畅。经本人整理优化后,拿出来供后续有需要的使用。

实现原理:

  • 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仿头条频道拖动管理功能,希望对大家有所帮助