超级详细的仿QQ滑动删除的效果
来源:互联网 发布:施工进度网络计划绘制 编辑:程序博客网 时间:2024/06/05 04:55
前言:
上周做购物车的要用到删除商品功能的时候想到要用这个效果,但是没想到一直搞到现在。一个是自己基础确实稍微差点,另一方面是因为有段时间没好好碰了,手稍微生了点。
注意:
1.这个所有代码我会贴出来,不需要积分下载
2.基本上每一步我都会写出来,甚至到每句代码,每个参数,不要嫌弃我啰嗦,是我做的时候,真的觉得大佬们写的太简略了。虽然可能主要还是我菜。实在不行,算我写给自己记录的
好,开始
这里的ListView和item都需要进行重写的,然后通过Adapter的getView方法来配置。整个过程主要是两个View对象根据用户的操作来改变显示位置,从而实现滑动的效果。这里的两个View的XML文件也需要自己定义。最后在item类中把他们添加到一起,然后再编写改变View对象的LayoutParam参数来改变位置实现滑动。有很多博客用到了一个Scroll的类,我这里没有用,但是依然是可以实现效果的,只是可能会存在一些瑕疵。毕竟大家都用的Scroll,肯定不会错。但是我这个过程理解了之后用不用Scroll其实都能会了。最多是多几个方法
第一步这里先从布局文件开始贴吧,这里的布局文件的参数是wrap_content还是match_parent对结果有很多影响的,反正我自己是改了好多次。如果显示有问题的话可以多试着修改下布局文件,和Item的OnLayout方法和与layoutParams有关的方法。还有就是contentView,menuView的布局文件用的layout需要和item类继承的layout相同。这里需要有的布局文件有三个,一个是主页面,一个是ContentView,一个是MenuView。
这里的MenuView和ContentView和Item的layout类型都是用的Relativelayout,也可以用其他的layout的,但是他们三个必须保持一致,不然可能会因为冲突出现各种问题。我调的时候基本都试了一下的。activity_main不记得是不是了。
MenuView:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="80dp" android:id="@+id/contain" android:orientation="horizontal" > <TextView android:id="@+id/txt_top" android:layout_width="50dp" android:layout_height="match_parent" android:text="置顶" android:layout_alignParentLeft="true" android:textColor="#ffffff" android:background="#a1a3a6" android:gravity="center" /> <TextView android:id="@+id/txt_notRead" android:layout_width="100dp" android:layout_height="match_parent" android:text="标为未读" android:layout_toRightOf="@+id/txt_top" android:textColor="#ffffff" android:background="#ee9a00" android:gravity="center" /> <TextView android:id="@+id/txt_delete" android:layout_width="50dp" android:layout_height="match_parent" android:text="删除" android:layout_toRightOf="@+id/txt_notRead" android:textColor="#ffffff" android:background="#FF3030" android:gravity="center" /></LinearLayout>
ContentView:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="80dp" android:id="@+id/content" android:orientation="vertical" ><TextView android:id="@+id/txt_username" android:layout_width="wrap_content" android:layout_height="20dp" android:text="熊熊熊" android:textSize="18dp" android:textColor="#000000" /><TextView android:layout_width="wrap_content" android:layout_height="20dp" android:text="熊小熊,我是你爸爸" android:layout_below="@+id/txt_username" /></LinearLayout>
activity_main:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.warrenmihail.slideview2.MainActivity" android:orientation="vertical" > <com.example.warrenmihail.slideview2.SlideView android:id="@+id/slideView" android:layout_width="wrap_content" android:layout_height="wrap_content" /></LinearLayout>
接下来开始写代码,整个框架不是很复杂所以我都写在一个包下的。这里先从ListView的Item的java代码开始写。之后再写适配器再写ListView,最后写MainActivity。我在尝试写这个东西的时候其实每个都在改来改去的。我都是先完成一步再接着调下一步,所以当做着发现需要从一个类里面获得另外一个类的数据的时候就又开始改的。在这个改的过程中就感觉对回调有了很自然的理解了。当一个类在某个触发条件之后需要用到另一个类里的东西的时候,很自然而然的就开始按着回调来写了。后面在代码里遇到的时候我再说一下。
SlideItem:
package com.example.warrenmihail.slideview2;import android.content.Context;import android.support.annotation.Nullable;import android.util.AttributeSet;import android.util.DisplayMetrics;import android.util.EventLog;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import android.widget.LinearLayout;import android.widget.RelativeLayout;import android.widget.TextView;import android.widget.Toast;/** * Created by warrenmihail on 17-11-17. */public class SlideItem extends LinearLayout{ private View contentView = null; private View menuView = null; Context context; private int downX; public SlideItem(Context context) { super(context); this.context = context; } public SlideItem(Context context, @Nullable AttributeSet attrs) { super(context, attrs); this.context = context; } public SlideItem(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.context = context; } public void setContentView(View contentView,View rightView){ this.contentView = contentView; this.menuView = rightView; initView(); } public void initView(){ LayoutParams contentParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); LayoutParams rightParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); this.addView(contentView); this.addView(menuView); } public boolean onSwipe(MotionEvent event){ switch (event.getAction()){ case MotionEvent.ACTION_DOWN: downX=(int) event.getX(); break; case MotionEvent.ACTION_MOVE: int dis = (int ) (downX - event.getX()); move(dis); break; case MotionEvent.ACTION_UP: break; } return true; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec,heightMeasureSpec); if(menuView != null){ menuView.measure(MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED),MeasureSpec.makeMeasureSpec(getMeasuredHeight(),MeasureSpec.EXACTLY)); } } private void move(int dis) { if(dis> menuView.getWidth()){ dis = menuView.getWidth(); } if (dis< 0){ dis = 0; } contentView.layout(-dis,contentView.getTop(),contentView.getWidth()-dis,contentView.getMeasuredHeight()); menuView.layout(contentView.getWidth()-dis,menuView.getTop(),contentView.getWidth()+menuView.getWidth()-dis,menuView.getBottom()); postInvalidate(); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if(contentView != null){ contentView.layout(0,0,getMeasuredWidth(),contentView.getMeasuredHeight()); } if(menuView != null){ menuView.layout(contentView.getWidth(),0,menuView.getWidth() + contentView.getWidth(),contentView.getMeasuredHeight()); } }}
这里的Item是作为一个自定义View的,所以需要继承view并且实现相应的构造方法。之后又一个onlayout的方法,是用来设置最开始的时候ContentView和MenuView的摆放位置的。move是整个效果的关键,很多博客上写的用的Scroll类然后重写方法来做的,我直接省略了这个。基本上没啥大问题,可能是暂时还没发觉。这里查看layout方法的源码,发现里面的四个参数是view的上下左右的位置。之后在Touch方法里判断,但动作是移动的时候就每次都调用onswipe,onswipe再调用move,move再用layout方法实现最后的移动。这里的Touch方法是在slideView类里面进行重写的,之后用传过来的event对象来获取x,y的坐标变化。计算出移动的距离(dis),传入layout方法。
MyAdapter:
package com.example.warrenmihail.slideview2;import android.content.Context;import android.view.LayoutInflater;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.LinearLayout;import android.widget.TextView;import android.widget.Toast;/** * Created by warrenmihail on 17-11-17. */public class MyAdapter extends BaseAdapter implements View.OnClickListener{ private Context context; private LayoutInflater inflater; private SlideView slideView; private int delete_postion; private MainActivity main; int x= 5; public MainActivity getMain() { return main; } public void setMain(MainActivity main) { this.main = main; } public int getDelete_postion() { return delete_postion; } public void setDelete_postion(int delete_postion) { this.delete_postion = delete_postion; } public SlideView getSlideView() { return slideView; } public void setSlideView(SlideView slideView) { this.slideView = slideView; } public MyAdapter(Context context){ this.context = context; this.inflater = LayoutInflater.from(context); } @Override public int getCount() { return x; } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if(convertView == null){ View content = inflater.inflate(R.layout.contentview,null); View menu = inflater.inflate(R.layout.menuview,null); holder = new ViewHolder(content,menu); SlideItem slideItem = new SlideItem(context); slideItem.setContentView(content,menu); convertView = slideItem; convertView.setTag(holder); }else{ holder = (ViewHolder) convertView.getTag(); } holder.txt_top.setOnClickListener(this); holder.txt_delete.setOnClickListener(this); holder.txt_notRead.setOnClickListener(this);// convertView.setOnTouchListener(new View.OnTouchListener() {// @Override// public boolean onTouch(View v, MotionEvent event) {// SlideItem item = (SlideItem) v;// item.onSwipe(event);// return true;// }// }); return convertView; } class ViewHolder{ TextView txt_top; TextView txt_notRead; TextView txt_delete; public ViewHolder(View center,View menu){ this.txt_top = (TextView) menu.findViewById(R.id.txt_top); this.txt_notRead = (TextView) menu.findViewById(R.id.txt_notRead); this.txt_delete = (TextView) menu.findViewById(R.id.txt_delete); } } @Override public void onClick(View v) { switch(v.getId()){ case R.id.txt_top: Toast.makeText(context,"消息置顶", Toast.LENGTH_SHORT).show(); break; case R.id.txt_notRead: Toast.makeText(context,"标为未读",Toast.LENGTH_SHORT).show(); break; case R.id.txt_delete: Toast.makeText(context,"删除消息",Toast.LENGTH_SHORT).show(); x--; notifyDataSetChanged();// remove(); break; } }}
这里的Adapter类是这几个类中最简单的了,主要有一个ViewHolder类。之前没有见过,但是其实没啥。主要是用来找到item里的控件对象的,避免重复创建。这里实现了一个删除的功能,删除的实现是通过改变Adapter的count返回值,再调用notifyDatasetChanged方法。这里不允许用listview的对象直接来remove。这里删除对应的item是根据数据的remove方法来实现的,调用数据的List对象的remove方法之后,list对象的length发生改变,item个数就会变少,同时要删除的item里的内容没了,看上去就像是item被删除了。(这里我先记着,后面改排版的位置,免得后面忘了)我因为是只是测试。所以根本就没有数据可以修改,所以在设置点击事件时,我直接让cout的值每次减一就可以了,因为本来就都是一样的,所以看上去也是对的。但是如果不一样的话要修改应该还是需要知道选中的是哪一个。更改稍麻烦,要做的时候自己再看一下。
SlideView:
import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.widget.ListView;import android.widget.Toast;/** * Created by warrenmihail on 17-11-17. */public class SlideView extends ListView { private SlideItem mTouchView = null; private int mTouchPosition; private float mDownX; private float mDownY; private Context context; MyAdapter myAdapter; public SlideView(Context context) { super(context); this.context = context; } public SlideView(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; } public SlideView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.context = context; } @Override public boolean onTouchEvent(MotionEvent ev) { myAdapter = (MyAdapter) getAdapter(); myAdapter.setSlideView(SlideView.this); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.d("xjh","down----------");// Toast.makeText(context,"当当当",Toast.LENGTH_SHORT).show(); int oldPostion = mTouchPosition; mDownX = ev.getX(); mTouchPosition = this.pointToPosition((int) ev.getX(),(int) ev.getY()); if(mTouchPosition == oldPostion && mTouchView != null){ mTouchView.onSwipe(ev); return true; } View currentView = getChildAt(mTouchPosition - getFirstVisiblePosition()); if(mTouchView != null){ mTouchView = null; return super.onTouchEvent(ev); } if(currentView instanceof SlideItem){ mTouchView = (SlideItem) currentView; } if(mTouchView != null){ mTouchView.onSwipe(ev); } break; case MotionEvent.ACTION_MOVE: Log.d("xjh","---------move"); if(mTouchView != null) { mTouchView.onSwipe(ev); return true; } break; case MotionEvent.ACTION_UP: if(mTouchView != null){ mTouchPosition = -1; ev.setAction(MotionEvent.ACTION_CANCEL); super.onTouchEvent(ev); return true; } myAdapter.setDelete_postion(mTouchPosition - getFirstVisiblePosition()); break; } return super.onTouchEvent(ev); }}
这里SlideView里面实现了最后几步。我滑动其中一个Item的时候首先触发的是SlideView的onTouch方法,但是item移动的方法我是写在SlieItem类的。所以我在touch方法里把这个首先通过currentView = getChildAt(mTouchPosition - getFirstVisiblePosition());来获得选中的子项,再将这个事件传给子项调用的它里面移动的方法的。第二点要注意的是关于选中子项的postion的问题,它这里的postion有一个mTouchpostion和oldpostion,用来确定滑动和按下的是否是同一个。还有在计算当前项的postion的时候this.pointToPosition获得的postion再减去getFirstVisiblePosition()才会是他真正的位置。差不多就这些。
MainActivity:
import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.MotionEvent;import android.view.View;import android.widget.TextView;public class MainActivity extends AppCompatActivity { SlideView slideView; TextView txt_top,txt_notRead,txt_delete; MyAdapter myAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myAdapter = new MyAdapter(MainActivity.this); slideView = (SlideView) findViewById(R.id.slideView); slideView.setAdapter(myAdapter); }}
MainActivity没啥多说的了,标准的写ListView的步骤。
有啥问题还可以评论问我
以上
这里是整个的代码
链接: https://pan.baidu.com/s/1i5IiLI9 密码: 77ip
- 超级详细的仿QQ滑动删除的效果
- 仿qq的listView 滑动删除
- 仿qq横向滑动删除的 SwipeMenuListView
- 仿QQ滑动删除消息效果
- 仿QQ身边的人的水平滑动效果
- android滑动删除的listview,仿手机QQ的样子
- 仿QQ打开“我”界面的滑动效果
- 仿QQ滑动删除
- 仿qq滑动删除
- ANDROID 动态添加的listView,仿QQ滑动删除
- Android 仿腾讯QQ 的 ListView滑动删除
- Android仿QQ消息列表ListView滑动删除效果
- 高仿 QQ 侧滑删除 Item 的效果
- Android实现类似QQ的滑动删除效果
- Android实现类似QQ的滑动删除效果
- Android实现类似QQ的滑动删除效果
- 仿QQ tab滑动效果
- 仿QQ的下拉效果
- 回溯求解-迷宫问题
- 11.20(1)
- Python机器学习库sklearn网格搜索与交叉验证
- 计算机视觉-椒盐噪声输出
- anaconda在windows系统上的报错修复
- 超级详细的仿QQ滑动删除的效果
- 用Go语言打造区块链[1]
- 微信公众号的开发学习《1》
- Redis安装系统服务1073错误
- Hibernate应用(一)
- 11.20(2)
- 计算机视觉-中值滤波
- 【docker】top命令报错 “TERM environment variable not set.”
- 史上最全最强SpringMVC详细示例实战教程(转)