Android开发学习之路-PopupWindow和仿QQ左滑删除
这周作业,要做一个类似QQ的左滑删除效果的ListView,因为不想给每个item都放一个按钮,所以决定用PopupWindow,这里记录一下
先放一下效果图:
先说明一下这里面的问题:
①没有做到像QQ那样可以允许item跟随手指移动,虽然PopupWindow有update方法让我们动态移动,但是在屏幕外移动会没有动画效果,直接弹进来
②仔细观察可以发现,item的滑动和删除按钮的滑动是分开的,无法保证它们会一起播放,QQ的动画可以
再说说大概的思路,因为我们没有让item都带上Button,所以用到了PopupWindow来做删除按钮,我们可以通过自定义ListView来监听手势,如果判断了是左滑动,则让PopupWindow动态显示出来,在让item做一个平移的效果,当两者的持续时间一致的时候,效果就出来了。接下来,如果用户点击了PopupWindow以外的区域,PopupWindow会消失,回调dismiss方法,我们再把item移回原位。
介绍一下PopupWindow,它允许我们在屏幕上的任意位置弹出一个提示框,提示框的布局我们可以自己定义,我们上面的删除按钮,其实就是一个PopupWindow。
如果我们要使用PopupWindow,可以像下面这样,定义一个自定义布局的PopupWindow
View view = LayoutInflater.from(context).inflate(R.layout.item_delete, null);
PopupWindow mPopupWindow = new PopupWindow(view, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, true);
如果我们要显示这个PopupWindow,可以调用showAtLocation或者showAsDropDown,前者是直接把PopupWindow显示在某个特定位置,后者是在某个位置从上向下弹出,这里我们调用第一个方法,有兴趣的可以尝试下第二个,参数什么的可以不用管,不懂的看看API就知道了,之后再调用update方法更新位置信息
mPopupWindow.showAtLocation(itemView, Gravity.LEFT | Gravity.TOP, locations[0]+ itemView.getWidth() - popWidth, locations[1]);
mPopupWindow.update();
至于对PopupWindow设置背景什么的,需要的时候看看API就知道了!这里不详说。
至于效果的实现,我们一步一步的来。
①自定义一个ListView,详细的做法,已经在下面做了注释
public class SlidableListView extends ListView {
private static final String TAG = "SlidableListView";
private int touchSlop; private View itemView; // 每个Item的View
private int mCurrentDownPosition; // 当前点击的位置,用来找出点击的Item
private PopupWindow mPopupWindow; // 删除按钮
private int popHeight, popWidth; // 删除按钮的高和宽
private boolean isSliding = false; // 手指是否在向左滑动
private int downX, downY, moveX, moveY; // 点击的X和Y,以及移动的X和Y
private OnItemDeleteListener onItemDeleteListener; // 点击删除按钮时候的回调接口 public SlidableListView(Context context) {
super(context);
} // 在布局中使用自定义控件调用的构造函数,这里进行一些必要的初始化
public SlidableListView(Context context, AttributeSet attrs) {
super(context, attrs);
// 初始化删除按钮,记得让按钮可以接收焦点
View view = LayoutInflater.from(context).inflate(R.layout.item_delete, null);
mPopupWindow = new PopupWindow(view, WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT, true);
// 获取删除按钮的宽和高
view.measure(0, 0);
popWidth = view.getMeasuredWidth();
popHeight = view.getMeasuredHeight();
// 获取滑动的最小距离
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
} public SlidableListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
} // 先拦截touch事件,根据不同情况进行分发
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int action = ev.getAction();
int x = (int) ev.getX(), y = (int) ev.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
downX = x;
downY = y;
// 如果删除按钮正在显示,那么不处理这个触摸事件,并把按钮隐藏起来
if (mPopupWindow.isShowing()) {
mPopupWindow.dismiss();
return false; // 阻止事件传递
}
mCurrentDownPosition = pointToPosition(downX, downY);
// 获取触摸的item
itemView = getChildAt(mCurrentDownPosition - getFirstVisiblePosition());
break;
case MotionEvent.ACTION_MOVE:
moveX = x;
moveY = y;
int deltaX = moveX - downX;
int deltaY = moveY - downY;
// 当判断为左滑的时候,把事件交给OnTouchEvent来处理
if (moveX < downX && Math.abs(deltaX) > touchSlop && Math.abs(deltaY) <
touchSlop) {
isSliding = true;
}
break;
}
return super.dispatchTouchEvent(ev);
} @Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
// 判断是否已经有左右滑动
if (isSliding) {
switch (action) {
case MotionEvent.ACTION_UP:
isSliding = false;
break;
case MotionEvent.ACTION_MOVE:
// 获取item在屏幕中的位置信息
int[] locations = new int[2];
itemView.getLocationOnScreen(locations);
// 给删除按钮设置动画,这里自定了出现从右到左平移,隐藏从左到右
mPopupWindow.setAnimationStyle(R.style.PopupWindowAnimations);
// 设置删除按钮的位置,这里设置在item的最右端
mPopupWindow.showAtLocation(itemView, Gravity.LEFT | Gravity.TOP, locations[0]
+ itemView.getWidth() - popWidth, locations[1]);
mPopupWindow.update();
// 给item播放平移动画,配合删除按钮
ObjectAnimator.ofFloat(itemView, "translationX", 0, -popWidth).setDuration
(200).start();
// 获取删除按钮的实例,这里是一个TextView
TextView delete = (TextView) mPopupWindow.getContentView().findViewById(R.id
.delete);
// 设置监听,如果删除按钮消失(点击按钮外的地方,按下back键)回调
mPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
// 把item移回原位置
ObjectAnimator.ofFloat(itemView, "translationX", -popWidth, 0)
.setDuration(200).start();
}
});
// 给删除按钮设置点击事件
delete.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 获取点击的item位置,并调用回调接口方法发送出去,隐藏删除按钮
int position = SlidableListView.this.getPositionForView(itemView);
onItemDeleteListener.onItemDelete(position);
mPopupWindow.dismiss();
}
});
break;
}
// 如果有在滑动,消化事件
return true;
}
return super.onTouchEvent(ev);
} public void setOnItemDeleteListener(OnItemDeleteListener onItemDeleteListener) {
this.onItemDeleteListener = onItemDeleteListener;
} // 回调删除事件的接口
public interface OnItemDeleteListener {
void onItemDelete(int position);
}
}
②自定义消失和进入的动画,因为系统中没有我们想要的平移进入
style.xml:
<style name="PopupWindowAnimations" parent="@android:style/Animation">
<item name="android:windowEnterAnimation">@anim/delete_enter</item>
<item name="android:windowExitAnimation">@anim/delete_exit</item>
</style>
anim/delete_enter.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromXDelta="200"
android:toXDelta="0"
android:fromYDelta="0"
android:toYDelta="0"
android:duration="200"/>
</set>
anim/delete_exit.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="0"
android:toXDelta="200"
android:fromYDelta="0"
android:toYDelta="0"
android:duration="200"/>
</set>
③自定义Adapter,这里不给出了,都懂
④在布局中使用自定义的ListView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"> <sixthweek.fndroid.com.sixthweek.SlidableListView
android:id="@+id/slidableListView"
android:layout_width="match_parent"
android:layout_height="match_parent" /> </LinearLayout>
⑤在Activity中使用adapter传入数据即可
public class SlidableListViewActivity extends AppCompatActivity {
private SlidableListView slidableListView;
private MyAdapter adapter;
private List<Map<String, String>> data; @Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_slidablelistview);
slidableListView = (SlidableListView) findViewById(R.id.slidableListView);
data = new ArrayList<>();
for (int i = 0; i < 20; i++) {
Map<String, String> map = new HashMap();
map.put("name", "QQ" + i);
data.add(map);
}
adapter = new MyAdapter(this, data);
slidableListView.setAdapter(adapter);
// 实现回调接口,处理逻辑
slidableListView.setOnItemDeleteListener(new SlidableListView.OnItemDeleteListener() {
@Override
public void onItemDelete(int position) {
data.remove(position);
adapter.notifyDataSetChanged();
}
});
}
}
Android开发学习之路-PopupWindow和仿QQ左滑删除的更多相关文章
- Vue 仿QQ左滑删除功能(非原创)
非原创,摘选来源:http://www.jb51.net/article/136221.htm. 废话不多说,相当实用,先记录. Html代码: <div class="contain ...
- Android开发学习之路-RecyclerView滑动删除和拖动排序
Android开发学习之路-RecyclerView使用初探 Android开发学习之路-RecyclerView的Item自定义动画及DefaultItemAnimator源码分析 Android开 ...
- Android开发学习之路--基于vitamio的视频播放器(二)
终于把该忙的事情都忙得差不多了,接下来又可以开始good good study,day day up了.在Android开发学习之路–基于vitamio的视频播放器(一)中,主要讲了播放器的界面的 ...
- Android开发学习之路--Android Studio cmake编译ffmpeg
最新的android studio2.2引入了cmake可以很好地实现ndk的编写.这里使用最新的方式,对于以前的android下的ndk编译什么的可以参考之前的文章:Android开发学习之路– ...
- Android开发学习之路--网络编程之xml、json
一般网络数据通过http来get,post,那么其中的数据不可能杂乱无章,比如我要post一段数据,肯定是要有一定的格式,协议的.常用的就是xml和json了.在此先要搭建个简单的服务器吧,首先呢下载 ...
- Android开发学习之路--Activity之初体验
环境也搭建好了,android系统也基本了解了,那么接下来就可以开始学习android开发了,相信这么学下去肯定可以把android开发学习好的,再加上时而再温故下linux下的知识,看看androi ...
- Android开发学习之路--Android系统架构初探
环境搭建好了,最简单的app也运行过了,那么app到底是怎么运行在手机上的,手机又到底怎么能运行这些应用,一堆的电子元器件最后可以运行这么美妙的界面,在此还是需要好好研究研究.这里从芯片及硬件模块-& ...
- Android开发学习之路--MAC下Android Studio开发环境搭建
自从毕业开始到现在还没有系统地学习android应用的开发,之前一直都是做些底层的驱动,以及linux上的c开发.虽然写过几个简单的app,也对android4.0.3的源代码做过部分的分析,也算入门 ...
- Android开发学习之路-记一次CSDN公开课
今天的CSDN公开课Android事件处理重难点快速掌握中老师讲到一个概念我觉得不正确. 原话是这样的:点击事件可以通过事件监听和回调两种方法实现. 我一听到之后我的表情是这样的: 这跟我学的看的都不 ...
随机推荐
- ip二进制计算,与运算算网段
每8位二进制,各位从左到右对应的权值分别是 128,64,32,16, 8,4,2,1 .(即2的n-1次方,n是从右到左当前位的位数) 所以随便拿一个256以内的数给你化为二进制,都可以分解为权值 ...
- h5容易遗忘的内容
1.表单中 input类型 小补充: 2.常用的表单元素 3.表单属性 4.表单事件 5.多媒体:音频和视频 5.1音频 5.2视频 6.Dom拓展
- Spring 使用 SLF4J代替 Commons Logging 写日志 异常
项目的日志更换成slf4j和logback后,发现项目无法启动.错误提示 Caused by: java.lang.NoClassDefFoundError: Lorg/apache/commons/ ...
- Chrome 开发者工具(DevTools)中所有快捷方式列表
Chrome DevTools提供了一些内置的快捷键,开发者利用这些快捷键可以节省常工作中很多日的开发时间.下面列出了每个快捷键在Windows/Linux及Mac中的对应键.其中一些快捷键对于Dev ...
- svn客户端重新设置用户名和密码
在第一次使用TortoiseSVN从服务器CheckOut的时候,会要求输入用户名和密码,这时输入框下面有个选项是保存认证信息,如果选了这个选项,那么以后就不用每次都输入一遍用户名密码了. 不过,如果 ...
- 图解修改mysql的默认数据库存放目录
按照下图三步完成:
- BestCoder Round #80 1002
HDU 5666 Segment 题意:给你条斜率为-1,常数项为q(q为质数)的直线,连接原点与直线上整数格点,问你在有多少个格点在形成的无数个三角形内,而不在线段上,结果对P取模. 思路:best ...
- 转载——CSS3 Object-fit和Object-position
在响应式设计(RWD)中,令大家最头痛的问题的是图片的自适应处理问题.往往我采取的处理方式是给图片的容器设置一个尺寸,然后给图片设置下面的代码: img { max-width: 100%; heig ...
- View的弹性滑动
View的弹性滑动 实现弹性滑动的思想:将一次大的滑动分成若干次小的滑动并在一个时间段内完成,具体的实现方式有很多,如通过Scroller.Handler#postDelayed以及Thread#sl ...
- 关于tableviewcell的一些必备常识
1.设置tableview的背景颜色当设置tableview.backgroundcolor无效时,这样设置: UIView *view = [[UIView alloc] initWithFr ...