最近要用Android开发一个桌面应用,要求桌面的图标能随意拖动,所以第一念头就是用重写GridView来实现这个功能
小编在网上搜罗了别人写好的DragGridView拿来直接用了,下面就是自定义的GridView
参考链接 http://blog.csdn.net/vipzjyno1/article/details/25005851
package com.debla.minicomp.Activity.SimpleMode; import android.content.Context; import android.graphics.Bitmap; import android.graphics.PixelFormat; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.widget.AdapterView; import android.widget.GridView; import android.widget.ImageView; /** * Created by Dave-PC on 2017/2/12. */ public class DragGridView extends GridView { private static final int DRAG_IMG_SHOW = 1; private static final int DRAG_IMG_NOT_SHOW = 0; private static final String LOG_TAG = "DragGridView"; private static final float AMP_FACTOR = 1.2f; /**被拖动的视图*/ private ImageView dragImageView; private WindowManager.LayoutParams dragImageViewParams; private WindowManager windowManager; private static boolean isViewOnDrag = false; /**前一次拖动的位置*/ private int preDraggedOverPositon = AdapterView.INVALID_POSITION; private int downRawX; private int downRawY; public static boolean IsViewOnDrag(){ return isViewOnDrag; } /**长按选中拖动item*/ private OnItemLongClickListener onLongClickListener = new OnItemLongClickListener(){ @Override //长按item开始拖动 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { //记录长按item位置 preDraggedOverPositon = position; //获取被长按item的drawing cache view.destroyDrawingCache(); view.setDrawingCacheEnabled(true); //通过被长按item,获取拖动item的bitmap Bitmap dragBitmap = Bitmap.createBitmap(view.getDrawingCache()); //设置拖动item的参数 dragImageViewParams.gravity = Gravity.TOP | Gravity.LEFT; //设置拖动item为原item 1.2倍 dragImageViewParams.width = (int)(AMP_FACTOR*dragBitmap.getWidth()); dragImageViewParams.height = (int)(AMP_FACTOR*dragBitmap.getHeight()); //设置图片透明度 dragImageViewParams.alpha=(float)0.5; //设置触摸点为绘制拖动item的中心 dragImageViewParams.x = (downRawX - dragImageViewParams.width/2); dragImageViewParams.y = (downRawY - dragImageViewParams.height/2); dragImageViewParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; dragImageViewParams.format = PixelFormat.TRANSLUCENT; dragImageViewParams.windowAnimations = 0; //dragImageView为被拖动item的容器,清空上一次的显示 if((int)dragImageView.getTag() == DRAG_IMG_SHOW) { windowManager.removeView(dragImageView); dragImageView.setTag(DRAG_IMG_NOT_SHOW); } //设置本次被长按的item dragImageView.setImageBitmap(dragBitmap); //添加拖动item到屏幕 windowManager.addView(dragImageView, dragImageViewParams); dragImageView.setTag(DRAG_IMG_SHOW); isViewOnDrag = true; //设置被长按item不显示 ((GridViewAdapter)getAdapter()).hideView(position); return true; } }; public DragGridView(Context context) { super(context); initView(); } public DragGridView(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public DragGridView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(); } public void initView() { setOnItemLongClickListener(onLongClickListener); //初始化显示被拖动item的image view dragImageView = new ImageView(getContext()); dragImageView.setTag(DRAG_IMG_NOT_SHOW); //初始化用于设置dragImageView的参数对象 dragImageViewParams = new WindowManager.LayoutParams(); //获取窗口管理对象,用于后面向窗口中添加dragImageView windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec); } @Override public boolean onTouchEvent(MotionEvent ev) { //被按下时记录按下的坐标 if(ev.getAction() == MotionEvent.ACTION_DOWN) { //获取触摸点相对于屏幕的坐标 downRawX = (int)ev.getRawX(); downRawY = (int)ev.getRawY(); } //dragImageView处于被拖动时,更新dragImageView位置 else if((ev.getAction() == MotionEvent.ACTION_MOVE) && (isViewOnDrag == true)) { Log.i(LOG_TAG, "" + ev.getRawX() + " " + ev.getRawY()); //设置触摸点为dragImageView中心 dragImageViewParams.x = (int)(ev.getRawX() - dragImageView.getWidth()/2); dragImageViewParams.y = (int)(ev.getRawY() - dragImageView.getHeight()/2); //更新窗口显示 windowManager.updateViewLayout(dragImageView, dragImageViewParams); //获取当前触摸点的item position int currDraggedPosition = pointToPosition((int)ev.getX(), (int)ev.getY()); //如果当前停留位置item不等于上次停留位置的item,交换本次和上次停留的item if((currDraggedPosition != AdapterView.INVALID_POSITION) && (currDraggedPosition != preDraggedOverPositon)) { ((GridViewAdapter)getAdapter()).swapView(preDraggedOverPositon, currDraggedPosition); preDraggedOverPositon = currDraggedPosition; } } //释放dragImageView else if((ev.getAction() == MotionEvent.ACTION_UP) && (isViewOnDrag == true)) { ((GridViewAdapter)getAdapter()).showHideView(); if((int)dragImageView.getTag() == DRAG_IMG_SHOW) { windowManager.removeView(dragImageView); dragImageView.setTag(DRAG_IMG_NOT_SHOW); } isViewOnDrag = false; } return super.onTouchEvent(ev); } }
其中GridView的Item布局
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="5px" android:background="#000000"> <LinearLayout android:id="@+id/item_view" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/item_img" android:layout_marginTop="20dp" android:scaleType="centerCrop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:padding="3dp" android:layout_centerHorizontal="true" /> <TextView android:id="@+id/item_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="28sp" android:layout_gravity="center"/> </LinearLayout> </LinearLayout>重写的BaseAdapter
package com.debla.minicomp.Activity.SimpleMode; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.debla.minicomp.Activity.Public.ItemView; import com.debla.minicomp.Activity.Public.ScreenUtils; import com.debla.minicomp.minicomp.R; import java.util.List; /** * Created by Dave-PC on 2017/2/12. */ public class GridViewAdapter extends BaseAdapter{ private Context context; private List<ItemView> list; //public static int ROW_NUMBER = 3; //private int[] picIds = null; //private DragGridView mGv; private int hidePosition = AdapterView.INVALID_POSITION; public GridViewAdapter(Context context, List<ItemView> list) { //this.mGv=mGv; this.context = context; this.list = list; } @Override public int getCount() { return list.size(); } @Override public Object getItem(int position) { return list.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { View view; if(convertView == null) { //view = new ImageView(context); view = LayoutInflater.from(context).inflate(R.layout.grid_layout,null); } else { view = (View)convertView; } //hide时隐藏Text if(position != hidePosition) { //view.setText(strList.get(position)); LinearLayout layout = (LinearLayout)view.findViewById(R.id.item_view); layout.setBackgroundColor(list.get(position).getColor()); ImageView img = (ImageView)view.findViewById(R.id.item_img); img.setScaleType(ImageView.ScaleType.FIT_CENTER); img.setImageResource(list.get(position).getItem_img()); TextView tv = (TextView) view.findViewById(R.id.item_title); tv.setText(list.get(position).getItem_title()); view.setVisibility(View.VISIBLE); } else { //view.setText(""); view.setVisibility(View.INVISIBLE); } int screenWidth = ScreenUtils.getScreenWidth(context); int screenHeight = ScreenUtils.getScreenHeight(context); int viewWidth = screenWidth/2; int viewHeight = screenHeight/3; AbsListView.LayoutParams params = new AbsListView.LayoutParams(viewWidth,viewHeight-20); view.setLayoutParams(params); //view.setScaleType(ImageView.ScaleType.CENTER); view.setId(position); //Log.e("debug",String.valueOf(params)); return view; } public void hideView(int pos) { hidePosition = pos; notifyDataSetChanged(); } public void showHideView() { hidePosition = AdapterView.INVALID_POSITION; notifyDataSetChanged(); } public void removeView(int pos) { //strList.remove(pos); list.remove(pos); notifyDataSetChanged(); } //更新拖动时的gridView public void swapView(int draggedPos, int destPos) { //从前向后拖动,其他item依次前移 if(draggedPos < destPos) { list.add(destPos+1, (ItemView)getItem(draggedPos)); list.remove(draggedPos); } //从后向前拖动,其他item依次后移 else if(draggedPos > destPos) { list.add(destPos, (ItemView)getItem(draggedPos)); list.remove(draggedPos+1); } hidePosition = destPos; notifyDataSetChanged(); } }需要存放在List中的ItemView类的定义,要包含图标的背景颜色,图片,以及文字描述,这些都要在GridView的元素中显示
package com.debla.minicomp.Activity.Public; /** * Created by Dave-PC on 2017/2/13. */ public class ItemView { private int Color; private int item_img; private String item_title; public int getColor() { return Color; } public void setColor(int color) { Color = color; } public String getItem_title() { return item_title; } public void setItem_title(String item_title) { this.item_title = item_title; } public int getItem_img() { return item_img; } public void setItem_img(int item_img) { this.item_img = item_img; } }在这里我是用ViewPager和Fragment来实现滑动分页的,因此在对应的Fragment中定义我们的DragGridView对象,findViewById来绑定布局,再通过setAdapter()方法来实现数据绑定,当然在绑定前需要初始化数据,这里我就不描述了。
在这些代码完成后应该就已经完成主要功能了
然而就在小编以为能够完美地跑起来的时候,却发现了一个严重的bug,由于在DragGridView中拖拽元素的时候,会和ViewPager的滑动产生冲突,造成拖拽时卡住
在各种百度膜拜大神之后,找到了第一个解决方法:重写ViewPager,通过判断滑动的程度(手指滑动的距离)来分离ViewPager和DragGridView的滑动事件,相关链接
http://blog.csdn.net/qiumeqiu/article/details/46652005
在参考了那段代码后小编跑起来一看,诶,果然有效,但是整个滑动ViewPager的效果变得特别差而且依然有可能发生冲突,最后小编灵机一动,在DragGridView中将
isViewOnDrag变量变成static类型,这个变量就是用来标识DragGridView的拖拽状态,写下这个变量的getter方法后,我们只需要在ViewPager方法中写入对DragGridView的拖拽状态进行判断,来实现滑动分离,下面是重写的ViewPager代码
package com.debla.minicomp.Activity.Public; import android.content.Context; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.MotionEvent; import com.debla.minicomp.Activity.SimpleMode.DragGridView; /** * Created by Dave-PC on 2017/2/13. * 自定义的ViewPager,用于防止DragView在拖拽的时候触发ViewPager对其滑动做设置 * */ public class MyViewPager extends ViewPager { private float xPosition;// 手指触摸点的x轴坐标 private final float MINIMUM_DISTANCE = 100.0f;// 手指移动的最小距离 private int distance;// 根据屏幕密度计算出来的,手指移动的最小距离 public static int dip2px (Context context, float dpValue) { final float scale = context.getResources ().getDisplayMetrics ().density; return (int) (dpValue * scale + 0.5f); } public MyViewPager (Context context) { super (context); distance = dip2px (context,MINIMUM_DISTANCE); } public MyViewPager (Context context, AttributeSet attrs) { super (context, attrs); distance = dip2px (context,MINIMUM_DISTANCE); } @Override public boolean onInterceptTouchEvent (MotionEvent ev) { // 先保存手指按下的x轴的坐标 if (ev.getAction () == MotionEvent.ACTION_DOWN) xPosition = ev.getX (); if (ev.getAction () == MotionEvent.ACTION_MOVE) { /* 计算手指移动时的坐标跟按下的坐标之间的绝对值,如果超过给定的值, 就认为viewpager需要滚动。通过调节distance的大小,可以改变滑动 灵敏度 */ if(DragGridView.IsViewOnDrag()) return false; if (Math.abs (ev.getX () - xPosition) < distance) return false; else// 意思就是:touch事件已经被PeopleViewPager自己消费了,不会传递到子控件 return true; } // 其他情况,依旧保持默认的处理方法 return super.onInterceptTouchEvent (ev); } }最终程序完美的运行,能够实现GridView的拖拽,而且不受ViewPager冲突的影响,最后感谢那些前辈们的帮助。