【Android開源項目解析】RecyclerView側滑刪除粒子效果實現——初探Android開源粒子庫 Leonids

來源:互聯網
上載者:User

標籤:android   粒子   側滑刪除   

前兩天在微博上看到了這個側滑刪除的粒子效果,但是只有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) 侵權必究!

著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

【Android開源項目解析】RecyclerView側滑刪除粒子效果實現——初探Android開源粒子庫 Leonids

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.