服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - Android自定义gridView仿头条频道拖动管理功能

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

2022-11-13 15:03梦天2015 Android

这篇文章主要介绍了Android自定义gridView仿头条频道拖动管理功能,本文通过实例代码效果图展示给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下

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

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

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

实现原理:

  • gridView作为基本控件
  • WindowManager.addView的方式实现可拖动的view
  • TranslateAnimation实现移动动画,动画完后更新adapter即可

主要的实现原理上面已经说明,源码中关键的地点也有注释,因此下面直接上源码。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
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实现起来就相当简单了。为了文章完整性,下面也贴上本效果图的主要使用代码。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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仿头条频道拖动管理功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对服务器之家网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

原文链接:https://blog.csdn.net/u014763302/article/details/103682923

延伸 · 阅读

精彩推荐