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平台,接近企业 ...
随机推荐
- Cartographer资料分享
中文资料稍后补充 Introducing Cartographer By Tully Foote on October 5, 2016 10:11 AM From Damon Kohler, Wolf ...
- Java: How to resolve Access Restriction error
Issue: Access restriction: The constructor 'BASE64Decoder()' is not API (restriction on required lib ...
- JavaScript与jQuery获取相邻控件
原始代码如下,需求是onclick中的OpenIframe方法捕捉到input中的value值,由于某些限制无法使用正常的操作dom根据name值来取,所以决定通过相邻空间的方式获取 <div& ...
- Servlet之Request对象
下面的方法可用在 Servlet 程序中读取 HTTP 头.这些方法通过HttpServletRequest 对象可用. 1 Cookie[] getCookies() 返回一个数组,包含客户端 ...
- Mac下安装Homebrew并升级subversion
1. 切 Tencent-GuestWiFi2. $ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/ins ...
- javaRMI详解
前几天在阿里内推一面的时候,面试官问到了一个关于java中RMI(Remote Method Invocation)的问题,当时感觉自己回答的还比较好,他比较满意,但那是因为他问的比较浅,所以自己看了 ...
- J2EE学习从菜鸟变大鸟之九 深入浅出理解 Servlet-----实例解析
关于Servlet的基础内容在前面已经和大家分享过了,参考J2EE学习从菜鸟变大鸟之七 Servlet,现在结合到DRP中学习,深刻的体会Servlet起到了枢纽中转的作用,控制逻辑(到MVC中更像是 ...
- Android开发技巧——PagerAdapter的再次简单封装
这次再对内容为View的ViewPager的适配器PagerAdapter进行简单的封装,支持List数据和SparseArray的数据,带更新视图功能. 首先,先贴上最上面的抽象类代码: /* * ...
- JSP标签JSTL(4)--URL
<c:url>标签作用是将一个URL地址格式化为一个字符串,并且保存在一个变量当中.它具有URL自动重写功能.value指定的URL可以是当前工程的一个URL地址,也可以是其他web工程的 ...
- moonmq: 用go实现的高性能message queue
介绍 moonmq是一个用go实现的高性能消息队列系统,后续准备用于我们消息推送服务以及各个后台的异步任务. 在设计上面,moonmq主要借鉴了rabbitmq以及rocketmq相关的思想,但是做了 ...