超级详细的仿QQ滑动删除的效果

前言:
上周做购物车的要用到删除商品功能的时候想到要用这个效果,但是没想到一直搞到现在。一个是自己基础确实稍微差点,另一方面是因为有段时间没好好碰了,手稍微生了点。
注意:
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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值