【Android开源项目解析】RecyclerView侧滑删除粒子效果实现——初探Android开源粒子库 Leonids

前两天在微博上看到了这个侧滑删除的粒子效果,但是只有IOS的,所以心血来潮,写了个玩玩,下面简单介绍下实现的思路

  • 项目简介
  • 实现原理解析
  • 代码实现
  • 如何使用
  • 更多参考

项目简介

先不废话,上效果图

项目地址:https://github.com/ZhaoKaiQiang/ParticleLayout

实现原理解析

其实看了那么多的关于侧滑删除的项目,再来思考这个问题,就so easy了!

咱们先分析下需求:

- 侧滑手势检测

- 粒子跟手效果

- 删除状态判断

- 数据源刷新

ok,知道需求了,咱们看对策

- 手势检测可以重写onTouch,判断移动方向和距离

- 粒子效果使用第三方的开源项目leonids,跟手效果就是简单的触摸位置的更新

- 假定滑动距离超过item的宽度一半,就代表删除

- 添加回调接口,完成数据源刷新

代码实现

知道了咱们的需求,并且每一个需求都有了解决方案,那么剩下的问题其实就是如何写代码的问题了。

下面这部分,最好参考这个项目的源码进行阅读~

首先,这肯定是属于自定义控件,那么咱们继承谁呢?我选择继承FrameLayout,为啥呢?因为在FrameLayout里面咱们可以控制遮罩效果。

其实完成遮罩效果,也有两种方案,

  1. 在FrameLayout中放置一个和背景色相同的布局,然后再onTouch中控制宽度,来模拟遮罩效果
  2. 直接重写dispatchDraw(Canvas canvas) ,使用Canvas.clipRect()控制绘制区域,模拟遮罩效果

其实这两种效果我都做过,在第一个版本中使用的是方案一,可以完成这个效果,但是不知道怎么回事,在5.0以上系统中,遮罩层的层级关系和5.0以下不一致,因此导致在5.0以上不能使用。除此之外, 使用第一种效果需要多一层布局,效率低,而且通用性不好,所以在这里我选择第二种方案。

咱们开始看代码~

public ParticleLayout(Context context) {
        this(context, null);
    }

    public ParticleLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ParticleLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        backLayoutRect = new Rect();
        backLocation = new int[2];
    }

构造函数非常简单,在三个参数构造函数中,初始化两个变量,backLayoutRect用于控制内容区域,用于后面的触摸边界检测,backLocation则用于存储布局位置,粒子效果需要用坐标。

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        if (getChildCount() != 1) {
            throw new IllegalArgumentException("the count of child view must be one !");
        }

        backLayout = (ViewGroup) getChildAt(0);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        backLayout.getLocationInWindow(backLocation);
        backLayoutRect.set(backLocation[0], backLocation[1],
                backLocation[0] + backLayout.getMeasuredWidth(),
                backLocation[1] + backLayout.getMeasuredHeight());
    }

上面的代码很好理解,在onSizeChange()里面对子View数量进行强制规定,必须为一个,方便获取到内容区域,而在onLayout()则在测量、布局之后,获取到布局所在位置和初始化内容区域backLayoutRect。

到现在位置,就初始化完毕了,下面其实就是触摸事件的写法。

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                if (event.getX() > backLayoutRect.width() * 4 / 5) {
                    isSwape = true;
                    startX = event.getX();

                    if (bitmapArrays == null || bitmapArrays.length == 0) {
                        particleSystem = new ParticleSystem((Activity) getContext(), COUNT_OF_PARTICAL_BITMAP, DEFAULT_PARTICLE_BITMAP, TIME_TO_LIVE);
                    } else {
                        Random random = new Random();
                        int resId = bitmapArrays[random.nextInt(bitmapArrays.length)];
                        particleSystem = new ParticleSystem((Activity) getContext(), COUNT_OF_PARTICAL_BITMAP, resId, TIME_TO_LIVE);
                    }

                    particleSystem.setAcceleration(0.00013f, 90)
                            .setSpeedByComponentsRange(0f, 0.3f, 0.05f, 0.3f)
                            .setFadeOut(TIME_TO_FADE_OUT, new AccelerateInterpolator())
                            .emitWithGravity(backLayout, Gravity.RIGHT, COUNT_OF_PARTICAL_BITMAP);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                clipWidth = (int) (startX - event.getX());
                if (isSwape && clipWidth > 0) {
                    requestLayout();
                    particleSystem.updateEmitVerticalLine(backLayoutRect.right - clipWidth, backLayoutRect.top - getStatuBarHeight(), backLayoutRect.bottom - getStatuBarHeight());
                } else {
                    particleSystem.stopEmitting();
                }
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                startX = 0;
                clipWidth = 0;
                invalidate();
                isSwape = false;
                particleSystem.stopEmitting();
                getParent().requestDisallowInterceptTouchEvent(false);

                if (event.getX() >= getWidth() / 2) {
                    isDelete = false;
                } else {
                    isDelete = true;
                }

                if (isDelete) {
                    if (mDeleteListener != null) {
                        mDeleteListener.onDelete();
                    }
                }
                break;
        }

        if (isSwape) {
            return true;
        }

        return super.onTouchEvent(event);
    }

虽然代码长了点,但是也不难啊,onInterceptTouchEvent返回true,就是为了这个区域内的任何触摸事件,我都要拦截一下,至于到底处不处理,看爷心情~

在onTouchEvent()里面实现了核心的代码逻辑,咱们一起看一下~

首先ACTION_DOWN的时候,如果触摸的位置是宽度的4/5处,则认为想要侧滑啦,记下开始的X坐标startX,然后在下面初始化了ParticleSystem对象。这个ParticleSystem对象是粒子库leonids里面的主要业务类,在这里实现了诸如粒子图片、存活时间、加速插值器、渐变消失时间等等参数。

到了ACTION_MOVE,如果x的移动距离是正数,也就是往左滑,并且isSwape为true,就 requestLayout()一下,这个是为啥呢?这是因为如果不 requestLayout(),那么布局的位置就还是初始化时候的位置,从而导致粒子效果位置不对,所以重新计算一下现在的位置,然后调用

 particleSystem.updateEmitVerticalLine(backLayoutRect.right - clipWidth, backLayoutRect.top - getStatuBarHeight(), backLayoutRect.bottom - getStatuBarHeight());

当然,这个方法在原先的粒子库是没有的,我自己添加上去的,就是为了能顺着一条竖线往外发送粒子,感兴趣的可以去看源码。

下面这句代码是为了只要处于触摸模式,那么外面的父控件,也就是RecyclerView就不会劫持触摸事件了。

getParent().requestDisallowInterceptTouchEvent(true);

最后,ACTION_UP和ACTION_CANCEL,在这里完成数据的初始化和删除状态的判断,并且如果有监听器,则调用。

当然,如果isSwape为true,那么触摸时间就被消耗了,否则,默认处理即可。

这个时候跟手粒子已经实现了,那么遮罩咋办?

简单~

 @Override
    protected void dispatchDraw(Canvas canvas) {
        canvas.clipRect(0, 0, backLayoutRect.right - clipWidth, getHeight());
        super.dispatchDraw(canvas);
    }

在画子View之前,clipRect一下,这样,就只会绘制范围内的布局,遮罩效果也就算是实现了。

如何使用

使用也非常简单,首先看布局文件

<?xml version="1.0" encoding="utf-8"?>
<com.socks.library.ParticleLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/root_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/white">

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="2dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginTop="2dp"
        app:cardBackgroundColor="@android:color/white"
        app:cardCornerRadius="4dp">

        <TextView
            android:id="@+id/tv"
            android:layout_width="match_parent"
            android:layout_height="48dp"
            android:gravity="center"
            android:text="测试"
            android:textColor="@android:color/primary_text_light"
            android:textSize="20sp" />
    </android.support.v7.widget.CardView>

</com.socks.library.ParticleLayout>

就和平常的FrameLayout一样使用即可,再看下MainActivity

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private ParticleAdapter mAdapter;
    private LinearLayoutManager layoutManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
        mAdapter = new ParticleAdapter();
        recyclerView.setAdapter(mAdapter);
    }

    private class ParticleAdapter extends RecyclerView.Adapter<ParticleAdapter.ParticleViewHolder> {

        private ArrayList<String> strings;

        ParticleAdapter() {
            strings = new ArrayList<>();
            for (int i = 0; i < 20; i++) {
                strings.add("POSITION = " + i);
            }
        }

        @Override
        public ParticleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = getLayoutInflater().inflate(R.layout.item_layout, parent, false);
            return new ParticleViewHolder(view);
        }

        @Override
        public void onBindViewHolder(final ParticleViewHolder holder, final int position) {
            holder.tv.setText(strings.get(position));
            holder.root_layout.setDeleteListener(new ParticleLayout.DeleteListener() {
                @Override
                public void onDelete() {
                    Toast.makeText(MainActivity.this, "DETELE", Toast.LENGTH_SHORT).show();
                    strings.remove(position);
                    mAdapter.notifyItemRemoved(position);
                    mAdapter.notifyItemRangeChanged(position, strings.size() - position);
                }
            });
            holder.root_layout.setBitmapArrays(R.drawable.ic_star, R.drawable.ic_partical, R.drawable.ic_boom);
        }

        @Override
        public int getItemCount() {
            return strings.size();
        }

        class ParticleViewHolder extends RecyclerView.ViewHolder {

            TextView tv;
            ParticleLayout root_layout;

            public ParticleViewHolder(View itemView) {
                super(itemView);
                tv = (TextView) itemView.findViewById(R.id.tv);
                root_layout = (ParticleLayout) itemView.findViewById(R.id.root_layout);
            }
        }
    }
}

代码非常简单,不需要我再说了吧?

唯一需要注意的是,在删除之后,需要刷新数据源,否则就会删除错误位置的数据,

下面的代码是为了添加粒子图片,随机显示。

 holder.root_layout.setBitmapArrays(R.drawable.ic_star, R.drawable.ic_partical, R.drawable.ic_boom);

是不是很简单?so easy~

拜拜~

更多参考

Android开源粒子库 Leonids



尊重原创,转载请注明:From 凯子哥(http://blog.csdn.net/zhaokaiqiang1992) 侵权必究!

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-12-17 04:07:43

【Android开源项目解析】RecyclerView侧滑删除粒子效果实现——初探Android开源粒子库 Leonids的相关文章

【开源项目解析】背景有波浪效果的TextView——从Titanic项目学习BitmapShader的使用

Hello,好久没写文章了,有木有想我呀~ 正式工作已经过去一个月了,发现在青岛实习和在北京工作,感觉完全不一样呢~ 现在每天晚上回到住的地方,都累的想睡觉-所以也没心情写太多文章和大家分享了,不过我会尽快调整状态,重振雄风的!(哪里起来怪怪的-) 项目介绍 我的想法 实现思路 项目介绍 这篇文章,会介绍一个开源项目,叫做Titanic,是的,中文名就叫"泰坦尼克"- 下面是项目地址 https://github.com/RomainPiel/Titanic 要实现的效果是下面这样滴

Android 下拉刷新上拉加载效果功能,使用开源项目android-pulltorefresh实现

应用场景: 在App开发中,对于信息的获取与演示,不可能全部将其获取与演示,为了在用户使用中,给予用户以友好.方便的用户体验,以滑动.下拉的效果动态加载数据的要求就会出现.为此,该效果功能就需要应用到所需要的展示页面中. 知识点介绍: 本文主要根据开源项目android-pulltorefresh展开介绍. android-pulltorefresh [一个强大的拉动刷新开源项目,支持各种控件下拉刷新 ListView.ViewPager.WevView.ExpandableListView.G

【开源项目解析】QQ“一键下班”功能实现解析——学习Path及贝塞尔曲线的基本使用

早在很久很久以前,QQ就实现了"一键下班"功能.何为"一键下班"?当你QQ有信息时,下部会有信息数量提示红点,点击拖动之后,就会出现"一键下班"效果.本文将结合github上关于此功能的一个简单实现,介绍这个功能的基本实现思路. 项目地址 https://github.com/chenupt/BezierDemo 最终实现效果 实现原理解析 我个人感觉,这个效果实现的很漂亮啊!那么咱们就来看看实现原理是什么~ 注:下面内容请参照项目源码观看. 其

renren-fast开源项目解析日志—1、项目的部署

renren_fast项目解析日志 一.环境搭建 1.后端部署 (1)下载源码 按照步骤,从码云上down了fast,zip的(引maven项目)项目包. (2)安装lombok插件 安装lombok的jar.相当简单,在eclipse文件夹的跟目录下,使用java -jar lombok.jar 会出现一个红辣椒的界面,按照说明安装就行了!(重点看红框) 目前我的理解lombok(印尼——龙目岛)就是一个用注解替代ide帮我们在javabean中所创建的一些get.set.toString等方

开源项目-Web Tools-开发者在线工具-初版发布和开源招募

项目链接:http://webtools.oschina.mopaas.com/ 写在前面的话: 作为一名网站开发人员,一直都会需要用到一些在线工具,如查看时间戳.颜色转换.JSON解析等.虽然这些工具都很多,但有时总觉得使用上难免有些体验上的细节问题.所以,在2014年光棍节来临前,我用了这个周末快速搭建了一个开发者在线工具的网站,并命名为Web Tools,希望能帮助到像我一样的开发者.同时在此,将此项目开源,以便有更多的开发者加入,从而让这个项目走得更远.谢谢! 曾经在地铁上一个广告中,看

【开源项目解析】仿支付宝付款成功及&quot;天女散花&quot;效果实现——看PathMeasure大展身手

话说,在前面两篇文章中,我们学习了BitmapShader.Path的基本使用,那么这一篇文章,咱们接着来学习一下PathMeasure的用法.什么,你没听说过PathMeasure?那你就要OUT咯~ 项目效果图 PathMeasure介绍 仿支付宝实现原理解析 天女散花实现效果解析 更多参考资料 项目效果图 废话不多说,在开始讲解之前,先看下最终实现的效果. 效果一: 仿支付宝支付成功效果 效果二: 这两个项目都是使用Path和PathMeature配合完成的,由其他项目改造而来 项目一是七

GitHub上不错的Android开源项目(三)

收集相关系列资料,自己用作参考,练习和实践.小伙伴们,总有一天,你也能写出 Niubility 的 Android App :-) GitHub上不错的Android开源项目(一):http://www.cnblogs.com/haochuang/p/4676090.html GitHub上不错的Android开源项目(二):http://www.cnblogs.com/haochuang/p/4676092.html GitHub上不错的Android开源项目(三):http://www.cn

【转】Android开源项目 分类 便于查看

之前转载了一个开源项目的文章,发现那些都是没有系统的总结,这里又转载一篇有系统总结的文章. Android开源项目系列汇总已完成,包括: Android开源项目第一篇——个性化控件(View)篇 Android开源项目第二篇——工具库篇 Android开源项目第三篇——优秀项目篇 Android开源项目第四篇——开发及测试工具篇 Android开源项目第五篇——优秀个人和团体篇 Android开源项目第一篇——个性化控件(View)篇 主要介绍那些不错个性化的View,包括ListView.Ac

Android开源项目和工具分类

之前转载了一个开源项目的文章,发现那些都是没有系统的总结,这里又转载一篇有系统总结的文章. Android开源项目系列汇总已完成,包括: Android开源项目第一篇--个性化控件(View)篇 Android开源项目第二篇--工具库篇 Android开源项目第三篇--优秀项目篇 Android开源项目第四篇--开发及测试工具篇 Android开源项目第五篇--优秀个人和团体篇 Android开源项目第一篇--个性化控件(View)篇 主要介绍那些不错个性化的View,包括ListView.Ac