Android高级控件(六)——自定义ListView高仿一个QQ可拖拽列表的实现
Android高级控件(六)——自定义ListView高仿一个QQ可拖拽列表的实现
我们做一些好友列表或者商品列表的时候,居多的需求可能就是需要列表拖拽了,而我们选择了ListView,也是因为使用ListView太久远了,导致对他已经有浓厚的感情了,我们之前也是写过几篇关于ListView的博文
而今天我们就来实现以下课拖拽的方案,大家可以借鉴以下
我们大致的思路,其实是这样子的,也是我的设想,我们可以先去实现一个简单的ListView的数据,但是他的Adapter,我们可以用系统封装好的,然后传递进去一个实体类,最后自定义一个listview去操作,所以我们先把准备的工作做好,比如?
list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iv_logo"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentLeft="true"
android:layout_centerInParent="true"
android:layout_marginLeft="10dp"/>
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@id/iv_logo"/>
</RelativeLayout>
这就只有一个头像和一句话了,然后我们把实体类也给写完了
DragBean
package com.liuguilin.draglistviewsample.entity;
/*
* 项目名: DragListViewSample
* 包名: com.liuguilin.draglistviewsample.entity
* 文件名: DragBean
* 创建者: LGL
* 创建时间: 2016/8/29 22:49
* 描述: 实体类
*/
public class DragBean {
private int ivId;
private String text;
public DragBean() {
}
public DragBean(int ivId, String text) {
this.ivId = ivId;
this.text = text;
}
public int getIvId() {
return ivId;
}
public String getText() {
return text;
}
}
ok,其实很简单,id是图片,然后是文本,这样我们就可以来实现一个Adapter了,这里我用的是ArrayAdapter这样能让我们插入和删除很轻松
DragAdapter
package com.liuguilin.draglistviewsample.adapter;
/*
* 项目名: DragListViewSample
* 包名: com.liuguilin.draglistviewsample.adapter
* 文件名: DragAdapter
* 创建者: LGL
* 创建时间: 2016/8/29 22:41
* 描述: 拖拽列表的数据源
*/
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.liuguilin.draglistviewsample.R;
import com.liuguilin.draglistviewsample.entity.DragBean;
import java.util.List;
public class DragAdapter extends ArrayAdapter<DragBean> {
/**
* 构造方法
*
* @param context
* @param mList
*/
public DragAdapter(Context context, List<DragBean> mList) {
super(context, 0, mList);
}
/**
* 实现View
*
* @param position
* @param convertView
* @param parent
* @return
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
ViewHolder viewHolder;
if (convertView == null) {
view = View.inflate(getContext(), R.layout.list_item, null);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) view
.findViewById(R.id.iv_logo);
viewHolder.textView = (TextView) view.findViewById(R.id.textView);
view.setTag(viewHolder);
} else {
view = convertView;
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.imageView.setImageResource(getItem(position).getIvId());
viewHolder.textView.setText(getItem(position).getText());
return view;
}
/**
* 缓存
*/
static class ViewHolder {
ImageView imageView;
TextView textView;
}
}
好的,其实到这里,他就是一个最普通的ListView了,我们给他填充点数据
MainActivity
package com.liuguilin.draglistviewsample;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.liuguilin.draglistviewsample.adapter.DragAdapter;
import com.liuguilin.draglistviewsample.entity.DragBean;
import com.liuguilin.draglistviewsample.view.DragListView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
//列表
private DragListView mListView;
//数据
private List<DragBean> mList = new ArrayList<>();
//数据源
private DragAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
/**
* 初始化View
*/
private void initView() {
mListView = (DragListView) findViewById(R.id.mListView);
//新增數據
for (int i = 0; i < 30; i++) {
DragBean bean = new DragBean(R.mipmap.ic_launcher, "刘某人程序员" + i);
mList.add(bean);
}
//初始化数据源
adapter = new DragAdapter(this,mList);
mListView.setAdapter(adapter);
}
}
现在可以看看实际的效果了
现在我们可以重写我们的ListView了
我们首先拦截他的事件
/**
* 获取触点所在条目的位置
* 获取选中条目的图片
* 事件的拦截机制
*
* @param ev
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//识别动作
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//获取触点的坐标
int x = (int) ev.getX();
int y = (int) ev.getY();
//这样就可以计算我按到哪个条目上了
mStartPosition = mEndPosition = pointToPosition(x, y);
//判断触点是否在logo的区域
ViewGroup itemView = (ViewGroup) getChildAt(mStartPosition - getFirstVisiblePosition());
//记录手指在条目中的相对Y坐标
dragPoint = y - itemView.getTop();
//ListView在屏幕中的Y坐标
dragOffset = (int) (ev.getRawY() - y);
//拖动的图标
View dragger = itemView.findViewById(R.id.iv_logo);
//判断触点是否在logo区域
if (dragger != null && x < dragger.getRight() + 10) {
//定义ListView的滚动条目
//上
upScroll = getHeight() / 3;
//下
downScroll = getHeight() * 2 / 3;
//获取选中的图片/截图
itemView.setDrawingCacheEnabled(true);
//获取截图
Bitmap bitMap = itemView.getDrawingCache();
//图片滚动
startDrag(bitMap, y);
}
break;
}
//还会传递事件到子View
return false;
}
获取到他的position之后我们直接截图,并且显示我们的window,这里做的事情就比较多了我们还要判断是否点击的是头像才去显示window
/**
* 图片在Y轴,也就是上下可滚动
*
* @param bitMap
* @param y
*/
private void startDrag(Bitmap bitMap, int y) {
//窗体仿照
wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
//设置窗体参数
lParams = new WindowManager.LayoutParams();
//设置宽高
lParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
lParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
//属性
lParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
//设置半透明
lParams.alpha = 0.8f;
//设置居中
lParams.gravity = Gravity.TOP;
//设置xy
lParams.x = 0;
lParams.y = y-dragPoint + dragOffset;
//属性
lParams.format = PixelFormat.TRANSLUCENT;
//设置动画
lParams.windowAnimations = 0;
//图片
dragImageView = new ImageView(getContext());
//设置截图
dragImageView.setImageBitmap(bitMap);
//添加显示窗体
wm.addView(dragImageView, lParams);
}
好的,我们初始化一下window,光显示还不行呢,我们还要可以滑动,怎么监听?onTouchEvent的ACTION_MOVE事件是可以做到的
/**
* 触摸事件
*
* @param ev
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
//错误的位置
if (dragImageView != null && mEndPosition != INVALID_POSITION) {
//在滑动事件中控制上下滑动
switch (ev.getAction()) {
case MotionEvent.ACTION_MOVE:
//直接获取到Y坐标进行移动
int moveY = (int) ev.getY();
doDrag(moveY);
break;
//停止拖动成像
case MotionEvent.ACTION_UP:
int upY = (int) ev.getY();
stopDrag();
onDrag(upY);
break;
}
}
//拦截到事件
return true;
}
我们在移动的时候不断的去更新他的位置
/**
* 控制窗体移动
*
* @param moveY
*/
private void doDrag(int moveY) {
if (dragImageView != null) {
lParams.y = moveY - dragPoint + dragOffset;
wm.updateViewLayout(dragImageView, lParams);
}
//判断移动到分割线 返回-1
int tempLine = pointToPosition(0, moveY);
//我们处理他
if (tempLine != INVALID_POSITION) {
//只要你不移动到分割线 我才处理
mEndPosition = tempLine;
}
//拖拽时滚动、滚动速度
int scrollSpeed = 0;
//上滚
if (moveY < upScroll) {
scrollSpeed = 10;
//下滚
} else if (moveY > downScroll) {
scrollSpeed = -10;
}
//开始滚动
if (scrollSpeed != 0) {
//计算条目的Y坐标
int dragItemY = getChildAt(mEndPosition - getFirstVisiblePosition()).getTop();
//当前速度
int dy = dragItemY + scrollSpeed;
//设置
setSelectionFromTop(mEndPosition, dy);
}
}
当你移动完成之后,我就可以停止你的window体了
/**
* 停止的位置
*/
private void stopDrag() {
//直接移除窗体
if (dragImageView != null) {
wm.removeView(dragImageView);
dragImageView = null;
}
}
这样,我就直接拼接你的position达到一个拖拽的效果
/**
* 最终开始成像
*
* @param upY
*/
private void onDrag(int upY) {
//分割线的处理
//判断移动到分割线 返回-1
int tempLine = pointToPosition(0, upY);
//我们处理他
if (tempLine != INVALID_POSITION) {
//只要你不移动到分割线 我才处理
mEndPosition = tempLine;
}
/**
* 你在最上方就直接落在第一个最下方就直接最下面一个
*/
//上边界处理
if (upY < getChildAt(1).getTop()) {
mEndPosition = 1;
//下边界处理
} else if (upY > getChildAt(getChildCount() - 1).getTop()) {
mEndPosition = getAdapter().getCount() - 1;
}
//开始更新item顺序
if (mEndPosition > 0 && mEndPosition < getAdapter().getCount()) {
DragAdapter adapter = (DragAdapter) getAdapter();
//删除原来的条目
adapter.remove(adapter.getItem(mStartPosition));
//更新
adapter.insert(adapter.getItem(mStartPosition), mEndPosition);
}
}
全部代码贴上
DragListView
package com.liuguilin.draglistviewsample.view;
/*
* 项目名: DragListViewSample
* 包名: com.liuguilin.draglistviewsample.view
* 文件名: DragListView
* 创建者: LGL
* 创建时间: 2016/8/29 20:50
* 描述: 自定义高仿QQ列表可拖拽的ListView
*/
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.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.ListView;
import com.liuguilin.draglistviewsample.R;
import com.liuguilin.draglistviewsample.adapter.DragAdapter;
public class DragListView extends ListView {
//按下选中的position
private int mStartPosition;
//需要达到的position
private int mEndPosition;
//手指在条目中的相对Y坐标
private int dragPoint;
//ListView在屏幕中的Y坐标
private int dragOffset;
//上
private int upScroll;
//下
private int downScroll;
//窗体
private WindowManager wm;
//显示的截图
private ImageView dragImageView;
//窗体参数
private WindowManager.LayoutParams lParams;
//构造方法
public DragListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* 获取触点所在条目的位置
* 获取选中条目的图片
* 事件的拦截机制
*
* @param ev
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//识别动作
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//获取触点的坐标
int x = (int) ev.getX();
int y = (int) ev.getY();
//这样就可以计算我按到哪个条目上了
mStartPosition = mEndPosition = pointToPosition(x, y);
//判断触点是否在logo的区域
ViewGroup itemView = (ViewGroup) getChildAt(mStartPosition - getFirstVisiblePosition());
//记录手指在条目中的相对Y坐标
dragPoint = y - itemView.getTop();
//ListView在屏幕中的Y坐标
dragOffset = (int) (ev.getRawY() - y);
//拖动的图标
View dragger = itemView.findViewById(R.id.iv_logo);
//判断触点是否在logo区域
if (dragger != null && x < dragger.getRight() + 10) {
//定义ListView的滚动条目
//上
upScroll = getHeight() / 3;
//下
downScroll = getHeight() * 2 / 3;
//获取选中的图片/截图
itemView.setDrawingCacheEnabled(true);
//获取截图
Bitmap bitMap = itemView.getDrawingCache();
//图片滚动
startDrag(bitMap, y);
}
break;
}
//还会传递事件到子View
return false;
}
/**
* 图片在Y轴,也就是上下可滚动
*
* @param bitMap
* @param y
*/
private void startDrag(Bitmap bitMap, int y) {
//窗体仿照
wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
//设置窗体参数
lParams = new WindowManager.LayoutParams();
//设置宽高
lParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
lParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
//属性
lParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
//设置半透明
lParams.alpha = 0.8f;
//设置居中
lParams.gravity = Gravity.TOP;
//设置xy
lParams.x = 0;
lParams.y = y-dragPoint + dragOffset;
//属性
lParams.format = PixelFormat.TRANSLUCENT;
//设置动画
lParams.windowAnimations = 0;
//图片
dragImageView = new ImageView(getContext());
//设置截图
dragImageView.setImageBitmap(bitMap);
//添加显示窗体
wm.addView(dragImageView, lParams);
}
/**
* 触摸事件
*
* @param ev
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
//错误的位置
if (dragImageView != null && mEndPosition != INVALID_POSITION) {
//在滑动事件中控制上下滑动
switch (ev.getAction()) {
case MotionEvent.ACTION_MOVE:
//直接获取到Y坐标进行移动
int moveY = (int) ev.getY();
doDrag(moveY);
break;
//停止拖动成像
case MotionEvent.ACTION_UP:
int upY = (int) ev.getY();
stopDrag();
onDrag(upY);
break;
}
}
//拦截到事件
return true;
}
/**
* 最终开始成像
*
* @param upY
*/
private void onDrag(int upY) {
//分割线的处理
//判断移动到分割线 返回-1
int tempLine = pointToPosition(0, upY);
//我们处理他
if (tempLine != INVALID_POSITION) {
//只要你不移动到分割线 我才处理
mEndPosition = tempLine;
}
/**
* 你在最上方就直接落在第一个最下方就直接最下面一个
*/
//上边界处理
if (upY < getChildAt(1).getTop()) {
mEndPosition = 1;
//下边界处理
} else if (upY > getChildAt(getChildCount() - 1).getTop()) {
mEndPosition = getAdapter().getCount() - 1;
}
//开始更新item顺序
if (mEndPosition > 0 && mEndPosition < getAdapter().getCount()) {
DragAdapter adapter = (DragAdapter) getAdapter();
//删除原来的条目
adapter.remove(adapter.getItem(mStartPosition));
//更新
adapter.insert(adapter.getItem(mStartPosition), mEndPosition);
}
}
/**
* 停止的位置
*/
private void stopDrag() {
//直接移除窗体
if (dragImageView != null) {
wm.removeView(dragImageView);
dragImageView = null;
}
}
/**
* 控制窗体移动
*
* @param moveY
*/
private void doDrag(int moveY) {
if (dragImageView != null) {
lParams.y = moveY - dragPoint + dragOffset;
wm.updateViewLayout(dragImageView, lParams);
}
//判断移动到分割线 返回-1
int tempLine = pointToPosition(0, moveY);
//我们处理他
if (tempLine != INVALID_POSITION) {
//只要你不移动到分割线 我才处理
mEndPosition = tempLine;
}
//拖拽时滚动、滚动速度
int scrollSpeed = 0;
//上滚
if (moveY < upScroll) {
scrollSpeed = 10;
//下滚
} else if (moveY > downScroll) {
scrollSpeed = -10;
}
//开始滚动
if (scrollSpeed != 0) {
//计算条目的Y坐标
int dragItemY = getChildAt(mEndPosition - getFirstVisiblePosition()).getTop();
//当前速度
int dy = dragItemY + scrollSpeed;
//设置
setSelectionFromTop(mEndPosition, dy);
}
}
}
然后我们引用
layout_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent">
<com.liuguilin.draglistviewsample.view.DragListView
android:id="@+id/mListView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
对了,别忘记了添加窗体的权限哦
<!--窗口权限-->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
做完这一系列事情之后,就觉得这个设想其实是对的,然后我们可以尝试性的运行一下
运行时成功的,说明我们的设想是没有问题的,但是随着而来的,也是诸多的bug,所以呢,这也只是思想的参考,不过这些bug我有时间野就修复一下,应该是可以的,好的,本篇博文就先到这里!
有兴趣的加群:555974449
DragListViewSample:http://download.csdn.net/detail/qq_26787115/9616355
Android高级控件(六)——自定义ListView高仿一个QQ可拖拽列表的实现的更多相关文章
- Android高级控件(五)——如何打造一个企业级应用对话列表,以QQ,微信为例
Android高级控件(五)--如何打造一个企业级应用对话列表,以QQ,微信为例 看标题这么高大上,实际上,还是运用我么拿到listview去扩展,我们讲什么呢,就是研究一下QQ,微信的这种对话列表, ...
- Android高级控件(一)——ListView绑定CheckBox实现全选,增加和删除等功能
Android高级控件(一)--ListView绑定CheckBox实现全选,增加和删除等功能 这个控件还是挺复杂的,也是项目中应该算是比较常用的了,所以写了一个小Demo来讲讲,主要是自定义adap ...
- Android高级控件(一)——ListView绑定CheckBox实现全选,添加和删除等功能
Android高级控件(一)--ListView绑定CheckBox实现全选,添加和删除等功能 这个控件还是挺复杂的.也是项目中应该算是比較经常使用的了,所以写了一个小Demo来讲讲,主要是自己定义a ...
- Android高级控件--AdapterView与Adapter
在J2EE中提供过一种非常好的框架--MVC框架,实现原理:数据模型M(Model)存放数据,利用控制器C(Controller)将数据显示在视图V(View)上.在Android中有这样一种高级控件 ...
- Android 高级控件(七)——RecyclerView的方方面面
Android 高级控件(七)--RecyclerView的方方面面 RecyclerView出来很长时间了,相信大家都已经比较了解了,这里我把知识梳理一下,其实你把他看成一个升级版的ListView ...
- Android高级控件(二)——SurfaceView实现GIF动画架包,播放GIF动画,自己实现功能的初体现
Android高级控件(二)--SurfaceView实现GIF动画架包,播放GIF动画,自己实现功能的初体现 写这个的原因呢,也是因为项目中用到了gif动画,虽然网上有很多的架包可以实现,不过我们还 ...
- Android高级控件(四)——VideoView 实现引导页播放视频欢迎效果,超级简单却十分的炫酷
Android高级控件(四)--VideoView 实现引导页播放视频欢迎效果,超级简单却十分的炫酷 是不是感觉QQ空间什么的每次新版本更新那炫炫的引导页就特别的激动,哈哈,其实他实现起来真的很简单很 ...
- Android高级控件(三)—— 使用Google ZXing实现二维码的扫描和生成相关功能体系
Android高级控件(三)-- 使用Google ZXing实现二维码的扫描和生成相关功能体系 摘要 现在的二维码可谓是烂大街了,到处都是二维码,什么都是二维码,扫一扫似乎已经流行到习以为常了,今天 ...
- Android高级控件(三)—— 使用Google ZXing实现二维码的扫描和生成相关功能体系
Android高级控件(三)-- 使用Google ZXing实现二维码的扫描和生成相关功能体系 摘要 如今的二维码可谓是烂大街了.到处都是二维码.什么都是二维码,扫一扫似乎已经流行到习以为常了,今天 ...
随机推荐
- 洛谷P3159 [CQOI2012]交换棋子
巧妙的拆点方式,首先把1看成黑点,0看成空的,几次交换就可以看成一条路径 1)从容量上看,这条路径为1-2-2-2-2-2----2-1 2)从费用上看,这条路径每条边费用都是1 于是用一种巧妙的拆点 ...
- [CEOI2008]order
Description 有N个工作,M种机器,每种机器你可以租或者买过来. 每个工作包括若干道工序,每道工序需要某种机器来完成,你可以通过购买或租用机器来完成. 现在给出这些参数,求最大利润 Solu ...
- JVM学习记录-对象已死吗
前言 先来回顾一下,在jvm运行时数据区,分为两部分,一个部分是线程共享区,主要包括堆和方法区,另一部是线程私有区分包括本地方法栈,虚拟机栈和程序计数器.在线程私有部分的三个区域是随着线程生和灭的.栈 ...
- MVC简单随笔
MVC的具体含义是:model+view+controller,即模型+视图+控制它们各自处理自己的任务: (1)模型(model):模型持有所有的数据.状态和程序逻辑.模型独立于视图和控制器.(2) ...
- Delphi7.0常用函数-属性-事件
abort 函数 引起放弃的意外处理 addexitproc 函数 将一过程添加到运行时库的结束过程表中 addr 函数 返回指定对象的地址 adjustlinebreaks 函数 将给定字符串的行分 ...
- Python中文件的操作
文件的操作介绍 文件打开的方法 主要有两种: no with 格式:open(file, mode='r', buffering=-1, encoding=None, errors=None, new ...
- Docker学习笔记【三】安装Redis
项目中使用到Redis,平常都是别人搭建的,今天试着在Google Cloud Platform 上搭建一个学习环境. 1.使用 docker pull redis 从docker hub中下载镜像 ...
- STM32 基于定时器的PWM发生器
脉冲宽度调制(PWM),是英文"Pulse Width Modulation" 的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术.简单一点,就 ...
- web性能优化之---JavaScript中的无阻塞加载性能优化方案
一.js阻塞特性 JS 有个很无语的阻塞特性,就是当浏览器在执行JS 代码时,不能同时做其他任何事情,无论其代码是内嵌的还是外部的. 即<script>每次出现都会让页面等待脚本的解析和执 ...
- textarea不能使用maxlength
知道文本框有个maxlength属性,有次开发项目中使用了textarea标签,没去看文档,直接加了maxlength属性,且有效果没有报错,喜滋滋的用了,结果没两天就测试出了bug 问题描述:文本域 ...