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平台,接近企业 ...
随机推荐
- Android 在 SElinux下 如何获得对一个内核节点的访问权限
点击打开链接 Android 5.0下,因为采取了SEAndroid/SElinux的安全机制,即使拥有root权限,或者对某内核节点设置为777的权限,仍然无法在JNI层访问. 本文将以用户自定义的 ...
- android连接打印机
android连接 网络打印,主要使用socket连接设备,发送指令给设备. 首先要有设备的IP,端口号一般默认的是9100 //打印设备网络IP etIp.setText("192.16 ...
- Android简易实战教程--第十三话《短信备份和还原~三》
之前写过短信备份的小案例,哪里仅仅是虚拟了几条短信信息.本篇封装一个业务类,且直接通过内容提供者,访问本系统的短信信息,再提供对外接口.如果想要短信备份和短信还原,直接复制这段代码即可.对于您调用这个 ...
- ubuntu安装水星MW150US无线网卡8188eu驱动
买了一个无线网卡插在ubuntu系统的电脑上,却不能识别出来.lsusb,可以看到下面的结果: Bus 002 Device 002: ID 0bda:8179 Realtek Semiconduct ...
- NoSQL数据库之Redis数据库:Redis的介绍与安装部署
NoSQL(NoSQL = Not Only SQL),它指的是非关系型的数据库.随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的w ...
- Unity插件 - MeshEditor(一) 3D线段作画 & 模型网格编辑器
之前,因为工作需要,项目中需要动态生成很多的电线,不能事先让模型做好,更不能用LineRenderer之类的,因为画出来没有3D的效果,最主要是拐角的时候还容易破面,而我们要的是真真实实纯3D的电线, ...
- #include <iostream>与#include <iostream.h>的区别
在新的C++标准中,生成新头文件的方法仅仅是将现有C++头文件名中的 .h 去掉.例如,<iostream.h> 变成了<iostream> ,<complex. ...
- Volley请求
1. Volley简介 我们平时在开发Android应用的时候不可避免地都需要用到网络技术,而多数情况下应用程序都会使用HTTP协议来发送和接收网络数据.Android系统中主要提供了两种方式来进行H ...
- 敏捷测试(7)--基于story的敏捷基础知识
基于story的敏捷基础知识----迭代启动会.迭代回顾会 除需求讲解意外,需要所有团队成员参加的会议仅有两个,分别是"迭代启动会"和"迭代回顾会". (1)迭 ...
- MinerBean.java 数据库表 miner bean
MinerBean.java 数据库表 miner bean package com.iteye.injavawetrust.miner; import java.util.Date; /** * 数 ...