Android 实现高仿iOS桌面效果之可拖动的GridView(上)
来源:互联网 发布:时代互联域名证书 编辑:程序博客网 时间:2024/05/23 21:49
转载请标明出处:http://blog.csdn.net/sk719887916/article/details/40074663,作者:skay
最近项目中遇到一个LIstview的拖动效果,github上一搜发现有叫DragListview的开源项目,然后自己再小手一搜拖动排序的GridView,却没发现什么很全很好的开源项目,后来在博客上发现有一遍比较好的拖动排序的文章,可是跟自己的期望的IOS Luncher效果一比,相差那甚远,因此就在别人的代码基础加以开发,好的利用,不好的摒弃。
一 UI和功能分析
发现目前主流的安卓厂商的手机桌面应用已经实现了此效果,也有APP实现的,如有UC浏览器,但是他貌似无抖动效果。先就Ios的桌面效果作如下需求总结:
我们可以把ios的luncher拆分一下 如下图:
特此我们可以将两个fragment加入同一个activty中,当然也可以将两个gridview放到一个线性布局中,即可,先从上面的Gridview进行分析。
1 GridView长按支持拖动排序,并支持Item实时交换。
2 GridView长按Item出现有抖动效果。
3 Item条目有抖动效果,时不需要长按点击就可以进行拖动效果。
4 拖动的Item和被拖动的Item左标完全重合后可新建文件夹
5 长按Item 出现删除按钮,此时点击删除按钮可以任意删除某一item
6 GridView横竖屏排列列数改变,横屏的行数是竖屏幕的列数
- 根据手指按下的X,Y坐标来获取我们在GridView上面点击的item位置
- 根据当前屏幕状态,动态设置gridview的列数。做到横竖屏展现不同个列数的效果。
- 长按手指达不到规定的时间阀值,将无法拖动状态。时间超过将松开手指后,将gridView的子控件一次开启抖动动画。
- 如果我们长按了item则隐藏item,然后使用WindowManager来添加一个item的镜像在屏幕用来代替刚刚隐藏的item
- 当我们手指在屏幕移动的时候,更新item镜像的位置,然后在根据手指移动的X,Y的坐标来确定当前镜像的位置。
- 到GridView的item过多的时候,可能一屏幕显示不完,我们手指拖动item镜像到屏幕下方,要触发GridView想上滚动,同理,当我们手指拖动item镜像到屏幕上面,触发GridView向下滚动
- GridView交换数据,刷新界面,移除item的镜像,显示被影藏的item.
- 当抖动效果出现,点击删除按钮时,为了赠加移动效果,将要删除的item和末位item交换,然后删除lastItem,通知适配器更新数据。
- 抖动效果出现后,如果Onclick,就视为可拖动状态。
接下来就开始实现需求所列的效果了。为了实现上面需求,需要写一个Gridview,Adapter,和抖动动画。包括移动是的动画。
二 新建动画控制器
1 item实现抖动效果
新建一个抖动的动画效果,用于每个item进行抖动。
/** * NeedShake * @return */public boolean isNeedShake() {return mNeedShake;}/** * @param mNeedShake */public void setNeedShake(boolean mNeedShake) {this.mNeedShake = mNeedShake;}/** * ShakeAnimation isRunning * @return */private boolean isShowShake() {return mNeedShake && mStartShake;}/** * start shakeAnimation * @param v */private void shakeAnimation(final View v) {float rotate = 0;int c = mCount++ % 15;if (c == 0) {rotate = DEGREE_0;} else if (c == 1) {rotate = DEGREE_1;} else if (c == 2) {rotate = DEGREE_2;} else if (c == 3) {rotate = DEGREE_3;} else {rotate = DEGREE_4;}final RotateAnimation mra = new RotateAnimation(rotate, -rotate,ICON_WIDTH * mDensity / 4, ICON_HEIGHT * mDensity / 4);final RotateAnimation mrb = new RotateAnimation(-rotate, rotate,ICON_WIDTH * mDensity / 4, ICON_HEIGHT * mDensity / 4);mra.setDuration(ANIMATION_DURATION);mrb.setDuration(ANIMATION_DURATION);mra.setAnimationListener(new AnimationListener() {@Overridepublic void onAnimationEnd(Animation animation) {if (mNeedShake && mStartShake) {mra.reset();v.startAnimation(mrb);}}@Overridepublic void onAnimationRepeat(Animation animation) {}@Overridepublic void onAnimationStart(Animation animation) {}});mrb.setAnimationListener(new AnimationListener() {@Overridepublic void onAnimationEnd(Animation animation) {if (mNeedShake && mStartShake) {mrb.reset();v.startAnimation(mra);}}@Overridepublic void onAnimationRepeat(Animation animation) {}@Overridepublic void onAnimationStart(Animation animation) {}});v.startAnimation(mra);}2 创建item交换是的动画
private AnimatorSet createTranslationAnimations(View view, float startX,float endX, float startY, float endY) {ObjectAnimator animX = ObjectAnimator.ofFloat(view, "translationX",startX, endX);ObjectAnimator animY = ObjectAnimator.ofFloat(view, "translationY",startY, endY);AnimatorSet animSetXY = new AnimatorSet();animSetXY.playTogether(animX, animY);return animSetXY;}/** * item的交换动画效果 * * @param oldPosition * @param newPosition */private void animateReorder(final int oldPosition, final int newPosition) {boolean isForward = newPosition > oldPosition;List<Animator> resultList = new LinkedList<Animator>();if (isForward) {for (int pos = oldPosition; pos < newPosition; pos++) {View view = getChildAt(pos - getFirstVisiblePosition());System.out.println(pos);if ((pos + 1) % mNumColumns == 0) {resultList.add(createTranslationAnimations(view,-view.getWidth() * (mNumColumns - 1), 0,view.getHeight(), 0));} else {resultList.add(createTranslationAnimations(view,view.getWidth(), 0, 0, 0));}}} else {for (int pos = oldPosition; pos > newPosition; pos--) {View view = getChildAt(pos - getFirstVisiblePosition());if ((pos + mNumColumns) % mNumColumns == 0) {resultList.add(createTranslationAnimations(view,view.getWidth() * (mNumColumns - 1), 0,-view.getHeight(), 0));} else {resultList.add(createTranslationAnimations(view,-view.getWidth(), 0, 0, 0));}}}AnimatorSet resultSet = new AnimatorSet();resultSet.playTogether(resultList);resultSet.setDuration(300);resultSet.setInterpolator(new AccelerateDecelerateInterpolator());resultSet.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationStart(Animator animation) {mAnimationEnd = false;}@Overridepublic void onAnimationEnd(Animator animation) {mAnimationEnd = true;}});resultSet.start();}
三 建立Adapter自定义监听器
新建的Adapter用于Item的删除,隐藏,排序等,除了以上方法,也承担adapter适配数据源到grifView上的功能。
ublic interface DragGridListener {/** * 重新排列数据 * @param oldPosition * @param newPosition */public void reorderItems(int oldPosition, int newPosition);/** * 设置某个item隐藏 * @param hidePosition */public void setHideItem(int hidePosition);/** * 删除某个item * @param hidePosition */public void removeItem(int hidePosition);}当然本次还未实现两个item建立文件夹,因此此接口后面还会陆续加入其扩展方法。
四 adpter
用来控制Item的添加和删除,已经隐藏交换等。
public class DragAdapter extends BaseAdapter implements DragGridListener{private List<HashMap<String, Object>> list;private LayoutInflater mInflater;private int mHidePosition = -1;public DragAdapter(Context context, List<HashMap<String, Object>> list){this.list = list;mInflater = LayoutInflater.from(context);}@Overridepublic int getCount() {return list.size();}@Overridepublic Object getItem(int position) {return list.get(position);}@Overridepublic long getItemId(int position) {return position;}/** * 由于复用convertView导致某些item消失了,所以这里不复用item, */@Overridepublic View getView(final int position, View convertView, ViewGroup parent) {convertView = mInflater.inflate(R.layout.grid_item, null);ImageView mImageView = (ImageView) convertView.findViewById(R.id.item_image);TextView mTextView = (TextView) convertView.findViewById(R.id.item_text);mImageView.setImageResource((Integer) list.get(position).get("item_image"));mTextView.setText((CharSequence) list.get(position).get("item_text"));if(position == mHidePosition){convertView.setVisibility(View.INVISIBLE);}return convertView;}@Overridepublic void reorderItems(int oldPosition, int newPosition) {HashMap<String, Object> temp = list.get(oldPosition);if(oldPosition < newPosition){for(int i=oldPosition; i<newPosition; i++){Collections.swap(list, i, i+1);}}else if(oldPosition > newPosition){for(int i=oldPosition; i>newPosition; i--){Collections.swap(list, i, i-1);}}list.set(newPosition, temp);}@Overridepublic void setHideItem(int hidePosition) {this.mHidePosition = hidePosition; notifyDataSetChanged();}@Overridepublic void removeItem(int deletePosition) {list.remove(deletePosition);notifyDataSetChanged();}
四 GridVIew
1 自定义DragridView继承GridView,重写onMueause()用来重新测量和根据横竖屏设置不同的列数
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {if (mNumColumns == AUTO_FIT){if (isLandscape(getContext())) {mPaddingTopInit = (int) getResources().getDimension(R.dimen.HriontalPaddingTop);setNumColumns(mColumnNum_Hriztal);} else {setNumColumns(mColumnNum);}}setPadding(mPaddingLeftInit, mPaddingTopInit, mPaddingRightInit, mPaddingBottomInit);super.onMeasure(widthMeasureSpec, heightMeasureSpec);}
2 重写dispatchTouchEvent()
安卓事件是在dispatchTouchEvent()进行分发的,因此我们在这里拦截按下和移动,以及按键弹起等事件,在按下事件里获取按下的坐标,以及按了哪个item, 去获取当前itemview,并开启一个延时Runnable,用来控制抖动生效的阀值,当时间达到此阀值使拖动状态可用,同时也截取当前itemView保存为镜像图片。用于手指Move时充当window视图。手势松开时候许注销此定时器。
mScrollRunnable是用来作为超出边界执行的定时器, 触发GridView向下滚动 或向上滚动。而我们这里还需要isShowShake是用来判断当前是否需要显示抖动效果,如果目前已经在抖动了,并且删除按钮可用的状态,长按的阀值将会设置为最小值,用来实现不用长按即可拖动Item的目的。
@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:mDownX = (int) ev.getX();mDownY = (int) ev.getY();// 根据按下的X,Y坐标获取所点击item的positionmDragPosition = pointToPosition(mDownX, mDownY);if (mDragPosition == AdapterView.INVALID_POSITION) {return super.dispatchTouchEvent(ev);}mStartDragItemView = getChildAt(mDragPosition- getFirstVisiblePosition());////performLongClick();if (isShowShake() && isShowDelele()) {dragResponseMS = dragResponseCT;}else {dragResponseMS = 1000 ;}// 使用Handler延迟dragResponseMS执行mLongClickRunnablemHandler.postDelayed(mLongClickRunnable, dragResponseMS);mPoint2ItemTop = mDownY - mStartDragItemView.getTop();mPoint2ItemLeft = mDownX - mStartDragItemView.getLeft();mOffset2Top = (int) (ev.getRawY() - mDownY);mOffset2Left = (int) (ev.getRawX() - mDownX);// 获取DragGridView自动向上滚动的偏移量,小于这个值,DragGridView向下滚动mDownScrollBorder = getHeight() / 5;// 获取DragGridView自动向下滚动的偏移量,大于这个值,DragGridView向上滚动mUpScrollBorder = getHeight() * 4 / 5;// 开启mDragItemView绘图缓存mStartDragItemView.setDrawingCacheEnabled(true);// 获取mDragItemView在缓存中的Bitmap对象mDragBitmap = Bitmap.createBitmap(mStartDragItemView.getDrawingCache());// 这一步很关键,释放绘图缓存,避免出现重复的镜像mStartDragItemView.destroyDrawingCache();break;case MotionEvent.ACTION_MOVE:int moveX = (int) ev.getX();int moveY = (int) ev.getY();if (!isTouchInItem(mStartDragItemView, moveX, moveY)) {mHandler.removeCallbacks(mLongClickRunnable);}break;case MotionEvent.ACTION_UP:mHandler.removeCallbacks(mLongClickRunnable);mHandler.removeCallbacks(mScrollRunnable);break;}return super.dispatchTouchEvent(ev);}2 onTuch()
主要用来处理当前手势,按下移动时就开始执行拖动,并执行抖动效果,弹起停止拖动效果,继续开启抖动动画,重新排列gridview
3 监听返回键
由于代码比较多 因此不一一做说明
@Overridepublic boolean onTouchEvent(MotionEvent ev) {if (isDrag && mDragImageView != null) {switch (ev.getAction()) {case MotionEvent.ACTION_MOVE:moveX = (int) ev.getX();moveY = (int) ev.getY();onDragItem(moveX, moveY);onStartAnimation();break;case MotionEvent.ACTION_UP:onStopDrag();isDrag = false;onStartAnimation();break;}return true;}return super.onTouchEvent(ev);}
拖动某一个Item时 根据移动的x,y来实时更新当前截取的item镜像窗体位置位置。
/** * 拖动item,在里面实现了item镜像的位置更新,item的相互交换以及GridView的自行滚动 * * @param x * @param y */private void onDragItem(int moveX, int moveY) {mDragAdapter.setHideItem(mDragPosition);//setHideSartItemView();mWindowLayoutParams.x = moveX - mPoint2ItemLeft + mOffset2Left;mWindowLayoutParams.y = moveY - mPoint2ItemTop + mOffset2Top- mStatusHeight;if (mDragImageView != null) {mWindowManager.updateViewLayout(mDragImageView, mWindowLayoutParams); // 更新镜像的位置}onSwapItem(moveX, moveY);// GridView自动滚动mHandler.post(mScrollRunnable);}手指弹起时,将镜像移除,将移动本身item设置为可见状态,
/** * 停止拖拽我们将之前隐藏的item显示出来,并将镜像移除 */private void onStopDrag() {View view = getChildAt(mDragPosition - getFirstVisiblePosition());if (view != null) {view.setVisibility(View.VISIBLE);}mDragAdapter.setHideItem(-1);removeDragImage();}
3 监听返回键
如果当前为抖动状态,并且删除按钮可见,就停止抖动动画,并影藏item的删除按钮。如果不在抖动状态,则直接退出。
@Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { pressAgainExit(); return true; } return super.onKeyDown(keyCode, event); } /** * pressAgainExit */private void pressAgainExit() { if (mEMrg.isExit() || mDeleteButton ==null ) { System.exit(0); } else { setHideDeleltButton(); if (mStartShake && mNeedShake) { onStopAnimation(); } mEMrg.doExitInOneSecond(); } }
具体代码如下详见开源地址:https://github.com/NeglectedByBoss/IOS_DragGridView
五 Activity
用于初始化数据等,这里不做详细说明。
/** * * * @author lyk * */public class DemoMainActivity extends Activity implements OnItemClickListener{private List<HashMap<String, Object>> dataSourceList = new ArrayList<HashMap<String, Object>>();/** * 一页可见提条目数 */private static final int VISIBIY_NUMS = 24;private DragAdapter mDragAdapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);DragGridView mDragGridView = (DragGridView)findViewById(R.id.dragGridView);mDragGridView.setOnItemClickListener(this);for (int i = 0; i < VISIBIY_NUMS; i++) {HashMap<String, Object> itemHashMap = new HashMap<String, Object>();Random random =new Random();if (random.nextInt(3) == 1) {itemHashMap.put("item_image",R.drawable.ic_icon);}if (random.nextInt(3) == 0) {itemHashMap.put("item_image",R.drawable.icon);}else {itemHashMap.put("item_image",R.drawable.icon4);}itemHashMap.put("item_text", "icon" + Integer.toString(i));dataSourceList.add(itemHashMap);}mDragAdapter = new DragAdapter(this, dataSourceList);mDragGridView.setAdapter(mDragAdapter);//设置需要抖动mDragGridView.setNeedShake(true);}@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position,long id) {Toast.makeText(this, "onClick:" + position, Toast.LENGTH_SHORT).show(); }}效果:
动态效果参考iphone桌面luncher效果。
结束语:
通过上面的实现方式我们简单的实现了需求中的以下几点:
1 GridView长按支持拖动排序,并支持Item实时交换。
2 GridView长按Item出现有抖动效果。
3 Item条目有抖动效果,时不需要长按点击就可以进行拖动效果。
5 长按Item 出现删除按钮,此时点击删除按钮可以任意删除某一item
6 GridView横竖屏排列列数改变,横屏的行数是竖屏幕的列数
对于建立文件夹,点击文件夹显示子集合view的暂未实现,包括一屏放置两个gridview并且互相拖动交换的功能也暂未实现,接下来的文章将会继续完善一下功能 欢迎阅读
如果你觉得此实现方式有欠缺的地方可以直接在gtihub上进行进一步完善,将自己的技术继续分享出来。谢谢你的阅读和支持
参考博文:http://blog.csdn.net/xiaanming/article/details/17718579
请尊重原创:http://blog.csdn.net/sk719887916/article/details/40074663,
点击下载源码:
1 0
- Android 实现高仿iOS桌面效果之可拖动的GridView(上)
- 自定义可拖动GridView 仿android桌面launcher
- Android自定义GridView之仿支付宝首页可拖动、可删除的九宫格
- Android自定义GridView之仿支付宝首页可拖动、可删除的九宫格
- Android之ViewGroup实现可拖动的GridView
- Android之ViewGroup实现可拖动的GridView
- Android之ViewGroup实现可拖动的GridView
- Android之ViewGroup实现可拖动的GridView
- Android之ViewGroup实现可拖动的GridView
- 使用ViewPager实现高仿Launcher的拖动效果
- Android-使用ViewPager实现高仿launcher拖动效果(转)
- Android 仿iOS上拉下拉界面的效果实现
- Android 高仿IOS的省、市、区三级联动效果(含数据,可直接用到项目中)
- android可拖动排序GridView实现
- Android 仿今日头条 可拖动的GridView 代码实例详解
- Android 高仿 频道管理----网易、今日头条、腾讯视频 (可以拖动的GridView)附源码DEMO
- Android 高仿 频道管理----网易、今日头条、腾讯视频 (可以拖动的GridView)附源码DEMO
- Android 高仿 频道管理----网易、今日头条、腾讯视频 (可以拖动的GridView)附源码DEMO
- Android TextView使用HTML处理字体样式、显示图片等
- tomcat-发布项目到根目录,也就是域名直接访问项目
- 如何找到属于自己的理想职业
- C#(TripleDES)对应Java(3DES)加密工具类
- Linux 输出重定向>和>>的区别是什么?
- Android 实现高仿iOS桌面效果之可拖动的GridView(上)
- listView解决滑动时黑色背景问题
- Cocos2d-X 学习笔记5 Win7 + vs2012 + cocos2d-x2.2 配置开发环境
- Android: 触屏fling/scroll/drag的区别及其详细过程
- cocos2d-x Tiled map editor 创建地图导入项目
- Mysql or Mongodb LBS快速实现方案
- 编码规范系列(一):Eclipse Code Templates设置
- codeforces 476D Dreamoon and Sets(数学)
- 论证是一门学问 如何让你的观点有说服力-7 概括 Generalizations