Android custom GridView imitation headline channel drag management function

There is a requirement in the project: the navigation function of the app needs drag sorting, which is similar to the channel drag management in the headlines. The effect is as follows. GIF is not very smooth. It's a lot better.

Although there are many similar articles searched online, they are not satisfactory, the notes are not clear, and the animation is not smooth enough. After my sorting and optimization, take it out for subsequent use.

Implementation principle:

The main implementation principle has been explained above, and the key places in the source code also have comments. Therefore, the following is the source code directly.

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,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,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,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;
  }
 }
}

The main code is draggridview, which is quite simple to implement. For the integrity of the article, the main use code of this effect diagram is also pasted below.

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;
      }
    });

This article is over. Students in need can use the wheel directly. Thank you!

I don't know if any sharp eyed students find that there is a todo in the getview method of adapter that needs to be optimized. The reason is this: if you open the code in the comment and reuse the convertview, the new position of the GridView will be blank. I don't know why. Therefore, the compromise method is to generate a new convertview every time. Hope

summary

The above is the Android custom GridView imitation headline channel drag management function introduced by Xiaobian. I hope it will be helpful to you. If you have any questions, please leave me a message, and Xiaobian will reply to you in time. Thank you very much for your support to our website! If you think this article is helpful to you, welcome to reprint, please indicate the source, thank you!

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>