android 自定义View开发实战(六) 可拖动的GridView
2017-01-06 19:52
901 查看
1前言
由于项目需求,需要把项目的主界面采用GridView显示,并且需要根据模块优先级支持拖动图标(砍死产品狗)。为此,自定义了一个支持拖拽图标的GridView。效果如下:具体效果如上图
2 可拖拽的GridView实现
要实现上面的效果有两个难点,第一就是如何创造一个可拖动的View在我们的Activity界面上。第二个就是如何实现两个View的交换关于第一个:我们可以用WindowManager 来往我们的界面上添加View,这样我们再重写GridView的onTouchEvent()方法,根据移动的距离来更新的我们View的位置即可
关于第二个:可以这样实现,当我们拖动时,创建一个透明度低一点的镜像item View。把要拖动的item对应的View先隐藏起来,此时Adapter的item先不交换,当我们把拖动的item移动到另一个item对应的范围内,我们再进行交换,先把这个item隐藏掉,然后在原来的位置显示出这个item。最后镜像item对应的view 再消失。
其实关于第二点,也有其他的交换策略,比如判断拖到镜像view到另一个item之上一段时间再进行交换等。
1 实现思路
好了,下面我们仔细总结了一下思路,有了思路我们就很好办了:1.根据手指按下的X,Y坐标来获取我们在GridView上面点击的item,再获取对应的View
2.手指按下的时候使用Handler和Runnable来实现一个定时器,假如定时时间为1000毫秒,在1000毫秒内,如果手指抬起了就移除定时器,没有抬起并且手指点击在GridView的item所在的区域,则表示我们长按了GridView的item
3. 如果我们长按了item则隐藏item,然后使用WindowManager来添加一个item的镜像在屏幕用来代替刚刚隐藏的item
4.当我们手指在屏幕移动的时候,更新item镜像的位置,然后在根据我们移动的X,Y的坐标来获取移动到GridView的哪一个位置
5. 到GridView的item过多的时候,可能一屏幕显示不完,我们手指拖动item镜像到屏幕下方,要触发GridView想上滚动,同理,当我们手指拖动item镜像到屏幕上面,触发GridView向下滚动
6.GridView交换数据,刷新界面,移除item的镜像
2 实现代码
这里定义一个XGridView继承GridView来实现代码如下,加了详细的注释,应该容易看懂
package com.qiyei.javatest; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Handler; import android.os.Vibrator; import android.util.AttributeSet; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.widget.GridView; import android.widget.ImageView; /** * Created by qiyei2015 on 2017/1/5. */ public class XGridView extends GridView { //拖拽响应的时间 默认为1s private long mDragResponseMs = 1000; //是否支持拖拽,默认不支持 private boolean isDrag = false; //振动器,用于提示替换 private Vibrator mVibrator; //拖拽的item的position private int mDragPosition; //拖拽的item对应的View private View mDragView; //窗口管理器,用于为Activity上添加拖拽的View private WindowManager mWindowManager; //item镜像的布局参数 private WindowManager.LayoutParams mLayoutParams; //item镜像的 显示镜像,这里用ImageView显示 private ImageView mDragMirrorView; //item镜像的bitmap private Bitmap mDragBitmap; //按下的点到所在item的左边缘距离 private int mPoint2ItemLeft; private int mPoint2ItemTop; //DragView到上边缘的距离 private int mOffset2Top; private int mOffset2Left; //按下时x,y private int mDownX; private int mDownY; //移动的时x.y private int mMoveX; private int mMoveY; //状态栏高度 private int mStatusHeight; //XGridView向下滚动的边界值 private int mDownScrollBorder; //XGridView向上滚动的边界值 private int mUpScrollBorder; //滚动的速度 private int mSpeed = 20; //item发生变化的回调接口 private OnItemChangeListener changeListener; private Handler mHandler; /** * 长按的Runnable */ private Runnable mLongClickRunable = new Runnable() { @Override public void run() { isDrag = true; mVibrator.vibrate(200); //隐藏该item mDragView.setVisibility(INVISIBLE); //在点击的地方创建并显示item镜像 createDragView(mDragBitmap,mDownX,mDownY); } }; /** * 当moveY的值大于向上滚动的边界值,触发GridView自动向上滚动 * 当moveY的值小于向下滚动的边界值,触犯GridView自动向下滚动 * 否则不进行滚动 */ private Runnable mScrollRunbale = new Runnable() { @Override public void run() { int scrollY = 0; if (mMoveY > mUpScrollBorder){ scrollY = mSpeed; mHandler.postDelayed(mScrollRunbale,25); }else if (mMoveY < mDownScrollBorder){ scrollY = -mSpeed; mHandler.postDelayed(mScrollRunbale,25); }else { scrollY = 0; mHandler.removeCallbacks(mScrollRunbale); } smoothScrollBy(scrollY,10); } }; public XGridView(Context context) { this(context,null); } public XGridView(Context context, AttributeSet attrs) { this(context, attrs,0); } public XGridView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); mHandler = new Handler(); mStatusHeight = getStatusHeight(context); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: mDownX = (int)ev.getX(); mDownY = (int)ev.getY(); //获取按下的position mDragPosition = pointToPosition(mDownX,mDownY); if (mDragPosition == INVALID_POSITION){ //无效就返回 return super.dispatchTouchEvent(ev); } //延时长按执行mLongClickRunable mHandler.postDelayed(mLongClickRunable,mDragResponseMs); //获取按下的item对应的View 由于存在复用机制,所以需要 处理FirstVisiblePosition mDragView = getChildAt(mDragPosition - getFirstVisiblePosition()); if (mDragView == null){ return super.dispatchTouchEvent(ev); } //计算按下的点到所在item的left top 距离 mPoint2ItemLeft = mDownX - mDragView.getLeft(); mPoint2ItemTop = mDownY - mDragView.getTop(); //计算GridView的left top 偏移量:原始距离 - 相对距离就是偏移量 mOffset2Left = (int)ev.getRawX() - mDownX; mOffset2Top = (int)ev.getRawY() - mDownY; //获取GridView自动向下滚动的偏移量,小于这个值,DragGridView向下滚动 mDownScrollBorder = getHeight() /4; //获取GridView自动向上滚动的偏移量,大于这个值,DragGridView向上滚动 mUpScrollBorder = getHeight() * 3/4; //开启视图缓存 mDragView.setDrawingCacheEnabled(true); //获取缓存的中的bitmap镜像 包含了item中的ImageView和TextView mDragBitmap = Bitmap.createBitmap(mDragView.getDrawingCache()); //释放视图缓存 避免出现重复的镜像 mDragView.destroyDrawingCache(); break; case MotionEvent.ACTION_MOVE: mMoveX = (int)ev.getX(); mMoveY = (int)ev.getY(); //如果只在按下的item上移动,未超过边界,就不移除mLongClickRunable if (!isTouchInItem(mDragView,mMoveX,mMoveY)){ mHandler.removeCallbacks(mLongClickRunable); } break; case MotionEvent.ACTION_UP: mHandler.removeCallbacks(mLongClickRunable); mHandler.removeCallbacks(mScrollRunbale); break; default: break; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { if (isDrag && mDragMirrorView != null){ switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: mMoveX = (int)ev.getX(); mMoveY = (int)ev.getY(); onDragItem(mMoveX,mMoveY); break; case MotionEvent.ACTION_UP: onStopDrag(); isDrag = false; break; default: break; } return true; } return super.onTouchEvent(ev); } /************************对外提供的接口***************************************/ public boolean isDrag() { return isDrag; } public void setDrag(boolean drag) { isDrag = drag; } public long getDragResponseMs() { return mDragResponseMs; } public void setDragResponseMs(long mDragResponseMs) { this.mDragResponseMs = mDragResponseMs; } public void setOnItemChangeListener(OnItemChangeListener changeListener) { this.changeListener = changeListener; } /******************************************************************************/ /** * 点是否在该View上面 * @param view * @param x * @param y * @return */ private boolean isTouchInItem(View view, int x, int y) { if (view == null){ return false; } if (view.getLeft() < x && x < view.getRight() && view.getTop() < y && y < view.getBottom()){ return true; }else { return false; } } /** * 获取状态栏的高度 * @param context * @return */ private static int getStatusHeight(Context context){ int statusHeight = 0; Rect localRect = new Rect(); ((Activity) context).getWindow().getDecorView().getWindowVisibleDisplayFrame(localRect); statusHeight = localRect.top; if (0 == statusHeight){ Class<?> localClass; try { localClass = Class.forName("com.android.internal.R$dimen"); Object localObject = localClass.newInstance(); int height = Integer.parseInt(localClass.getField("status_bar_height").get(localObject).toString()); statusHeight = context.getResources().getDimensionPixelSize(height); } catch (Exception e) { e.printStackTrace(); } } return statusHeight; } /** * 停止拖动 */ private void onStopDrag() { View view = getChildAt(mDragPosition - getFirstVisiblePosition()); if (view != null){ view.setVisibility(VISIBLE); } removeDragImage(); } /** * WindowManager 移除镜像 */ private void removeDragImage() { if (mDragMirrorView != null){ mWindowManager.removeView(mDragMirrorView); mDragMirrorView = null; } } /** * 拖动item到指定位置 * @param x * @param y */ private void onDragItem(int x, int y) { mLayoutParams.x = x - mPoint2ItemLeft + mOffset2Left; mLayoutParams.y = y - mPoint2ItemTop + mOffset2Top - mStatusHeight; //更新镜像位置 mWindowManager.updateViewLayout(mDragMirrorView,mLayoutParams); onSwapItem(x,y); mHandler.post(mScrollRunbale); } /** * 交换 item 并且控制 item之间的显示与隐藏 * @param x * @param y */ private void onSwapItem(int x, int y) { //获取我们手指移动到那个item int tmpPosition = pointToPosition(x,y); if (tmpPosition != INVALID_POSITION && tmpPosition != mDragPosition){ if (changeListener != null){ changeListener.onChange(mDragPosition,tmpPosition); } //隐藏tmpPosition getChildAt(tmpPosition - getFirstVisiblePosition()).setVisibility(INVISIBLE); //显示之前的item getChildAt(mDragPosition - getFirstVisiblePosition()).setVisibility(VISIBLE); mDragPosition = tmpPosition; } } /** * 创建拖动的镜像 * @param bitmap * @param downX * @param downY */ private void createDragView(Bitmap bitmap, int downX, int downY) { mLayoutParams = new WindowManager.LayoutParams(); mLayoutParams.format = PixelFormat.TRANSLUCENT; //图片之外其他地方透明 mLayoutParams.gravity = Gravity.TOP | Gravity.LEFT; //左 上 //指定位置 其实就是 该 item 对应的 rawX rawY 因为Window 添加View是需要知道 raw x ,y的 mLayoutParams.x = mOffset2Left + (downX - mPoint2ItemLeft); mLayoutParams.y = mOffset2Top + (downY - mPoint2ItemTop) + mStatusHeight; //指定布局大小 mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT; mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; //透明度 mLayoutParams.alpha = 0.4f; //指定标志 不能获取焦点和触摸 mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; mDragMirrorView = new ImageView(getContext()); mDragMirrorView.setImageBitmap(bitmap); //添加View到窗口中 mWindowManager.addView(mDragMirrorView,mLayoutParams); } /** * item 交换时的回调接口 */ public interface OnItemChangeListener{ void onChange(int from,int to); }
3 Demo测试
下面是测试XGridView的Demo1 xml
activity_test.xml:<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_test" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.qiyei.javatest.TestActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="的顶顶顶顶顶" android:id="@+id/tv1"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btn1" android:text="属性动画"/> <com.qiyei.javatest.XGridView android:layout_width="match_parent" android:layout_height="wrap_content" android:listSelector="@android:color/transparent" android:cacheColorHint="@android:color/transparent" android:layout_margin="10dp" android:id="@+id/x_grid_view" android:numColumns="4" android:gravity="center" android:verticalSpacing="10dp" android:horizontalSpacing="10dp" android:stretchMode="columnWidth"> </com.qiyei.javatest.XGridView> </LinearLayout>
item布局文件
item_grid_view.xml:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:id="@+id/item_imv" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/item_imv" android:layout_centerHorizontal="true" android:id="@+id/item_tv" /> </RelativeLayout>
2 java 代码
TestActivity.java:package com.qiyei.javatest; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.SimpleAdapter; import android.widget.TextView; import android.widget.Toast; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; public class TestActivity extends AppCompatActivity { private Context mContext; private Button btn1; private TextView tv1; private XGridView xGridView; private List<HashMap<String,Object>> dataSourceList = new ArrayList<>(); private SimpleAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); initData(); initView(); setListener(); } private void initView() { tv1 = (TextView) findViewById(R.id.tv1); btn1 = (Button) findViewById(R.id.btn1); xGridView = (XGridView) findViewById(R.id.x_grid_view); xGridView.setAdapter(adapter); xGridView.setOnItemChangeListener(new XGridView.OnItemChangeListener() { @Override public void onChange(int from, int to) { Toast.makeText(mContext,"From:" + from + " To:" + to,Toast.LENGTH_LONG); HashMap<String, Object> temp = dataSourceList.get(from); //直接交互 //Collections.swap(dataSourceList,from,to); //非直接交互 这里的处理需要注意下 排序交换 if(from < to){ for(int i = from; i < to; i++){ Collections.swap(dataSourceList, i, i+1); } }else if(from > to){ for(int i = from; i > to; i--){ Collections.swap(dataSourceList, i, i-1); } } dataSourceList.set(to, temp); adapter.notifyDataSetChanged(); } }); } private void initData() { mContext = this; for (int i = 0; i < 30;i++){ HashMap<String,Object> itemMap = new HashMap<>(); itemMap.put("item_image",R.drawable.icon_alipay_main_icon); itemMap.put("item_text","拖拽" + i); dataSourceList.add(itemMap); } adapter = new SimpleAdapter(this,dataSourceList,R.layout.item_grid_view ,new String[]{"item_image","item_text"},new int[]{R.id.item_imv,R.id.item_tv}); } private void setListener() { final ValueAnimator animator = ValueAnimator.ofInt(0,400); animator.setDuration(1000); final ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(btn1,"translationX",20f,400f); objectAnimator.setDuration(1000); final ObjectAnimator rotateAnimator = ObjectAnimator.ofFloat(btn1,"rotation",10f,270f); rotateAnimator.setDuration(2000); final AnimatorSet set = new AnimatorSet(); set.play(objectAnimator).with(rotateAnimator); btn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //animator.start(); //objectAnimator.start(); set.start(); } }); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (int) animation.getAnimatedValue(); tv1.setTranslationX(value); } }); } }
请忽略动画相关的部分,我这里加入了一些动画,也是做一些测试的
4 效果
效果一开始就给出来了,我们不再贴了。完整的Demo欢迎访问我的github源码下载
相关文章推荐
- android 自定义View开发实战(五) TextView滚动显示
- Android开发之自定义View专题(三):自定义GridView
- android 自定义View开发实战(二) CustomCircleView
- android开发自定义View,可以自由拖动的控件
- android 自定义View开发实战(四) 圆角矩形ImageView实现
- Android开发 自定义悬浮可拖动view
- [Android开发] 自定义View之GridView单选 金额选择Layout-ChooseMoneyLayout
- android 自定义View开发实战(一) CustomTitleView
- Android开发实战2----圆点导航指示器(使用自定义View实现)
- android 自定义View开发实战(三) 自定义ViewGroup--FourLayout
- Android游戏开发教程之六:自定义View详解
- Android软件开发之盘点自定义View界面大合集
- Android开发之自定义View(视图)
- 【Android开发:UI优化系列一】ViewStub的实战开发
- Android开发-将自定义View布局到Layout中并调用
- Android开发:setContentView切换界面,自定义带CheckBox的ListView显示SQlite条目-----实现
- android从零开始-开发自定义View-跟随手指移动的小球
- Android软件开发之盘点自定义View界面大合集(二)
- Android软件开发之盘点自定义View界面大合集(二) .
- (android 地图实战开发)3 在地图上显示当前位置和自定义银行位置