Android 实现高仿iOS桌面效果之可拖动的GridView(上)
转载请标明出处:http://blog.csdn.net/sk719887916/article/details/40074663,作者:skay
一 UI和功能分析
如下图:
分析完主要功能之后我们就开始代码实现策略,方便我们理清思路。
- 根据手指按下的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,就视为可拖动状态。
二
新建动画控制器
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() { @Override public void onAnimationEnd(Animation animation) { if (mNeedShake && mStartShake) { mra.reset(); v.startAnimation(mrb); } } @Override public void onAnimationRepeat(Animation animation) { } @Override public void onAnimationStart(Animation animation) { } }); mrb.setAnimationListener(new AnimationListener() { @Override public void onAnimationEnd(Animation animation) { if (mNeedShake && mStartShake) { mrb.reset(); v.startAnimation(mra); } } @Override public void onAnimationRepeat(Animation animation) { } @Override public 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() { @Override public void onAnimationStart(Animator animation) { mAnimationEnd = false; } @Override public 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); } @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; } /** * 由于复用convertView导致某些item消失了,所以这里不复用item, */ @Override public 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; } @Override public 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); } @Override public void setHideItem(int hidePosition) { this.mHidePosition = hidePosition; notifyDataSetChanged(); } @Override public void removeItem(int deletePosition) { list.remove(deletePosition); notifyDataSetChanged(); }
四
GridVIew
1 自定义DragridView继承GridView,重写onMueause()用来重新测量和根据横竖屏设置不同的列数
@Override protected 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视图。手势松开时候许注销此定时器。
触发GridView向下滚动 或向上滚动。而我们这里还需要isShowShake是用来判断当前是否需要显示抖动效果,如果目前已经在抖动了,并且删除按钮可用的状态,长按的阀值将会设置为最小值,用来实现不用长按即可拖动Item的目的。
@Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mDownX = (int) ev.getX(); mDownY = (int) ev.getY(); // 根据按下的X,Y坐标获取所点击item的position mDragPosition = 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执行mLongClickRunnable mHandler.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()
@Override public 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(); } }
由于代码比较多 因此不一一做说明
五
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; @Override protected 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); } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Toast.makeText(this, "onClick:" + position, Toast.LENGTH_SHORT).show(); } }
效果:
结束语:
通过上面的实现方式我们简单的实现了需求中的以下几点:
欢迎阅读
Android 实现高仿iOS桌面效果之可拖动的GridView(上)的更多相关文章
- Android仿IOS回弹效果 ScrollView回弹 总结
Android仿IOS回弹效果 ScrollView回弹 总结 应项目中的需求 须要仿IOS 下拉回弹的效果 , 我在网上搜了非常多 大多数都是拿scrollview 改吧改吧 试了一些 发现总 ...
- android版高仿淘宝客户端源码V2.3
android版高仿淘宝客户端源码V2.3,这个版本我已经更新到2.3了,源码也上传到源码天堂那里了,大家可以看一下吧,该应用实现了我们常用的购物功能了,也就是在手机上进行网购的流程的,如查看产品(浏 ...
- 高仿IOS下拉刷新的粘虫效果
最近看需要做一款下拉刷新的效果,由于需要和Ios界面保持一致,所以这用安卓的方式实现了ios下的下拉刷新的粘虫效果. 最新的安卓手机版本的QQ也有这种类似的效果,就是拖动未读信息的那个红色圆圈,拖动近 ...
- Android实现高仿QQ附近的人搜索展示
本文主要实现了高仿QQ附近的人搜索展示,用到了自定义控件的方法 最终效果如下 1.下面展示列表我们可以使用ViewPager来实现(当然如果你不觉得麻烦,你也可以用HorizontalScrollVi ...
- Android DrawerLayout 高仿QQ5.2双向侧滑菜单
1.概述 之前写了一个Android 高仿 QQ5.0 侧滑菜单效果 自定义控件来袭 ,恰逢QQ5.2又加了一个右侧菜单,刚好看了下DrawerLayout,一方面官方的东西,我都比较感兴趣:另一方面 ...
- 高仿ios版美团框架项目源码
高仿美团框架基本已搭好.代码简单易懂,适合新人.适合新人.新人. <ignore_js_op> 源码你可以到ios教程网那里下载吧,这里我就不上传了,http://ios.662p ...
- 分享一个android仿ios桌面卸载的图标抖动动画
直接上代码,如有更好的,还请不吝赐教 <span style="font-size:18px;"><?xml version="1.0" en ...
- android开发学习 ------- 仿QQ侧滑效果的实现
需要做一个仿QQ侧滑删除的一个效果: 一开始是毫无头绪,百度找思路,找到 https://blog.csdn.net/xiaxiazaizai01/article/details/53036994 ...
- Swift高仿iOS网易云音乐Moya+RxSwift+Kingfisher+MVC+MVVM
效果 列文章目录 因为目录比较多,每次更新这里比较麻烦,所以推荐点击到主页,然后查看iOS Swift云音乐专栏. 目简介 这是一个使用Swift(还有OC版本)语言,从0开发一个iOS平台,接近企业 ...
随机推荐
- Eclipse打jar包,资源文件的读取
最近的工作中需要将java程序打一个jar包,然后在Linux中供调用.程序中需要读取一个配置文件.遇到了三个问题.第一个是依赖的第三方Jar包打成Jar包后找不到:第二个问题是资源文件所在的文件夹打 ...
- SQL语句容易出现错误的地方-连载
1.语言问题 修改语言注册表\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432\ORACLE\KEY_DevSuitHome1中的NLS_LANG修改为AMERICAN_AMER ...
- Android简易实战教程--第三话《自己实现打电话》
需要一个文本输入框输入号码,需要一个按钮打电话.本质:点击按钮,调用系统打电话功能. xml布局文件代码:: <LinearLayout xmlns:android="http://s ...
- (NO.00004)iOS实现打砖块游戏(七):关卡类的实现
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 关卡游戏的精髓都集中在游戏的关卡里,其中包含了游戏的所有要素,至 ...
- 关于USB驱动的软件测试方法
在工作中难免会使用一些外部设备挂载到平台进行测试,比如U盘,那么判断一个U盘是否能正常读写的方法如下: 1.在U盘中放入一个二进制文件(xxx.bin) 2.通过U盘在软件上读取该二进制文件,并计算其 ...
- div效果很好的遮盖层效果
[html] view plaincopyprint? <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN&qu ...
- iOS开发中的零碎知识点笔记 韩俊强的博客
每日更新关注:http://weibo.com/hanjunqiang 新浪微博 1.关联 objc_setAssociatedObject关联是指把两个对象相互关联起来,使得其中的一个对象作为另外 ...
- 《java入门第一季》之集合框架TreeSet存储元素自然排序以及图解
这一篇对TreeSet做介绍,先看一个简单的例子: * TreeSet:能够对元素按照某种规则进行排序. * 排序有两种方式 * A:自然排序: 从小到大排序 * B:比较器排序 Comp ...
- HTTP 消息结构
HTTP 消息结构 HTTP是基于客户端/服务端(C/S)的架构模型,通过一个可靠的链接来交换信息,是一个无状态的请求/响应协议. 一个HTTP"客户端"是一个应用程序(Web浏览 ...
- Java 多线程 死锁 隐性死锁 数据竞争 恶性数据竞争 错误解决深入分析 全方向举例
在几乎所有编程语言中,由于多线程引发的错误都有着难以再现的特点,程序的死锁或其它多线程错误可能只在某些特殊的情形下才出现,或在不同的VM上运行同一个程序时错误表现不同.因此,在编写多线程程序时,事先认 ...