Android开发之自定义View专题(三):自定义GridView

gridview作为android开发中常用的组件,其功能十分强大。但是,我们有时候有很多特殊的需求,需要在其基础上进行改造。有时候会有移动gridView中item位置的需求,这个网上已经有很多例子,博主就不在描述。今天博主讲的是移动gridView中item中的内容。博主没看过网上那些移动item位置的demo,不知道其原理是不是和博主想的一样。博主思考过,似乎博主的这种实现原理似乎也可以用作实现移动item位置。而之前博主百思不得其解的小米手机的桌面的自定义乱序排放,似乎也可以用这个原理去实现。好了,废话不多说,先上效果图

这里博主就以数字为内容来演示,不同的数字item的背景颜色不同来区分,长按住一个item后可以移动,每次移动,移动的item减1,移动到的item加1。效果还可以吧。好了,上代码,大家一起学习。了解其实现原理的同学完全可以自己修改代码开发可移动item的gridView

完整项目下载地址:http://download.csdn.net/detail/victorfreedom/8326829

(最近网络不给力,github上传有问题,等明天最后一篇自定义view专题讲解完了,再将所有项目一起上传吧,不介意那点分的同学可以先下载)

package com.freedom.gridview;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.Toast;

import com.freedom.gridview.adapter.GridViewAdapter;
import com.freedom.gridview.bean.Data;

/**
 * @ClassName: FreedomGridView
 * @author victor_freedom ([email protected])
 * @createddate 2015-1-4 下午10:25:52
 * @Description: 可移动item内容的gridView
 */
@SuppressLint("NewApi")
public class FreedomGridView extends GridView {

	// 是否在移动中
	private boolean isMove = false;
	// 是否是第一次移动
	private boolean isFirst = true;
	// 是否是长按
	private boolean isLongClick = false;
	// 图形
	private Bitmap bitmap;
	// 移动的视图
	private ImageView moveView = null;
	// 偏移量
	private int offsetX, offsetY;
	// 在屏幕中触摸的位置
	private int touchPositionInScreen;
	// 移动的目的位置
	private int moveToPosition;
	// 在ITEM中触摸的坐标
	private int touchPositionInItemX, touchPositionInItemY;
	// 移动速度
	private int scaledTouchSlop;
	// 移动过程中,上下边距判定自动滑动距离
	private int upScrollBounce;
	private int downScrollBounce;
	// 窗体管理者,用于添加视图
	private WindowManager windowManager = null;
	private WindowManager.LayoutParams layoutParams = null;
	private GridViewAdapter adapter;

	public FreedomGridView(Context context) {
		super(context);
		scaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
	}

	public FreedomGridView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public FreedomGridView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	/**
	 * 长按判定
	 */
	private Runnable longPressRun = new Runnable() {
		@Override
		public void run() {
			isLongClick = true;
		}
	};

	/**
	 * @Title: contains
	 * @Description: 判断是否触摸坐标是否在视图里面
	 * @param v
	 * @param xInView
	 * @param yInView
	 * @return
	 * @throws
	 */
	private boolean contains(View v, int xInView, int yInView) {
		if (v instanceof ImageView) {
			return ((ImageView) v).getDrawable().getBounds()
					.contains(xInView, yInView);
		}
		return v.getBackground().getBounds().contains(xInView, yInView);
	}

	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		// 拿到适配器
		if (null == adapter || adapter.isEmpty()) {
			adapter = (GridViewAdapter) getAdapter();
		}
		switch (ev.getAction()) {
		case MotionEvent.ACTION_DOWN:
			// 拿到相对于触摸视图的坐标
			int x = (int) ev.getX();
			int y = (int) ev.getY();
			// 拿到触摸位置
			touchPositionInScreen = moveToPosition = this.pointToPosition(x, y);

			// 判断位置是否有效
			if (moveToPosition == AdapterView.INVALID_POSITION) {
				break;
			}

			// 拿到当前触摸的可见item
			ViewGroup itemView = (ViewGroup) this.getChildAt(moveToPosition
					- this.getFirstVisiblePosition());
			// 拿到点击位置相对于ITEM视图的偏移量
			touchPositionInItemY = y - itemView.getTop();
			touchPositionInItemX = x - itemView.getLeft();

			// 拿到item视图里面的控件
			View view = itemView.findViewById(R.id.desk_back);
			// 判断点击位置是否在视图里面
			if (this.contains(view, touchPositionInItemX, touchPositionInItemX)) {
				try {
					int[] locationInScreen = new int[2];
					view.getLocationOnScreen(locationInScreen);
				} catch (NullPointerException e) {
					break;
				}
			}
			// 移动视图时候的偏移量
			this.offsetX = (int) (ev.getRawX() - x);
			this.offsetY = (int) (ev.getRawY() - y);

			// 获取触发当拖动视图到最顶端或者最底端自动滚动视图的边距
			upScrollBounce = Math.min(y - scaledTouchSlop, getHeight() / 3);
			downScrollBounce = Math.max(y + scaledTouchSlop,
					getHeight() * 2 / 3);
			itemView.setDrawingCacheEnabled(true);
			// 拿item视图的bitmap
			bitmap = Bitmap.createBitmap(itemView.getDrawingCache());
			itemView.destroyDrawingCache();
			postDelayed(longPressRun, 1000);
			break;
		case MotionEvent.ACTION_MOVE:
			if (isLongClick) {
				int mx = (int) ev.getX();
				int my = (int) ev.getY();
				// 第一次移动,创建移动视图
				if (isFirst)
					initWindowManager(bitmap, mx, my);
				onMove(mx, my);
				// 移除之前的runable
				removeCallbacks(longPressRun);
				return true;
			}
			break;
		case MotionEvent.ACTION_UP:
			int upY = (int) ev.getY();
			int upX = (int) ev.getX();
			if (isMove && isLongClick) {
				stopMove();
				completeMove(upX, upY);
				isMove = false;
				isLongClick = false;
				break;
			}
			removeCallbacks(longPressRun);
		}
		return super.onTouchEvent(ev);
	}

	/**
	 * @Title: initWindowManager
	 * @Description: 创建移动视图
	 * @param bm
	 * @param x
	 * @param y
	 * @throws
	 */
	public void initWindowManager(Bitmap bm, int x, int y) {
		stopMove();
		isFirst = false;
		layoutParams = new WindowManager.LayoutParams();
		layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
		layoutParams.horizontalMargin = layoutParams.verticalMargin = 0;
		layoutParams.x = x - touchPositionInItemX + offsetX;
		layoutParams.y = y - touchPositionInItemY + offsetY;
		layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
		layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
		layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
				| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
				| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
				| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
		layoutParams.format = PixelFormat.TRANSLUCENT;
		layoutParams.windowAnimations = 0;
		windowManager = (WindowManager) this.getContext().getSystemService(
				"window");

		ImageView moveViewTemp = new ImageView(getContext());
		moveViewTemp.setImageBitmap(bm);

		windowManager = (WindowManager) this.getContext().getSystemService(
				"window");
		windowManager.addView(moveViewTemp, layoutParams);
		moveView = moveViewTemp;

	}

	/**
	 * @Title: stopMove
	 * @Description: 停止移动
	 * @throws
	 */
	public void stopMove() {
		if (moveView != null) {
			windowManager.removeView(moveView);
			moveView = null;
		}
	}

	/**
	 * @Title: onMove
	 * @Description: 视图移动的时候触发的方法
	 * @param x
	 * @param y
	 * @throws
	 */
	public void onMove(int x, int y) {
		isMove = true;
		// 避免拖动到无效区域
		int tempPosition = this.pointToPosition(x, y);
		if (tempPosition != FreedomGridView.INVALID_POSITION) {
			this.moveToPosition = tempPosition;
		}

		// 移动的时候更新视图位置
		if (moveView != null) {
			layoutParams.alpha = 0.8f;
			layoutParams.y = y - touchPositionInItemY + offsetY;
			layoutParams.x = x - touchPositionInItemX + offsetX;
			windowManager.updateViewLayout(moveView, layoutParams);
		}

		int scrollHeight = 0;
		if (y < upScrollBounce) {
			scrollHeight = 30;
		} else if (y > downScrollBounce) {
			scrollHeight = -30;
		}
		// 触发自动滚动
		if (scrollHeight != 0) {
			smoothScrollToPositionFromTop(moveToPosition,
					getChildAt(moveToPosition - getFirstVisiblePosition())
							.getTop() + scrollHeight, 1);
		}
	}

	/**
	 * @Title: completeMove
	 * @Description: 移动完成时
	 * @param x
	 * @param y
	 * @throws
	 */
	public void completeMove(int x, int y) {
		isFirst = true;
		// 拿到停止的位置
		int tempPosition = this.pointToPosition(x, y);
		if (tempPosition != FreedomGridView.INVALID_POSITION) {
			this.moveToPosition = tempPosition;
		}

		if (y < getChildAt(0).getTop()) {
			return;
		} else if (y > getChildAt(getChildCount() - 1).getBottom()) {
			moveToPosition = getAdapter().getCount() - 1;
			return;
		} else {
			// 如果在有效位置
			if (moveToPosition >= 0 && moveToPosition < getAdapter().getCount()) {

				ViewGroup itemView = (ViewGroup) this.getChildAt(moveToPosition
						- this.getFirstVisiblePosition());
				if (itemView != null) {

					ImageView imaveView = (ImageView) itemView
							.findViewById(R.id.desk_back);
					// 判断是否移入了有效视图里面
					boolean isIn = this.contains(imaveView,
							x - itemView.getLeft(), y - itemView.getTop());

					// 如果已经移入了,并且不是触摸时的起始位置
					if (isIn) {
						if (moveToPosition != touchPositionInScreen) {
							itemView.startAnimation(AnimationUtils
									.loadAnimation(getContext(),
											R.anim.desk_scale));
							Data touchData = ((Data) adapter
									.getItem(touchPositionInScreen));
							if (touchData.getNum() == 0) {
								Toast.makeText(getContext(), "数目为0不可变化",
										Toast.LENGTH_SHORT).show();
								return;
							}

							Data toData = (Data) adapter
									.getItem(moveToPosition);
							if (toData.getNum() == 2) {
								Toast.makeText(getContext(), "数目为2不可变化",
										Toast.LENGTH_SHORT).show();
								return;
							}

							touchData.setNum(touchData.getNum() - 1);
							toData.setNum(toData.getNum() + 1);
							adapter.notifyDataSetChanged();
						}
					}
				}
			}
		}
	}

}
时间: 01-03

Android开发之自定义View专题(三):自定义GridView的相关文章

Android开发之自定义View专题(二):自定义饼图

在图表里面,常用的图标一般为折线图.柱形图和饼图,上周,博主已经将柱形图分享.在博主的项目里面其实还用到了饼图,但没用到折线图.其实学会了其中一个,再去写其他的,应该都是知道该怎么写的,原理都是自己绘制图形,然后获取触摸位置判定点击事件.好了,废话不多说,直接上今天的饼图的效果图 这次也是博主从项目里面抽离出来的,这次的代码注释会比上次的柱形图更加的详细,更加便于有兴趣的朋友一起学习.图中的那个圆形指向箭头不属于饼图的部分,是在布局文件中为了美化另外添加进去的,有兴趣的朋友可以下载完整的项目下来

Android开发之自定义View专题(四):自定义ViewGroup

有时候,我们会有这样的需求,一个activity里面需要有两个或者多个界面切换,就像Viewpager那样.但是在这些界面里面又需要能够有listView,gridview等组件.如果是纵向的,似乎还好,没什么影响,那么如果是横向的,那么就会出事情.因为Viewpager会拦截触摸事件.而如果将Viewpager的触摸事件拦截掉给里面的子控件,那么Viewpager又不能响应滑动事件了.那么如何又能让界面之间能够来回切换,又能让里面的子控件的触摸事件也能毫无影响的响应呢,这个时候,我们需要自定义

实现自定义View的三种方式

一.组合控件 组合控件,顾名思义,就是将系统原有的控件进行组合,构成一个新的控件.这种方式下,不需要开发者自己去绘制图上显示的内容,也不需要开发者重写onMeasure,onLayout,onDraw方法来实现测量.布局以及draw流程.所以,在实现自定义view的三种方式中,这一种相对比较简单. 实际开发中,标题栏就是一个比较常见的例子.因为在一个app的各个界面中,标题栏基本上是大同小异,复用率很高.所以经常会将标题栏单独做成一个自定义view,在不同的界面直接引入即可,而不用每次都把标题栏

android开发最常用例子整理----(1)自定义按钮实现

android开发最常用例子整理----(1)自定义按钮实现 一.Activity MainActivity.java源码: public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }

android开发最常用例子整理----(2)自定义ListView(SimpleAdapter实现)

android开发最常用例子整理----(2)自定义ListView(SimpleAdapter实现) 一.Activity MainActivity.java源码: public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layou

Android开发之图片处理专题(三):利用ThreadPoolExcutor线程池实现多图片的异步加载

在上一篇专题Android开发之图片处理专题(二):利用AsyncTask和回调接口实现图片的异步加载和压缩中我们实现了listView的图片的大量加载.今天,我们换一种方式,采用线程池的方式来实现. 我们需要准备两个东西: 1.图片下载任务类 2.线程池. 1.图片下载任务类. 图片下载任务类,将需要显示的iamgeView,线程通讯消息管理者handler进行了封装.当图片下载无论成功还是失败,handler发送对应的消息,传入的iamgeView显示对应的图片.这里就不在应用软引用技术,采

自定义View的三个构造函数

自定义View有三个构造方法,它们的作用是不同的. public MyView(Context context) { super(context); } public MyView(Context context, AttributeSet attrs) { super(context, attrs); } public MyView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, de

Android开发之图片处理专题(二):利用AsyncTask和回调接口实现图片的异步加载和压缩

在上一篇专题Android开发之图片处理专题(一):利用软引用构建图片高速缓存中我们讲述了如何利用软引用技术构建高速缓存.那么想要用到图片,首先得有图片的来源.一般而言,一个应用的图片资源都是从服务器处获得的.今天,我们利用Android开发之网络请求通信专题(二):基于HttpClient的文件上传下载里面封装好的httpUtils来实现图片的下载,然后加载到本地配合软引用缓存使用,以一个listView为例子来说明. 一.准备工作 我们需要准备以下几个类(图片对象和软引用缓存类请参考上一篇专

Android自定义View的三种实现方式

在毕设项目中多处用到自定义控件,一直打算总结一下自定义控件的实现方式,今天就来总结一下吧.在此之前学习了郭霖大神博客上面关于自定义View的几篇博文,感觉受益良多,本文中就参考了其中的一些内容. 总结来说,自定义控件的实现有三种方式,分别是:组合控件.自绘控件和继承控件.下面将分别对这三种方式进行介绍. (一)组合控件 组合控件,顾名思义就是将一些小的控件组合起来形成一个新的控件,这些小的控件多是系统自带的控件.比如很多应用中普遍使用的标题栏控件,其实用的就是组合控件,那么下面将通过实现一个简单